@drivenets/design-system 0.16.0 → 0.18.0

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 (176) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.cjs +1893 -824
  3. package/dist/index.d.cts +363 -247
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +363 -247
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1896 -829
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +25 -25
  11. package/src/components/ds-alert-banner/ds-alert-banner-global.figma.ts +49 -0
  12. package/src/components/ds-alert-banner/ds-alert-banner.figma.ts +51 -0
  13. package/src/components/ds-alert-banner/ds-alert-banner.tsx +7 -6
  14. package/src/components/ds-autocomplete/ds-autocomplete.figma.ts +2 -2
  15. package/src/components/ds-autocomplete/ds-autocomplete.module.scss +15 -4
  16. package/src/components/ds-autocomplete/ds-autocomplete.tsx +10 -3
  17. package/src/components/ds-autocomplete/ds-autocomplete.types.ts +8 -0
  18. package/src/components/ds-avatar/ds-avatar.figma.ts +2 -2
  19. package/src/components/ds-avatar-group/ds-avatar-group.figma.ts +2 -2
  20. package/src/components/ds-breadcrumb/ds-breadcrumb.figma.ts +2 -2
  21. package/src/components/ds-button-v3/ds-button-v3.figma.ts +7 -5
  22. package/src/components/ds-button-v3/ds-button-v3.module.scss +84 -17
  23. package/src/components/ds-button-v3/ds-button-v3.tsx +2 -0
  24. package/src/components/ds-button-v3/ds-button-v3.types.ts +11 -2
  25. package/src/components/ds-button-v3/ds-button-v4.figma.ts +121 -0
  26. package/src/components/ds-card/figma/ds-card-content.figma.ts +2 -2
  27. package/src/components/ds-card/figma/ds-card-footer.figma.ts +2 -2
  28. package/src/components/ds-card/figma/ds-card-header.figma.ts +2 -2
  29. package/src/components/ds-card/figma/ds-card.figma.ts +2 -2
  30. package/src/components/ds-catalog-layout/ds-catalog-layout.figma.ts +51 -0
  31. package/src/components/ds-catalog-layout/ds-catalog-layout.tsx +9 -8
  32. package/src/components/ds-checkbox/ds-checkbox-level-2.figma.ts +2 -2
  33. package/src/components/ds-checkbox/ds-checkbox.figma.ts +2 -2
  34. package/src/components/ds-checkbox-group/ds-checkbox-group.figma.ts +2 -2
  35. package/src/components/ds-comment-bubble/ds-comment-bubble.figma.ts +52 -0
  36. package/src/components/ds-comment-card/ds-comment-card.figma.ts +36 -0
  37. package/src/components/ds-comment-indicator/ds-comment-indicator.figma.ts +32 -0
  38. package/src/components/ds-comments-drawer/components/comments-filter-modal/index.ts +0 -1
  39. package/src/components/ds-comments-drawer/ds-comments-drawer.figma.ts +19 -0
  40. package/src/components/ds-confirmation/ds-confirmation.tsx +13 -11
  41. package/src/components/ds-date-picker/ds-date-picker.figma.ts +2 -2
  42. package/src/components/ds-divider/ds-divider.figma.ts +2 -2
  43. package/src/components/ds-drawer/ds-drawer.tsx +11 -10
  44. package/src/components/ds-drawer/figma/ds-drawer-content.figma.ts +2 -2
  45. package/src/components/ds-drawer/figma/ds-drawer-footer.figma.ts +2 -2
  46. package/src/components/ds-drawer/figma/ds-drawer-header.figma.ts +2 -2
  47. package/src/components/ds-drawer/figma/ds-drawer.figma.ts +2 -2
  48. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu-footer.figma.ts +2 -2
  49. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu-group.figma.ts +2 -2
  50. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu-item.figma.ts +2 -2
  51. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu-separator.figma.ts +2 -2
  52. package/src/components/ds-dropdown-menu/figma/ds-dropdown-menu.figma.ts +2 -2
  53. package/src/components/ds-expandable-text-input/ds-expandable-text-input.figma.ts +2 -2
  54. package/src/components/ds-file-upload/components/file-upload-item/index.ts +0 -1
  55. package/src/components/ds-file-upload/ds-file-upload.figma.ts +2 -2
  56. package/src/components/ds-filter-status-icon/ds-filter-status-icon.figma.ts +2 -2
  57. package/src/components/ds-form-control/ds-form-control.figma.ts +2 -2
  58. package/src/components/ds-form-control/ds-form-control.tsx +15 -13
  59. package/src/components/ds-icon/figma/ds-icon-material.figma.batch.ts +1 -1
  60. package/src/components/ds-icon/figma/ds-icon-special.figma.batch.ts +1 -1
  61. package/src/components/ds-key-value-pair/ds-key-value-pair.figma.ts +2 -2
  62. package/src/components/ds-loader/ds-loader.figma.ts +2 -2
  63. package/src/components/ds-modal/ds-modal.tsx +10 -9
  64. package/src/components/ds-modal/figma/ds-modal-content.figma.ts +2 -2
  65. package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +2 -2
  66. package/src/components/ds-modal/figma/ds-modal-header.figma.ts +2 -2
  67. package/src/components/ds-modal/figma/ds-modal.figma.ts +2 -2
  68. package/src/components/ds-number-input/ds-number-input.tsx +1 -0
  69. package/src/components/ds-panel/ds-panel.figma.ts +2 -2
  70. package/src/components/ds-popover/figma/ds-popover-content-item.figma.ts +2 -2
  71. package/src/components/ds-popover/figma/ds-popover-content.figma.ts +2 -2
  72. package/src/components/ds-popover/figma/ds-popover-footer.figma.ts +2 -2
  73. package/src/components/ds-popover/figma/ds-popover-header.figma.ts +2 -2
  74. package/src/components/ds-popover/figma/ds-popover.figma.ts +2 -2
  75. package/src/components/ds-progress-arc/ds-progress-arc.figma.ts +2 -2
  76. package/src/components/ds-progress-donut/ds-progress-donut.figma.ts +2 -2
  77. package/src/components/ds-progress-linear/ds-progress-linear.figma.ts +2 -2
  78. package/src/components/ds-progress-task-bar/ds-progress-task-bar.figma.ts +2 -2
  79. package/src/components/ds-radio-group/figma/ds-radio-group-item.figma.ts +2 -2
  80. package/src/components/ds-radio-group/figma/ds-radio-group.figma.ts +2 -2
  81. package/src/components/ds-segment-group/figma/ds-segment-group-item.figma.ts +2 -2
  82. package/src/components/ds-segment-group/figma/ds-segment-group.figma.ts +2 -2
  83. package/src/components/ds-select/ds-select.figma.ts +4 -3
  84. package/src/components/ds-select/ds-select.tsx +6 -1
  85. package/src/components/ds-select/ds-select.types.ts +2 -1
  86. package/src/components/ds-site-node/ds-site-node.figma.ts +2 -2
  87. package/src/components/ds-slider/ds-slider.figma.ts +29 -0
  88. package/src/components/ds-slider/ds-slider.module.scss +104 -0
  89. package/src/components/ds-slider/ds-slider.tsx +118 -0
  90. package/src/components/ds-slider/ds-slider.types.ts +102 -0
  91. package/src/components/ds-slider/ds-slider.utils.ts +14 -0
  92. package/src/components/ds-slider/index.ts +11 -0
  93. package/src/components/ds-smart-tabs/ds-smart-tabs.tsx +5 -4
  94. package/src/components/ds-smart-tabs/figma/ds-smart-tabs-tab.figma.ts +83 -0
  95. package/src/components/ds-smart-tabs/figma/ds-smart-tabs.figma.ts +23 -0
  96. package/src/components/ds-spinner/ds-spinner.figma.ts +2 -2
  97. package/src/components/ds-split-button/ds-split-button.figma.ts +2 -2
  98. package/src/components/ds-status-badge-v2/ds-status-badge-v2.figma.ts +2 -2
  99. package/src/components/ds-stepper/ds-stepper.figma.ts +2 -2
  100. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +5 -1
  101. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +15 -3
  102. package/src/components/ds-table/components/ds-table-group-header-cell/index.ts +0 -1
  103. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +17 -3
  104. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +30 -9
  105. package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.module.scss +23 -0
  106. package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.tsx +218 -0
  107. package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.types.ts +14 -0
  108. package/src/components/ds-table/components/ds-table-resize-handle/index.ts +1 -0
  109. package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.module.scss +18 -0
  110. package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.tsx +16 -0
  111. package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.types.ts +13 -0
  112. package/src/components/ds-table/components/ds-table-resize-overlay/index.ts +1 -0
  113. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +1 -1
  114. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +9 -2
  115. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +2 -1
  116. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +9 -2
  117. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +2 -2
  118. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +2 -1
  119. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +2 -1
  120. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +2 -1
  121. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +2 -1
  122. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +12 -3
  123. package/src/components/ds-table/context/ds-table-context.tsx +208 -0
  124. package/src/components/ds-table/ds-table-defaults.ts +42 -0
  125. package/src/components/ds-table/ds-table.module.scss +19 -1
  126. package/src/components/ds-table/ds-table.tsx +163 -235
  127. package/src/components/ds-table/ds-table.types.ts +59 -47
  128. package/src/components/ds-table/figma/ds-table-expandable.figma.ts +2 -2
  129. package/src/components/ds-table/figma/ds-table.figma.ts +2 -2
  130. package/src/components/ds-table/grouping/utils/column-group.ts +7 -5
  131. package/src/components/ds-table/hooks/use-cell-editor.ts +5 -1
  132. package/src/components/ds-table/hooks/use-column-resize.ts +385 -0
  133. package/src/components/ds-table/hooks/use-editing-state.ts +190 -0
  134. package/src/components/ds-table/index.ts +1 -0
  135. package/src/components/ds-table/styles/_variables.scss +2 -1
  136. package/src/components/ds-table/utils/column-size-meta.augmentation.ts +16 -0
  137. package/src/components/ds-table/utils/column-size.ts +213 -10
  138. package/src/components/ds-table/utils/column-size.unit.test.ts +213 -0
  139. package/src/components/ds-table/utils/constants.ts +28 -0
  140. package/src/components/ds-table/utils/frozen-body.ts +33 -0
  141. package/src/components/ds-table/utils/frozen-body.unit.test.ts +44 -0
  142. package/src/components/ds-table/utils/table-api.ts +42 -0
  143. package/src/components/ds-table/utils/table-api.unit.test.ts +82 -0
  144. package/src/components/ds-table/utils/table-columns.tsx +119 -0
  145. package/src/components/ds-table/utils/table-columns.unit.test.ts +199 -0
  146. package/src/components/ds-tabs/figma/ds-tabs-tab-vertical.figma.ts +2 -2
  147. package/src/components/ds-tabs/figma/ds-tabs-tab.figma.ts +2 -2
  148. package/src/components/ds-tabs/figma/ds-tabs-vertical.figma.ts +2 -2
  149. package/src/components/ds-tabs/figma/ds-tabs.figma.ts +2 -2
  150. package/src/components/ds-tag/ds-tag.figma.ts +51 -0
  151. package/src/components/ds-tag-filter/ds-tag-filter.figma.ts +16 -0
  152. package/src/components/ds-text-input/ds-input-field-v02.figma.ts +31 -6
  153. package/src/components/ds-text-input/ds-text-input.figma.ts +5 -4
  154. package/src/components/ds-text-input/ds-text-input.tsx +1 -0
  155. package/src/components/ds-text-input/ds-text-input.types.ts +1 -1
  156. package/src/components/ds-time-picker/ds-time-picker.figma.ts +2 -2
  157. package/src/components/ds-toast/ds-toast.figma.ts +3 -3
  158. package/src/components/ds-toggle/ds-toggle.figma.ts +2 -2
  159. package/src/components/ds-tooltip/ds-tooltip.figma.ts +2 -2
  160. package/src/components/ds-tree/ds-tree.figma.ts +54 -0
  161. package/src/components/ds-tree/ds-tree.module.scss +2 -2
  162. package/src/components/ds-tree/ds-tree.tsx +6 -0
  163. package/src/components/ds-tree/ds-tree.types.ts +16 -0
  164. package/src/components/ds-typography/ds-typography.figma.ts +2 -2
  165. package/src/components/ds-user-card/ds-user-card.figma.ts +2 -2
  166. package/src/components/ds-vertical-tabs/ds-vertical-tabs.tsx +13 -6
  167. package/src/components/ds-workspace-layout/ds-workspace-layout.figma.ts +99 -0
  168. package/src/components/ds-workspace-layout/ds-workspace-layout.module.scss +1 -8
  169. package/src/components/ds-workspace-layout/ds-workspace-layout.tsx +11 -10
  170. package/src/index.ts +1 -0
  171. package/src/styles/_gradients.scss +19 -0
  172. package/src/styles/_root.scss +39 -15
  173. package/src/styles/_typography.scss +1 -0
  174. package/src/styles/shared/_input.scss +10 -0
  175. package/src/styles/styles.scss +1 -0
  176. 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 { type DsTableContextType, DsTableContext, useEditingState } from './context/ds-table-context';
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 { DsSkeletonText } from '../ds-skeleton';
30
- import {
31
- EMPTY_TABLE_STATE_TEXT,
32
- EXPANDER_COLUMN_ID,
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,45 +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
- ref,
53
- columns: columnsProp,
54
- data: tableData,
55
- virtualized = false,
56
- virtualizedOptions,
57
- className,
58
- onRowClick,
59
- emptyState,
60
- stickyHeader = true,
61
- bordered = true,
62
- fullWidth = true,
63
- rowSize = 'medium',
64
- loading = false,
65
- expandable = false,
66
- renderExpandedRow,
67
- selectable = false,
68
- showSelectAllCheckbox = true,
69
- onSelectionChange,
70
- onSortingChange,
71
- onScroll,
72
- actions = [],
73
- onRowDoubleClick,
74
- primaryRowActions = [],
75
- secondaryRowActions,
76
- reorderable = false,
77
- onOrderChange,
78
- columnFilters: externalColumnFilters,
79
- onColumnFiltersChange,
80
- columnVisibility: externalColumnVisibility,
81
- onColumnVisibilityChange,
82
- collapsedColumnGroups: externalCollapsedColumnGroups,
83
- onCollapsedColumnGroupsChange,
84
- locale,
85
- activeRowId,
86
- infiniteScroll,
87
- onCellEdit,
88
- onCellValidate,
89
- }: 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;
90
79
  const [data, setData] = React.useState(tableData);
91
80
  const [sorting, setSorting] = React.useState<SortingState>([]);
92
81
  const [internalColumnFilters, setInternalColumnFilters] = React.useState<ColumnFiltersState>([]);
@@ -102,8 +91,6 @@ const DsTable = <TData extends { id: string }, TValue>({
102
91
  items: data,
103
92
  });
104
93
 
105
- const tableContainerRef = React.useRef<HTMLDivElement>(null);
106
-
107
94
  const columnFilters = externalColumnFilters ?? internalColumnFilters;
108
95
  const handleColumnFiltersChange = (
109
96
  updaterOrValue: ColumnFiltersState | ((old: ColumnFiltersState) => ColumnFiltersState),
@@ -156,74 +143,33 @@ const DsTable = <TData extends { id: string }, TValue>({
156
143
  onSelectionChange?.(newRowSelection);
157
144
  };
158
145
 
159
- const hasExpanderColumn = !!expandable;
160
- const hasSelectColumn = !!selectable;
161
- const hasReorderColumn = reorderable && !virtualized;
162
-
163
- const columns = useMemo<ColumnDef<TData, TValue>[]>(() => {
164
- const augmentedColumns: ColumnDef<TData, TValue>[] = [...columnsProp];
165
-
166
- if (hasSelectColumn) {
167
- const selectColumn: ColumnDef<TData, TValue> = {
168
- id: SELECT_COLUMN_ID,
169
- size: SELECT_COLUMN_WIDTH,
170
- enableSorting: false,
171
- enableResizing: false,
172
- header: ({ table }) => (showSelectAllCheckbox ? <DsTableHeaderSelectableCell table={table} /> : null),
173
- cell: ({ row }) => <DsTableRowSelectableCell row={row} />,
174
- };
175
- augmentedColumns.unshift(selectColumn);
176
- }
177
-
178
- if (hasExpanderColumn) {
179
- const expanderColumn: ColumnDef<TData, TValue> = {
180
- id: EXPANDER_COLUMN_ID,
181
- size: EXPANDER_COLUMN_WIDTH,
182
- enableSorting: false,
183
- enableResizing: false,
184
- header: () => null,
185
- cell: ({ row }) => (row.getCanExpand() ? <DsTableRowExpandableCell row={row} /> : null),
186
- };
187
- augmentedColumns.unshift(expanderColumn);
188
- }
189
-
190
- if (hasReorderColumn) {
191
- // Cell is rendered inline by DsTableRow when it encounters REORDER_COLUMN_ID,
192
- // since the drag handle needs row-level useSortable state.
193
- const reorderColumn: ColumnDef<TData, TValue> = {
194
- id: REORDER_COLUMN_ID,
195
- size: REORDER_COLUMN_WIDTH,
196
- enableSorting: false,
197
- enableResizing: false,
198
- header: 'Order',
199
- cell: () => null,
200
- };
201
- augmentedColumns.unshift(reorderColumn);
202
- }
203
-
204
- return augmentedColumns;
205
- }, [columnsProp, hasExpanderColumn, hasReorderColumn, hasSelectColumn, showSelectAllCheckbox]);
206
-
207
- const skeletonColumns = useMemo<ColumnDef<TData, TValue>[]>(() => {
208
- const toSkeleton = (cols: ColumnDef<TData, TValue>[]): ColumnDef<TData, TValue>[] =>
209
- cols.map((column) =>
210
- 'columns' in column && column.columns
211
- ? { ...column, columns: toSkeleton(column.columns) }
212
- : { ...column, cell: column.loadingCell ?? (() => <DsSkeletonText width="60%" />) },
213
- );
214
-
215
- return toSkeleton(columns);
216
- }, [columns]);
217
-
218
- 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(
219
151
  () =>
220
- Array.from(
221
- { length: SKELETON_ROW_COUNT },
222
- (_, i) => ({ id: `ds-table-skeleton-${String(i)}` }) as TData,
223
- ),
224
- [],
152
+ getAugmentedColumns(columnsProp, {
153
+ selectable: hasSelectColumn,
154
+ expandable: hasExpanderColumn,
155
+ reorderable,
156
+ virtualized,
157
+ showSelectAllCheckbox,
158
+ }),
159
+ [columnsProp, hasSelectColumn, hasExpanderColumn, reorderable, virtualized, showSelectAllCheckbox],
225
160
  );
226
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
+
227
173
  const table = useReactTable({
228
174
  data: loading ? skeletonData : reorderable ? data : tableData,
229
175
  columns: loading ? skeletonColumns : columns,
@@ -237,68 +183,22 @@ const DsTable = <TData extends { id: string }, TValue>({
237
183
  getRowId: (row) => row.id,
238
184
  getExpandedRowModel: getExpandedRowModel(),
239
185
  getRowCanExpand: typeof expandable === 'function' ? (row) => expandable(row.original) : () => expandable,
186
+ ...resize.tableOptions,
240
187
  state: {
241
188
  sorting,
242
189
  columnFilters,
243
190
  columnVisibility,
244
191
  rowSelection,
192
+ ...resize.state,
245
193
  },
246
194
  enableRowSelection: typeof selectable === 'function' ? (row) => selectable(row.original) : selectable,
247
195
  });
248
196
 
249
- useImperativeHandle(
250
- ref,
251
- () => ({
252
- selectRow: (rowId: string) => {
253
- table.getRow(rowId).toggleSelected(true);
254
- },
255
- deselectRow: (rowId: string) => {
256
- table.getRow(rowId).toggleSelected(false);
257
- },
258
- selectAllRows: () => {
259
- table.toggleAllRowsSelected(true);
260
- },
261
- deselectAllRows: () => {
262
- table.toggleAllRowsSelected(false);
263
- },
264
- selectRows: (rowIds: string[]) => {
265
- const selection: Record<string, boolean> = {};
266
- rowIds.forEach((id) => (selection[id] = true));
267
- table.setRowSelection(selection);
268
- },
269
- getSelectedRows: () => {
270
- return table.getFilteredSelectedRowModel().rows.map((r) => r.original);
271
- },
272
- expandRow: (rowId: string) => {
273
- table.getRow(rowId).toggleExpanded(true);
274
- },
275
- collapseRow: (rowId: string) => {
276
- table.getRow(rowId).toggleExpanded(false);
277
- },
278
- expandAllRows: () => {
279
- table.toggleAllRowsExpanded(true);
280
- },
281
- collapseAllRows: () => {
282
- table.toggleAllRowsExpanded(false);
283
- },
284
- expandRows: (rowIds: string[]) => {
285
- const expansion: Record<string, boolean> = {};
286
- rowIds.forEach((id) => (expansion[id] = true));
287
- table.setExpanded(expansion);
288
- },
289
- }),
290
- [table],
291
- );
197
+ const { columnSizeVars, activeResize, context: resizeContext } = resize.bind(table);
292
198
 
293
- const { rows, rowsById } = table.getRowModel();
199
+ useImperativeHandle(ref, () => createTableApi(table), [table]);
294
200
 
295
- const renderEmptyState = () => (
296
- <TableRow>
297
- <TableCell colSpan={columns.length} className={styles.emptyState}>
298
- {emptyState || EMPTY_TABLE_STATE_TEXT}
299
- </TableCell>
300
- </TableRow>
301
- );
201
+ const { rowsById } = table.getRowModel();
302
202
 
303
203
  const selectedRows = Object.entries(rowSelection)
304
204
  .filter(([, selected]) => selected)
@@ -307,70 +207,58 @@ const DsTable = <TData extends { id: string }, TValue>({
307
207
 
308
208
  const isBulkActionsVisible = selectable && actions.length > 0 && selectedRows.length > 0;
309
209
 
310
- const editingState = useEditingState<TData, TValue>(onCellEdit, onCellValidate);
311
-
312
- const contextValue: DsTableContextType<TData, TValue> = {
313
- stickyHeader,
314
- bordered,
315
- fullWidth,
316
- rowSize,
317
- expandable,
318
- selectable,
319
- reorderable,
320
- showSelectAllCheckbox,
321
- loading,
322
- onRowClick: loading ? undefined : onRowClick,
323
- onRowDoubleClick: loading ? undefined : onRowDoubleClick,
324
- primaryRowActions,
325
- secondaryRowActions,
326
- renderExpandedRow,
327
- virtualized,
328
- activeRowId,
329
- collapsedColumnGroups,
330
- onToggleColumnGroup: toggleColumnGroup,
331
- locale,
332
- onCellEdit,
333
- ...editingState,
334
- };
335
-
336
210
  return (
337
- <DsTableContext.Provider value={contextValue}>
211
+ <DsTableContextProvider
212
+ tableProps={tableProps}
213
+ derived={{
214
+ grouping: {
215
+ collapsedColumnGroups,
216
+ onToggleColumnGroup: toggleColumnGroup,
217
+ },
218
+ resize: resizeContext,
219
+ }}
220
+ >
338
221
  <div
339
- ref={tableContainerRef}
340
- className={classnames(
341
- styles.container,
342
- !virtualized && styles.dataTableContainer,
343
- virtualized && styles.virtualizedContainer,
344
- isBulkActionsVisible && styles.bulkActionsVisible,
345
- className,
346
- )}
222
+ className={classnames(styles.container, isBulkActionsVisible && styles.bulkActionsVisible, className)}
347
223
  >
348
- <DragWrapper>
349
- <Table className={classnames(fullWidth && styles.fullWidth, !bordered && styles.tableNoBorder)}>
350
- <DsTableHeader table={table} />
351
- {virtualized ? (
352
- <DsTableBodyVirtualized
353
- table={table}
354
- emptyState={emptyState}
355
- estimateSize={virtualizedOptions?.estimateSize || ROW_SIZE_HEIGHT_MAP[rowSize]}
356
- overscan={virtualizedOptions?.overscan}
357
- onScroll={onScroll}
358
- rowSelection={rowSelection}
359
- infiniteScroll={infiniteScroll}
360
- />
361
- ) : (
362
- <TableBody>
363
- <SortableWrapper>
364
- {rows.length
365
- ? rows.map((row) => (
366
- <DsTableRow key={row.id} row={row} isSelected={!!rowSelection[row.id]} />
367
- ))
368
- : renderEmptyState()}
369
- </SortableWrapper>
370
- </TableBody>
371
- )}
372
- </Table>
373
- </DragWrapper>
224
+ {controls && <div className={styles.controls}>{controls}</div>}
225
+ <div
226
+ ref={resizeContext.resizeContainerRef}
227
+ className={classnames(
228
+ !virtualized && styles.dataTableContainer,
229
+ virtualized && styles.virtualizedContainer,
230
+ )}
231
+ >
232
+ <DragWrapper>
233
+ <Table
234
+ className={classnames(fullWidth && styles.fullWidth, !bordered && styles.tableNoBorder)}
235
+ style={columnSizeVars}
236
+ >
237
+ <DsTableHeader table={table} />
238
+ {virtualized ? (
239
+ <MemoizedDsTableBodyVirtualized
240
+ table={table}
241
+ emptyState={emptyState}
242
+ estimateSize={virtualizedOptions?.estimateSize || ROW_SIZE_HEIGHT_MAP[rowSize]}
243
+ overscan={virtualizedOptions?.overscan}
244
+ onScroll={onScroll}
245
+ rowSelection={rowSelection}
246
+ infiniteScroll={infiniteScroll}
247
+ />
248
+ ) : (
249
+ <MemoizedDsTableRowsBody
250
+ table={table}
251
+ rowSelection={rowSelection}
252
+ emptyState={emptyState}
253
+ SortableWrapper={SortableWrapper}
254
+ />
255
+ )}
256
+ </Table>
257
+ </DragWrapper>
258
+ {activeResize ? (
259
+ <DsTableResizeOverlay offset={activeResize.offset} phase={activeResize.phase} />
260
+ ) : null}
261
+ </div>
374
262
  {selectable && actions.length > 0 && (
375
263
  <DsTableBulkActions
376
264
  numSelectedRows={selectedRows.length}
@@ -382,10 +270,50 @@ const DsTable = <TData extends { id: string }, TValue>({
382
270
  />
383
271
  )}
384
272
  </div>
385
- </DsTableContext.Provider>
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>
386
307
  );
387
308
  };
388
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
+
389
317
  DsTable.displayName = 'DsTable';
390
318
 
391
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
  /**
@@ -228,6 +234,12 @@ export interface DsDataTableProps<TData, TValue> {
228
234
  */
229
235
  data: TData[];
230
236
 
237
+ /**
238
+ * Optional content rendered in a pinned controls bar above the table header
239
+ * (e.g. filters, action buttons). Layout is owned by the consumer.
240
+ */
241
+ controls?: React.ReactNode;
242
+
231
243
  /**
232
244
  * Whether the table is virtualized
233
245
  * @default false
@@ -314,6 +326,31 @@ export interface DsDataTableProps<TData, TValue> {
314
326
  */
315
327
  rowSize?: DsTableRowSize;
316
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
+
317
354
  /**
318
355
  * When `true`, renders placeholder skeleton rows instead of the data. Column
319
356
  * headers stay intact and row interactions are disabled while loading.
@@ -505,53 +542,28 @@ export interface DsDataTableProps<TData, TValue> {
505
542
  locale?: Partial<DsTableLocale>;
506
543
 
507
544
  /**
508
- * Callback fired when an editable cell commits a new value via one of the
509
- * `DsTableEditCell*` wrappers or the `useCellEditor` hook. The parent is
510
- * expected to update the row data in response.
511
- *
512
- * Commits occur on confirm (check button or Enter). Opening another cell
513
- * discards the previous draft without calling this callback.
514
- *
515
- * @param row - The row data of the cell that was edited
516
- * @param columnId - The id of the column whose cell was edited
517
- * @param value - The new committed value
518
- *
519
- * @example
520
- * ```tsx
521
- * const [people, setPeople] = useState(initialPeople);
522
- *
523
- * <DsTable
524
- * data={people}
525
- * columns={columns}
526
- * onCellEdit={(row, columnId, value) => {
527
- * setPeople((rows) =>
528
- * rows.map((r) => (r.id === row.id ? { ...r, [columnId]: value } : r)),
529
- * );
530
- * }}
531
- * />
532
- * ```
533
- */
534
- onCellEdit?: (row: TData, columnId: string, value: TValue) => void;
535
-
536
- /**
537
- * Validates a cell value before it is committed. Return `null` to allow the
538
- * commit (which fires `onCellEdit`), or an error message string to reject it
539
- * and keep the cell in edit mode. Synchronous only.
540
- *
541
- *
542
- * @param row - The row data of the cell being edited
543
- * @param columnId - The id of the column whose cell is being edited
544
- * @param value - The candidate value to validate
545
- *
546
- * @example
547
- * ```tsx
548
- * <DsTable
549
- * data={people}
550
- * columns={columns}
551
- * onCellValidate={(row, columnId, value) => validateField(columnId)(value, row)}
552
- * onCellEdit={...}
553
- * />
554
- * ```
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.
555
567
  */
556
568
  onCellValidate?: (row: TData, columnId: string, value: TValue) => string | null;
557
569
  }
@@ -11,7 +11,7 @@ import figma from 'figma';
11
11
 
12
12
  export default {
13
13
  example: figma.code`<DsTable columns={columns} data={data} expandable renderExpandedRow={(row) => null} />`,
14
- imports: ["import { DsTable } from '@drivenets/design-system'"],
14
+ imports: ["import { DsTable } from '@drivenets/design-system';"],
15
15
  id: 'ds-table-expandable',
16
16
  metadata: { nestable: false },
17
- };
17
+ } satisfies figma.Template;
@@ -10,7 +10,7 @@ import figma from 'figma';
10
10
 
11
11
  export default {
12
12
  example: figma.code`<DsTable columns={columns} data={data} />`,
13
- imports: ["import { DsTable } from '@drivenets/design-system'"],
13
+ imports: ["import { DsTable } from '@drivenets/design-system';"],
14
14
  id: 'ds-table',
15
15
  metadata: { nestable: false },
16
- };
16
+ } satisfies figma.Template;
@@ -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`; default-sized leaves each add one
14
- * unit of `flexGrow`, mirroring how {@link getColumnSizeStyle} sizes leaf cells so
15
- * both header rows distribute free space identically.
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 (size !== defaultColumnSizing.size) {
27
+ if (isExplicitColumnWidth(leaf, columnSizing)) {
26
28
  fixedWidth += size;
27
29
  } else {
28
30
  flexCount += 1;