@elliemae/ds-data-table 3.70.0-next.3 → 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,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
|