@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.
- package/dist/cjs/TruncatedTooltipText.js +94 -0
- package/dist/cjs/TruncatedTooltipText.js.map +7 -0
- package/dist/cjs/exported-related/EditableCell.js +1 -1
- package/dist/cjs/exported-related/EditableCell.js.map +2 -2
- package/dist/cjs/parts/Cells/Cell.js +2 -2
- package/dist/cjs/parts/Cells/Cell.js.map +2 -2
- package/dist/cjs/parts/Cells/CellFactory.js +2 -2
- package/dist/cjs/parts/Cells/CellFactory.js.map +2 -2
- package/dist/cjs/parts/Headers/HeaderCellTitle.js +2 -2
- package/dist/cjs/parts/Headers/HeaderCellTitle.js.map +2 -2
- package/dist/cjs/react-desc-prop-types.js +2 -2
- package/dist/cjs/react-desc-prop-types.js.map +1 -1
- package/dist/esm/TruncatedTooltipText.js +68 -0
- package/dist/esm/TruncatedTooltipText.js.map +7 -0
- package/dist/esm/exported-related/EditableCell.js +1 -1
- package/dist/esm/exported-related/EditableCell.js.map +2 -2
- package/dist/esm/parts/Cells/Cell.js +2 -2
- package/dist/esm/parts/Cells/Cell.js.map +2 -2
- package/dist/esm/parts/Cells/CellFactory.js +2 -2
- package/dist/esm/parts/Cells/CellFactory.js.map +2 -2
- package/dist/esm/parts/Headers/HeaderCellTitle.js +2 -2
- package/dist/esm/parts/Headers/HeaderCellTitle.js.map +2 -2
- package/dist/esm/react-desc-prop-types.js +2 -2
- package/dist/esm/react-desc-prop-types.js.map +1 -1
- package/dist/types/TruncatedTooltipText.d.ts +9 -0
- package/dist/types/tests/DSDataTable.get-owner-props-arguments-slots.test.d.ts +1 -0
- package/dist/types/tests/callbacks/editableCell.events.test.d.ts +1 -0
- package/dist/types/tests/playwright/DSDataTable.slot-contracts-dynamic.test.playwright.d.ts +1 -0
- package/dist/types/tests/playwright/DSDataTableDropIndicatorTestRenderer.d.ts +1 -0
- package/dist/types/tests/render/cellStyle.test.d.ts +1 -0
- package/package.json +33 -33
- package/skills/ds-data-table-boundaries/SKILL.md +363 -0
- package/skills/ds-data-table-columns/SKILL.md +273 -0
- package/skills/ds-data-table-expandable/SKILL.md +235 -0
- package/skills/ds-data-table-feedback/SKILL.md +190 -0
- package/skills/ds-data-table-filtering/SKILL.md +322 -0
- package/skills/ds-data-table-health-check/SKILL.md +172 -0
- package/skills/ds-data-table-migration/SKILL.md +201 -0
- package/skills/ds-data-table-pagination/SKILL.md +182 -0
- package/skills/ds-data-table-row-variants/SKILL.md +260 -0
- package/skills/ds-data-table-selection/SKILL.md +449 -0
- package/skills/ds-data-table-setup/SKILL.md +229 -0
- package/skills/ds-data-table-slots/SKILL.md +257 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-data-table-filtering
|
|
3
|
+
description: >
|
|
4
|
+
V2 filter system for @elliemae/ds-data-table. FILTER_TYPES V2 values (MULTI_SELECT_V2,
|
|
5
|
+
SELECT_V2, etc.), column-level filter config, filterOptions for multi-select, controlled
|
|
6
|
+
filters + onFiltersChange handler, applyOutOfTheBoxFilters, withFilterBar, filterBarProps,
|
|
7
|
+
composable DSFilterBar + PillsFromDataTableFilters. V1 filter strings are a11y-unsafe tech
|
|
8
|
+
debt — migrate to V2 whenever found in existing code, never use in new implementations.
|
|
9
|
+
type: core
|
|
10
|
+
library: ds-data-table
|
|
11
|
+
library_version: '3.60.0'
|
|
12
|
+
requires:
|
|
13
|
+
- ds-data-table-setup
|
|
14
|
+
- ds-filter-bar-setup
|
|
15
|
+
sources:
|
|
16
|
+
- '@elliemae/ds-data-table:dist/types/exported-related/FilterTypes.d.ts'
|
|
17
|
+
- '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```jsx
|
|
23
|
+
import { DataTable, FILTER_TYPES, applyOutOfTheBoxFilters } from '@elliemae/ds-data-table';
|
|
24
|
+
|
|
25
|
+
const columns = [
|
|
26
|
+
{ Header: 'ID', accessor: 'id', filter: FILTER_TYPES.MULTI_SELECT_V2 },
|
|
27
|
+
{
|
|
28
|
+
Header: 'Position',
|
|
29
|
+
accessor: 'position',
|
|
30
|
+
filter: FILTER_TYPES.MULTI_SELECT_V2,
|
|
31
|
+
filterOptions: [
|
|
32
|
+
{ dsId: '1', type: 'option', value: 'React dev', label: 'React dev' },
|
|
33
|
+
{ dsId: '2', type: 'option', value: 'Fullstack dev', label: 'Fullstack dev' },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// In your state manager — adapt to Redux/Zustand/Jotai
|
|
39
|
+
const [filters, setFilters] = /* state manager slice */;
|
|
40
|
+
const [filteredData, setFilteredData] = /* state manager slice */;
|
|
41
|
+
|
|
42
|
+
// onFiltersChange MUST be memoized
|
|
43
|
+
const onFiltersChange = useCallback((newFilters) => {
|
|
44
|
+
dispatch(setFilters(newFilters));
|
|
45
|
+
dispatch(setFilteredData(applyOutOfTheBoxFilters(originalData, newFilters)));
|
|
46
|
+
}, [originalData]);
|
|
47
|
+
|
|
48
|
+
<DataTable
|
|
49
|
+
columns={columns}
|
|
50
|
+
data={filteredData}
|
|
51
|
+
height={500}
|
|
52
|
+
withFilterBar
|
|
53
|
+
filters={filters}
|
|
54
|
+
onFiltersChange={onFiltersChange}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Core Patterns
|
|
59
|
+
|
|
60
|
+
### Available V2 filter types
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { FILTER_TYPES } from '@elliemae/ds-data-table';
|
|
64
|
+
|
|
65
|
+
// V2 types — always use these
|
|
66
|
+
FILTER_TYPES.MULTI_SELECT_V2; // multi-select dropdown
|
|
67
|
+
FILTER_TYPES.SELECT_V2; // single-select dropdown
|
|
68
|
+
FILTER_TYPES.CREATABLE_SELECT_V2; // single-select with user-creatable options
|
|
69
|
+
FILTER_TYPES.CREATABLE_MULTI_SELECT_V2; // multi-select with user-creatable options
|
|
70
|
+
FILTER_TYPES.SINGLE_DATE_V2; // single date picker
|
|
71
|
+
FILTER_TYPES.DATE_RANGE_V2; // date range picker
|
|
72
|
+
FILTER_TYPES.DATE_SWITCHER_V2; // date / date-range toggle
|
|
73
|
+
FILTER_TYPES.NUMBER_RANGE_V2; // numeric range input
|
|
74
|
+
FILTER_TYPES.FREE_TEXT_SEARCH_V2; // text input
|
|
75
|
+
FILTER_TYPES.CURRENCY_RANGE_V2; // currency range input
|
|
76
|
+
|
|
77
|
+
// V1 types — DO NOT USE, migrate on sight
|
|
78
|
+
FILTER_TYPES.MULTI_SELECT; // ← a11y-unsafe legacy
|
|
79
|
+
FILTER_TYPES.SELECT; // ← a11y-unsafe legacy
|
|
80
|
+
FILTER_TYPES.CREATABLE_SELECT; // ← a11y-unsafe legacy
|
|
81
|
+
FILTER_TYPES.CREATABLE_MULTI_SELECT; // ← a11y-unsafe legacy
|
|
82
|
+
FILTER_TYPES.SINGLE_DATE; // ← a11y-unsafe legacy
|
|
83
|
+
FILTER_TYPES.DATE_RANGE; // ← a11y-unsafe legacy
|
|
84
|
+
FILTER_TYPES.DATE_SWITCHER; // ← a11y-unsafe legacy
|
|
85
|
+
FILTER_TYPES.NUMBER_RANGE; // ← a11y-unsafe legacy
|
|
86
|
+
FILTER_TYPES.FREE_TEXT_SEARCH; // ← a11y-unsafe legacy
|
|
87
|
+
FILTER_TYPES.CURRENCY_RANGE; // ← a11y-unsafe legacy
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Composable filter bar with external DSFilterBar
|
|
91
|
+
|
|
92
|
+
The composable pattern pairs `PillsFromDataTableFilters` (this package) with `DSFilterBar` from `@elliemae/ds-filter-bar`. The `@elliemae/ds-filter-bar` assembly has its own a11y requirements (focus management, screen reader announcements) that are outside the scope of this skill.
|
|
93
|
+
|
|
94
|
+
**Before implementing the filter bar assembly:**
|
|
95
|
+
|
|
96
|
+
1. Load the `ds-filter-bar-setup` skill from `@elliemae/ds-filter-bar` — run `npx @tanstack/intent install` in the project or look for a `skills/` directory inside the package. That skill covers the full assembly: `useFilterBarPrevNextFocus`, `runAfterRemoval`, `ScreenReaderOnly` announcements, `DSFilterBarMenuButton` wiring, and all a11y requirements.
|
|
97
|
+
2. If no skill is found, ask the human to share a correct implementation example from the Dimsum storybook. Do not approximate the filter bar assembly from incomplete information — omitting the focus management hooks breaks keyboard accessibility.
|
|
98
|
+
|
|
99
|
+
The DataTable side of the wiring:
|
|
100
|
+
|
|
101
|
+
```jsx
|
|
102
|
+
import { DataTable, PillsFromDataTableFilters, applyOutOfTheBoxFilters } from '@elliemae/ds-data-table';
|
|
103
|
+
import {} from /* see @elliemae/ds-filter-bar skill or storybook */ '@elliemae/ds-filter-bar';
|
|
104
|
+
import { useCallback, useRef } from 'react';
|
|
105
|
+
|
|
106
|
+
const onFiltersChange = useCallback(
|
|
107
|
+
(newFilters) => {
|
|
108
|
+
dispatch(setFilters(newFilters));
|
|
109
|
+
dispatch(setFilteredData(applyOutOfTheBoxFilters(originalData, newFilters)));
|
|
110
|
+
},
|
|
111
|
+
[originalData],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// fallbackRef is shared with the DSFilterBar assembly (passed as fallbackRef to
|
|
115
|
+
// useFilterBarPrevNextFocus). When the last filter pill is removed, the filter bar
|
|
116
|
+
// has nowhere left to move focus — this ref tells it to land on the first column header.
|
|
117
|
+
const fallbackRef = useRef(null);
|
|
118
|
+
|
|
119
|
+
// dsDatatableHeadTh injects fallbackRef into the first focusable column header,
|
|
120
|
+
// completing the focus chain: last pill removed → focus moves to table header
|
|
121
|
+
const dsDatatableHeadTh = useCallback(({ columnId, firstFocuseableColumnHeaderId }) => {
|
|
122
|
+
if (columnId === firstFocuseableColumnHeaderId) return { innerRef: fallbackRef };
|
|
123
|
+
return {};
|
|
124
|
+
}, []);
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
/*
|
|
128
|
+
... DSFilterBar assembly here (from @elliemae/ds-filter-bar).
|
|
129
|
+
Use PillsFromDataTableFilters inside DSFilterBarContent.
|
|
130
|
+
Follow all a11y requirements from the ds-filter-bar skill or storybook —
|
|
131
|
+
do not omit focus management hooks or ScreenReaderOnly announcements.
|
|
132
|
+
*/
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
<DataTable
|
|
136
|
+
columns={columns}
|
|
137
|
+
data={filteredData}
|
|
138
|
+
height={500}
|
|
139
|
+
filters={filters}
|
|
140
|
+
onFiltersChange={onFiltersChange}
|
|
141
|
+
dsDatatableHeadTh={dsDatatableHeadTh}
|
|
142
|
+
/>;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Do not pass `withFilterBar` to DataTable when using a composable `DSFilterBar` — the filter bar is external.
|
|
146
|
+
|
|
147
|
+
### filterBarProps for inline filter bar customization
|
|
148
|
+
|
|
149
|
+
`filterBarAddonRenderer` must be a **stable React component reference** — defined at module level or imported, never inline or via `useCallback`. Defining it inline (even with `useCallback`) creates a new component type on re-render, causing React to unmount and remount the addon on every render cycle, producing erratic behavior whenever the addon uses hooks or internal state.
|
|
150
|
+
|
|
151
|
+
The addon needs access to filter state and clear action from outside itself. Because `FilterBarAddon` is at module level, it reads what it needs at render time rather than closing over parent state — that is what makes this pattern work. Adapt to your project's state management stack:
|
|
152
|
+
|
|
153
|
+
- **Redux**: define `FilterBarAddon` at module level and connect directly with `useSelector`/`useDispatch` — no context needed
|
|
154
|
+
- **Zustand / Jotai**: subscribe to the relevant store or atom directly inside `FilterBarAddon`
|
|
155
|
+
- **React Context**: use the pattern below (last resort — prefer the state manager options above)
|
|
156
|
+
|
|
157
|
+
```jsx
|
|
158
|
+
import { DSButtonV3 } from '@elliemae/ds-button-v2';
|
|
159
|
+
import { ScreenReaderOnly } from '@elliemae/ds-accessibility';
|
|
160
|
+
import { DataTable, applyOutOfTheBoxFilters } from '@elliemae/ds-data-table';
|
|
161
|
+
import React, { createContext, useCallback, useContext, useState, useMemo } from 'react';
|
|
162
|
+
|
|
163
|
+
const FilterBarAddonContext = createContext(null);
|
|
164
|
+
|
|
165
|
+
const FilterBarAddon = () => {
|
|
166
|
+
const { filters, onClearFilters } = useContext(FilterBarAddonContext);
|
|
167
|
+
if (filters.length === 0) return null;
|
|
168
|
+
return (
|
|
169
|
+
<DSButtonV3 buttonType="text" onClick={onClearFilters}>
|
|
170
|
+
Clear all filters
|
|
171
|
+
</DSButtonV3>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const [announcement, setAnnouncement] = useState('');
|
|
176
|
+
|
|
177
|
+
const onFiltersChange = useCallback(
|
|
178
|
+
(newFilters) => {
|
|
179
|
+
dispatch(setFilters(newFilters));
|
|
180
|
+
dispatch(setFilteredData(applyOutOfTheBoxFilters(originalData, newFilters)));
|
|
181
|
+
},
|
|
182
|
+
[originalData],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const handleClearFilters = useCallback(() => {
|
|
186
|
+
onFiltersChange([]);
|
|
187
|
+
setAnnouncement('All filters cleared');
|
|
188
|
+
}, [onFiltersChange]);
|
|
189
|
+
|
|
190
|
+
const ctxValue = useMemo(() => ({ filters, onClearFilters: handleClearFilters }), [filters, handleClearFilters]);
|
|
191
|
+
|
|
192
|
+
<FilterBarAddonContext.Provider value={ctxValue}>
|
|
193
|
+
<ScreenReaderOnly aria-live="polite">{announcement}</ScreenReaderOnly>
|
|
194
|
+
<DataTable
|
|
195
|
+
columns={columns}
|
|
196
|
+
data={filteredData}
|
|
197
|
+
height={500}
|
|
198
|
+
withFilterBar
|
|
199
|
+
filters={filters}
|
|
200
|
+
filterBarProps={{ filterBarAddonRenderer: FilterBarAddon }}
|
|
201
|
+
onFiltersChange={onFiltersChange}
|
|
202
|
+
/>
|
|
203
|
+
</FilterBarAddonContext.Provider>;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Common Mistakes
|
|
207
|
+
|
|
208
|
+
### CRITICAL Using V1 filter string literals in new or existing code
|
|
209
|
+
|
|
210
|
+
Wrong:
|
|
211
|
+
|
|
212
|
+
```jsx
|
|
213
|
+
{ Header: 'Position', accessor: 'position', filter: FILTER_TYPES.MULTI_SELECT }
|
|
214
|
+
{ Header: 'Status', accessor: 'status', filter: FILTER_TYPES.SELECT }
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Correct:
|
|
218
|
+
|
|
219
|
+
```jsx
|
|
220
|
+
{ Header: 'Position', accessor: 'position', filter: FILTER_TYPES.MULTI_SELECT_V2 }
|
|
221
|
+
{ Header: 'Status', accessor: 'status', filter: FILTER_TYPES.SELECT_V2 }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
V1 filter types are not a11y safe. When found in existing code, migrate all instances — not just the one in scope.
|
|
225
|
+
|
|
226
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-filtersv2--multi-select-filter-vs-multi-select-v-2
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### HIGH withFilterBar without the filters + onFiltersChange controlled pair
|
|
231
|
+
|
|
232
|
+
Wrong:
|
|
233
|
+
|
|
234
|
+
```jsx
|
|
235
|
+
<DataTable columns={columns} data={rows} height={500} withFilterBar />
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Correct:
|
|
239
|
+
|
|
240
|
+
```jsx
|
|
241
|
+
<DataTable
|
|
242
|
+
columns={columns}
|
|
243
|
+
data={filteredData}
|
|
244
|
+
height={500}
|
|
245
|
+
withFilterBar
|
|
246
|
+
filters={filters}
|
|
247
|
+
onFiltersChange={onFiltersChange}
|
|
248
|
+
/>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`withFilterBar` only renders the UI. Without `filters` and `onFiltersChange`, filter interactions produce no result.
|
|
252
|
+
|
|
253
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-filtersv2--multi-select-filter-v-2
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
### HIGH onFiltersChange updates filter state but does not produce updated data
|
|
258
|
+
|
|
259
|
+
DataTable does not filter data internally. `onFiltersChange` receives the new filter state — the app is entirely responsible for producing the updated `data` array that reflects those filters. Storing the filters without updating `data` leaves the table showing stale rows regardless of what the user selects.
|
|
260
|
+
|
|
261
|
+
**Before writing the filtering logic, determine which filtering model applies:**
|
|
262
|
+
|
|
263
|
+
**Server-side / API-based filtering** — the standard model for any non-trivial dataset. On filter change, dispatch the new filter state and trigger an API call with the filters as query parameters. The response is the filtered dataset.
|
|
264
|
+
|
|
265
|
+
```jsx
|
|
266
|
+
const onFiltersChange = useCallback((newFilters) => {
|
|
267
|
+
dispatch(setFilters(newFilters));
|
|
268
|
+
dispatch(fetchFilteredData(newFilters)); // API call with filters as query params
|
|
269
|
+
}, []);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Client-side filtering** — only appropriate for very small datasets or as a temporary workaround while backend filtering endpoints are being developed. Triple-check that this is actually the correct model before implementing. Filtering is by industry standards a backend concern — backends have specialized tools for it (SQL WHERE clauses, database engine algorithms optimized for exactly this problem). Implementing it on the frontend duplicates that concern on the wrong layer with inferior tools, making the flow suboptimal and fragile. Applying filters to an in-memory array is better than offering no filtering at all, but it covers a backend limitation rather than solving the underlying problem. It also introduces stale data risk: the in-memory array reflects the dataset at load time; if the source changes and the array is never refreshed, users are filtering a snapshot, not the live data. Server-side filtering inherently avoids this because every request fetches fresh data from the authoritative source. The cost compounds further when combined with sorting and pagination.
|
|
273
|
+
|
|
274
|
+
```jsx
|
|
275
|
+
// Only use applyOutOfTheBoxFilters when client-side filtering is confirmed correct
|
|
276
|
+
const onFiltersChange = useCallback(
|
|
277
|
+
(newFilters) => {
|
|
278
|
+
dispatch(setFilters(newFilters));
|
|
279
|
+
dispatch(setFilteredData(applyOutOfTheBoxFilters(originalData, newFilters)));
|
|
280
|
+
},
|
|
281
|
+
[originalData],
|
|
282
|
+
);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-filtersv2--multi-select-filter-v-2
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### HIGH Non-primitive filtering props not memoized cause unnecessary re-renders
|
|
290
|
+
|
|
291
|
+
Every non-primitive prop passed to DataTable must be a stable reference. For filtering, the props that require memoization are:
|
|
292
|
+
|
|
293
|
+
| Prop | Type | Memoization |
|
|
294
|
+
| ------------------------------------- | --------- | -------------------------------------- |
|
|
295
|
+
| `onFiltersChange` | callback | `useCallback` |
|
|
296
|
+
| `filterBarProps` | object | `useMemo` (if constructed inline) |
|
|
297
|
+
| `filterOptions` on column definitions | array | `useMemo` on the columns array |
|
|
298
|
+
| `filterBarAddonRenderer` | component | defined at module level — never inline |
|
|
299
|
+
|
|
300
|
+
Non-stable references cause DataTable to re-render on every parent render cycle regardless of whether filtering state actually changed. `filterBarAddonRenderer` is covered in detail in the `filterBarProps` pattern above.
|
|
301
|
+
|
|
302
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-filtersv2--multi-select-filter-v-2
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### HIGH Tension: custom Cell renderers and filter display diverge
|
|
307
|
+
|
|
308
|
+
V2 filter types derive display values from the raw data value in each cell. Custom Cell renderers change the display layer independently — the filter system has no visibility into what the custom renderer shows. Agents adding filters to columns with custom cells often produce tables where filter pills and cell display are inconsistent.
|
|
309
|
+
|
|
310
|
+
When a column uses a custom Cell renderer, `applyOutOfTheBoxFilters` is no longer sufficient — it operates on raw data values and cannot account for the transformed presentation the renderer produces. The application is responsible for implementing its own filtering logic for those columns, using the IoC patterns DataTable exposes (`onFiltersChange` callback, `filters` controlled state). The OOB filter algorithms are designed to cover standard data-value scenarios only; anything beyond that is the application's domain.
|
|
311
|
+
|
|
312
|
+
See also: ds-data-table-migration/SKILL.md — V1 to V2 migration patterns
|
|
313
|
+
See also: ds-filter-bar-setup/SKILL.md — full DSFilterBar assembly with @elliemae/ds-filter-bar primitives
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## When this isn't enough
|
|
318
|
+
|
|
319
|
+
If the documented patterns cannot satisfy the requirement, contact the Dimsum team:
|
|
320
|
+
|
|
321
|
+
**ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
|
|
322
|
+
**Partners:** Your organization's Dimsum point of contact
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-data-table-health-check
|
|
3
|
+
description: >
|
|
4
|
+
Audit the current state of a @elliemae/ds-data-table implementation. Use before
|
|
5
|
+
starting a task that depends on DataTable, mid-task to verify a foundation before
|
|
6
|
+
building on it, or when diagnosing unexpected behavior. Produces a complete state
|
|
7
|
+
report: what is correct and sound, what requires fixing, what is suboptimal, and
|
|
8
|
+
what gaps may affect the current task.
|
|
9
|
+
type: health-check
|
|
10
|
+
library: ds-data-table
|
|
11
|
+
library_version: '3.60.0'
|
|
12
|
+
requires:
|
|
13
|
+
- ds-data-table-setup
|
|
14
|
+
- ds-data-table-slots
|
|
15
|
+
- ds-data-table-selection
|
|
16
|
+
- ds-data-table-filtering
|
|
17
|
+
- ds-data-table-columns
|
|
18
|
+
- ds-data-table-pagination
|
|
19
|
+
- ds-data-table-expandable
|
|
20
|
+
- ds-data-table-row-variants
|
|
21
|
+
- ds-data-table-feedback
|
|
22
|
+
- ds-data-table-boundaries
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Purpose
|
|
26
|
+
|
|
27
|
+
This skill audits an existing DataTable implementation. It is not a setup guide — it does not tell you how to build a DataTable. It tells you the current state of one that already exists.
|
|
28
|
+
|
|
29
|
+
**Use it:**
|
|
30
|
+
|
|
31
|
+
- Before starting a task that heavily involves a DataTable — to understand what you are about to touch before you touch it
|
|
32
|
+
- Before making a DataTable load-bearing for a new feature — to verify the foundation is sound
|
|
33
|
+
- When something is behaving unexpectedly — to surface what the implementation gets wrong
|
|
34
|
+
|
|
35
|
+
The output is a prioritized state report. A clean report means you can proceed with confidence. A report with critical issues means fix those before doing anything else.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Load the embedded knowledge
|
|
40
|
+
|
|
41
|
+
Load the following skills **as reference knowledge only**. You are reading their embedded understanding of correct patterns — their Common Mistakes, practically mandatory props, and OOB-first guidance become the diagnostic lens. You are not running them as tasks. Do not execute any setup or implementation step from these skills.
|
|
42
|
+
|
|
43
|
+
- `ds-data-table-setup` — required props, state management, a11y baseline, height/uniqueRowAccessor/domIdAffix requirements
|
|
44
|
+
- `ds-data-table-slots` — slot injection surface, OOB-first hierarchy, event listener red flags
|
|
45
|
+
- `ds-data-table-selection` — auto-injection model, cross-page selection, selectedControl behavior
|
|
46
|
+
- `ds-data-table-filtering` — V1 vs V2 filter types, withFilterBar requirements, applyOutOfTheBoxFilters scope
|
|
47
|
+
- `ds-data-table-columns` — column config, sort/resize callbacks, custom Cell escalation path
|
|
48
|
+
- `ds-data-table-pagination` — Pagination render prop vs deprecated lowercase, data slicing responsibility
|
|
49
|
+
- `ds-data-table-expandable` — tableRowDetails deprecation, controlled pair requirement, uniqueRowAccessor dependency
|
|
50
|
+
- `ds-data-table-row-variants` — getRowVariant + data format dual requirement, groupBy utility
|
|
51
|
+
- `ds-data-table-feedback` — height constraint for skeleton, isLoading vs isSkeleton distinction
|
|
52
|
+
- `ds-data-table-boundaries` — useWholeStore smell, DataTable-as-form prohibition
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Procedure
|
|
57
|
+
|
|
58
|
+
### Step 1 — Locate and read the implementation
|
|
59
|
+
|
|
60
|
+
Find every file where `DataTable` from `@elliemae/ds-data-table` is rendered. For each instance:
|
|
61
|
+
|
|
62
|
+
- Read the full props being passed
|
|
63
|
+
- Read every callback handler wired to the DataTable
|
|
64
|
+
- Read where the state driving the DataTable lives (state manager slice, store, atoms, or — a finding in itself — `useState`)
|
|
65
|
+
- Read any column definitions, especially `Cell` and `Header` as component references
|
|
66
|
+
- Note any slot props (`dsDatatableCellsContainer`, `dsDatatableCell`, `dsDatatableRow`, `dsDatatableHeadTh`)
|
|
67
|
+
- Note if `renderRowActions` is present
|
|
68
|
+
- Note if `withFilterBar`, `filters`, `onFiltersChange` are present
|
|
69
|
+
- Note if `selection`, `onSelectionChange`, `selectSingle` are present
|
|
70
|
+
- Note if `isExpandable`, `expandedRows`, `onRowExpand` are present
|
|
71
|
+
- Note if `Pagination` (capital P) or `pagination` (lowercase) is present
|
|
72
|
+
|
|
73
|
+
### Step 2 — Apply the diagnostic checks
|
|
74
|
+
|
|
75
|
+
Work through the following checks. For each finding, assign a severity:
|
|
76
|
+
|
|
77
|
+
**CRITICAL** — prevents correct function, a11y failure, or silent data corruption; fix before proceeding
|
|
78
|
+
**HIGH** — meaningful risk of regression, wrong behavior, or performance degradation
|
|
79
|
+
**MEDIUM** — suboptimal but not immediately breaking
|
|
80
|
+
**SOUND** — correct; note it so you know what you can rely on
|
|
81
|
+
|
|
82
|
+
#### Foundation checks (from ds-data-table-setup)
|
|
83
|
+
|
|
84
|
+
- [ ] `height` is a numeric pixel value — non-px silently defeats virtualization and scroll
|
|
85
|
+
- [ ] `uniqueRowAccessor` is present when selection, expand, or drag & drop is used
|
|
86
|
+
- [ ] `dsDataTableTableWrapper={{ 'aria-label': '...' }}` is present — absence is a WCAG 4.1.2 VPAT failure
|
|
87
|
+
- [ ] `domIdAffix` is an explicit string when multiple DataTable instances coexist on the page
|
|
88
|
+
- [ ] All DataTable-driving state lives in the project's state manager — not `useState`
|
|
89
|
+
- [ ] No `useEffect` watches DataTable callback-driven state to trigger secondary logic
|
|
90
|
+
|
|
91
|
+
#### Column checks (from ds-data-table-columns)
|
|
92
|
+
|
|
93
|
+
- [ ] Column `width` values are pixel numbers — non-px is unsupported
|
|
94
|
+
- [ ] `isResizeable` is not combined with `colsLayoutStyle='auto'` — incompatible
|
|
95
|
+
- [ ] `onColumnSort` is used, not deprecated `onColumnSortChange`
|
|
96
|
+
- [ ] `onColumnSizeChange` is used, not deprecated `onColumnResize`
|
|
97
|
+
- [ ] Columns using `Cell` as a React component: flag each one — was OOB or slot injection considered first?
|
|
98
|
+
|
|
99
|
+
#### Selection checks (from ds-data-table-selection)
|
|
100
|
+
|
|
101
|
+
- [ ] `selection` prop is present — DataTable auto-injects the checkbox column when truthy; `multiSelectColumn` import is for customization only
|
|
102
|
+
- [ ] `onSelectionChange` is present when `selection` is present — without it, selection silently does nothing
|
|
103
|
+
- [ ] `uniqueRowAccessor` is set — without it, selection maps to wrong rows
|
|
104
|
+
- [ ] Select-all cross-page: if `selectedControl === 'All'` and the dataset is paginated, is `newSelection` being merged with existing selection rather than replacing it?
|
|
105
|
+
|
|
106
|
+
#### Filtering checks (from ds-data-table-filtering)
|
|
107
|
+
|
|
108
|
+
- [ ] All `column.filter` values use `FILTER_TYPES.*_V2` — V1 strings are a11y-unsafe legacy
|
|
109
|
+
- [ ] `withFilterBar={true}` is accompanied by both `filters` and `onFiltersChange` — either alone silently does nothing
|
|
110
|
+
- [ ] `applyOutOfTheBoxFilters` is used only for client-side filtering of small datasets — not as the default model for any non-trivial dataset
|
|
111
|
+
- [ ] Columns with custom `Cell` renderers that also have filters: flag — `applyOutOfTheBoxFilters` operates on raw data values, not on what the custom renderer displays
|
|
112
|
+
|
|
113
|
+
#### Pagination checks (from ds-data-table-pagination)
|
|
114
|
+
|
|
115
|
+
- [ ] Capital-P `Pagination` render prop is used — lowercase `pagination` prop is deprecated
|
|
116
|
+
- [ ] `TablePagination` is defined at module level — not inside the component body
|
|
117
|
+
- [ ] `data` passed to DataTable is the current page slice — DataTable does not paginate internally
|
|
118
|
+
- [ ] Page index resets to 1 on filter change where applicable
|
|
119
|
+
|
|
120
|
+
#### Expandable checks (from ds-data-table-expandable)
|
|
121
|
+
|
|
122
|
+
- [ ] `tableRowDetails` is not used — deprecated; WCAG violations; propose page-level alternative
|
|
123
|
+
- [ ] `expandedRows` and `onRowExpand` are both present when `isExpandable` is set
|
|
124
|
+
- [ ] `uniqueRowAccessor` is set — required for expand state stability
|
|
125
|
+
|
|
126
|
+
#### Slot checks (from ds-data-table-slots)
|
|
127
|
+
|
|
128
|
+
- [ ] Any `dsDatatableCellsContainer` or `dsDatatableCell` callbacks: confirm they return plain props objects with no hooks inside
|
|
129
|
+
- [ ] Any `dsDatatableCellsContainer` or `dsDatatableCell` callbacks: are event listeners (`onClick`, `onKeyDown`) being injected? **Flag as HIGH** — an OOB callback should handle the interaction; if not, stop and surface to the human in the loop before proceeding
|
|
130
|
+
- [ ] All slot callbacks are wrapped in `useCallback`
|
|
131
|
+
- [ ] `aria-*` and `data-*` injections target `CELL_CONTAINER` (`dsDatatableCellsContainer`), not `ROW` (`dsDatatableRow`) — injecting into ROW is a silent a11y failure
|
|
132
|
+
|
|
133
|
+
#### Feedback checks (from ds-data-table-feedback)
|
|
134
|
+
|
|
135
|
+
- [ ] `isSkeleton` is preferred over `isLoading` for initial data fetch
|
|
136
|
+
- [ ] `noResultsButtonLabel` and `onNoResultsButtonClick` are either both present or both absent — either alone has no visible effect
|
|
137
|
+
- [ ] If `isSkeleton` is used, `height` is a numeric pixel value — non-px silently prevents skeleton rows from rendering
|
|
138
|
+
|
|
139
|
+
#### Boundary checks (from ds-data-table-boundaries)
|
|
140
|
+
|
|
141
|
+
- [ ] No call to `useWholeStore()` — it re-renders on any change anywhere; use CellRendererProps IoC arguments instead
|
|
142
|
+
- [ ] DataTable is not being used to drive full form data entry — use `@elliemae/ds-grid` for that
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### Step 3 — Produce the state report
|
|
147
|
+
|
|
148
|
+
Organize findings into four sections:
|
|
149
|
+
|
|
150
|
+
**Foundation — what is correct and sound**
|
|
151
|
+
List what the implementation gets right. Be specific. This is what you can rely on when making changes.
|
|
152
|
+
|
|
153
|
+
**Critical issues — fix before proceeding**
|
|
154
|
+
Each item with the specific prop, file, and line where the problem is. Link back to the relevant skill section for the fix.
|
|
155
|
+
|
|
156
|
+
**Warnings — should address, not blocking**
|
|
157
|
+
Each item with context on the risk. Note whether it is a quick fix or requires design discussion.
|
|
158
|
+
|
|
159
|
+
**Awareness — no action, but good to know**
|
|
160
|
+
Suboptimal patterns that are not breaking but may become relevant as the task evolves.
|
|
161
|
+
|
|
162
|
+
**Gaps relevant to this task**
|
|
163
|
+
Based on what the current task requires, are there missing props or wiring that will be needed? Surface these now rather than mid-implementation.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## What this skill does NOT do
|
|
168
|
+
|
|
169
|
+
- It does not generate or modify code
|
|
170
|
+
- It does not run the setup skills as tasks
|
|
171
|
+
- It does not replace human judgment on trade-offs — critical issues are surfaced, not automatically fixed
|
|
172
|
+
- It does not test the component — use the slot contract tests and compliance tests for that
|