@adobe-commerce/elsie 1.5.0-alpha3001 → 1.5.0-alpha3002
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -96,6 +96,20 @@ const meta: Meta<TableProps> = {
|
|
|
96
96
|
},
|
|
97
97
|
control: 'object',
|
|
98
98
|
},
|
|
99
|
+
loading: {
|
|
100
|
+
description: 'When true, renders skeleton rows instead of actual data. Useful for showing loading state while data is being fetched.',
|
|
101
|
+
table: {
|
|
102
|
+
type: { summary: 'boolean' },
|
|
103
|
+
},
|
|
104
|
+
control: 'boolean',
|
|
105
|
+
},
|
|
106
|
+
skeletonRowCount: {
|
|
107
|
+
description: 'Number of skeleton rows to render when loading is true. Defaults to 10 rows.',
|
|
108
|
+
table: {
|
|
109
|
+
type: { summary: 'number' },
|
|
110
|
+
},
|
|
111
|
+
control: 'number',
|
|
112
|
+
},
|
|
99
113
|
},
|
|
100
114
|
};
|
|
101
115
|
|
|
@@ -618,4 +632,42 @@ export const RowDetails: Story = {
|
|
|
618
632
|
},
|
|
619
633
|
};
|
|
620
634
|
|
|
635
|
+
/**
|
|
636
|
+
* Table in loading state with skeleton rows.
|
|
637
|
+
* Demonstrates how the table appears while data is being fetched.
|
|
638
|
+
* Each cell shows a skeleton placeholder that matches the table structure.
|
|
639
|
+
*
|
|
640
|
+
* **Features**:
|
|
641
|
+
* - Shows skeleton rows instead of actual data when `loading` is true
|
|
642
|
+
* - Configurable number of skeleton rows via `skeletonRowCount` prop
|
|
643
|
+
* - Maintains table structure and column headers during loading
|
|
644
|
+
* - Each cell contains a single-line skeleton component
|
|
645
|
+
*
|
|
646
|
+
* ```tsx
|
|
647
|
+
* <Table
|
|
648
|
+
* loading={true}
|
|
649
|
+
* skeletonRowCount={5}
|
|
650
|
+
* columns={[
|
|
651
|
+
* { key: 'name', label: 'Name' },
|
|
652
|
+
* { key: 'email', label: 'Email' },
|
|
653
|
+
* { key: 'status', label: 'Status' }
|
|
654
|
+
* ]}
|
|
655
|
+
* rowData={[]} // Empty array when loading
|
|
656
|
+
* />
|
|
657
|
+
* ```
|
|
658
|
+
*/
|
|
659
|
+
export const LoadingState: Story = {
|
|
660
|
+
args: {
|
|
661
|
+
loading: true,
|
|
662
|
+
skeletonRowCount: 5,
|
|
663
|
+
columns: [
|
|
664
|
+
{ key: 'name', label: 'Name' },
|
|
665
|
+
{ key: 'email', label: 'Email' },
|
|
666
|
+
{ key: 'status', label: 'Status' },
|
|
667
|
+
{ key: 'actions', label: 'Actions' },
|
|
668
|
+
],
|
|
669
|
+
rowData: [], // Empty when loading
|
|
670
|
+
},
|
|
671
|
+
};
|
|
672
|
+
|
|
621
673
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { FunctionComponent, VNode, Fragment } from 'preact';
|
|
11
11
|
import { HTMLAttributes } from 'preact/compat';
|
|
12
12
|
import { classes, VComponent } from '@adobe-commerce/elsie/lib';
|
|
13
|
-
import { Icon, Button } from '@adobe-commerce/elsie/components';
|
|
13
|
+
import { Icon, Button, Skeleton, SkeletonRow } from '@adobe-commerce/elsie/components';
|
|
14
14
|
import { useText } from '@adobe-commerce/elsie/i18n';
|
|
15
15
|
|
|
16
16
|
import '@adobe-commerce/elsie/components/Table/Table.css';
|
|
@@ -28,13 +28,15 @@ type RowData = {
|
|
|
28
28
|
_rowDetails?: VNode | string; // Special property for expandable row content
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
export interface TableProps extends HTMLAttributes<HTMLTableElement> {
|
|
31
|
+
export interface TableProps extends Omit<HTMLAttributes<HTMLTableElement>, 'loading'> {
|
|
32
32
|
columns: Column[];
|
|
33
33
|
rowData: RowData[];
|
|
34
34
|
mobileLayout?: 'stacked' | 'none';
|
|
35
35
|
caption?: string;
|
|
36
|
-
onSortChange?: (columnKey: string, direction: Sortable) => void;
|
|
37
36
|
expandedRows?: Set<number>;
|
|
37
|
+
loading?: boolean;
|
|
38
|
+
skeletonRowCount?: number;
|
|
39
|
+
onSortChange?: (columnKey: string, direction: Sortable) => void;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export const Table: FunctionComponent<TableProps> = ({
|
|
@@ -44,14 +46,13 @@ export const Table: FunctionComponent<TableProps> = ({
|
|
|
44
46
|
rowData = [],
|
|
45
47
|
mobileLayout = 'none',
|
|
46
48
|
caption,
|
|
47
|
-
onSortChange,
|
|
48
49
|
expandedRows = new Set(),
|
|
50
|
+
loading = false,
|
|
51
|
+
skeletonRowCount = 10,
|
|
52
|
+
onSortChange,
|
|
49
53
|
...props
|
|
50
54
|
}) => {
|
|
51
55
|
const translations = useText({
|
|
52
|
-
ariaSortNone: 'Dropin.Table.ariaSortNone',
|
|
53
|
-
ariaSortAscending: 'Dropin.Table.ariaSortAscending',
|
|
54
|
-
ariaSortDescending: 'Dropin.Table.ariaSortDescending',
|
|
55
56
|
sortedAscending: 'Dropin.Table.sortedAscending',
|
|
56
57
|
sortedDescending: 'Dropin.Table.sortedDescending',
|
|
57
58
|
sortBy: 'Dropin.Table.sortBy',
|
|
@@ -73,7 +74,7 @@ export const Table: FunctionComponent<TableProps> = ({
|
|
|
73
74
|
onSortChange(column.key, nextDirection);
|
|
74
75
|
};
|
|
75
76
|
|
|
76
|
-
const
|
|
77
|
+
const renderSortButton = (column: Column) => {
|
|
77
78
|
if (column.sortBy === undefined) return null;
|
|
78
79
|
|
|
79
80
|
let iconSource: string;
|
|
@@ -102,6 +103,74 @@ export const Table: FunctionComponent<TableProps> = ({
|
|
|
102
103
|
);
|
|
103
104
|
};
|
|
104
105
|
|
|
106
|
+
const renderSkeletonRows = () => {
|
|
107
|
+
return Array.from({ length: skeletonRowCount }, (_, rowIndex) => (
|
|
108
|
+
<tr key={`skeleton-${rowIndex}`} className="dropin-table__body__row">
|
|
109
|
+
{columns.map((column) => (
|
|
110
|
+
<td key={column.key} className="dropin-table__body__cell" data-label={column.label}>
|
|
111
|
+
<Skeleton>
|
|
112
|
+
<SkeletonRow variant="row" size="small" fullWidth />
|
|
113
|
+
</Skeleton>
|
|
114
|
+
</td>
|
|
115
|
+
))}
|
|
116
|
+
</tr>
|
|
117
|
+
));
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const renderDataRows = () => {
|
|
121
|
+
return rowData.map((row, rowIndex) => {
|
|
122
|
+
const hasDetails = row._rowDetails !== undefined;
|
|
123
|
+
const isExpanded = expandedRows.has(rowIndex);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Fragment key={rowIndex}>
|
|
127
|
+
<tr className="dropin-table__body__row">
|
|
128
|
+
{columns.map((column) => {
|
|
129
|
+
const cell = row[column.key];
|
|
130
|
+
|
|
131
|
+
if (typeof cell === 'string' || typeof cell === 'number') {
|
|
132
|
+
return (
|
|
133
|
+
<td key={column.key} className="dropin-table__body__cell" data-label={column.label}>
|
|
134
|
+
{cell}
|
|
135
|
+
</td>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<td key={column.key} className="dropin-table__body__cell" data-label={column.label}>
|
|
141
|
+
<VComponent node={cell!} />
|
|
142
|
+
</td>
|
|
143
|
+
);
|
|
144
|
+
})}
|
|
145
|
+
</tr>
|
|
146
|
+
{hasDetails && isExpanded && (
|
|
147
|
+
<tr
|
|
148
|
+
key={`${rowIndex}-details`}
|
|
149
|
+
className="dropin-table__row-details dropin-table__row-details--expanded"
|
|
150
|
+
id={`row-${rowIndex}-details`}
|
|
151
|
+
>
|
|
152
|
+
<td
|
|
153
|
+
className="dropin-table__row-details__cell"
|
|
154
|
+
colSpan={columns.length}
|
|
155
|
+
role="region"
|
|
156
|
+
aria-labelledby={`row-${rowIndex}-details`}
|
|
157
|
+
>
|
|
158
|
+
{typeof row._rowDetails === 'string' ? row._rowDetails : <VComponent node={row._rowDetails!} />}
|
|
159
|
+
</td>
|
|
160
|
+
</tr>
|
|
161
|
+
)}
|
|
162
|
+
</Fragment>
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const getAriaSort = (column: Column): 'none' | 'ascending' | 'descending' | 'other' | undefined => {
|
|
168
|
+
if (column.sortBy === true) return 'none';
|
|
169
|
+
if (column.sortBy === 'asc') return 'ascending';
|
|
170
|
+
if (column.sortBy === 'desc') return 'descending';
|
|
171
|
+
return undefined;
|
|
172
|
+
};
|
|
173
|
+
|
|
105
174
|
return (
|
|
106
175
|
<div className={classes(['dropin-table', `dropin-table--mobile-layout-${mobileLayout}`, className])}>
|
|
107
176
|
<table {...props} className="dropin-table__table">
|
|
@@ -116,63 +185,22 @@ export const Table: FunctionComponent<TableProps> = ({
|
|
|
116
185
|
['dropin-table__header__cell--sorted', column.sortBy === 'asc' || column.sortBy === 'desc'],
|
|
117
186
|
['dropin-table__header__cell--sortable', column.sortBy !== undefined]
|
|
118
187
|
])}
|
|
119
|
-
aria-sort={
|
|
120
|
-
column.sortBy === 'asc' ? translations.ariaSortAscending :
|
|
121
|
-
column.sortBy === 'desc' ? translations.ariaSortDescending :
|
|
122
|
-
column.sortBy === true ? translations.ariaSortNone : undefined
|
|
123
|
-
}
|
|
188
|
+
aria-sort={getAriaSort(column)}
|
|
124
189
|
>
|
|
125
190
|
{column.label}
|
|
126
|
-
{
|
|
191
|
+
{renderSortButton(column)}
|
|
127
192
|
</th>
|
|
128
193
|
))}
|
|
129
194
|
</tr>
|
|
130
195
|
</thead>
|
|
131
196
|
<tbody className="dropin-table__body">
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
{columns.map((column) => {
|
|
140
|
-
const cell = row[column.key];
|
|
141
|
-
|
|
142
|
-
if (typeof cell === 'string' || typeof cell === 'number') {
|
|
143
|
-
return (
|
|
144
|
-
<td key={column.key} className="dropin-table__body__cell" data-label={column.label}>
|
|
145
|
-
{cell}
|
|
146
|
-
</td>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<td key={column.key} className="dropin-table__body__cell" data-label={column.label}>
|
|
152
|
-
<VComponent node={cell!} />
|
|
153
|
-
</td>
|
|
154
|
-
);
|
|
155
|
-
})}
|
|
156
|
-
</tr>
|
|
157
|
-
{hasDetails && isExpanded && (
|
|
158
|
-
<tr
|
|
159
|
-
key={`${rowIndex}-details`}
|
|
160
|
-
className="dropin-table__row-details dropin-table__row-details--expanded"
|
|
161
|
-
id={`row-${rowIndex}-details`}
|
|
162
|
-
>
|
|
163
|
-
<td
|
|
164
|
-
className="dropin-table__row-details__cell"
|
|
165
|
-
colSpan={columns.length}
|
|
166
|
-
role="region"
|
|
167
|
-
aria-labelledby={`row-${rowIndex}-details`}
|
|
168
|
-
>
|
|
169
|
-
{typeof row._rowDetails === 'string' ? row._rowDetails : <VComponent node={row._rowDetails!} />}
|
|
170
|
-
</td>
|
|
171
|
-
</tr>
|
|
172
|
-
)}
|
|
173
|
-
</Fragment>
|
|
174
|
-
);
|
|
175
|
-
})}
|
|
197
|
+
{loading ? (
|
|
198
|
+
// Render skeleton rows when loading
|
|
199
|
+
renderSkeletonRows()
|
|
200
|
+
) : (
|
|
201
|
+
// Render actual data when not loading
|
|
202
|
+
renderDataRows()
|
|
203
|
+
)}
|
|
176
204
|
</tbody>
|
|
177
205
|
</table>
|
|
178
206
|
</div>
|
package/src/i18n/en_US.json
CHANGED
|
@@ -143,9 +143,6 @@
|
|
|
143
143
|
"picker": "Select a date"
|
|
144
144
|
},
|
|
145
145
|
"Table": {
|
|
146
|
-
"ariaSortNone": "none",
|
|
147
|
-
"ariaSortAscending": "ascending",
|
|
148
|
-
"ariaSortDescending": "descending",
|
|
149
146
|
"sortedAscending": "Sort {label} ascending",
|
|
150
147
|
"sortedDescending": "Sort {label} descending",
|
|
151
148
|
"sortBy": "Sort by {label}"
|