Skip to content

Commit 0ffd767

Browse files
authored
feat: Add expanded view for canvas elements graph, table, view (parse-community#3156)
1 parent 5364179 commit 0ffd767

File tree

9 files changed

+412
-71
lines changed

9 files changed

+412
-71
lines changed

src/dashboard/Data/CustomDashboard/elements/DataTableElement.react.js

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* This source code is licensed under the license found in the LICENSE file in
66
* the root directory of this source tree.
77
*/
8-
import React from 'react';
8+
import React, { useState } from 'react';
99
import Icon from 'components/Icon/Icon.react';
10+
import ExpandModal from './ExpandModal.react';
1011
import styles from './DataTableElement.scss';
1112

1213
const formatValue = (value) => {
@@ -39,6 +40,8 @@ const DataTableElement = ({
3940
error,
4041
onRefresh,
4142
}) => {
43+
const [isExpanded, setIsExpanded] = useState(false);
44+
4245
if (!config || !config.className) {
4346
return (
4447
<div className={styles.noConfig}>
@@ -85,39 +88,60 @@ const DataTableElement = ({
8588
(columns ? Object.keys(columns) : Object.keys(data[0]))
8689
).filter(k => k !== 'ACL');
8790

91+
const title = config.title || config.className;
92+
93+
const renderTable = () => (
94+
<table className={styles.table}>
95+
<thead>
96+
<tr>
97+
{displayColumns.map(col => (
98+
<th key={col}>{col}</th>
99+
))}
100+
</tr>
101+
</thead>
102+
<tbody>
103+
{data.map((row, i) => (
104+
<tr key={row.objectId || i}>
105+
{displayColumns.map(col => (
106+
<td key={col} title={formatValue(row[col])}>
107+
{formatValue(row[col])}
108+
</td>
109+
))}
110+
</tr>
111+
))}
112+
</tbody>
113+
</table>
114+
);
115+
88116
return (
89117
<div className={styles.dataTableElement}>
90118
<div className={styles.tableHeader}>
91-
<span className={styles.tableTitle}>{config.title || config.className}</span>
119+
<span className={styles.tableTitle}>{title}</span>
92120
<span className={styles.rowCount}>{data.length} rows</span>
121+
<button
122+
type="button"
123+
onClick={() => setIsExpanded(true)}
124+
className={styles.expandButton}
125+
title="Expand"
126+
>
127+
<Icon name="expand-outline" width={12} height={12} fill="#94a3b8" />
128+
</button>
93129
{onRefresh && (
94130
<button type="button" onClick={onRefresh} className={styles.refreshButton}>
95131
<Icon name="refresh-solid" width={12} height={12} fill="#94a3b8" />
96132
</button>
97133
)}
98134
</div>
99135
<div className={styles.tableContainer}>
100-
<table className={styles.table}>
101-
<thead>
102-
<tr>
103-
{displayColumns.map(col => (
104-
<th key={col}>{col}</th>
105-
))}
106-
</tr>
107-
</thead>
108-
<tbody>
109-
{data.map((row, i) => (
110-
<tr key={row.objectId || i}>
111-
{displayColumns.map(col => (
112-
<td key={col} title={formatValue(row[col])}>
113-
{formatValue(row[col])}
114-
</td>
115-
))}
116-
</tr>
117-
))}
118-
</tbody>
119-
</table>
136+
{renderTable()}
120137
</div>
138+
{isExpanded && (
139+
<ExpandModal title={title} onClose={() => setIsExpanded(false)}>
140+
<div className={styles.expandedTableContainer}>
141+
{renderTable()}
142+
</div>
143+
</ExpandModal>
144+
)}
121145
</div>
122146
);
123147
};

src/dashboard/Data/CustomDashboard/elements/DataTableElement.scss

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
border-radius: 4px;
3232
}
3333

34-
.refreshButton {
34+
.expandButton {
3535
margin-left: auto;
3636
display: flex;
3737
align-items: center;
@@ -44,6 +44,33 @@
4444
border-radius: 4px;
4545
opacity: 0;
4646
transition: background-color 0.15s ease, opacity 0.15s ease;
47+
position: relative;
48+
z-index: 10;
49+
50+
:global(.canvasElementWrapper):hover & {
51+
opacity: 1;
52+
}
53+
54+
&:hover {
55+
background-color: rgba(0, 0, 0, 0.1);
56+
}
57+
}
58+
59+
.refreshButton {
60+
display: flex;
61+
align-items: center;
62+
justify-content: center;
63+
width: 24px;
64+
height: 24px;
65+
background: transparent;
66+
border: none;
67+
cursor: pointer;
68+
border-radius: 4px;
69+
opacity: 0;
70+
transition: background-color 0.15s ease, opacity 0.15s ease;
71+
position: relative;
72+
z-index: 10;
73+
margin-left: -6px;
4774

4875
:global(.canvasElementWrapper):hover & {
4976
opacity: 1;
@@ -59,6 +86,12 @@
5986
overflow: auto;
6087
}
6188

89+
.expandedTableContainer {
90+
width: 100%;
91+
height: 100%;
92+
overflow: auto;
93+
}
94+
6295
.table {
6396
width: 100%;
6497
border-collapse: collapse;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright (c) 2016-present, Parse, LLC
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the LICENSE file in
6+
* the root directory of this source tree.
7+
*/
8+
import React, { useEffect, useCallback, useState } from 'react';
9+
import { createPortal } from 'react-dom';
10+
import Icon from 'components/Icon/Icon.react';
11+
import styles from './ExpandModal.scss';
12+
13+
const ExpandModal = ({ title, children, onClose }) => {
14+
const [container] = useState(() => {
15+
const el = document.createElement('div');
16+
el.className = styles.portalContainer;
17+
return el;
18+
});
19+
20+
const handleKeyDown = useCallback((e) => {
21+
if (e.key === 'Escape') {
22+
e.preventDefault();
23+
e.stopPropagation();
24+
onClose();
25+
}
26+
}, [onClose]);
27+
28+
useEffect(() => {
29+
const previousOverflow = document.body.style.overflow;
30+
document.body.appendChild(container);
31+
// Use capture phase to handle event before it reaches other listeners
32+
document.addEventListener('keydown', handleKeyDown, true);
33+
document.body.style.overflow = 'hidden';
34+
35+
return () => {
36+
document.removeEventListener('keydown', handleKeyDown, true);
37+
document.body.style.overflow = previousOverflow;
38+
if (document.body.contains(container)) {
39+
document.body.removeChild(container);
40+
}
41+
};
42+
}, [container, handleKeyDown]);
43+
44+
const handleBackdropClick = (e) => {
45+
if (e.target === e.currentTarget) {
46+
e.stopPropagation();
47+
onClose();
48+
}
49+
};
50+
51+
const handleContentClick = (e) => {
52+
e.stopPropagation();
53+
};
54+
55+
return createPortal(
56+
<div className={styles.modalOverlay} onClick={handleBackdropClick}>
57+
<div className={styles.modalContent} onClick={handleContentClick}>
58+
<div className={styles.modalHeader}>
59+
<span className={styles.modalTitle}>{title}</span>
60+
<button type="button" onClick={onClose} className={styles.closeButton}>
61+
<Icon name="x-outline" width={16} height={16} fill="#64748b" />
62+
</button>
63+
</div>
64+
<div className={styles.modalBody}>
65+
{children}
66+
</div>
67+
</div>
68+
</div>,
69+
container
70+
);
71+
};
72+
73+
export default ExpandModal;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
@import 'stylesheets/globals.scss';
2+
3+
.portalContainer {
4+
position: fixed;
5+
top: 0;
6+
left: 0;
7+
right: 0;
8+
bottom: 0;
9+
z-index: 10000;
10+
pointer-events: none;
11+
12+
> * {
13+
pointer-events: auto;
14+
}
15+
}
16+
17+
.modalOverlay {
18+
position: fixed;
19+
top: 0;
20+
left: 0;
21+
right: 0;
22+
bottom: 0;
23+
background-color: rgba(17, 13, 17, 0.8);
24+
display: flex;
25+
align-items: center;
26+
justify-content: center;
27+
z-index: 1000;
28+
animation: fadeIn 0.15s ease;
29+
}
30+
31+
@keyframes fadeIn {
32+
from {
33+
opacity: 0;
34+
}
35+
to {
36+
opacity: 1;
37+
}
38+
}
39+
40+
.modalContent {
41+
width: 90vw;
42+
height: 85vh;
43+
background: white;
44+
border-radius: 8px;
45+
display: flex;
46+
flex-direction: column;
47+
overflow: hidden;
48+
animation: scaleIn 0.15s ease;
49+
}
50+
51+
@keyframes scaleIn {
52+
from {
53+
transform: scale(0.95);
54+
opacity: 0;
55+
}
56+
to {
57+
transform: scale(1);
58+
opacity: 1;
59+
}
60+
}
61+
62+
.modalHeader {
63+
display: flex;
64+
align-items: center;
65+
justify-content: space-between;
66+
padding: 16px 20px;
67+
border-bottom: 1px solid #e3e3ea;
68+
flex-shrink: 0;
69+
}
70+
71+
.modalTitle {
72+
font-size: 16px;
73+
font-weight: 600;
74+
color: #0e0e1a;
75+
}
76+
77+
.closeButton {
78+
display: flex;
79+
align-items: center;
80+
justify-content: center;
81+
width: 32px;
82+
height: 32px;
83+
background: transparent;
84+
border: none;
85+
cursor: pointer;
86+
border-radius: 4px;
87+
transition: background-color 0.15s ease;
88+
89+
&:hover {
90+
background-color: rgba(0, 0, 0, 0.1);
91+
}
92+
}
93+
94+
.modalBody {
95+
flex: 1;
96+
padding: 20px;
97+
overflow: auto;
98+
min-height: 0;
99+
}

0 commit comments

Comments
 (0)