@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,257 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-data-table-slots
|
|
3
|
+
description: >
|
|
4
|
+
Low-level slot injection surface for @elliemae/ds-data-table. Reach for this only after OOB
|
|
5
|
+
features cannot satisfy the requirement. Valid uses: data-* attribute injection (analytics,
|
|
6
|
+
tracking), aria-* overrides, and conditional styling. Full slot surface in DATA_TABLE_SLOTS /
|
|
7
|
+
DSDataTableSlots. App side owns long-term maintenance of anything injected via slots.
|
|
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/constants/index.d.ts'
|
|
15
|
+
- '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Slot surface — DOM hierarchy
|
|
19
|
+
|
|
20
|
+
Slots are injection points into specific rendered DOM nodes. The `data-dimsum-slot` attribute value on each node identifies the slot — values use camelCase with the component prefix, e.g. `dsDatatableCellsContainer`. Understanding the hierarchy is required to inject props into the right element — injecting `aria-*` attributes at the wrong level produces silent accessibility failures.
|
|
21
|
+
|
|
22
|
+
**About this map:** Verified against `@elliemae/ds-data-table@3.60.0` via probe tests. Slot names and positions are governed by the Dimsum breaking change protocol — renames and structural moves are intentional, coordinated, and documented in the official resources; they do not happen silently. The map is likely accurate for nearby versions, but not guaranteed for all time.
|
|
23
|
+
|
|
24
|
+
**If something doesn't match the version you're integrating with:** Find a way to inspect the actual rendered HTML of the component — how you do that depends on your environment and what tools are available to you. If you cannot do it autonomously, ask the human in the loop to render the component with synthetic or placeholder data and share the resulting HTML structure. **Important:** the human must not copy HTML from a production environment or any page containing real user data, real loan information, or any personally identifiable information — the goal is slot position verification, which requires only the DOM structure, not real content. Placeholder text and fake IDs are sufficient and correct for this purpose.
|
|
25
|
+
|
|
26
|
+
Each entry below shows: `SLOT_KEY (data-dimsum-slot="value") · tag · notes`.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
ROOT (dsDatatableRoot) · div
|
|
30
|
+
├── FILTER_BAR_WRAPPER (dsDatatableFilterBarWrapper) · div ← only when withFilterBar=true
|
|
31
|
+
│ └── FILTER_BAR_MENU_BUTTON ← static, no interaction needed
|
|
32
|
+
│ wraps DSMenuButton → DOM shows dsButtonRoot (no dsDatatableFilterBarMenuButton attr)
|
|
33
|
+
└── CONTENT_WRAPPER (dsDatatableContentWrapper) · div
|
|
34
|
+
├── TABLE_WRAPPER (dsDatatableTableWrapper) · div[role="table"]
|
|
35
|
+
│ └── VIRTUAL_LIST_WRAPPER (dsDatatableVirtualListWrapper) · div[role="rowgroup" tabIndex="-1"]
|
|
36
|
+
│ └── TABLE_CONTENT_WRAPPER (dsDatatableTableContentWrapper) · div
|
|
37
|
+
│ ├── HEAD_WRAPPER (dsDatatableHeadWrapper) · div
|
|
38
|
+
│ │ └── HEAD_TR (dsDatatableHeadTr) · div[role="row"]
|
|
39
|
+
│ │ └── HEADER_CELL_WRAPPER (dsDatatableHeaderCellWrapper) · div (one per column)
|
|
40
|
+
│ │ └── HEAD_TH (dsDatatableHeadTh) · div[role="columnheader"]
|
|
41
|
+
│ │ ║ prop: dsDatatableHeadTh
|
|
42
|
+
│ │ ║ callback: ({ columnId, firstFocuseableColumnHeaderId }) => props
|
|
43
|
+
│ │ ├── HEAD_RIGHT_ICONS_WRAPPER (dsDatatableHeadRightIconsWrapper) · div
|
|
44
|
+
│ │ │ present when: canSort=true OR column.filter is set
|
|
45
|
+
│ │ │ ├── sort button (dsButtonRoot — no DataTable slot attr)
|
|
46
|
+
│ │ │ └── FILTER_POPOVER_BUTTON (dsDatatableFilterPopoverButton) · span
|
|
47
|
+
│ │ │ present when: column.filter is set
|
|
48
|
+
│ │ │ └── filter trigger button (dsButtonRoot — no DataTable slot attr)
|
|
49
|
+
│ │ │ clicking this opens the filter popover (portal — see below)
|
|
50
|
+
│ │ ├── EXPAND_CELL_CONTAINER (dsDatatableExpandCellContainer) · span[role="button"]
|
|
51
|
+
│ │ │ present in: the expand column header cell only
|
|
52
|
+
│ │ └── RESIZER (dsDatatableResizer) ← isResizeable=true only
|
|
53
|
+
│ │ prop: dsDatatableResizer · wraps DSInputText
|
|
54
|
+
│ │ DSInputText root shows dsInputtextRoot · data-dimsum-slot="dsDatatableResizer"
|
|
55
|
+
│ │ and data-testid="ds-datatable-resizer" land on the inner input element
|
|
56
|
+
│ │
|
|
57
|
+
│ ├── LOADER_WRAPPER (dsDatatableLoaderWrapper) · div[role="row"]
|
|
58
|
+
│ │ present when: isLoading=true (sibling of HEAD_WRAPPER)
|
|
59
|
+
│ │
|
|
60
|
+
│ ├── CENTER_CONTENT_FLEX_WRAPPER (dsDatatableCenterContentFlexWrapper) · div[role="row"]
|
|
61
|
+
│ │ present when: data=[] (sibling of HEAD_WRAPPER)
|
|
62
|
+
│ │ └── EMPTY_STATE_WRAPPER (dsDatatableEmptyStateWrapper) · div[role="cell"]
|
|
63
|
+
│ │ ├── warning icon (dsIconRoot — no DataTable slot attr)
|
|
64
|
+
│ │ ├── EMPTY_PRIMARY_MESSAGE (dsDatatableEmptyPrimaryMessage) · p
|
|
65
|
+
│ │ ├── EMPTY_SECONDARY_MESSAGE (dsDatatableEmptySecondaryMessage) · p
|
|
66
|
+
│ │ └── CTA button (dsButtonRoot — no DataTable slot attr)
|
|
67
|
+
│ │ present when: noResultsButtonLabel + onNoResultsButtonClick provided
|
|
68
|
+
│ │
|
|
69
|
+
│ └── ROW (dsDatatableRow) · div[position:absolute] (one per visible row)
|
|
70
|
+
│ ║ prop: dsDatatableRow · layout wrapper — not the semantic row element
|
|
71
|
+
│ └── FULLSIZE_GRID (dsDatatableFullsizeGrid) · div
|
|
72
|
+
│ ║ prop: dsDatatableFullsizeGrid · owns onClick/onKeyDown row interaction handlers
|
|
73
|
+
│ └── CELL_CONTAINER (dsDatatableCellsContainer) · div[role="row"]
|
|
74
|
+
│ ║ ★ THE semantic row element — what screen readers traverse
|
|
75
|
+
│ ║ prop: dsDatatableCellsContainer · callback: (row) => props
|
|
76
|
+
│ ║ inject aria-*, data-* attributes, bg/color xstyled props here
|
|
77
|
+
│ │
|
|
78
|
+
│ ├── GROUP_HEADER_CONTAINER (dsDatatableGroupHeaderContainer) · div[role="cell"]
|
|
79
|
+
│ │ present in: group header rows only (requires getRowVariant returning 'ds-header-group-row')
|
|
80
|
+
│ │ └── GROUP_HEADER_TITLE (dsDatatableHeaderTitle) · span
|
|
81
|
+
│ │
|
|
82
|
+
│ ├── CELL (dsDatatableCell) · div[role="cell"] (one per data column)
|
|
83
|
+
│ │ ║ prop: dsDatatableCell · callback: (cell) => props
|
|
84
|
+
│ │ ├── EDITABLE_CONTAINER (dsDatatableEditableContainer) · div[role="group"]
|
|
85
|
+
│ │ │ present when: column.editable is set
|
|
86
|
+
│ │ │ ├── CELL_CONTENT (dsDatatableCellContent) · div
|
|
87
|
+
│ │ │ ├── pencil icon (dsIconRoot — no DataTable slot attr)
|
|
88
|
+
│ │ │ └── TEXT_EDITABLE_CELL_INPUT (dsDatatableTextEditableCellInput) · input
|
|
89
|
+
│ │ │ present after user clicks to enter edit mode (autoFocus on mount)
|
|
90
|
+
│ │ └── CELL_CONTENT (dsDatatableCellContent) · div (standard columns)
|
|
91
|
+
│ │ ├── EXPAND_CELL_CONTAINER (dsDatatableExpandCellContainer) · span[role="button"]
|
|
92
|
+
│ │ │ present in: expand column data cells only
|
|
93
|
+
│ │ ├── SINGLE_CELL_CONTAINER (dsDatatableSingleCellContainer) · div
|
|
94
|
+
│ │ │ present in: single-select column data cells only
|
|
95
|
+
│ │ └── DRAG_AND_DROP_GRIPPER (dsDatatableDragAndDropGripper) · div[role="button"]
|
|
96
|
+
│ │ present in: DnD column data cells (dragAndDropRows=true)
|
|
97
|
+
│ │
|
|
98
|
+
│ └── ACTION_CELL (dsDatatableActionCell) · div[role="cell"]
|
|
99
|
+
│ present when: renderRowActions prop is set
|
|
100
|
+
│ └── TOOLBAR_POSITION (dsDatatableToolbarPosition) · div
|
|
101
|
+
│ └── TOOLBAR_WRAPPER (dsDatatableToolbarWrapper) · div
|
|
102
|
+
│ ├── TOOLBAR_BUTTONS_WRAPPER (dsDatatableToolbarButtonsWrapper) · div
|
|
103
|
+
│ │ present after user opens the toolbar
|
|
104
|
+
│ └── trigger button (dsButtonRoot — no DataTable slot attr)
|
|
105
|
+
│ data-testid="data-table-toolbar-trigger"
|
|
106
|
+
│
|
|
107
|
+
└── PAGINATION_WRAPPER (dsDatatablePaginationWrapper) · div
|
|
108
|
+
present when: pagination prop is set
|
|
109
|
+
└── (ds-pagination component — dsPaginationWrapper etc.)
|
|
110
|
+
|
|
111
|
+
── FILTER_POPOVER portal ← rendered at document body level, not inside DataTable DOM
|
|
112
|
+
present after user clicks a column's FILTER_POPOVER_BUTTON trigger
|
|
113
|
+
DOM shows: dsFloatingwrapperRoot[role="dialog"] · (root)
|
|
114
|
+
└── dsFloatingwrapperContent
|
|
115
|
+
└── FILTER_POPOVER_CONTENT (dsDatatableFilterPopoverContent) · div
|
|
116
|
+
├── FREE_TEXT_SEARCH_WRAPPER (dsDatatableFreeTextSearchWrapper) · div
|
|
117
|
+
│ └── FREE_TEXT_SEARCH_FILTER (dsDatatableFreeTextSearchFilter)
|
|
118
|
+
│ prop: dsDatatableFreeTextSearchFilter · wraps DSInputText
|
|
119
|
+
│ data-dimsum-slot="dsDatatableFreeTextSearchFilter" and data-testid="data-table-filter-free-text-search"
|
|
120
|
+
│ land on the inner input element
|
|
121
|
+
└── FILTER_POPOVER_FOOTER — no dsDatatableFilterPopoverFooter attr in DOM
|
|
122
|
+
└── submit/reset buttons (dsButtonRoot)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Slots that are injectable but do not appear as `dsDatatable*` in the DOM:**
|
|
126
|
+
|
|
127
|
+
These slot props work for injection, but when you inspect the DOM you will not see a `dsDatatableXxx` attribute. The table below shows what you will see instead. This matters if you are debugging injection or writing DOM-based test assertions.
|
|
128
|
+
|
|
129
|
+
| Slot key | What the DOM shows | Where to find it |
|
|
130
|
+
| ------------------------ | ---------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
131
|
+
| `PENCIL_ICON` | `dsIconRoot` + `dsIconSvg` | Inside `EDITABLE_CONTAINER`; present in DOM but CSS-hidden until hover/focus |
|
|
132
|
+
| `TOOLBAR_BUTTON` | `dsButtonRoot` | Inside `TOOLBAR_BUTTONS_WRAPPER`, visible after toolbar is opened |
|
|
133
|
+
| `EMPTY_BUTTON` | `dsButtonRoot` | Inside `EMPTY_STATE_WRAPPER`, the CTA button |
|
|
134
|
+
| `FILTER_POPOVER` | `dsFloatingwrapperRoot` + `dsFloatingwrapperContent` | Portal at document body — `role="dialog"`, not inside the DataTable DOM subtree |
|
|
135
|
+
| `FILTER_POPOVER_FOOTER` | `dsDialogFooter` | Inside `FILTER_POPOVER_CONTENT` |
|
|
136
|
+
| `FILTER_BAR_MENU_BUTTON` | `dsButtonRoot` | Inside `FILTER_BAR_WRAPPER`, always visible when `withFilterBar=true` |
|
|
137
|
+
|
|
138
|
+
**Slots only visible in interactive states (not in static render):**
|
|
139
|
+
|
|
140
|
+
| Slot key | Required interaction | How to trigger in a test |
|
|
141
|
+
| ----------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `TOOLBAR_BUTTONS_WRAPPER` | Click toolbar trigger | `userEvent.click(screen.getByTestId('data-table-toolbar-trigger'))` |
|
|
143
|
+
| `TEXT_EDITABLE_CELL_INPUT` | Click editable cell to enter edit mode | `userEvent.click(screen.getByTestId('ds-datatable-editable-container'))` |
|
|
144
|
+
| `FILTER_POPOVER_CONTENT` / `FREE_TEXT_SEARCH_WRAPPER` | Click column filter button | `fireEvent.click(screen.getByTestId('data-table-filter-menu-button').querySelector('button'))` |
|
|
145
|
+
| `DROP_INDICATOR` | Active drag in progress | Requires a real browser drag interaction — not reliably reproducible in JSDOM |
|
|
146
|
+
|
|
147
|
+
**Critical distinction — `CELL_CONTAINER` vs `ROW` vs `FULLSIZE_GRID`:**
|
|
148
|
+
|
|
149
|
+
Three slots wrap each row. They are not interchangeable:
|
|
150
|
+
|
|
151
|
+
- `ROW` (`dsDatatableRow`) — the absolute-positioned layout div. Inject layout-level CSS only. Not a semantic element.
|
|
152
|
+
- `FULLSIZE_GRID` (`dsDatatableFullsizeGrid`) — the grid div that owns the click/keyboard handlers. Injectable via `dsDatatableFullsizeGrid` slot prop.
|
|
153
|
+
- `CELL_CONTAINER` (`dsDatatableCellsContainer`) — the `role="row"` div. **This is the semantic row.** Screen readers traverse this. `aria-rowindex`, `aria-label`, `aria-selected`, and background color belong here.
|
|
154
|
+
|
|
155
|
+
Injecting `aria-selected` or a background color into `ROW` instead of `CELL_CONTAINER` is a silent failure — the attribute exists in the DOM but attaches to a layout wrapper that screen readers skip.
|
|
156
|
+
|
|
157
|
+
**Header slots:**
|
|
158
|
+
|
|
159
|
+
- `HEAD_TR` (`dsDatatableHeadTr`) — the `role="row"` header row container. Injectable via `dsDatatableHeadTr` slot prop.
|
|
160
|
+
- `HEAD_TH` (`dsDatatableHeadTh`) — the `role="columnheader"` element. Use for `innerRef` focus-bridge wiring and column-level aria overrides.
|
|
161
|
+
|
|
162
|
+
`DATA_TABLE_SLOTS.HEAD_TH` (prop: `dsDatatableHeadTh`) has a distinct callback argument shape from row and cell slots. It receives `{ columnId: string, firstFocuseableColumnHeaderId: string | undefined }` — the ID of the current header cell and the ID of the first focusable column header in the table. This is used for focus-bridge patterns such as wiring a `fallbackRef` from an external filter bar to the first column header when the last filter pill is removed.
|
|
163
|
+
|
|
164
|
+
```jsx
|
|
165
|
+
const dsDatatableHeadTh = useCallback(
|
|
166
|
+
({ columnId, firstFocuseableColumnHeaderId }) => {
|
|
167
|
+
if (columnId === firstFocuseableColumnHeaderId) return { innerRef: fallbackRef };
|
|
168
|
+
return {};
|
|
169
|
+
},
|
|
170
|
+
[fallbackRef],
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## When to reach for slots
|
|
175
|
+
|
|
176
|
+
**Try OOB first.** The DataTable exposes first-class callbacks for every stateful interaction — `onSelectionChange`, `onFiltersChange`, `onColumnSort`, `onRowExpand`, `onCellValueChange`. These are the correct and complete sites for application logic. Slots are not a shortcut to those.
|
|
177
|
+
|
|
178
|
+
Slots are a low-level escape hatch. They inject props directly into internal styled components. The application side owns long-term maintenance of everything injected this way — if the DataTable's internal structure changes, slot-based injections break silently.
|
|
179
|
+
|
|
180
|
+
**Valid uses:**
|
|
181
|
+
|
|
182
|
+
- `data-*` attributes — analytics tracking, feature flags, test selectors that the DataTable doesn't expose natively. Example: `data-private="true"`, `data-loan-id={row.original.id}`.
|
|
183
|
+
- `aria-*` overrides — accessibility attributes the DataTable doesn't set automatically for your specific use case.
|
|
184
|
+
- Conditional styling — `bg`, `color`, `style` driven by row/cell data. The app team accepts ownership of visual maintenance.
|
|
185
|
+
|
|
186
|
+
**Red flag — event listeners via slots.** If you find yourself attaching `onClick`, `onKeyDown`, or other event listeners through `dsDatatableCellsContainer` or `dsDatatableCell`, stop. The DataTable already manages its own interaction model. Before proceeding:
|
|
187
|
+
|
|
188
|
+
1. Check whether an OOB callback already handles what you need.
|
|
189
|
+
2. Re-examine the UX requirement — if the system provides no first-class support for this interaction, that is a signal the design may be solving a problem in a non-standard way.
|
|
190
|
+
3. Engage the Dimsum team (ICE engineers: Teams/Jira; partners: your org's Dimsum contact) to verify whether an OOB solution exists or should be added.
|
|
191
|
+
|
|
192
|
+
## Setup
|
|
193
|
+
|
|
194
|
+
The two primary consumer props. Both receive a memoized callback returning a plain props object — not a React component, no hooks inside.
|
|
195
|
+
|
|
196
|
+
```jsx
|
|
197
|
+
import { DataTable } from '@elliemae/ds-data-table';
|
|
198
|
+
|
|
199
|
+
// Row level — targets CELL_CONTAINER (the semantic role="row" element)
|
|
200
|
+
const rowContainerProps = useCallback((row) => {
|
|
201
|
+
if (row.original.status === 'error') {
|
|
202
|
+
return {
|
|
203
|
+
'data-error-row': 'true', // data-* injection
|
|
204
|
+
'aria-invalid': 'true', // aria-* injection
|
|
205
|
+
bg: { _: 'red.100', hover: 'red.200 !important' }, // xstyled
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {};
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
// Cell level — targets CELL (individual role="cell" element)
|
|
212
|
+
const cellProps = useCallback((cell) => {
|
|
213
|
+
if (cell.row.original.status === 'error') {
|
|
214
|
+
return { style: { color: 'red' } }; // React CSSProperties
|
|
215
|
+
}
|
|
216
|
+
return {};
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
<DataTable
|
|
220
|
+
columns={columns}
|
|
221
|
+
data={rows}
|
|
222
|
+
height={500}
|
|
223
|
+
dsDatatableCellsContainer={rowContainerProps}
|
|
224
|
+
dsDatatableCell={cellProps}
|
|
225
|
+
/>;
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Always wrap callbacks in `useCallback`. Inline callbacks create a new reference on every render, causing all virtualized rows to re-render unnecessarily.
|
|
229
|
+
|
|
230
|
+
In `dsDatatableCell`, access row data via `cell.row.original` (raw data) or `cell.row.uid` (UID from `uniqueRowAccessor`). Do not use `cell.row.id` — that is react-table's internal positional index and shifts when data is reordered.
|
|
231
|
+
|
|
232
|
+
## `column.cellStyle` vs `dsDatatableCell`
|
|
233
|
+
|
|
234
|
+
`column.cellStyle` (`React.CSSProperties`) applies the same CSS to every cell in that column. Use it for uniform column-level layout: sticky positioning, fixed-width overrides. It requires no callback.
|
|
235
|
+
|
|
236
|
+
`dsDatatableCell` varies per row based on data. Use it when the styling depends on row state (error, highlight, selection). Reaching for `dsDatatableCell` to apply the same style to all rows in a column is the wrong tool — `column.cellStyle` is simpler and sufficient for that case.
|
|
237
|
+
|
|
238
|
+
## Critical constraint — no hooks inside slot callbacks
|
|
239
|
+
|
|
240
|
+
`dsDatatableCellsContainer` and `dsDatatableCell` are JavaScript functions called at render time inside the DataTable's own rendering context. They are not React components. Hook calls inside them register against the calling component's hook list — a rules-of-hooks violation. Obtain all external values (theme tokens, feature flags, state) at component level and close over them:
|
|
241
|
+
|
|
242
|
+
```jsx
|
|
243
|
+
const theme = useTheme();
|
|
244
|
+
const errorBg = theme.colors.danger[50];
|
|
245
|
+
|
|
246
|
+
const rowContainerProps = useCallback(
|
|
247
|
+
(row) => {
|
|
248
|
+
if (row.original.status === 'error') return { bg: errorBg };
|
|
249
|
+
return {};
|
|
250
|
+
},
|
|
251
|
+
[errorBg],
|
|
252
|
+
);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
See also: ds-data-table-boundaries/SKILL.md — when slots cannot satisfy the requirement, escalation path
|