@drivenets/design-system 0.17.0 → 0.18.1
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/CHANGELOG.md +24 -0
- package/dist/index.cjs +1797 -805
- package/dist/index.d.cts +117 -53
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +117 -53
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1799 -809
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +3 -3
- package/src/components/ds-alert-banner/ds-alert-banner-global.figma.ts +49 -0
- package/src/components/ds-alert-banner/ds-alert-banner.figma.ts +51 -0
- package/src/components/ds-autocomplete/ds-autocomplete.module.scss +15 -4
- package/src/components/ds-autocomplete/ds-autocomplete.tsx +10 -3
- package/src/components/ds-autocomplete/ds-autocomplete.types.ts +8 -0
- package/src/components/ds-button-v3/ds-button-v3.figma.ts +5 -3
- package/src/components/ds-button-v3/ds-button-v3.module.scss +84 -17
- package/src/components/ds-button-v3/ds-button-v3.tsx +2 -0
- package/src/components/ds-button-v3/ds-button-v3.types.ts +11 -2
- package/src/components/ds-button-v3/ds-button-v4.figma.ts +121 -0
- package/src/components/ds-comment-bubble/ds-comment-bubble.figma.ts +52 -0
- package/src/components/ds-comment-card/ds-comment-card.figma.ts +36 -0
- package/src/components/ds-comment-indicator/ds-comment-indicator.figma.ts +32 -0
- package/src/components/ds-comments-drawer/ds-comments-drawer.figma.ts +19 -0
- package/src/components/ds-select/ds-select.figma.ts +2 -1
- package/src/components/ds-select/ds-select.tsx +6 -1
- package/src/components/ds-select/ds-select.types.ts +2 -1
- package/src/components/ds-slider/ds-slider.figma.ts +29 -0
- package/src/components/ds-slider/ds-slider.module.scss +104 -0
- package/src/components/ds-slider/ds-slider.tsx +118 -0
- package/src/components/ds-slider/ds-slider.types.ts +102 -0
- package/src/components/ds-slider/ds-slider.utils.ts +14 -0
- package/src/components/ds-slider/index.ts +11 -0
- package/src/components/ds-smart-tabs/figma/ds-smart-tabs-tab.figma.ts +83 -0
- package/src/components/ds-smart-tabs/figma/ds-smart-tabs.figma.ts +23 -0
- package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +5 -1
- package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +15 -3
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +15 -1
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +29 -6
- package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.module.scss +23 -0
- package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.tsx +218 -0
- package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.types.ts +14 -0
- package/src/components/ds-table/components/ds-table-resize-handle/index.ts +1 -0
- package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.module.scss +18 -0
- package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.tsx +16 -0
- package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.types.ts +13 -0
- package/src/components/ds-table/components/ds-table-resize-overlay/index.ts +1 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +1 -1
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +9 -2
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +2 -1
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +9 -2
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +2 -2
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +2 -1
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +2 -1
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +2 -1
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +2 -1
- package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +12 -3
- package/src/components/ds-table/context/ds-table-context.tsx +208 -0
- package/src/components/ds-table/ds-table-defaults.ts +42 -0
- package/src/components/ds-table/ds-table.module.scss +4 -1
- package/src/components/ds-table/ds-table.tsx +139 -214
- package/src/components/ds-table/ds-table.types.ts +53 -47
- package/src/components/ds-table/grouping/utils/column-group.ts +7 -5
- package/src/components/ds-table/hooks/use-cell-editor.ts +5 -1
- package/src/components/ds-table/hooks/use-column-resize.ts +385 -0
- package/src/components/ds-table/hooks/use-editing-state.ts +190 -0
- package/src/components/ds-table/index.ts +1 -0
- package/src/components/ds-table/styles/_variables.scss +2 -1
- package/src/components/ds-table/utils/column-size-meta.augmentation.ts +16 -0
- package/src/components/ds-table/utils/column-size.ts +213 -10
- package/src/components/ds-table/utils/column-size.unit.test.ts +213 -0
- package/src/components/ds-table/utils/constants.ts +28 -0
- package/src/components/ds-table/utils/frozen-body.ts +33 -0
- package/src/components/ds-table/utils/frozen-body.unit.test.ts +44 -0
- package/src/components/ds-table/utils/table-api.ts +42 -0
- package/src/components/ds-table/utils/table-api.unit.test.ts +82 -0
- package/src/components/ds-table/utils/table-columns.tsx +119 -0
- package/src/components/ds-table/utils/table-columns.unit.test.ts +199 -0
- package/src/components/ds-tag/ds-tag.figma.ts +51 -0
- package/src/components/ds-tag-filter/ds-tag-filter.figma.ts +16 -0
- package/src/components/ds-tag-filter/ds-tag-filter.module.scss +7 -35
- package/src/components/ds-tag-filter/ds-tag-filter.tsx +34 -57
- package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -1
- package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +13 -18
- package/src/components/ds-text-input/ds-input-field-v02.figma.ts +29 -4
- package/src/components/ds-text-input/ds-text-input.figma.ts +3 -2
- package/src/components/ds-text-input/ds-text-input.tsx +1 -0
- package/src/components/ds-text-input/ds-text-input.types.ts +1 -1
- package/src/components/ds-tree/ds-tree.figma.ts +54 -0
- package/src/components/ds-workspace-layout/ds-workspace-layout.figma.ts +99 -0
- package/src/components/ds-workspace-layout/ds-workspace-layout.module.scss +1 -8
- package/src/index.ts +1 -0
- package/src/styles/_gradients.scss +19 -0
- package/src/styles/_root.scss +39 -15
- package/src/styles/_typography.scss +1 -0
- package/src/styles/shared/_input.scss +10 -0
- package/src/styles/styles.scss +1 -0
- package/src/components/ds-table/context/ds-table-context.ts +0 -112
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useImperativeHandle, useMemo } from 'react';
|
|
3
3
|
import {
|
|
4
|
-
type ColumnDef,
|
|
5
4
|
type RowSelectionState,
|
|
6
5
|
type ColumnFiltersState,
|
|
7
6
|
getCoreRowModel,
|
|
@@ -9,6 +8,7 @@ import {
|
|
|
9
8
|
getFilteredRowModel,
|
|
10
9
|
getSortedRowModel,
|
|
11
10
|
type SortingState,
|
|
11
|
+
type Table as TanstackTable,
|
|
12
12
|
useReactTable,
|
|
13
13
|
type VisibilityState,
|
|
14
14
|
} from '@tanstack/react-table';
|
|
@@ -16,27 +16,20 @@ import classnames from 'classnames';
|
|
|
16
16
|
import { Table, TableBody, TableCell, TableRow } from './components/core-table';
|
|
17
17
|
import { DsTableBulkActions } from './components/ds-table-bulk-actions';
|
|
18
18
|
import { DsTableHeader } from './components/ds-table-header';
|
|
19
|
+
import { DsTableResizeOverlay } from './components/ds-table-resize-overlay';
|
|
19
20
|
import styles from './ds-table.module.scss';
|
|
20
21
|
import type { DsDataTableProps, DsTableRowSize } from './ds-table.types';
|
|
22
|
+
import { applyDsTableDefaults } from './ds-table-defaults';
|
|
21
23
|
import { DsTableRow } from './components/ds-table-row';
|
|
22
|
-
import { DsTableRowExpandableCell } from './components/ds-table-row-expandable-cell';
|
|
23
|
-
import { DsTableRowSelectableCell } from './components/ds-table-row-selectable-cell';
|
|
24
|
-
import { DsTableHeaderSelectableCell } from './components/ds-table-header-selectable-cell';
|
|
25
24
|
import { useDragAndDrop } from './hooks/use-drag-and-drop';
|
|
26
|
-
import {
|
|
25
|
+
import { useColumnResize } from './hooks/use-column-resize';
|
|
26
|
+
import { DsTableContextProvider } from './context/ds-table-context';
|
|
27
27
|
import { DsTableBodyVirtualized } from './components/ds-table-body-virtualized';
|
|
28
28
|
import { useColumnGroups } from './grouping';
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
EXPANDER_COLUMN_WIDTH,
|
|
34
|
-
REORDER_COLUMN_ID,
|
|
35
|
-
REORDER_COLUMN_WIDTH,
|
|
36
|
-
SELECT_COLUMN_ID,
|
|
37
|
-
SELECT_COLUMN_WIDTH,
|
|
38
|
-
SKELETON_ROW_COUNT,
|
|
39
|
-
} from './utils/constants';
|
|
29
|
+
import { EMPTY_TABLE_STATE_TEXT } from './utils/constants';
|
|
30
|
+
import { createSkeletonRows, getAugmentedColumns, toSkeletonColumns } from './utils/table-columns';
|
|
31
|
+
import { createTableApi } from './utils/table-api';
|
|
32
|
+
import { areBodiesFrozen } from './utils/frozen-body';
|
|
40
33
|
|
|
41
34
|
// Row size to pixel height mapping (matches CSS variables)
|
|
42
35
|
const ROW_SIZE_HEIGHT_MAP: Record<DsTableRowSize, number> = {
|
|
@@ -48,46 +41,41 @@ const ROW_SIZE_HEIGHT_MAP: Record<DsTableRowSize, number> = {
|
|
|
48
41
|
/**
|
|
49
42
|
* Design system Table component
|
|
50
43
|
*/
|
|
51
|
-
const DsTable = <TData extends { id: string }, TValue>({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
activeRowId,
|
|
87
|
-
infiniteScroll,
|
|
88
|
-
onCellEdit,
|
|
89
|
-
onCellValidate,
|
|
90
|
-
}: DsDataTableProps<TData, TValue>) => {
|
|
44
|
+
const DsTable = <TData extends { id: string }, TValue>(props: DsDataTableProps<TData, TValue>) => {
|
|
45
|
+
const tableProps = applyDsTableDefaults(props);
|
|
46
|
+
const {
|
|
47
|
+
ref,
|
|
48
|
+
columns: columnsProp,
|
|
49
|
+
data: tableData,
|
|
50
|
+
controls,
|
|
51
|
+
virtualized,
|
|
52
|
+
virtualizedOptions,
|
|
53
|
+
className,
|
|
54
|
+
emptyState,
|
|
55
|
+
bordered,
|
|
56
|
+
fullWidth,
|
|
57
|
+
rowSize,
|
|
58
|
+
loading,
|
|
59
|
+
expandable,
|
|
60
|
+
selectable,
|
|
61
|
+
showSelectAllCheckbox,
|
|
62
|
+
onSelectionChange,
|
|
63
|
+
onSortingChange,
|
|
64
|
+
onScroll,
|
|
65
|
+
actions,
|
|
66
|
+
reorderable,
|
|
67
|
+
onOrderChange,
|
|
68
|
+
columnFilters: externalColumnFilters,
|
|
69
|
+
onColumnFiltersChange,
|
|
70
|
+
columnVisibility: externalColumnVisibility,
|
|
71
|
+
onColumnVisibilityChange,
|
|
72
|
+
collapsedColumnGroups: externalCollapsedColumnGroups,
|
|
73
|
+
onCollapsedColumnGroupsChange,
|
|
74
|
+
infiniteScroll,
|
|
75
|
+
resizableColumns,
|
|
76
|
+
columnSizing,
|
|
77
|
+
onColumnSizingChange,
|
|
78
|
+
} = tableProps;
|
|
91
79
|
const [data, setData] = React.useState(tableData);
|
|
92
80
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
93
81
|
const [internalColumnFilters, setInternalColumnFilters] = React.useState<ColumnFiltersState>([]);
|
|
@@ -103,8 +91,6 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
103
91
|
items: data,
|
|
104
92
|
});
|
|
105
93
|
|
|
106
|
-
const tableContainerRef = React.useRef<HTMLDivElement>(null);
|
|
107
|
-
|
|
108
94
|
const columnFilters = externalColumnFilters ?? internalColumnFilters;
|
|
109
95
|
const handleColumnFiltersChange = (
|
|
110
96
|
updaterOrValue: ColumnFiltersState | ((old: ColumnFiltersState) => ColumnFiltersState),
|
|
@@ -157,74 +143,33 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
157
143
|
onSelectionChange?.(newRowSelection);
|
|
158
144
|
};
|
|
159
145
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
const columns = useMemo
|
|
165
|
-
const augmentedColumns: ColumnDef<TData, TValue>[] = [...columnsProp];
|
|
166
|
-
|
|
167
|
-
if (hasSelectColumn) {
|
|
168
|
-
const selectColumn: ColumnDef<TData, TValue> = {
|
|
169
|
-
id: SELECT_COLUMN_ID,
|
|
170
|
-
size: SELECT_COLUMN_WIDTH,
|
|
171
|
-
enableSorting: false,
|
|
172
|
-
enableResizing: false,
|
|
173
|
-
header: ({ table }) => (showSelectAllCheckbox ? <DsTableHeaderSelectableCell table={table} /> : null),
|
|
174
|
-
cell: ({ row }) => <DsTableRowSelectableCell row={row} />,
|
|
175
|
-
};
|
|
176
|
-
augmentedColumns.unshift(selectColumn);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (hasExpanderColumn) {
|
|
180
|
-
const expanderColumn: ColumnDef<TData, TValue> = {
|
|
181
|
-
id: EXPANDER_COLUMN_ID,
|
|
182
|
-
size: EXPANDER_COLUMN_WIDTH,
|
|
183
|
-
enableSorting: false,
|
|
184
|
-
enableResizing: false,
|
|
185
|
-
header: () => null,
|
|
186
|
-
cell: ({ row }) => (row.getCanExpand() ? <DsTableRowExpandableCell row={row} /> : null),
|
|
187
|
-
};
|
|
188
|
-
augmentedColumns.unshift(expanderColumn);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (hasReorderColumn) {
|
|
192
|
-
// Cell is rendered inline by DsTableRow when it encounters REORDER_COLUMN_ID,
|
|
193
|
-
// since the drag handle needs row-level useSortable state.
|
|
194
|
-
const reorderColumn: ColumnDef<TData, TValue> = {
|
|
195
|
-
id: REORDER_COLUMN_ID,
|
|
196
|
-
size: REORDER_COLUMN_WIDTH,
|
|
197
|
-
enableSorting: false,
|
|
198
|
-
enableResizing: false,
|
|
199
|
-
header: 'Order',
|
|
200
|
-
cell: () => null,
|
|
201
|
-
};
|
|
202
|
-
augmentedColumns.unshift(reorderColumn);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return augmentedColumns;
|
|
206
|
-
}, [columnsProp, hasExpanderColumn, hasReorderColumn, hasSelectColumn, showSelectAllCheckbox]);
|
|
207
|
-
|
|
208
|
-
const skeletonColumns = useMemo<ColumnDef<TData, TValue>[]>(() => {
|
|
209
|
-
const toSkeleton = (cols: ColumnDef<TData, TValue>[]): ColumnDef<TData, TValue>[] =>
|
|
210
|
-
cols.map((column) =>
|
|
211
|
-
'columns' in column && column.columns
|
|
212
|
-
? { ...column, columns: toSkeleton(column.columns) }
|
|
213
|
-
: { ...column, cell: column.loadingCell ?? (() => <DsSkeletonText width="60%" />) },
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
return toSkeleton(columns);
|
|
217
|
-
}, [columns]);
|
|
218
|
-
|
|
219
|
-
const skeletonData = useMemo<TData[]>(
|
|
146
|
+
// Boolean flags only — an inline `selectable` / `expandable` callback must
|
|
147
|
+
// not rebuild column defs (and remount cells) on every parent render.
|
|
148
|
+
const hasSelectColumn = Boolean(selectable);
|
|
149
|
+
const hasExpanderColumn = Boolean(expandable);
|
|
150
|
+
const columns = useMemo(
|
|
220
151
|
() =>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
152
|
+
getAugmentedColumns(columnsProp, {
|
|
153
|
+
selectable: hasSelectColumn,
|
|
154
|
+
expandable: hasExpanderColumn,
|
|
155
|
+
reorderable,
|
|
156
|
+
virtualized,
|
|
157
|
+
showSelectAllCheckbox,
|
|
158
|
+
}),
|
|
159
|
+
[columnsProp, hasSelectColumn, hasExpanderColumn, reorderable, virtualized, showSelectAllCheckbox],
|
|
226
160
|
);
|
|
227
161
|
|
|
162
|
+
const skeletonColumns = useMemo(() => toSkeletonColumns(columns), [columns]);
|
|
163
|
+
const skeletonData = useMemo(() => createSkeletonRows<TData>(), []);
|
|
164
|
+
|
|
165
|
+
const resize = useColumnResize({
|
|
166
|
+
enabled: resizableColumns,
|
|
167
|
+
columnSizing,
|
|
168
|
+
onColumnSizingChange,
|
|
169
|
+
columns,
|
|
170
|
+
columnVisibility,
|
|
171
|
+
});
|
|
172
|
+
|
|
228
173
|
const table = useReactTable({
|
|
229
174
|
data: loading ? skeletonData : reorderable ? data : tableData,
|
|
230
175
|
columns: loading ? skeletonColumns : columns,
|
|
@@ -238,68 +183,22 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
238
183
|
getRowId: (row) => row.id,
|
|
239
184
|
getExpandedRowModel: getExpandedRowModel(),
|
|
240
185
|
getRowCanExpand: typeof expandable === 'function' ? (row) => expandable(row.original) : () => expandable,
|
|
186
|
+
...resize.tableOptions,
|
|
241
187
|
state: {
|
|
242
188
|
sorting,
|
|
243
189
|
columnFilters,
|
|
244
190
|
columnVisibility,
|
|
245
191
|
rowSelection,
|
|
192
|
+
...resize.state,
|
|
246
193
|
},
|
|
247
194
|
enableRowSelection: typeof selectable === 'function' ? (row) => selectable(row.original) : selectable,
|
|
248
195
|
});
|
|
249
196
|
|
|
250
|
-
|
|
251
|
-
ref,
|
|
252
|
-
() => ({
|
|
253
|
-
selectRow: (rowId: string) => {
|
|
254
|
-
table.getRow(rowId).toggleSelected(true);
|
|
255
|
-
},
|
|
256
|
-
deselectRow: (rowId: string) => {
|
|
257
|
-
table.getRow(rowId).toggleSelected(false);
|
|
258
|
-
},
|
|
259
|
-
selectAllRows: () => {
|
|
260
|
-
table.toggleAllRowsSelected(true);
|
|
261
|
-
},
|
|
262
|
-
deselectAllRows: () => {
|
|
263
|
-
table.toggleAllRowsSelected(false);
|
|
264
|
-
},
|
|
265
|
-
selectRows: (rowIds: string[]) => {
|
|
266
|
-
const selection: Record<string, boolean> = {};
|
|
267
|
-
rowIds.forEach((id) => (selection[id] = true));
|
|
268
|
-
table.setRowSelection(selection);
|
|
269
|
-
},
|
|
270
|
-
getSelectedRows: () => {
|
|
271
|
-
return table.getFilteredSelectedRowModel().rows.map((r) => r.original);
|
|
272
|
-
},
|
|
273
|
-
expandRow: (rowId: string) => {
|
|
274
|
-
table.getRow(rowId).toggleExpanded(true);
|
|
275
|
-
},
|
|
276
|
-
collapseRow: (rowId: string) => {
|
|
277
|
-
table.getRow(rowId).toggleExpanded(false);
|
|
278
|
-
},
|
|
279
|
-
expandAllRows: () => {
|
|
280
|
-
table.toggleAllRowsExpanded(true);
|
|
281
|
-
},
|
|
282
|
-
collapseAllRows: () => {
|
|
283
|
-
table.toggleAllRowsExpanded(false);
|
|
284
|
-
},
|
|
285
|
-
expandRows: (rowIds: string[]) => {
|
|
286
|
-
const expansion: Record<string, boolean> = {};
|
|
287
|
-
rowIds.forEach((id) => (expansion[id] = true));
|
|
288
|
-
table.setExpanded(expansion);
|
|
289
|
-
},
|
|
290
|
-
}),
|
|
291
|
-
[table],
|
|
292
|
-
);
|
|
197
|
+
const { columnSizeVars, activeResize, context: resizeContext } = resize.bind(table);
|
|
293
198
|
|
|
294
|
-
|
|
199
|
+
useImperativeHandle(ref, () => createTableApi(table), [table]);
|
|
295
200
|
|
|
296
|
-
const
|
|
297
|
-
<TableRow>
|
|
298
|
-
<TableCell colSpan={columns.length} className={styles.emptyState}>
|
|
299
|
-
{emptyState || EMPTY_TABLE_STATE_TEXT}
|
|
300
|
-
</TableCell>
|
|
301
|
-
</TableRow>
|
|
302
|
-
);
|
|
201
|
+
const { rowsById } = table.getRowModel();
|
|
303
202
|
|
|
304
203
|
const selectedRows = Object.entries(rowSelection)
|
|
305
204
|
.filter(([, selected]) => selected)
|
|
@@ -308,50 +207,36 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
308
207
|
|
|
309
208
|
const isBulkActionsVisible = selectable && actions.length > 0 && selectedRows.length > 0;
|
|
310
209
|
|
|
311
|
-
const editingState = useEditingState<TData, TValue>(onCellEdit, onCellValidate);
|
|
312
|
-
|
|
313
|
-
const contextValue: DsTableContextType<TData, TValue> = {
|
|
314
|
-
stickyHeader,
|
|
315
|
-
bordered,
|
|
316
|
-
fullWidth,
|
|
317
|
-
rowSize,
|
|
318
|
-
expandable,
|
|
319
|
-
selectable,
|
|
320
|
-
reorderable,
|
|
321
|
-
showSelectAllCheckbox,
|
|
322
|
-
loading,
|
|
323
|
-
onRowClick: loading ? undefined : onRowClick,
|
|
324
|
-
onRowDoubleClick: loading ? undefined : onRowDoubleClick,
|
|
325
|
-
primaryRowActions,
|
|
326
|
-
secondaryRowActions,
|
|
327
|
-
renderExpandedRow,
|
|
328
|
-
virtualized,
|
|
329
|
-
activeRowId,
|
|
330
|
-
collapsedColumnGroups,
|
|
331
|
-
onToggleColumnGroup: toggleColumnGroup,
|
|
332
|
-
locale,
|
|
333
|
-
onCellEdit,
|
|
334
|
-
...editingState,
|
|
335
|
-
};
|
|
336
|
-
|
|
337
210
|
return (
|
|
338
|
-
<
|
|
211
|
+
<DsTableContextProvider
|
|
212
|
+
tableProps={tableProps}
|
|
213
|
+
derived={{
|
|
214
|
+
grouping: {
|
|
215
|
+
collapsedColumnGroups,
|
|
216
|
+
onToggleColumnGroup: toggleColumnGroup,
|
|
217
|
+
},
|
|
218
|
+
resize: resizeContext,
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
339
221
|
<div
|
|
340
222
|
className={classnames(styles.container, isBulkActionsVisible && styles.bulkActionsVisible, className)}
|
|
341
223
|
>
|
|
342
224
|
{controls && <div className={styles.controls}>{controls}</div>}
|
|
343
225
|
<div
|
|
344
|
-
ref={
|
|
226
|
+
ref={resizeContext.resizeContainerRef}
|
|
345
227
|
className={classnames(
|
|
346
228
|
!virtualized && styles.dataTableContainer,
|
|
347
229
|
virtualized && styles.virtualizedContainer,
|
|
348
230
|
)}
|
|
349
231
|
>
|
|
350
232
|
<DragWrapper>
|
|
351
|
-
<Table
|
|
233
|
+
<Table
|
|
234
|
+
className={classnames(fullWidth && styles.fullWidth, !bordered && styles.tableNoBorder)}
|
|
235
|
+
style={columnSizeVars}
|
|
236
|
+
>
|
|
352
237
|
<DsTableHeader table={table} />
|
|
353
238
|
{virtualized ? (
|
|
354
|
-
<
|
|
239
|
+
<MemoizedDsTableBodyVirtualized
|
|
355
240
|
table={table}
|
|
356
241
|
emptyState={emptyState}
|
|
357
242
|
estimateSize={virtualizedOptions?.estimateSize || ROW_SIZE_HEIGHT_MAP[rowSize]}
|
|
@@ -361,18 +246,18 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
361
246
|
infiniteScroll={infiniteScroll}
|
|
362
247
|
/>
|
|
363
248
|
) : (
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
: renderEmptyState()}
|
|
371
|
-
</SortableWrapper>
|
|
372
|
-
</TableBody>
|
|
249
|
+
<MemoizedDsTableRowsBody
|
|
250
|
+
table={table}
|
|
251
|
+
rowSelection={rowSelection}
|
|
252
|
+
emptyState={emptyState}
|
|
253
|
+
SortableWrapper={SortableWrapper}
|
|
254
|
+
/>
|
|
373
255
|
)}
|
|
374
256
|
</Table>
|
|
375
257
|
</DragWrapper>
|
|
258
|
+
{activeResize ? (
|
|
259
|
+
<DsTableResizeOverlay offset={activeResize.offset} phase={activeResize.phase} />
|
|
260
|
+
) : null}
|
|
376
261
|
</div>
|
|
377
262
|
{selectable && actions.length > 0 && (
|
|
378
263
|
<DsTableBulkActions
|
|
@@ -385,10 +270,50 @@ const DsTable = <TData extends { id: string }, TValue>({
|
|
|
385
270
|
/>
|
|
386
271
|
)}
|
|
387
272
|
</div>
|
|
388
|
-
</
|
|
273
|
+
</DsTableContextProvider>
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
interface DsTableRowsBodyProps<TData extends { id: string }> {
|
|
278
|
+
table: TanstackTable<TData>;
|
|
279
|
+
rowSelection: RowSelectionState;
|
|
280
|
+
emptyState?: React.ReactNode;
|
|
281
|
+
SortableWrapper: React.ComponentType<React.PropsWithChildren>;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const DsTableRowsBody = <TData extends { id: string }>({
|
|
285
|
+
table,
|
|
286
|
+
rowSelection,
|
|
287
|
+
emptyState,
|
|
288
|
+
SortableWrapper,
|
|
289
|
+
}: DsTableRowsBodyProps<TData>) => {
|
|
290
|
+
const { rows } = table.getRowModel();
|
|
291
|
+
const columnCount = table.getVisibleLeafColumns().length;
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<TableBody>
|
|
295
|
+
<SortableWrapper>
|
|
296
|
+
{rows.length ? (
|
|
297
|
+
rows.map((row) => <DsTableRow key={row.id} row={row} isSelected={!!rowSelection[row.id]} />)
|
|
298
|
+
) : (
|
|
299
|
+
<TableRow>
|
|
300
|
+
<TableCell colSpan={columnCount} className={styles.emptyState}>
|
|
301
|
+
{emptyState || EMPTY_TABLE_STATE_TEXT}
|
|
302
|
+
</TableCell>
|
|
303
|
+
</TableRow>
|
|
304
|
+
)}
|
|
305
|
+
</SortableWrapper>
|
|
306
|
+
</TableBody>
|
|
389
307
|
);
|
|
390
308
|
};
|
|
391
309
|
|
|
310
|
+
const MemoizedDsTableRowsBody = React.memo(DsTableRowsBody, areBodiesFrozen) as typeof DsTableRowsBody;
|
|
311
|
+
|
|
312
|
+
const MemoizedDsTableBodyVirtualized = React.memo(
|
|
313
|
+
DsTableBodyVirtualized,
|
|
314
|
+
areBodiesFrozen,
|
|
315
|
+
) as typeof DsTableBodyVirtualized;
|
|
316
|
+
|
|
392
317
|
DsTable.displayName = 'DsTable';
|
|
393
318
|
|
|
394
319
|
export default DsTable;
|
|
@@ -31,6 +31,11 @@ export interface DsTableLocale {
|
|
|
31
31
|
* Accessible label for the toggle that expands a column group.
|
|
32
32
|
*/
|
|
33
33
|
expandColumnGroup: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Accessible label for the column resize handle.
|
|
37
|
+
*/
|
|
38
|
+
resizeColumn: string;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
/**
|
|
@@ -39,6 +44,7 @@ export interface DsTableLocale {
|
|
|
39
44
|
export const defaultDsTableLocale: DsTableLocale = Object.freeze({
|
|
40
45
|
collapseColumnGroup: 'Collapse column group',
|
|
41
46
|
expandColumnGroup: 'Expand column group',
|
|
47
|
+
resizeColumn: 'Resize column',
|
|
42
48
|
});
|
|
43
49
|
|
|
44
50
|
/**
|
|
@@ -320,6 +326,31 @@ export interface DsDataTableProps<TData, TValue> {
|
|
|
320
326
|
*/
|
|
321
327
|
rowSize?: DsTableRowSize;
|
|
322
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Enables resizing columns by dragging the right edge of a header cell.
|
|
331
|
+
* Double-click a handle to restore the column's original width. Opt out per
|
|
332
|
+
* column with `columnDef.enableResizing: false`. Leaf columns may set
|
|
333
|
+
* `minSize` and `maxSize`. Those bounds on a column group are ignored.
|
|
334
|
+
* @default false
|
|
335
|
+
*/
|
|
336
|
+
resizableColumns?: boolean;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Persisted column widths as a column id → width (px) map, e.g. a map saved
|
|
340
|
+
* from {@link onColumnSizingChange}. Pass it (typically on the next mount) to
|
|
341
|
+
* restore widths; columns absent from the map are measured and sized
|
|
342
|
+
* automatically. This seeds widths — live resizing still updates them
|
|
343
|
+
* internally — so it is not a fully controlled value.
|
|
344
|
+
*/
|
|
345
|
+
columnSizing?: Record<string, number>;
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Called with the next column id → width (px) map when a resize finishes
|
|
349
|
+
* (drag end, double-click restore, or keyboard nudge). Use this to persist
|
|
350
|
+
* widths across sessions.
|
|
351
|
+
*/
|
|
352
|
+
onColumnSizingChange?: (columnSizing: Record<string, number>) => void;
|
|
353
|
+
|
|
323
354
|
/**
|
|
324
355
|
* When `true`, renders placeholder skeleton rows instead of the data. Column
|
|
325
356
|
* headers stay intact and row interactions are disabled while loading.
|
|
@@ -511,53 +542,28 @@ export interface DsDataTableProps<TData, TValue> {
|
|
|
511
542
|
locale?: Partial<DsTableLocale>;
|
|
512
543
|
|
|
513
544
|
/**
|
|
514
|
-
*
|
|
515
|
-
* `
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
* }}
|
|
537
|
-
* />
|
|
538
|
-
* ```
|
|
539
|
-
*/
|
|
540
|
-
onCellEdit?: (row: TData, columnId: string, value: TValue) => void;
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Validates a cell value before it is committed. Return `null` to allow the
|
|
544
|
-
* commit (which fires `onCellEdit`), or an error message string to reject it
|
|
545
|
-
* and keep the cell in edit mode. Synchronous only.
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* @param row - The row data of the cell being edited
|
|
549
|
-
* @param columnId - The id of the column whose cell is being edited
|
|
550
|
-
* @param value - The candidate value to validate
|
|
551
|
-
*
|
|
552
|
-
* @example
|
|
553
|
-
* ```tsx
|
|
554
|
-
* <DsTable
|
|
555
|
-
* data={people}
|
|
556
|
-
* columns={columns}
|
|
557
|
-
* onCellValidate={(row, columnId, value) => validateField(columnId)(value, row)}
|
|
558
|
-
* onCellEdit={...}
|
|
559
|
-
* />
|
|
560
|
-
* ```
|
|
545
|
+
* Commits an edited cell value. Runs when the user confirms (check button or
|
|
546
|
+
* Enter) after `onCellValidate` passes. This is the authoritative commit: use it
|
|
547
|
+
* for server-side validation and persistence. Return `void`/`null` to accept
|
|
548
|
+
* (closes the editor) or an error `string` to reject (keeps the cell open with
|
|
549
|
+
* the message); may be async. While a returned Promise is pending the editor is
|
|
550
|
+
* locked. `signal` is aborted when the edit is cancelled (Cancel/Escape) or
|
|
551
|
+
* superseded by opening another cell, so in-flight requests can be cancelled.
|
|
552
|
+
*/
|
|
553
|
+
onCellEdit?: (
|
|
554
|
+
row: TData,
|
|
555
|
+
columnId: string,
|
|
556
|
+
value: TValue,
|
|
557
|
+
signal: AbortSignal,
|
|
558
|
+
) => string | null | undefined | Promise<string | null | undefined>;
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Synchronous, per-keystroke validation for the active editor. Runs live on
|
|
562
|
+
* every draft change and once more as a pre-commit gate before `onCellEdit`.
|
|
563
|
+
* Return `null` to allow or an error `string` to reject. A live error is shown
|
|
564
|
+
* inline and disables the Confirm button; `onCellEdit` never runs while the
|
|
565
|
+
* value is invalid. Keep it pure and cheap — for async or server-side checks,
|
|
566
|
+
* use `onCellEdit` instead.
|
|
561
567
|
*/
|
|
562
568
|
onCellValidate?: (row: TData, columnId: string, value: TValue) => string | null;
|
|
563
569
|
}
|
|
@@ -2,27 +2,29 @@ import type { CSSProperties } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
type Column,
|
|
4
4
|
type ColumnDef,
|
|
5
|
+
type ColumnSizingState,
|
|
5
6
|
type VisibilityState,
|
|
6
|
-
defaultColumnSizing,
|
|
7
7
|
} from '@tanstack/react-table';
|
|
8
|
+
import { isExplicitColumnWidth } from '../../utils/column-size';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Builds the flex style for a group header cell so it lines up with the combined
|
|
11
12
|
* width of its visible leaf columns.
|
|
12
13
|
*
|
|
13
|
-
* Fixed-width leaves contribute to `flexBasis`;
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Fixed-width leaves contribute to `flexBasis`; fill leaves each add one unit of
|
|
15
|
+
* `flexGrow`, mirroring how {@link getColumnSizeStyle} sizes leaf cells so both
|
|
16
|
+
* header rows distribute free space identically.
|
|
16
17
|
*/
|
|
17
18
|
export const getGroupColumnSizeStyle = <TData, TValue>(
|
|
18
19
|
leafColumns: Column<TData, TValue>[],
|
|
20
|
+
columnSizing: ColumnSizingState,
|
|
19
21
|
): CSSProperties => {
|
|
20
22
|
let fixedWidth = 0;
|
|
21
23
|
let flexCount = 0;
|
|
22
24
|
|
|
23
25
|
for (const leaf of leafColumns) {
|
|
24
26
|
const size = leaf.getSize();
|
|
25
|
-
if (
|
|
27
|
+
if (isExplicitColumnWidth(leaf, columnSizing)) {
|
|
26
28
|
fixedWidth += size;
|
|
27
29
|
} else {
|
|
28
30
|
flexCount += 1;
|
|
@@ -19,8 +19,10 @@ export interface UseCellEditorContainerProps {
|
|
|
19
19
|
export interface UseCellEditorResult<TValue> {
|
|
20
20
|
/** The in-progress draft value for the active editor. */
|
|
21
21
|
value: TValue;
|
|
22
|
-
/**
|
|
22
|
+
/** Current validation error — from live `onCellValidate` or the most recent commit attempt, if any. */
|
|
23
23
|
error: string | null;
|
|
24
|
+
/** True while an async `onCellEdit` commit Promise is pending for this cell. */
|
|
25
|
+
isPending: boolean;
|
|
24
26
|
/** Update the in-progress draft value. */
|
|
25
27
|
setValue: (value: TValue) => void;
|
|
26
28
|
/** Validate and commit the draft. */
|
|
@@ -48,6 +50,7 @@ export const useCellEditor = <TData extends RowData, TValue>({
|
|
|
48
50
|
ctx.editing !== null && ctx.editing.cell.row.id === row.id && ctx.editing.cell.column.id === column.id;
|
|
49
51
|
const value = isActiveCell && ctx.editing ? ctx.editing.draftValue : getValue();
|
|
50
52
|
const error = isActiveCell && ctx.editing ? ctx.editing.error : null;
|
|
53
|
+
const isPending = isActiveCell && ctx.editing ? ctx.editing.pending : false;
|
|
51
54
|
|
|
52
55
|
const setValue = (next: TValue) => {
|
|
53
56
|
ctx.setDraft(next);
|
|
@@ -68,6 +71,7 @@ export const useCellEditor = <TData extends RowData, TValue>({
|
|
|
68
71
|
return {
|
|
69
72
|
value,
|
|
70
73
|
error,
|
|
74
|
+
isPending,
|
|
71
75
|
setValue,
|
|
72
76
|
commit,
|
|
73
77
|
cancel,
|