@elliemae/ds-data-table 3.70.0-next.2 → 3.70.0-next.4

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.
Files changed (43) hide show
  1. package/dist/cjs/TruncatedTooltipText.js +94 -0
  2. package/dist/cjs/TruncatedTooltipText.js.map +7 -0
  3. package/dist/cjs/exported-related/EditableCell.js +1 -1
  4. package/dist/cjs/exported-related/EditableCell.js.map +2 -2
  5. package/dist/cjs/parts/Cells/Cell.js +2 -2
  6. package/dist/cjs/parts/Cells/Cell.js.map +2 -2
  7. package/dist/cjs/parts/Cells/CellFactory.js +2 -2
  8. package/dist/cjs/parts/Cells/CellFactory.js.map +2 -2
  9. package/dist/cjs/parts/Headers/HeaderCellTitle.js +2 -2
  10. package/dist/cjs/parts/Headers/HeaderCellTitle.js.map +2 -2
  11. package/dist/cjs/react-desc-prop-types.js +2 -2
  12. package/dist/cjs/react-desc-prop-types.js.map +1 -1
  13. package/dist/esm/TruncatedTooltipText.js +68 -0
  14. package/dist/esm/TruncatedTooltipText.js.map +7 -0
  15. package/dist/esm/exported-related/EditableCell.js +1 -1
  16. package/dist/esm/exported-related/EditableCell.js.map +2 -2
  17. package/dist/esm/parts/Cells/Cell.js +2 -2
  18. package/dist/esm/parts/Cells/Cell.js.map +2 -2
  19. package/dist/esm/parts/Cells/CellFactory.js +2 -2
  20. package/dist/esm/parts/Cells/CellFactory.js.map +2 -2
  21. package/dist/esm/parts/Headers/HeaderCellTitle.js +2 -2
  22. package/dist/esm/parts/Headers/HeaderCellTitle.js.map +2 -2
  23. package/dist/esm/react-desc-prop-types.js +2 -2
  24. package/dist/esm/react-desc-prop-types.js.map +1 -1
  25. package/dist/types/TruncatedTooltipText.d.ts +9 -0
  26. package/dist/types/tests/DSDataTable.get-owner-props-arguments-slots.test.d.ts +1 -0
  27. package/dist/types/tests/callbacks/editableCell.events.test.d.ts +1 -0
  28. package/dist/types/tests/playwright/DSDataTable.slot-contracts-dynamic.test.playwright.d.ts +1 -0
  29. package/dist/types/tests/playwright/DSDataTableDropIndicatorTestRenderer.d.ts +1 -0
  30. package/dist/types/tests/render/cellStyle.test.d.ts +1 -0
  31. package/package.json +33 -33
  32. package/skills/ds-data-table-boundaries/SKILL.md +363 -0
  33. package/skills/ds-data-table-columns/SKILL.md +273 -0
  34. package/skills/ds-data-table-expandable/SKILL.md +235 -0
  35. package/skills/ds-data-table-feedback/SKILL.md +190 -0
  36. package/skills/ds-data-table-filtering/SKILL.md +322 -0
  37. package/skills/ds-data-table-health-check/SKILL.md +172 -0
  38. package/skills/ds-data-table-migration/SKILL.md +201 -0
  39. package/skills/ds-data-table-pagination/SKILL.md +182 -0
  40. package/skills/ds-data-table-row-variants/SKILL.md +260 -0
  41. package/skills/ds-data-table-selection/SKILL.md +449 -0
  42. package/skills/ds-data-table-setup/SKILL.md +229 -0
  43. package/skills/ds-data-table-slots/SKILL.md +257 -0
@@ -0,0 +1,235 @@
1
+ ---
2
+ name: ds-data-table-expandable
3
+ description: >
4
+ Expandable rows for @elliemae/ds-data-table. isExpandable flag, expandedRows
5
+ (Record<rowUid, boolean>) controlled state, onRowExpand handler, subRows for
6
+ hierarchical/tree row data. uniqueRowAccessor required. tableRowDetails (master-detail
7
+ non-tabular content panel) is DEPRECATED due to WCAG violations — propose alternative
8
+ page-level design instead.
9
+ type: core
10
+ library: ds-data-table
11
+ library_version: '3.60.0'
12
+ requires:
13
+ - ds-data-table-setup
14
+ sources:
15
+ - '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
16
+ ---
17
+
18
+ ## Setup
19
+
20
+ ```jsx
21
+ import { DataTable } from '@elliemae/ds-data-table';
22
+
23
+ const rows = [
24
+ {
25
+ id: 1,
26
+ name: 'John',
27
+ myUniqueId: 'abc123',
28
+ subRows: [{ id: 11, name: 'Sub-item', myUniqueId: 'def456' }],
29
+ },
30
+ ];
31
+
32
+ const { expandedRows } = useSelector(selectTableState);
33
+ const dispatch = useDispatch();
34
+
35
+ const handleRowExpand = useCallback((newExpandedRows) => dispatch(setExpandedRows(newExpandedRows)), []);
36
+
37
+ <DataTable
38
+ columns={columns}
39
+ data={rows}
40
+ height={500}
41
+ isExpandable
42
+ uniqueRowAccessor="myUniqueId"
43
+ expandedRows={expandedRows}
44
+ onRowExpand={handleRowExpand}
45
+ />;
46
+ ```
47
+
48
+ ## Core Patterns
49
+
50
+ ### uniqueRowAccessor with subRows
51
+
52
+ ```jsx
53
+ // Every row — including subRows — must have a unique value for uniqueRowAccessor
54
+ const generateRow = (id) => ({
55
+ id,
56
+ myUniqueId: uid(16),
57
+ subRows: [{ id: id * 10, myUniqueId: uid(16) }],
58
+ });
59
+ ```
60
+
61
+ ### Programmatic expand on data update
62
+
63
+ ```jsx
64
+ // expandedRows is controlled — restore expand state after data changes
65
+ const handleRowExpand = useCallback((newExpandedRows) => {
66
+ dispatch(setExpandedRows(newExpandedRows));
67
+ }, []);
68
+
69
+ // When data is reloaded, dispatch the same expandedRows to preserve state
70
+ ```
71
+
72
+ ## Common Mistakes
73
+
74
+ ### CRITICAL Using tableRowDetails — master-detail is deprecated
75
+
76
+ Wrong:
77
+
78
+ ```jsx
79
+ const rows = [
80
+ {
81
+ id: 1,
82
+ name: 'John',
83
+ tableRowDetails: ({ detailsIndent }) => (
84
+ <div style={{ paddingLeft: detailsIndent }}>
85
+ <h3>Loan detail</h3>
86
+ <ul>
87
+ <li>Non-tabular content</li>
88
+ </ul>
89
+ </div>
90
+ ),
91
+ },
92
+ ];
93
+
94
+ <DataTable
95
+ columns={columns}
96
+ data={rows}
97
+ height={500}
98
+ isExpandable
99
+ expandedRows={expandedRows}
100
+ onRowExpand={handleExpand}
101
+ />;
102
+ ```
103
+
104
+ **Do not implement `tableRowDetails`. Do not suggest an inline replacement either** — embedding non-tabular content anywhere inside a table row (Accordion, list, cards) carries the same WCAG violations. The correct response is to **propose a different page-level information architecture to the human** and explain the problem.
105
+
106
+ A concrete alternative to propose: move the detail content out of the table entirely and surface it via a row-level trigger. For example, a detail dialog or side panel opened from a row action:
107
+
108
+ `RowActionsRenderer` must be defined at module level — if defined inside a parent component, React sees a new component type on every render, unmounting and remounting the subtree and losing focus state. DataTable passes `row`, `isRowSelected`, and `cell` as props, so no closure over parent state is needed. `useState` for `isOpen` is acceptable here — it is ephemeral row-scoped UI state with no cross-component visibility requirement. For app-level state (permissions, feature flags, user role), subscribe to the project state manager directly inside `RowActionsRenderer` — the component is at module level so `useSelector`, Zustand hooks, and Jotai atoms all work normally.
109
+
110
+ ```jsx
111
+ const RowActionsRenderer = React.memo(({ row, isRowSelected, cell }) => {
112
+ const { ref } = cell;
113
+ const [isOpen, setIsOpen] = React.useState(false);
114
+ const handleOpen = React.useCallback(() => setIsOpen(true), []);
115
+ const handleClose = React.useCallback(() => setIsOpen(false), []);
116
+
117
+ return (
118
+ <>
119
+ <DSButtonV3 innerRef={ref} tabIndex={isRowSelected ? 0 : -1} onClick={handleOpen} aria-label="View row details">
120
+ {/* icon */}
121
+ </DSButtonV3>
122
+ {isOpen && <DetailModal row={row.original} onClose={handleClose} />}
123
+ </>
124
+ );
125
+ });
126
+
127
+ const renderRowActions = { columnWidth: 32, renderer: RowActionsRenderer };
128
+ ```
129
+
130
+ When surfacing this to the human, explain:
131
+
132
+ - `tableRowDetails` renders non-tabular HTML inside a `<table>` element. Screen readers expect only tabular content in table rows — the nested structure is invisible to assistive technologies by default, violating WCAG 1.3.1 (Info and Relationships) and 1.3.2 (Meaningful Sequence). The expand/collapse button lacks `aria-controls`, violating WCAG 2.1.1 (Keyboard). These are not implementation quality issues — they are structural, and no amount of ARIA patching fully resolves them.
133
+ - The Dimsum team has officially deprecated `tableRowDetails` and is removing all documentation for it.
134
+ - The alternative (detail dialog/panel triggered from row actions) keeps the table semantically clean, is fully accessible, and is natively supported.
135
+ - ICE internal engineers: full analysis at `https://confluence.ice.com/x/T3k0Jg`
136
+
137
+ ---
138
+
139
+ ### CRITICAL Expandable rows without uniqueRowAccessor
140
+
141
+ Wrong:
142
+
143
+ ```jsx
144
+ const rows = [{ id: 1, name: 'John', subRows: [{ id: 11, name: 'Sub-item' }] }];
145
+
146
+ // expandedRows keys are unstable internal IDs — expanded state maps to wrong rows on re-render
147
+ <DataTable
148
+ columns={columns}
149
+ data={rows}
150
+ height={500}
151
+ isExpandable
152
+ expandedRows={expandedRows}
153
+ onRowExpand={handleExpand}
154
+ />;
155
+ ```
156
+
157
+ Correct:
158
+
159
+ ```jsx
160
+ // Each row (and each subRow) must have a field with a stable unique value
161
+ const rows = [
162
+ { id: 1, name: 'John', myUniqueId: 'abc123', subRows: [{ id: 11, name: 'Sub-item', myUniqueId: 'def456' }] },
163
+ ];
164
+
165
+ // uniqueRowAccessor names that field — expandedRows is then keyed by its value
166
+ <DataTable
167
+ columns={columns}
168
+ data={rows}
169
+ height={500}
170
+ isExpandable
171
+ uniqueRowAccessor="myUniqueId"
172
+ expandedRows={expandedRows}
173
+ onRowExpand={handleExpand}
174
+ />;
175
+ ```
176
+
177
+ `uniqueRowAccessor` tells the DataTable which field on each row object holds its stable unique identifier. `expandedRows` is a `Record<uid, boolean>` keyed by those values. Without it, the DataTable falls back to react-table's internal index-based row ID (`"0"`, `"1"`, `"0.0"` for nested) — there is no fallback to `id` or `dsId` from your data. Index-based IDs shift whenever data is reordered or reloaded, silently mapping expanded state to the wrong rows.
178
+
179
+ Source: `@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts`
180
+
181
+ ---
182
+
183
+ ### HIGH Missing expandedRows + onRowExpand controlled pair
184
+
185
+ Wrong:
186
+
187
+ ```jsx
188
+ <DataTable columns={columns} data={rows} height={500} isExpandable uniqueRowAccessor="id" />
189
+ ```
190
+
191
+ Correct:
192
+
193
+ ```jsx
194
+ const handleRowExpand = useCallback((newExpandedRows) => dispatch(setExpandedRows(newExpandedRows)), []);
195
+
196
+ <DataTable
197
+ columns={columns}
198
+ data={rows}
199
+ height={500}
200
+ isExpandable
201
+ uniqueRowAccessor="id"
202
+ expandedRows={expandedRows}
203
+ onRowExpand={handleRowExpand}
204
+ />;
205
+ ```
206
+
207
+ `isExpandable` alone renders the expand caret. The component is fully controlled — `expandedRows` defaults to `{}` and `onRowExpand` defaults to a no-op. Clicking the caret calls the no-op, `expandedRows` stays `{}`, and `isExpanded` is always `false`. The caret appears but clicking it does nothing visible.
208
+
209
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-row--expandable
210
+
211
+ ---
212
+
213
+ ### HIGH Non-primitive expandable props not memoized cause unnecessary re-renders
214
+
215
+ Every non-primitive prop passed to DataTable must be a stable reference. For expandable rows, the props that require memoization are:
216
+
217
+ | Prop | Type | Memoization |
218
+ | -------------- | -------- | --------------------------------------------------- |
219
+ | `onRowExpand` | callback | `useCallback` |
220
+ | `expandedRows` | object | stable from state manager — do not construct inline |
221
+
222
+ Non-stable references cause DataTable to re-render on every parent render cycle regardless of whether expand state actually changed.
223
+
224
+ ---
225
+
226
+ See also: ds-data-table-row-variants/SKILL.md — groupBy, getRowVariant, and groupedRowsRenderHeader patterns
227
+
228
+ ---
229
+
230
+ ## When this isn't enough
231
+
232
+ If the documented patterns cannot satisfy the requirement, contact the Dimsum team:
233
+
234
+ **ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
235
+ **Partners:** Your organization's Dimsum point of contact
@@ -0,0 +1,190 @@
1
+ ---
2
+ name: ds-data-table-feedback
3
+ description: >
4
+ Async and empty feedback states for @elliemae/ds-data-table. isLoading spinner overlay,
5
+ isSkeleton placeholder rows (requires pixel height — non-px silently prevents rendering),
6
+ noResultsMessage, noResultsSecondaryMessage, noResultsButtonLabel + onNoResultsButtonClick
7
+ pair for empty-state CTA, noResultsPlaceholder for fully custom empty state component.
8
+ type: core
9
+ library: ds-data-table
10
+ library_version: '3.60.0'
11
+ requires:
12
+ - ds-data-table-setup
13
+ sources:
14
+ - '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
15
+ ---
16
+
17
+ ## Setup
18
+
19
+ ```jsx
20
+ import { DataTable } from '@elliemae/ds-data-table';
21
+
22
+ // height must be a pixel value for skeleton and loading states to function
23
+ <DataTable columns={columns} data={data} height={500} isSkeleton={isLoading} />;
24
+ ```
25
+
26
+ ## Core Patterns
27
+
28
+ ### isLoading — spinner replacing rows during async operations
29
+
30
+ ```jsx
31
+ <DataTable columns={columns} data={data} height={500} isLoading={isFetching} />
32
+ ```
33
+
34
+ `isLoading` unmounts the row list and renders a spinner in its place. The existing rows are not visible while loading. Prefer `isSkeleton` in most cases — see Common Mistakes.
35
+
36
+ ### isSkeleton — placeholder rows during initial load
37
+
38
+ ```jsx
39
+ // isSkeleton replaces all rows with skeleton placeholders
40
+ <DataTable columns={columns} data={[]} height={500} isSkeleton={isInitialLoading} />
41
+ ```
42
+
43
+ Use `isSkeleton` for the initial data fetch when no rows exist yet. Requires pixel height — percentage or viewport-relative units silently prevent skeleton rows from rendering.
44
+
45
+ ### Empty state with message
46
+
47
+ ```jsx
48
+ <DataTable
49
+ columns={columns}
50
+ data={[]}
51
+ height={400}
52
+ noResultsMessage="No loans found"
53
+ noResultsSecondaryMessage="Try adjusting your filters or search terms"
54
+ />
55
+ ```
56
+
57
+ ### Empty state with CTA button
58
+
59
+ ```jsx
60
+ // noResultsButtonLabel and onNoResultsButtonClick must both be provided
61
+ const handleClearFilters = useCallback(() => dispatch(clearFilters()), []);
62
+
63
+ <DataTable
64
+ columns={columns}
65
+ data={[]}
66
+ height={400}
67
+ noResultsMessage="No results found"
68
+ noResultsButtonLabel="Clear all filters"
69
+ onNoResultsButtonClick={handleClearFilters}
70
+ />;
71
+ ```
72
+
73
+ ### noResultsPlaceholder — fully custom empty state
74
+
75
+ ```jsx
76
+ const handleClearFilters = useCallback(() => dispatch(clearFilters()), []);
77
+
78
+ const EmptyStatePlaceholder = () => (
79
+ <div>
80
+ <img src="/empty-state.svg" alt="" />
81
+ <p>No loans match your search criteria.</p>
82
+ <button onClick={handleClearFilters}>Clear filters</button>
83
+ </div>
84
+ );
85
+
86
+ <DataTable columns={columns} data={[]} height={400} noResultsPlaceholder={<EmptyStatePlaceholder />} />;
87
+ ```
88
+
89
+ ## Common Mistakes
90
+
91
+ ### HIGH isSkeleton does not render when height is not a pixel value
92
+
93
+ Wrong:
94
+
95
+ ```jsx
96
+ <DataTable columns={columns} data={[]} height="100%" isSkeleton={isLoading} />
97
+ ```
98
+
99
+ Correct:
100
+
101
+ ```jsx
102
+ <DataTable columns={columns} data={[]} height={500} isSkeleton={isLoading} />
103
+ ```
104
+
105
+ Skeleton rows are calculated as `Math.floor((parseInt(height, 10) - 25) / 52)`. `parseInt` strips the unit suffix and reads only the numeric prefix — `"60vh"` → 60, `"100%"` → 100. Most non-pixel values produce zero or far too few rows. Always pass a numeric pixel value so the row count matches the actual table height.
106
+
107
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-row--skeleton-rows-variant
108
+
109
+ ---
110
+
111
+ ### MEDIUM Providing noResultsButtonLabel without onNoResultsButtonClick (or vice versa)
112
+
113
+ Wrong:
114
+
115
+ ```jsx
116
+ <DataTable columns={columns} data={[]} height={400} noResultsButtonLabel="Clear filters" />
117
+ // or
118
+ <DataTable columns={columns} data={[]} height={400} onNoResultsButtonClick={handleClear} />
119
+ ```
120
+
121
+ Correct:
122
+
123
+ ```jsx
124
+ const handleClearFilters = useCallback(() => dispatch(clearFilters()), []);
125
+
126
+ <DataTable
127
+ columns={columns}
128
+ data={[]}
129
+ height={400}
130
+ noResultsButtonLabel="Clear filters"
131
+ onNoResultsButtonClick={handleClearFilters}
132
+ />;
133
+ ```
134
+
135
+ The empty-state CTA button only renders when both props are provided. Either alone has no visible effect.
136
+
137
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable--documentation
138
+
139
+ ---
140
+
141
+ ### MEDIUM Using isLoading instead of isSkeleton
142
+
143
+ Wrong:
144
+
145
+ ```jsx
146
+ <DataTable columns={columns} data={[]} height={500} isLoading={isLoading} />
147
+ ```
148
+
149
+ Correct:
150
+
151
+ ```jsx
152
+ <DataTable columns={columns} data={[]} height={500} isSkeleton={isLoading} />
153
+ ```
154
+
155
+ `isSkeleton` is the default choice. Both props replace the rows with a visual placeholder — the difference is purely graphical. `isSkeleton` renders structured placeholder rows sized to the table height; `isLoading` shows a generic spinner.
156
+
157
+ Two reasons to default to `isSkeleton`:
158
+
159
+ - `isSkeleton` is preferred when the expected load time is short (skeleton feels snappier). `isLoading` is appropriate only when the operation is known to take longer and a spinner is a deliberate UX choice.
160
+ - In applications using `pui-react-boilerplate`, a global loader overlay is already applied during async operations. Using `isLoading` in this context produces a double loader — the DataTable spinner renders beneath the app-level overlay simultaneously.
161
+
162
+ Only use `isLoading` when there is a specific, known business reason to prefer a spinner over skeleton rows.
163
+
164
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-row--skeleton-rows-variant
165
+
166
+ ---
167
+
168
+ ### HIGH Non-primitive feedback props not memoized cause unnecessary re-renders
169
+
170
+ Every non-primitive prop passed to DataTable must be a stable reference. For feedback states, the props that require memoization are:
171
+
172
+ | Prop | Type | Memoization |
173
+ | ------------------------ | ------------- | ------------------------------------------------------------- |
174
+ | `onNoResultsButtonClick` | callback | `useCallback` |
175
+ | `noResultsPlaceholder` | React element | `useMemo` or defined as a stable component outside the parent |
176
+
177
+ Non-stable references cause DataTable to re-render on every parent render cycle regardless of whether feedback state actually changed.
178
+
179
+ ---
180
+
181
+ See also: ds-data-table-setup/SKILL.md — pixel height requirement applies to both skeleton and virtualization
182
+
183
+ ---
184
+
185
+ ## When this isn't enough
186
+
187
+ If the documented patterns cannot satisfy the requirement, contact the Dimsum team:
188
+
189
+ **ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
190
+ **Partners:** Your organization's Dimsum point of contact