@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,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