@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe-commerce/elsie",
3
- "version": "1.5.0-alpha3001",
3
+ "version": "1.5.0-alpha3002",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "description": "Domain Package SDK",
6
6
  "engines": {
@@ -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 getSortButton = (column: Column) => {
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
- {getSortButton(column)}
191
+ {renderSortButton(column)}
127
192
  </th>
128
193
  ))}
129
194
  </tr>
130
195
  </thead>
131
196
  <tbody className="dropin-table__body">
132
- {rowData.map((row, rowIndex) => {
133
- const hasDetails = row._rowDetails !== undefined;
134
- const isExpanded = expandedRows.has(rowIndex);
135
-
136
- return (
137
- <Fragment key={rowIndex}>
138
- <tr className="dropin-table__body__row">
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>
@@ -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}"