@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,363 @@
1
+ ---
2
+ name: ds-data-table-boundaries
3
+ description: >
4
+ When NOT to use @elliemae/ds-data-table. Row actions column via renderRowActions +
5
+ DSMenuButton (not Column.Cell). State access priority inside custom Cell renderers:
6
+ (1) project state manager for app-level state; (2) CellRendererProps IoC arguments
7
+ (row, column, cell, isRowSelected, isDisabledRow, draggableProps); (3) useWholeStore
8
+ last resort only. Arrow key conflict handling required when DSMenuButton is inside a
9
+ DataTable cell. DataTable-as-form forbidden — use @elliemae/ds-grid.
10
+ type: core
11
+ library: ds-data-table
12
+ library_version: '3.60.0'
13
+ sources:
14
+ - '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
15
+ ---
16
+
17
+ ## useWholeStore — exported hook, highest render cost
18
+
19
+ `useWholeStore` is the only exported store hook. It subscribes to all internal DataTable state with no selector and re-renders the consumer on any state change anywhere in the table.
20
+
21
+ ## State access hierarchy inside custom Cell renderers
22
+
23
+ When a custom Cell component (`Column.Cell`) needs state, follow this priority order:
24
+
25
+ **1. Project state manager** — for app-level state (filter values, pagination, global UI flags). Connect directly to the Redux selector, Zustand store, or Jotai atom. This produces the best performance (selective subscriptions) and the best app-side maintainability.
26
+
27
+ **2. CellRendererProps IoC arguments** — for DataTable-provided context. These are passed in directly with zero subscription cost:
28
+
29
+ ```ts
30
+ interface CellRendererProps {
31
+ row: InternalRow;
32
+ column: InternalColumn;
33
+ cell: Cell;
34
+ isRowSelected: boolean;
35
+ isDisabledRow?: boolean;
36
+ isDragOverlay: boolean;
37
+ shouldAddExpandCell: boolean;
38
+ draggableProps?: DraggablePropsT;
39
+ domIdAffix?: string;
40
+ }
41
+ ```
42
+
43
+ **3. `useWholeStore` — last resort only.** Subscribes to all internal DataTable state with no selector. Re-renders the consumer on any change anywhere in the table. If you have reached for `useWholeStore`, stop and reconsider whether the state manager or an OOB feature can satisfy the requirement instead.
44
+
45
+ If neither the state manager nor `CellRendererProps` cover the requirement, that is an architecture signal: the Cell is doing too much, or the requirement should route through an OOB feature.
46
+
47
+ ## When NOT to use DataTable
48
+
49
+ **Reach for a layout grid with form components instead when:**
50
+
51
+ - The layout is a form with many editable fields arranged in rows and columns
52
+ - The primary purpose is data entry, not data display
53
+ - There are no sorting, filtering, selection, or pagination requirements
54
+
55
+ In this design system, `@elliemae/ds-grid` is the correct layout primitive for that use case. Consult its own documentation for usage.
56
+
57
+ **Editable cells are acceptable when:**
58
+
59
+ - Used sparingly for inline editing of individual values in a data display context
60
+ - `column.editable: 'ds-edit-text'` or `column.editable: 'ds-edit-combobox'` applies to a small number of columns
61
+
62
+ **Editable cells are NOT acceptable when:**
63
+
64
+ - The entire table is editable and functions as a data-entry form
65
+
66
+ ## Row actions column — renderRowActions
67
+
68
+ When a column is needed exclusively for per-row action buttons, use the dedicated `renderRowActions` prop — not `Column.Cell`. Two valid patterns depending on the number of action levels:
69
+
70
+ ### Single-level flat actions — Toolbar + DSToolbarV2
71
+
72
+ Use when each action is a direct button (delete, print, copy, edit) with no sub-menus.
73
+
74
+ ```jsx
75
+ import { DataTable, Toolbar } from '@elliemae/ds-data-table';
76
+ import { DSToolbarV2, DSToolbarItemV2 } from '@elliemae/ds-toolbar-v2';
77
+ import { DSButtonV3 } from '@elliemae/ds-button-v2';
78
+
79
+ const RowActionsRenderer = React.memo((props) => (
80
+ <Toolbar {...props}>
81
+ <DSToolbarV2 withDepth={false}>
82
+ <DSToolbarItemV2
83
+ render={(itemProps) => (
84
+ <DSButtonV3 autoFocus buttonType="icon" {...itemProps} aria-label="Delete">
85
+ {/* icon */}
86
+ </DSButtonV3>
87
+ )}
88
+ />
89
+ {/* ...additional DSToolbarItemV2 per action */}
90
+ </DSToolbarV2>
91
+ </Toolbar>
92
+ ));
93
+
94
+ const renderRowActions = { columnWidth: 32, renderer: RowActionsRenderer };
95
+
96
+ <DataTable columns={columns} data={rows} height={500} renderRowActions={renderRowActions} />;
97
+ ```
98
+
99
+ `Toolbar` (from `@elliemae/ds-data-table`) is a required wrapper — it handles the DataTable keyboard integration. `DSToolbarItemV2` passes `itemProps` which includes `tabIndex` and `innerRef` wiring automatically.
100
+
101
+ ### Multi-level menus — DSMenuButton
102
+
103
+ Use when actions have sub-menus or multi-level structure.
104
+
105
+ ```tsx
106
+ import { BUTTON_TYPES } from '@elliemae/ds-button-v2';
107
+ import { type DSDataTableT } from '@elliemae/ds-data-table';
108
+ import { Grid } from '@elliemae/ds-grid';
109
+ import { MoreOptionsVert } from '@elliemae/ds-icons';
110
+ import { DSMenuButton } from '@elliemae/ds-menu-button';
111
+
112
+ const RowActionsRenderer: DSDataTableT.RenderRowActionsConfig['renderer'] = React.memo(
113
+ ({ isRowSelected, cell }) => {
114
+ const { ref } = cell as unknown as { ref: React.MutableRefObject<HTMLButtonElement> };
115
+
116
+ const handleConflictingKeyPresses = React.useCallback<React.KeyboardEventHandler<HTMLDivElement>>(
117
+ (event) => {
118
+ if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
119
+ event.stopPropagation();
120
+ event.preventDefault();
121
+ }
122
+ },
123
+ [],
124
+ );
125
+
126
+ return (
127
+ <Grid minHeight="100%" minWidth="100%" justifyContent="center" alignItems="center" onKeyDown={handleConflictingKeyPresses}>
128
+ <DSMenuButton
129
+ innerRef={ref}
130
+ tabIndex={isRowSelected ? 0 : -1}
131
+ buttonType={BUTTON_TYPES.ICON}
132
+ aria-label="Show row actions, to resume table row navigation press tab"
133
+ {/* ...DSMenuButton props per scenario (options, onActivateItem, etc.) */}
134
+ >
135
+ <MoreOptionsVert />
136
+ </DSMenuButton>
137
+ </Grid>
138
+ );
139
+ },
140
+ );
141
+
142
+ const renderRowActions: DSDataTableT.RenderRowActionsConfig = { columnWidth: 32, renderer: RowActionsRenderer };
143
+
144
+ <DataTable columns={columns} data={rows} height={500} renderRowActions={renderRowActions} />
145
+ ```
146
+
147
+ **`onKeyDown` on the Grid wrapper is required.** `DSMenuButton` uses ArrowUp/ArrowDown to navigate menu items. Without `stopPropagation`, those keystrokes also move the DataTable's row focus — the two navigation models conflict silently.
148
+
149
+ **`aria-label` must include the return instruction.** Screen reader users cannot infer how to get back to table navigation. The label `"Show row actions, to resume table row navigation press tab"` makes this explicit.
150
+
151
+ ## Common Mistakes
152
+
153
+ ### CRITICAL Using DataTable to implement a form in rows and columns
154
+
155
+ Wrong:
156
+
157
+ ```jsx
158
+ // DataTable driving an editable grid of loan fields
159
+ const columns = [
160
+ { Header: 'Loan Number', accessor: 'loanNumber', editable: 'ds-edit-text' },
161
+ { Header: 'Borrower', accessor: 'borrower', editable: 'ds-edit-combobox' },
162
+ { Header: 'Amount', accessor: 'amount', editable: 'ds-edit-text' },
163
+ { Header: 'Rate', accessor: 'rate', editable: 'ds-edit-text' },
164
+ // ... every field is editable
165
+ ];
166
+ <DataTable columns={columns} data={formRows} height={500} />;
167
+ ```
168
+
169
+ Correct: use a layout grid with form components instead — `@elliemae/ds-grid` in this design system. DataTable has no role in data entry.
170
+
171
+ DataTable is a data-viewing component. Turning it into a fully editable form violates its semantic role, breaks a11y, and loses all data management features.
172
+
173
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-grid--documentation
174
+
175
+ ---
176
+
177
+ ### HIGH Reaching for useWholeStore — always signals being outside the IoC surface
178
+
179
+ Wrong:
180
+
181
+ ```jsx
182
+ import { useWholeStore } from '@elliemae/ds-data-table';
183
+
184
+ const MyCell = ({ row, column }) => {
185
+ const { tableProps } = useWholeStore(); // subscribes to everything
186
+ return <span style={{ color: tableProps.filters.length ? 'red' : 'black' }}>{row.original.name}</span>;
187
+ };
188
+ ```
189
+
190
+ Correct:
191
+
192
+ ```jsx
193
+ // These examples are illustrative only — the style logic shown does not represent
194
+ // a real use case. The point is to show where each category of state comes from.
195
+
196
+ // Option A — app-level state comes from the project state manager directly
197
+ const MyCell = ({ row, column }) => {
198
+ const activeFilters = useSelector(selectTableFilters); // Redux/Zustand/Jotai
199
+ return <span style={{ color: activeFilters.length ? 'red' : 'black' }}>{row.original.name}</span>;
200
+ };
201
+
202
+ // Option B — DataTable-provided context comes from CellRendererProps IoC arguments
203
+ const MyCell = ({ row, isRowSelected }) => {
204
+ return <span style={{ fontWeight: isRowSelected ? 'bold' : 'normal' }}>{row.original.name}</span>;
205
+ };
206
+ ```
207
+
208
+ `useWholeStore` re-renders the consumer on any change in either internal store. All out-of-the-box interactions expose everything needed via IoC callback arguments. Needing `useWholeStore` means the Cell requires something outside its intended surface — stop, evaluate OOB alternatives, and if blocked, contact the Dimsum team through your organization's established channel (ICE engineers: Teams/Jira; partners: your org's point of contact for Dimsum).
209
+
210
+ ---
211
+
212
+ ### HIGH Using editable cells as the primary data-entry mechanism for a form
213
+
214
+ Wrong:
215
+
216
+ ```jsx
217
+ // Every column is editable — DataTable used as a spreadsheet-style form
218
+ const columns = allFormFields.map((field) => ({
219
+ Header: field.label,
220
+ accessor: field.key,
221
+ editable: 'ds-edit-text',
222
+ }));
223
+ ```
224
+
225
+ Correct:
226
+
227
+ ```jsx
228
+ // Sparse inline editing is acceptable
229
+ const columns = [
230
+ { Header: 'Name', accessor: 'name' },
231
+ { Header: 'Notes', accessor: 'notes', editable: 'ds-edit-text' }, // one editable column
232
+ ];
233
+ ```
234
+
235
+ Editable cells (`ds-edit-text`, `ds-edit-combobox`) are tolerable for sparse inline editing of individual values. Using them to turn DataTable into a primary data-entry form violates its semantic role.
236
+
237
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable--documentation
238
+
239
+ ---
240
+
241
+ ### CRITICAL Interactive element in custom Cell without tabIndex + cell.ref breaks keyboard navigation
242
+
243
+ Wrong:
244
+
245
+ ```jsx
246
+ // Button always tabbable — bypasses the table's row keyboard navigation model
247
+ Cell: ({ row }) => (
248
+ <DSButtonV3 onClick={() => handleAction(row.original.id)}>
249
+ Action
250
+ </DSButtonV3>
251
+ ),
252
+ ```
253
+
254
+ Correct:
255
+
256
+ ```jsx
257
+ // tabIndex and cell.ref wire the element into the table's keyboard navigation model
258
+ Cell: ({ row, cell, isRowSelected }) => (
259
+ <DSButtonV3
260
+ tabIndex={isRowSelected ? 0 : -1}
261
+ innerRef={cell.ref}
262
+ onClick={() => handleAction(row.original.id)}
263
+ >
264
+ Action
265
+ </DSButtonV3>
266
+ ),
267
+ ```
268
+
269
+ The DataTable uses an Enter-to-enter-row keyboard model: Tab moves between rows; Enter "enters" a row to navigate its interactive elements; Escape exits back to row navigation. An interactive element with `tabIndex={0}` unconditionally is permanently tabbable from outside the row — it intercepts Tab focus and breaks the row navigation model entirely.
270
+
271
+ Two props from `CellRendererProps` are required:
272
+
273
+ - `tabIndex={isRowSelected ? 0 : -1}` — makes the element focusable only when the row is entered
274
+ - `cell.ref` — registers the element as the row's focusable target so Enter moves focus to it
275
+
276
+ For components that accept `innerRef`: pass `innerRef={cell.ref}`. For components that wrap the element: pass `containerProps={{ ref: cell.ref }}`. For standard DOM elements: pass `ref={cell.ref}`.
277
+
278
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-cell--custom-cell-row-and-header
279
+
280
+ ---
281
+
282
+ ### HIGH Using Column.Cell for a row actions button instead of renderRowActions
283
+
284
+ Wrong:
285
+
286
+ ```tsx
287
+ const columns = [
288
+ {
289
+ Header: '',
290
+ accessor: 'actions',
291
+ Cell: ({ row, cell, isRowSelected }) => (
292
+ <DSMenuButton
293
+ innerRef={cell.ref}
294
+ tabIndex={isRowSelected ? 0 : -1}
295
+ options={getActions(row.original)}
296
+ buttonType={BUTTON_TYPES.ICON}
297
+ >
298
+ <MoreOptionsVert />
299
+ </DSMenuButton>
300
+ ),
301
+ },
302
+ ];
303
+ ```
304
+
305
+ Correct — use `renderRowActions`:
306
+
307
+ ```tsx
308
+ const renderRowActions: DSDataTableT.RenderRowActionsConfig = {
309
+ columnWidth: 32,
310
+ renderer: RowActionsRenderer,
311
+ };
312
+
313
+ <DataTable columns={columns} data={rows} height={500} renderRowActions={renderRowActions} />;
314
+ ```
315
+
316
+ `renderRowActions` is the dedicated first-class API for per-row action columns. `Column.Cell` misses the arrow key conflict handler and loses built-in column width management for the actions column.
317
+
318
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-advanced--with-multilevel-menu-button
319
+
320
+ ---
321
+
322
+ ### HIGH Nesting DSMenuButton inside DSToolbarV2 for multi-level row actions
323
+
324
+ Wrong:
325
+
326
+ ```tsx
327
+ // Toolbar wrapping a DSMenuButton for a multi-level actions column
328
+ const RowActionsRenderer = React.memo((props) => (
329
+ <Toolbar {...props}>
330
+ <DSToolbarV2 withDepth={false}>
331
+ <DSToolbarItemV2
332
+ render={(itemProps) => (
333
+ <DSMenuButton {...itemProps} {/* ...options, onActivateItem, etc. */} />
334
+ )}
335
+ />
336
+ </DSToolbarV2>
337
+ </Toolbar>
338
+ ));
339
+ ```
340
+
341
+ Correct — use `DSMenuButton` directly without the `Toolbar` / `DSToolbarV2` wrapper (see Multi-level menus pattern above).
342
+
343
+ Nesting `DSMenuButton` inside `DSToolbarV2` for multi-level row actions is not natively supported by Dimsum. It requires a four-step manual a11y remediation (chevron as dialog trigger → dialog → toolbar → menu button) that the application team owns entirely and must maintain. `DSMenuButton` via `renderRowActions` directly is the designated supported path for this scenario.
344
+
345
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-advanced--with-multilevel-menu-button
346
+
347
+ ---
348
+
349
+ ### HIGH Non-primitive row action props not memoized cause unnecessary re-renders
350
+
351
+ Every non-primitive prop passed to DataTable must be a stable reference. For row actions, the props that require memoization are:
352
+
353
+ | Prop | Type | Memoization |
354
+ | ------------------- | -------- | ------------------------------------------------------------------------------ |
355
+ | `renderRowActions` | object | `useMemo` if constructed inside the component body, or defined at module level |
356
+ | `dsDatatableHeadTh` | callback | `useCallback` |
357
+
358
+ `renderer` inside `renderRowActions` must be a stable component reference — define it at module level with `React.memo`. Non-stable references cause DataTable to re-render on every parent render cycle.
359
+
360
+ ---
361
+
362
+ See also: ds-data-table-slots/SKILL.md — slot injection as the correct first alternative to custom Cell
363
+ See also: ds-data-table-columns/SKILL.md — Column.Cell and Column.Header escalation path
@@ -0,0 +1,273 @@
1
+ ---
2
+ name: ds-data-table-columns
3
+ description: >
4
+ Column object configuration for @elliemae/ds-data-table. accessor, Header, width (px only),
5
+ textWrap, canSort + onColumnSort, isResizeable + onColumnSizeChange + colsLayoutStyle fixed,
6
+ hiddenColumns, dragAndDropColumns + onColumnsReorder, column grouping via nested columns array.
7
+ Column.Cell and Column.Header custom renderers are strongly discouraged — try slot injection first.
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
+ const columns = [
23
+ { Header: 'Name', accessor: 'name' },
24
+ { Header: 'Salary', accessor: 'salary', width: 120 },
25
+ { Header: 'Position', accessor: 'position', canSort: true },
26
+ ];
27
+
28
+ <DataTable columns={columns} data={rows} height={500} />;
29
+ ```
30
+
31
+ ## Core Patterns
32
+
33
+ ### IoC callback contract
34
+
35
+ All column feature callbacks share the same contract:
36
+
37
+ - **First argument** (`newColumns` / `newData`) — the pre-baked OOB result. For standard cases with no custom business logic, no custom cells, and no column grouping edge cases, dispatch this directly to the state manager. This is the intended fast path.
38
+ - **Remaining arguments** — raw data (column ID, width, direction, reorder indexes). Use these when the OOB result falls short and custom handling is required (server-side operations, constraint validation, non-standard persistence).
39
+
40
+ ### Sorting
41
+
42
+ ```jsx
43
+ const columns = [
44
+ { Header: 'Name', accessor: 'name', canSort: true },
45
+ { Header: 'Salary', accessor: 'salary', canSort: true },
46
+ ];
47
+
48
+ // Full signature: (newColumns: Column[], headerId: string, direction: 'ASC' | 'DESC') => void
49
+ // First arg is the OOB result — dispatch directly for standard cases
50
+ // Use headerId + direction only if custom logic is needed (e.g. server-side sort)
51
+ const handleColumnSort = useCallback((newColumns, headerId, direction) => dispatch(setColumns(newColumns)), []);
52
+
53
+ <DataTable columns={columns} data={rows} height={500} onColumnSort={handleColumnSort} />;
54
+ ```
55
+
56
+ ### Column resizing
57
+
58
+ ```jsx
59
+ // isResizeable requires colsLayoutStyle='fixed' (the default — set it explicitly)
60
+
61
+ // Full signature: (newColumns: Column[], headerId: string, width: number) => void
62
+ // First arg is the OOB result — dispatch directly for standard cases
63
+ // Use headerId + width only if custom logic is needed (e.g. resize constraints, persistence)
64
+ const handleColumnSizeChange = useCallback((newColumns, headerId, width) => dispatch(setColumns(newColumns)), []);
65
+
66
+ <DataTable
67
+ columns={columns}
68
+ data={rows}
69
+ height={500}
70
+ colsLayoutStyle="fixed"
71
+ isResizeable
72
+ onColumnSizeChange={handleColumnSizeChange}
73
+ />;
74
+ ```
75
+
76
+ ### Column drag & drop reorder
77
+
78
+ ```jsx
79
+ // disableDnD: true on a column prevents that specific column from being dragged
80
+ const columns = [
81
+ {
82
+ Header: 'Name',
83
+ columns: [
84
+ { Header: 'First', accessor: 'first' },
85
+ { Header: 'Last', accessor: 'last' },
86
+ ],
87
+ },
88
+ { Header: 'Info', columns: [{ Header: 'Date', accessor: 'date', disableDnD: true }] },
89
+ ];
90
+
91
+ // Full signature: (newData: Column[], indexes: { targetIndex: number; fromIndex: number }) => void
92
+ // First arg is the OOB result — dispatch directly for standard cases
93
+ // Use targetIndex + fromIndex only if custom logic is needed (e.g. reorder validation, server sync)
94
+ const handleColumnsReorder = useCallback((newData, { targetIndex, fromIndex }) => dispatch(setColumns(newData)), []);
95
+
96
+ <DataTable columns={columns} data={rows} height={500} dragAndDropColumns onColumnsReorder={handleColumnsReorder} />;
97
+ ```
98
+
99
+ ### Column grouping (nested columns)
100
+
101
+ ```jsx
102
+ const columns = [
103
+ {
104
+ Header: 'Identity',
105
+ columns: [
106
+ { Header: 'First Name', accessor: 'firstName' },
107
+ { Header: 'Last Name', accessor: 'lastName' },
108
+ ],
109
+ },
110
+ {
111
+ Header: 'Details',
112
+ columns: [
113
+ { Header: 'Position', accessor: 'position' },
114
+ { Header: 'Country', accessor: 'country' },
115
+ ],
116
+ },
117
+ ];
118
+ ```
119
+
120
+ ## Common Mistakes
121
+
122
+ ### HIGH isResizeable with colsLayoutStyle auto silently breaks resizing
123
+
124
+ Wrong:
125
+
126
+ ```jsx
127
+ <DataTable columns={columns} data={rows} height={500} colsLayoutStyle="auto" isResizeable />
128
+ ```
129
+
130
+ Correct:
131
+
132
+ ```jsx
133
+ <DataTable
134
+ columns={columns}
135
+ data={rows}
136
+ height={500}
137
+ colsLayoutStyle="fixed"
138
+ isResizeable
139
+ onColumnSizeChange={handleResize}
140
+ />
141
+ ```
142
+
143
+ `colsLayoutStyle='auto'` fills the container by distributing column widths equally. Under `auto`, `onColumnSizeChange` does not reliably track individual column widths — custom resize redistribution is not supported. Use `colsLayoutStyle='fixed'` (the default) whenever `isResizeable` and `onColumnSizeChange` are required.
144
+
145
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-column--dynamic-columns-layout-1
146
+
147
+ ---
148
+
149
+ ### HIGH Using deprecated onColumnSortChange instead of onColumnSort
150
+
151
+ Wrong:
152
+
153
+ ```jsx
154
+ <DataTable columns={columns} data={rows} height={500} onColumnSortChange={handleSort} />
155
+ ```
156
+
157
+ Correct:
158
+
159
+ ```jsx
160
+ const handleColumnSort = useCallback((newColumns, columnId, direction) => handleSort(newColumns), []);
161
+
162
+ <DataTable columns={columns} data={rows} height={500} onColumnSort={handleColumnSort} />;
163
+ ```
164
+
165
+ `onColumnSortChange` is deprecated. Migrate on sight.
166
+
167
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable--documentation
168
+
169
+ ---
170
+
171
+ ### HIGH Using deprecated onColumnResize instead of onColumnSizeChange
172
+
173
+ Wrong:
174
+
175
+ ```jsx
176
+ <DataTable columns={columns} data={rows} height={500} onColumnResize={handleResize} />
177
+ ```
178
+
179
+ Correct:
180
+
181
+ ```jsx
182
+ const handleColumnSizeChange = useCallback((newColumns, headerId, width) => handleResize(newColumns), []);
183
+
184
+ <DataTable columns={columns} data={rows} height={500} onColumnSizeChange={handleColumnSizeChange} />;
185
+ ```
186
+
187
+ `onColumnResize` is deprecated. Migrate on sight.
188
+
189
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable--documentation
190
+
191
+ ---
192
+
193
+ ### HIGH Reaching for Column.Cell when slot injection solves the requirement
194
+
195
+ Wrong:
196
+
197
+ ```jsx
198
+ const columns = [
199
+ {
200
+ Header: 'Status',
201
+ accessor: 'status',
202
+ Cell: ({ row }) => (
203
+ <span style={{ color: row.original.status === 'error' ? 'red' : 'inherit' }}>{row.original.status}</span>
204
+ ),
205
+ },
206
+ ];
207
+ ```
208
+
209
+ Correct:
210
+
211
+ ```jsx
212
+ // Use dsDatatableCell slot injection — no custom Cell needed
213
+ const cellProps = useCallback((cell) => {
214
+ if (cell.row.original.status === 'error') return { style: { color: 'red' } };
215
+ return {};
216
+ }, []);
217
+
218
+ <DataTable columns={columns} data={rows} height={500} dsDatatableCell={cellProps} />;
219
+ ```
220
+
221
+ Custom Cell renderers lose built-in filtering integration, degrade virtualization performance, and remove embedded a11y. Try slot injection first. If a custom Cell is unavoidable, read `ds-data-table-boundaries/SKILL.md` in full, re-evaluate the plan against everything in that skill, and only then implement.
222
+
223
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-advanced--target-specific-row-style
224
+
225
+ ---
226
+
227
+ ### MEDIUM Column width in non-pixel units
228
+
229
+ Wrong:
230
+
231
+ ```jsx
232
+ { Header: 'Name', accessor: 'name', width: '20%' }
233
+ ```
234
+
235
+ Correct:
236
+
237
+ ```jsx
238
+ { Header: 'Name', accessor: 'name', width: 200 }
239
+ ```
240
+
241
+ Column `width` accepts only pixel values (number or `"Npx"` string). Percentage widths are not supported.
242
+
243
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable--documentation
244
+
245
+ ---
246
+
247
+ ### HIGH Non-primitive column props not memoized cause unnecessary re-renders
248
+
249
+ Every non-primitive prop passed to DataTable must be a stable reference. For column configuration, the props that require memoization are:
250
+
251
+ | Prop | Type | Memoization |
252
+ | -------------------- | -------- | ------------------------------- |
253
+ | `columns` | array | `useMemo` |
254
+ | `hiddenColumns` | array | `useMemo` if constructed inline |
255
+ | `onColumnSort` | callback | `useCallback` |
256
+ | `onColumnSizeChange` | callback | `useCallback` |
257
+ | `onColumnsReorder` | callback | `useCallback` |
258
+
259
+ Non-stable references cause DataTable to re-render on every parent render cycle regardless of whether column state actually changed.
260
+
261
+ ---
262
+
263
+ See also: ds-data-table-slots/SKILL.md — row and cell theming without custom Cell
264
+ See also: ds-data-table-migration/SKILL.md — deprecated sort/resize callback migration
265
+
266
+ ---
267
+
268
+ ## When this isn't enough
269
+
270
+ If the documented patterns cannot satisfy the requirement, contact the Dimsum team:
271
+
272
+ **ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
273
+ **Partners:** Your organization's Dimsum point of contact