@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,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-data-table-migration
|
|
3
|
+
description: >
|
|
4
|
+
Migrating deprecated @elliemae/ds-data-table APIs to their modern replacements. V1 filter strings
|
|
5
|
+
(FILTER_TYPES.MULTI_SELECT, SELECT, etc.) → FILTER_TYPES V2 values. Lowercase pagination prop →
|
|
6
|
+
capital-P Pagination render prop. onColumnSortChange → onColumnSort. onColumnResize →
|
|
7
|
+
onColumnSizeChange. Scan for all instances before migrating — partial migration is not a migration.
|
|
8
|
+
type: lifecycle
|
|
9
|
+
library: ds-data-table
|
|
10
|
+
library_version: '3.60.0'
|
|
11
|
+
sources:
|
|
12
|
+
- '@elliemae/ds-data-table:dist/types/exported-related/FilterTypes.d.ts'
|
|
13
|
+
- '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Migration checklist
|
|
17
|
+
|
|
18
|
+
Before migrating, scan the entire codebase for all instances. Determine the correct source directory for the project you are working in (common conventions include `src/`, `lib/`, `app/`, `view/`, or a monorepo package root — inspect the project structure before running any search):
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
# Patterns to search for — adapt the directory to the project's structure
|
|
22
|
+
|
|
23
|
+
# V1 filter usage — covers all types that have V1/V2 variants:
|
|
24
|
+
FILTER_TYPES\.(MULTI_SELECT|SELECT|CREATABLE_SELECT|CREATABLE_MULTI_SELECT|SINGLE_DATE|DATE_RANGE|DATE_SWITCHER|NUMBER_RANGE|FREE_TEXT_SEARCH|CURRENCY_RANGE)\b[^_]
|
|
25
|
+
ds-filter-multi-select
|
|
26
|
+
ds-filter-select
|
|
27
|
+
|
|
28
|
+
# Deprecated pagination prop:
|
|
29
|
+
pagination={
|
|
30
|
+
|
|
31
|
+
# Deprecated sort/resize callbacks:
|
|
32
|
+
onColumnSortChange
|
|
33
|
+
onColumnResize
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Migrate all instances in one pass. Partial migration leaves tech debt in place and creates mixed patterns that are harder to finish later.
|
|
37
|
+
|
|
38
|
+
## V1 filters → V2
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
// Before (V1 — a11y-unsafe, do not use in any context)
|
|
42
|
+
import { FILTER_TYPES } from '@elliemae/ds-data-table';
|
|
43
|
+
|
|
44
|
+
const columns = [
|
|
45
|
+
{ Header: 'Position', accessor: 'position', filter: FILTER_TYPES.MULTI_SELECT },
|
|
46
|
+
{ Header: 'Status', accessor: 'status', filter: FILTER_TYPES.SELECT },
|
|
47
|
+
];
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
// After (V2 — always use these)
|
|
52
|
+
import { FILTER_TYPES } from '@elliemae/ds-data-table';
|
|
53
|
+
|
|
54
|
+
const columns = [
|
|
55
|
+
{ Header: 'Position', accessor: 'position', filter: FILTER_TYPES.MULTI_SELECT_V2 },
|
|
56
|
+
{ Header: 'Status', accessor: 'status', filter: FILTER_TYPES.SELECT_V2 },
|
|
57
|
+
];
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
V1 filter types are not a11y safe and are kept only for legacy app migration. They have no valid use case in new or updated code.
|
|
61
|
+
|
|
62
|
+
## V2 filter type mapping
|
|
63
|
+
|
|
64
|
+
Every filter type in `FILTER_TYPES` has both a V1 and a V2 variant. The V1 name is the bare type name; the V2 name carries the `_V2` suffix.
|
|
65
|
+
|
|
66
|
+
| V1 (deprecated — a11y-unsafe) | V2 (current) |
|
|
67
|
+
| ------------------------------------- | ---------------------------------------- |
|
|
68
|
+
| `FILTER_TYPES.MULTI_SELECT` | `FILTER_TYPES.MULTI_SELECT_V2` |
|
|
69
|
+
| `FILTER_TYPES.SELECT` | `FILTER_TYPES.SELECT_V2` |
|
|
70
|
+
| `FILTER_TYPES.CREATABLE_SELECT` | `FILTER_TYPES.CREATABLE_SELECT_V2` |
|
|
71
|
+
| `FILTER_TYPES.CREATABLE_MULTI_SELECT` | `FILTER_TYPES.CREATABLE_MULTI_SELECT_V2` |
|
|
72
|
+
| `FILTER_TYPES.SINGLE_DATE` | `FILTER_TYPES.SINGLE_DATE_V2` |
|
|
73
|
+
| `FILTER_TYPES.DATE_RANGE` | `FILTER_TYPES.DATE_RANGE_V2` |
|
|
74
|
+
| `FILTER_TYPES.DATE_SWITCHER` | `FILTER_TYPES.DATE_SWITCHER_V2` |
|
|
75
|
+
| `FILTER_TYPES.NUMBER_RANGE` | `FILTER_TYPES.NUMBER_RANGE_V2` |
|
|
76
|
+
| `FILTER_TYPES.FREE_TEXT_SEARCH` | `FILTER_TYPES.FREE_TEXT_SEARCH_V2` |
|
|
77
|
+
| `FILTER_TYPES.CURRENCY_RANGE` | `FILTER_TYPES.CURRENCY_RANGE_V2` |
|
|
78
|
+
| `'ds-filter-multi-select'` | `FILTER_TYPES.MULTI_SELECT_V2` |
|
|
79
|
+
| `'ds-filter-select'` | `FILTER_TYPES.SELECT_V2` |
|
|
80
|
+
|
|
81
|
+
## Lowercase `pagination` → capital-P `Pagination`
|
|
82
|
+
|
|
83
|
+
```jsx
|
|
84
|
+
// Before (deprecated — legacy prop)
|
|
85
|
+
<DataTable
|
|
86
|
+
columns={columns}
|
|
87
|
+
data={rows}
|
|
88
|
+
height={500}
|
|
89
|
+
pagination={{ pageSize: 10, totalCount: 1000 }}
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
// After
|
|
93
|
+
<DataTable columns={columns} data={pagedData} height={500} Pagination={TablePagination} />
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Two things change in the migration:
|
|
97
|
+
|
|
98
|
+
1. **Integration model** — the legacy prop accepted total count and delegated slicing internally; the modern pattern requires the app to own data fetching and page slicing via AJAX
|
|
99
|
+
2. **Component model** — `Pagination` receives a React component reference, not a config object; that component must be defined at module level and read state from the project's state manager
|
|
100
|
+
|
|
101
|
+
For the full correct implementation of `TablePagination`, load `ds-data-table-pagination/SKILL.md`.
|
|
102
|
+
|
|
103
|
+
## `onColumnSortChange` → `onColumnSort`
|
|
104
|
+
|
|
105
|
+
```jsx
|
|
106
|
+
// Before (deprecated)
|
|
107
|
+
<DataTable columns={columns} data={rows} height={500} onColumnSortChange={handleSort} />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```jsx
|
|
111
|
+
// After
|
|
112
|
+
// Signature: (newColumns, sortedColumnId, sortDirection) => void
|
|
113
|
+
const handleColumnSort = useCallback((newColumns) => dispatch(setColumns(newColumns)), []);
|
|
114
|
+
|
|
115
|
+
<DataTable columns={columns} data={rows} height={500} onColumnSort={handleColumnSort} />;
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## `onColumnResize` → `onColumnSizeChange`
|
|
119
|
+
|
|
120
|
+
```jsx
|
|
121
|
+
// Before (deprecated)
|
|
122
|
+
<DataTable columns={columns} data={rows} height={500} isResizeable onColumnResize={handleResize} />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```jsx
|
|
126
|
+
// After
|
|
127
|
+
// Signature: (newColumns, headerId, width) => void
|
|
128
|
+
const handleColumnSizeChange = useCallback((newColumns) => dispatch(setColumns(newColumns)), []);
|
|
129
|
+
|
|
130
|
+
<DataTable
|
|
131
|
+
columns={columns}
|
|
132
|
+
data={rows}
|
|
133
|
+
height={500}
|
|
134
|
+
colsLayoutStyle="fixed"
|
|
135
|
+
isResizeable
|
|
136
|
+
onColumnSizeChange={handleColumnSizeChange}
|
|
137
|
+
/>;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Common Mistakes
|
|
141
|
+
|
|
142
|
+
### CRITICAL Migrating one V1 filter instance while leaving others in the same codebase
|
|
143
|
+
|
|
144
|
+
Wrong:
|
|
145
|
+
|
|
146
|
+
```jsx
|
|
147
|
+
// File A — migrated
|
|
148
|
+
{ accessor: 'status', filter: FILTER_TYPES.SELECT_V2 }
|
|
149
|
+
|
|
150
|
+
// File B — still V1 (same codebase)
|
|
151
|
+
{ accessor: 'position', filter: FILTER_TYPES.MULTI_SELECT }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Correct:
|
|
155
|
+
|
|
156
|
+
```jsx
|
|
157
|
+
// Scan all files and migrate every instance before closing the task
|
|
158
|
+
// V1 and V2 can coexist technically but it is treated as incomplete migration
|
|
159
|
+
{ accessor: 'status', filter: FILTER_TYPES.SELECT_V2 }
|
|
160
|
+
{ accessor: 'position', filter: FILTER_TYPES.MULTI_SELECT_V2 }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
V1 filters are tech debt. Partial migration leaves the a11y risk in place on the unmigrated columns and creates a mixed pattern that is harder to finish.
|
|
164
|
+
|
|
165
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-filtersv2--multi-select-filter-vs-multi-select-v-2
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### HIGH Not accounting for the data-slicing responsibility change when migrating pagination
|
|
170
|
+
|
|
171
|
+
Wrong:
|
|
172
|
+
|
|
173
|
+
```jsx
|
|
174
|
+
// Migrated the prop name but still passing allData to DataTable
|
|
175
|
+
<DataTable columns={columns} data={allData} height={500} Pagination={MemoizedPagination} />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Correct:
|
|
179
|
+
|
|
180
|
+
```jsx
|
|
181
|
+
// App must slice data to the current page — DataTable does not paginate internally
|
|
182
|
+
<DataTable columns={columns} data={pagedData} height={500} Pagination={MemoizedPagination} />
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The legacy `pagination` prop delegated slicing to DataTable. The modern `Pagination` render prop does not — the app is responsible for slicing `data` before passing it.
|
|
186
|
+
|
|
187
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-pagination--paginated-table
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
See also: ds-data-table-filtering/SKILL.md — V2 filter configuration patterns
|
|
192
|
+
See also: ds-data-table-pagination/SKILL.md — modern Pagination render prop patterns
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## When this isn't enough
|
|
197
|
+
|
|
198
|
+
If the documented patterns cannot satisfy the requirement, contact the Dimsum team:
|
|
199
|
+
|
|
200
|
+
**ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
|
|
201
|
+
**Partners:** Your organization's Dimsum point of contact
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-data-table-pagination
|
|
3
|
+
description: >
|
|
4
|
+
Pagination for @elliemae/ds-data-table using the capital-P Pagination render prop with
|
|
5
|
+
@elliemae/ds-pagination primitives (DSPaginationContainer, DSPaginator, DSPagePrevButton,
|
|
6
|
+
DSPageNextButton, DSPerPageSelector). App slices data to current page before passing to DataTable.
|
|
7
|
+
Lowercase pagination prop is deprecated legacy — migrate to Pagination component on sight.
|
|
8
|
+
type: core
|
|
9
|
+
library: ds-data-table
|
|
10
|
+
library_version: '3.60.0'
|
|
11
|
+
requires:
|
|
12
|
+
- ds-data-table-setup
|
|
13
|
+
- ds-pagination-setup
|
|
14
|
+
sources:
|
|
15
|
+
- '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
`TablePagination` assembles `@elliemae/ds-pagination` primitives and reads pagination state from the project's state manager. Its assembly is outside the scope of this skill.
|
|
21
|
+
|
|
22
|
+
**Before implementing `TablePagination`:**
|
|
23
|
+
|
|
24
|
+
1. Check whether `@elliemae/ds-pagination` has a published intent skill — run `npx @tanstack/intent install` in the project or look for a `skills/` directory inside the `@elliemae/ds-pagination` package. The `ds-pagination-setup` skill covers the full composable assembly. If found, load it.
|
|
25
|
+
2. If no skill is found, ask the human to share a correct implementation example from the Dimsum storybook before proceeding. Do not approximate the assembly from incomplete information — omitting the AJAX model or memoization requirements produces incorrect implementations.
|
|
26
|
+
|
|
27
|
+
The DataTable side of the wiring. `TablePagination` is defined at module level — it is already a stable reference and does not need `useCallback`. Pass `pagedData` (the current page slice), not the full dataset — DataTable does not paginate internally; it renders whatever array it receives.
|
|
28
|
+
|
|
29
|
+
```jsx
|
|
30
|
+
import { DataTable } from '@elliemae/ds-data-table';
|
|
31
|
+
|
|
32
|
+
const TablePagination = () => {
|
|
33
|
+
// ... full assembly — see @elliemae/ds-pagination ds-pagination-setup skill
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
<DataTable columns={columns} data={pagedData} height={438} Pagination={TablePagination} />;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Core Patterns
|
|
40
|
+
|
|
41
|
+
### Resetting page index on filter change
|
|
42
|
+
|
|
43
|
+
Page index must always reset to 1 when filters change. With AJAX pagination, this means dispatching the new filters and page 1 together in a single API call:
|
|
44
|
+
|
|
45
|
+
```jsx
|
|
46
|
+
const onFiltersChange = useCallback(
|
|
47
|
+
(newFilters) => {
|
|
48
|
+
dispatch(setFilters(newFilters));
|
|
49
|
+
dispatch(setPageIndex(1));
|
|
50
|
+
dispatch(fetchPagedData({ filters: newFilters, page: 1, pageSize }));
|
|
51
|
+
},
|
|
52
|
+
[pageSize],
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If you encounter pagination combined with client-side filtering (`applyOutOfTheBoxFilters` slicing an in-memory array), treat it as a code smell. Pagination is by industry standards a backend concern — backends have specialized tools for it (SQL LIMIT/OFFSET, cursor-based queries, 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 whole flow suboptimal and fragile. Rendering paginated chunks from an in-memory array is better than rendering all rows at once, but it covers a backend limitation rather than solving the underlying problem.
|
|
57
|
+
|
|
58
|
+
Combined with client-side filtering the situation is worse: filters applied to an in-memory slice operate only on the current page, not the full dataset — the results are silently incorrect. Additionally, the in-memory array grows stale the moment the source data changes; server-side pagination and filtering inherently avoid this because every request fetches fresh data from the authoritative source. Raise this with the team before implementing.
|
|
59
|
+
|
|
60
|
+
## Common Mistakes
|
|
61
|
+
|
|
62
|
+
### HIGH Using deprecated lowercase pagination prop
|
|
63
|
+
|
|
64
|
+
Wrong:
|
|
65
|
+
|
|
66
|
+
```jsx
|
|
67
|
+
<DataTable columns={columns} data={rows} height={500} pagination={{ pageSize: 10 }} />
|
|
68
|
+
// or
|
|
69
|
+
<DataTable columns={columns} data={rows} height={500} pagination={false} />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Correct:
|
|
73
|
+
|
|
74
|
+
```jsx
|
|
75
|
+
// Capital-P Pagination render prop — TablePagination defined at module level
|
|
76
|
+
<DataTable columns={columns} data={pagedData} height={500} Pagination={TablePagination} />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The lowercase `pagination` prop is legacy kept for backwards compatibility. Migrate on sight.
|
|
80
|
+
|
|
81
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-pagination--paginated-table
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### HIGH Passing full dataset to DataTable instead of current page slice
|
|
86
|
+
|
|
87
|
+
Wrong:
|
|
88
|
+
|
|
89
|
+
```jsx
|
|
90
|
+
<DataTable columns={columns} data={allData} height={500} Pagination={TablePagination} />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Correct:
|
|
94
|
+
|
|
95
|
+
```jsx
|
|
96
|
+
<DataTable columns={columns} data={pagedData} height={500} Pagination={TablePagination} />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
DataTable does not internally paginate data. The app must slice to the current page. Passing all rows defeats virtualization.
|
|
100
|
+
|
|
101
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-pagination--paginated-table
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### HIGH Pagination component defined inside parent component instead of at module level
|
|
106
|
+
|
|
107
|
+
Wrong:
|
|
108
|
+
|
|
109
|
+
```jsx
|
|
110
|
+
// Defined inside a component — new reference or new component type on every render
|
|
111
|
+
const ParentComponent = () => {
|
|
112
|
+
const MyPagination = () => <DSPaginationContainer>...</DSPaginationContainer>;
|
|
113
|
+
// or: const MyPagination = useCallback(() => <DSPaginationContainer>...</DSPaginationContainer>, [...]);
|
|
114
|
+
return <DataTable columns={columns} data={pagedData} height={500} Pagination={MyPagination} />;
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Correct:
|
|
119
|
+
|
|
120
|
+
```jsx
|
|
121
|
+
// Defined at module level — stable reference, reads state from state manager directly
|
|
122
|
+
// See ds-pagination-setup skill from @elliemae/ds-pagination for the full implementation
|
|
123
|
+
const TablePagination = () => {
|
|
124
|
+
/* ... */
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const ParentComponent = () => (
|
|
128
|
+
<DataTable columns={columns} data={pagedData} height={500} Pagination={TablePagination} />
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A component defined inside another component creates a new component type identity on every render. React unmounts and remounts the subtree on every cycle, regardless of `useCallback` memoization. State manager integration (Redux/Zustand/Jotai) is the correct mechanism for sharing pagination state with a module-level component.
|
|
133
|
+
|
|
134
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-pagination--paginated-table
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### MEDIUM Not resetting page index when filters change
|
|
139
|
+
|
|
140
|
+
Wrong:
|
|
141
|
+
|
|
142
|
+
```jsx
|
|
143
|
+
const onFiltersChange = useCallback(
|
|
144
|
+
(newFilters) => {
|
|
145
|
+
dispatch(setFilters(newFilters));
|
|
146
|
+
dispatch(fetchPagedData({ filters: newFilters, page: pageIndex, pageSize }));
|
|
147
|
+
// pageIndex not reset — user stays on page 3 of a now-smaller result set
|
|
148
|
+
},
|
|
149
|
+
[pageIndex, pageSize],
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Correct:
|
|
154
|
+
|
|
155
|
+
```jsx
|
|
156
|
+
const onFiltersChange = useCallback(
|
|
157
|
+
(newFilters) => {
|
|
158
|
+
dispatch(setFilters(newFilters));
|
|
159
|
+
dispatch(setPageIndex(1));
|
|
160
|
+
dispatch(fetchPagedData({ filters: newFilters, page: 1, pageSize }));
|
|
161
|
+
},
|
|
162
|
+
[pageSize],
|
|
163
|
+
);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Stale page index after a filter change leaves the user on a page that may not exist in the filtered result set.
|
|
167
|
+
|
|
168
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-features-pagination--paginated-table
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
See also: ds-data-table-migration/SKILL.md — lowercase pagination prop migration
|
|
173
|
+
See also: ds-pagination-setup/SKILL.md — full TablePagination assembly with @elliemae/ds-pagination primitives
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## When this isn't enough
|
|
178
|
+
|
|
179
|
+
If the documented patterns cannot satisfy the requirement, contact the Dimsum team:
|
|
180
|
+
|
|
181
|
+
**ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
|
|
182
|
+
**Partners:** Your organization's Dimsum point of contact
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-data-table-row-variants
|
|
3
|
+
description: >
|
|
4
|
+
Row styling variants and visual grouping for @elliemae/ds-data-table. getRowVariant callback
|
|
5
|
+
returning ROW_VARIANTS values (PRIMARY, SECONDARY, HEADER_GROUP, COMPACT_PRIMARY,
|
|
6
|
+
COMPACT_SECONDARY). disabledRows Record<uid, boolean>. groupBy utility converting flat data into
|
|
7
|
+
nested group objects (dimsumHeaderValue + subRows) that DataTable renders as header-group rows.
|
|
8
|
+
groupedRowsRenderHeader for group label rendering (JSX.Element function or static string).
|
|
9
|
+
type: core
|
|
10
|
+
library: ds-data-table
|
|
11
|
+
library_version: '3.60.0'
|
|
12
|
+
requires:
|
|
13
|
+
- ds-data-table-setup
|
|
14
|
+
sources:
|
|
15
|
+
- '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
|
|
16
|
+
- '@elliemae/ds-data-table:dist/types/exported-related/RowVariants.d.ts'
|
|
17
|
+
- '@elliemae/ds-data-table:dist/types/exported-related/groupBy.d.ts'
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```jsx
|
|
23
|
+
import { DataTable, ROW_VARIANTS } from '@elliemae/ds-data-table';
|
|
24
|
+
|
|
25
|
+
// getRowVariant receives the row object and returns a ROW_VARIANTS value.
|
|
26
|
+
// Default is ROW_VARIANTS.PRIMARY — only override rows that need a different variant.
|
|
27
|
+
//
|
|
28
|
+
// getRowVariant is a plain function called at render time — not a React component.
|
|
29
|
+
// Do not call hooks in the callback body (the linter may even flag this).
|
|
30
|
+
// If returning a component type, that component IS rendered via <Component {...props} />
|
|
31
|
+
// and can use hooks normally — the constraint is on the callback body only.
|
|
32
|
+
const getRowVariant = (row) => {
|
|
33
|
+
if (row.original.status === 'inactive') return ROW_VARIANTS.SECONDARY;
|
|
34
|
+
return ROW_VARIANTS.PRIMARY;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
<DataTable columns={columns} data={rows} height={500} getRowVariant={getRowVariant} />;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Available ROW_VARIANTS values
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { ROW_VARIANTS } from '@elliemae/ds-data-table';
|
|
44
|
+
|
|
45
|
+
ROW_VARIANTS.PRIMARY; // 'ds-primary-row' — standard row (default)
|
|
46
|
+
ROW_VARIANTS.SECONDARY; // 'ds-secondary-row' — alternate visual treatment
|
|
47
|
+
ROW_VARIANTS.HEADER_GROUP; // 'ds-header-group-row' — group header row (used with groupBy)
|
|
48
|
+
ROW_VARIANTS.COMPACT_PRIMARY; // 'ds-compact-primary-row' — compact density, primary style
|
|
49
|
+
ROW_VARIANTS.COMPACT_SECONDARY; // 'ds-compact-secondary-row' — compact density, secondary style
|
|
50
|
+
ROW_VARIANTS.SKELETON; // 'ds-skeleton-row' — skeleton loading row; accessible via getRowVariant
|
|
51
|
+
// but the standard skeleton UX is controlled by the isSkeleton prop,
|
|
52
|
+
// not by returning this from getRowVariant (see ds-data-table-feedback)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Core Patterns
|
|
56
|
+
|
|
57
|
+
### groupBy — visual header-group rows
|
|
58
|
+
|
|
59
|
+
`groupBy(data, fieldName)` converts a flat data array into a nested group structure that DataTable
|
|
60
|
+
renders as header-group rows with their data rows below them.
|
|
61
|
+
|
|
62
|
+
**What groupBy produces:**
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { groupBy } from '@elliemae/ds-data-table';
|
|
66
|
+
|
|
67
|
+
const rows = [
|
|
68
|
+
{ id: 1, position: 'Developer', name: 'John', uid: 'abc' },
|
|
69
|
+
{ id: 2, position: 'Developer', name: 'Jane', uid: 'def' },
|
|
70
|
+
{ id: 3, position: 'Designer', name: 'Sam', uid: 'ghi' },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const groupedData = groupBy(rows, 'position');
|
|
74
|
+
// Result:
|
|
75
|
+
// [
|
|
76
|
+
// {
|
|
77
|
+
// dimsumHeaderValue: 'Developer', // group label — used by getRowVariant and groupedRowsRenderHeader
|
|
78
|
+
// subRows: [ // the data rows belonging to this group
|
|
79
|
+
// { id: 1, position: 'Developer', name: 'John', uid: 'abc' },
|
|
80
|
+
// { id: 2, position: 'Developer', name: 'Jane', uid: 'def' },
|
|
81
|
+
// ],
|
|
82
|
+
// uid: '<auto-generated>', // stable key for DataTable row identity
|
|
83
|
+
// },
|
|
84
|
+
// {
|
|
85
|
+
// dimsumHeaderValue: 'Designer',
|
|
86
|
+
// subRows: [{ id: 3, position: 'Designer', name: 'Sam', uid: 'ghi' }],
|
|
87
|
+
// uid: '<auto-generated>',
|
|
88
|
+
// },
|
|
89
|
+
// ]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
DataTable receives this nested structure and renders it visually as a flat list:
|
|
93
|
+
one header-group row per group (styled via `ROW_VARIANTS.HEADER_GROUP`), followed by
|
|
94
|
+
its data rows. DataTable handles the flattening internally — do not manually flatten before passing.
|
|
95
|
+
|
|
96
|
+
**Wiring getRowVariant and groupedRowsRenderHeader:**
|
|
97
|
+
|
|
98
|
+
```jsx
|
|
99
|
+
// getRowVariant distinguishes group header rows from data rows using dimsumHeaderValue
|
|
100
|
+
const getRowVariant = useCallback((row) => {
|
|
101
|
+
if (row.original.dimsumHeaderValue) return ROW_VARIANTS.HEADER_GROUP;
|
|
102
|
+
return ROW_VARIANTS.PRIMARY;
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
// groupedRowsRenderHeader: function returning JSX.Element, or a static string.
|
|
106
|
+
// groupedRowsRenderHeader is a plain function called at render time — not a React component.
|
|
107
|
+
// Do not call hooks in the callback body (the linter will flag this).
|
|
108
|
+
// If returning a JSX element containing a component (e.g.
|
|
109
|
+
// <MyGroupHeader value={value} subRows={subRows} />), that component gets its own fiber
|
|
110
|
+
// as a rendered child and can use hooks normally — the constraint is on the callback body only.
|
|
111
|
+
const groupedRowsRenderHeader = useCallback(
|
|
112
|
+
(value, subRows) => (
|
|
113
|
+
<span>
|
|
114
|
+
{value} ({subRows?.length ?? 0} items)
|
|
115
|
+
</span>
|
|
116
|
+
),
|
|
117
|
+
[],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
<DataTable
|
|
121
|
+
columns={columns}
|
|
122
|
+
data={groupedData}
|
|
123
|
+
height={500}
|
|
124
|
+
uniqueRowAccessor="uid"
|
|
125
|
+
getRowVariant={getRowVariant}
|
|
126
|
+
groupedRowsRenderHeader={groupedRowsRenderHeader}
|
|
127
|
+
/>;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Switching group-by column dynamically
|
|
131
|
+
|
|
132
|
+
The active group-by key belongs in the state manager — it drives which data the table displays and should survive navigation or component remounts.
|
|
133
|
+
|
|
134
|
+
```jsx
|
|
135
|
+
const { activeGroupBy } = useSelector(selectTableState);
|
|
136
|
+
const dispatch = useDispatch();
|
|
137
|
+
|
|
138
|
+
const groupedByPosition = useMemo(() => groupBy(rows, 'position'), [rows]);
|
|
139
|
+
const groupedByCountry = useMemo(() => groupBy(rows, 'country'), [rows]);
|
|
140
|
+
|
|
141
|
+
const displayedData = activeGroupBy === 'country' ? groupedByCountry : groupedByPosition;
|
|
142
|
+
|
|
143
|
+
const handleGroupByPosition = useCallback(() => dispatch(setGroupBy('position')), [dispatch]);
|
|
144
|
+
const handleGroupByCountry = useCallback(() => dispatch(setGroupBy('country')), [dispatch]);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### disabledRows
|
|
148
|
+
|
|
149
|
+
```jsx
|
|
150
|
+
// disabledRows: Record<rowUid, boolean>
|
|
151
|
+
// Disabled rows are visually styled as inactive and excluded from selection
|
|
152
|
+
const disabledRows = useMemo(() => ({ 'row-1': true, 'row-4': true }), []);
|
|
153
|
+
|
|
154
|
+
<DataTable columns={columns} data={rows} height={500} uniqueRowAccessor="id" disabledRows={disabledRows} />;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Common Mistakes
|
|
158
|
+
|
|
159
|
+
### HIGH Using ROW_VARIANTS that does not exist
|
|
160
|
+
|
|
161
|
+
Wrong:
|
|
162
|
+
|
|
163
|
+
```jsx
|
|
164
|
+
import { ROW_VARIANTS } from '@elliemae/ds-data-table';
|
|
165
|
+
const getRowVariant = (row) => {
|
|
166
|
+
if (row.original.isError) return ROW_VARIANTS.HIGHLIGHTED; // undefined — not a real value
|
|
167
|
+
return ROW_VARIANTS.PRIMARY;
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Correct:
|
|
172
|
+
|
|
173
|
+
```jsx
|
|
174
|
+
const getRowVariant = (row) => {
|
|
175
|
+
if (row.original.isError) return ROW_VARIANTS.SECONDARY;
|
|
176
|
+
return ROW_VARIANTS.PRIMARY;
|
|
177
|
+
};
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`ROW_VARIANTS.HIGHLIGHTED` does not exist. The full set of valid values is `PRIMARY`, `SECONDARY`, `HEADER_GROUP`, `COMPACT_PRIMARY`, `COMPACT_SECONDARY`, `SKELETON`. Returning an unrecognised string throws at runtime: `Error: <value> is not an out-of-the-box row variant` — it does not silently fall back.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### HIGH Returning a JSX element from getRowVariant instead of a component reference or variant string
|
|
185
|
+
|
|
186
|
+
Wrong:
|
|
187
|
+
|
|
188
|
+
```jsx
|
|
189
|
+
// Returns a JSX element (React.createElement result) — a new object on every call
|
|
190
|
+
const getRowVariant = (row) => {
|
|
191
|
+
if (row.original.isHeader) return <CustomHeaderRow row={row} />;
|
|
192
|
+
return ROW_VARIANTS.PRIMARY;
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Correct — use ROW_VARIANTS (preferred):
|
|
197
|
+
|
|
198
|
+
```jsx
|
|
199
|
+
const getRowVariant = (row) => {
|
|
200
|
+
if (row.original.dimsumHeaderValue) return ROW_VARIANTS.HEADER_GROUP;
|
|
201
|
+
return ROW_VARIANTS.PRIMARY;
|
|
202
|
+
};
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Correct — component reference as last resort (strongly discouraged, see note below):
|
|
206
|
+
|
|
207
|
+
```jsx
|
|
208
|
+
// CustomHeaderRow defined at module level — stable reference
|
|
209
|
+
const CustomHeaderRow = (props) => {
|
|
210
|
+
/* ... */
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const getRowVariant = useCallback((row) => {
|
|
214
|
+
if (row.original.dimsumHeaderValue) return CustomHeaderRow; // reference, not instance
|
|
215
|
+
return ROW_VARIANTS.PRIMARY;
|
|
216
|
+
}, []);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`getRowVariant` accepts `RowVariant | React.ComponentType<RowVariantProps>` — the component _reference_ (not a JSX element instance). Returning `<CustomHeaderRow row={row} />` creates a new element object on every call and passes the wrong type. Returning `CustomHeaderRow` (a stable module-level reference) is type-correct and does not violate the rule that component props must receive stable module-level references.
|
|
220
|
+
|
|
221
|
+
However, DataTable renders the component by spreading the full internal `RowVariantProps` onto it — including `focusedRowId`, `drilldownRowId`, `itemIndex`, `draggableProps`, `isDragOverlay`, `minHeight`, `height`, `dropIndicatorPosition`, `isDropValid`, and `CustomRowContentRenderer`. A custom component must handle all of these correctly or risk breaking row focus, drag-and-drop, height virtualization, and drop indicator rendering. This is why the custom component path is strongly discouraged. Use `ROW_VARIANTS` string values for all standard scenarios. If no available variant satisfies the requirement, contact the Dimsum team through your organization's established channel.
|
|
222
|
+
|
|
223
|
+
Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-datatable-advanced--group-by-column
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### MEDIUM Manually constructing groupBy objects instead of using the utility
|
|
228
|
+
|
|
229
|
+
Wrong:
|
|
230
|
+
|
|
231
|
+
```jsx
|
|
232
|
+
// Manually building what groupBy produces — fragile and likely to diverge
|
|
233
|
+
const groupedData = [{ dimsumHeaderValue: 'Developer', subRows: [row1, row2], uid: 'hardcoded-1' }];
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Correct:
|
|
237
|
+
|
|
238
|
+
```jsx
|
|
239
|
+
const groupedData = groupBy(rows, 'position');
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`groupBy` generates a stable `uid` for each group object via a 16-character random ID. Manually constructing these objects risks producing malformed group data (wrong uid, missing subRows shape) that breaks row identity tracking inside DataTable.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
### HIGH Non-primitive row variant props not memoized cause unnecessary re-renders
|
|
247
|
+
|
|
248
|
+
Every non-primitive prop passed to DataTable must be a stable reference. For row variants and grouping, the props that require memoization are:
|
|
249
|
+
|
|
250
|
+
| Prop | Type | Memoization |
|
|
251
|
+
| ------------------------- | ------------------ | ------------------------------------------------- |
|
|
252
|
+
| `getRowVariant` | callback | `useCallback` |
|
|
253
|
+
| `groupedRowsRenderHeader` | callback or string | `useCallback` if function; stable const if string |
|
|
254
|
+
| `disabledRows` | object | `useMemo` if constructed inline |
|
|
255
|
+
|
|
256
|
+
Non-stable references cause DataTable to re-render on every parent render cycle regardless of whether row variant state actually changed.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
See also: ds-data-table-expandable/SKILL.md — expand patterns; tableRowDetails master-detail is deprecated
|