@drivenets/design-system 0.18.1 → 0.20.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 (216) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/index.cjs +17038 -10114
  3. package/dist/index.d.cts +1355 -444
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +1354 -443
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +17073 -10172
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +41 -41
  11. package/src/components/ds-bot-button/ds-bot-button.figma.ts +38 -0
  12. package/src/components/ds-bot-button/ds-bot-button.module.scss +41 -0
  13. package/src/components/ds-bot-button/ds-bot-button.tsx +48 -0
  14. package/src/components/ds-bot-button/ds-bot-button.types.ts +33 -0
  15. package/src/components/ds-bot-button/index.ts +2 -0
  16. package/src/components/ds-bot-button/net-gen-logo.tsx +46 -0
  17. package/src/components/ds-bulk-actions/components/action-button.tsx +42 -0
  18. package/src/components/ds-bulk-actions/components/menu-entries.tsx +76 -0
  19. package/src/components/ds-bulk-actions/components/overflow.tsx +75 -0
  20. package/src/components/ds-bulk-actions/ds-bulk-actions.figma.ts +28 -0
  21. package/src/components/ds-bulk-actions/ds-bulk-actions.module.scss +199 -0
  22. package/src/components/ds-bulk-actions/ds-bulk-actions.tsx +220 -0
  23. package/src/components/ds-bulk-actions/ds-bulk-actions.types.ts +170 -0
  24. package/src/components/ds-bulk-actions/ds-bulk-actions.unit.test.ts +195 -0
  25. package/src/components/ds-bulk-actions/ds-bulk-actions.utils.ts +91 -0
  26. package/src/components/ds-bulk-actions/ds-bulk-actions.utils.unit.test.ts +134 -0
  27. package/src/components/ds-bulk-actions/hooks/compute-bulk-actions-overflow.ts +85 -0
  28. package/src/components/ds-bulk-actions/hooks/use-bulk-actions-overflow.ts +153 -0
  29. package/src/components/ds-bulk-actions/index.ts +18 -0
  30. package/src/components/ds-button-v3/ds-button-v3.module.scss +2 -2
  31. package/src/components/ds-catalog-layout/ds-catalog-layout.figma.ts +1 -1
  32. package/src/components/ds-checkbox/ds-checkbox.figma.ts +3 -0
  33. package/src/components/ds-checkbox/ds-checkbox.module.scss +102 -20
  34. package/src/components/ds-checkbox/ds-checkbox.tsx +30 -2
  35. package/src/components/ds-checkbox/ds-checkbox.types.ts +10 -0
  36. package/src/components/ds-code-input/collapsed-code-field/collapsed-code-field.module.scss +169 -0
  37. package/src/components/ds-code-input/collapsed-code-field/collapsed-code-field.tsx +92 -0
  38. package/src/components/ds-code-input/collapsed-code-field/index.ts +1 -0
  39. package/src/components/ds-code-input/ds-code-input-panel/ds-code-input-panel.module.scss +110 -0
  40. package/src/components/ds-code-input/ds-code-input-panel/ds-code-input-panel.tsx +61 -0
  41. package/src/components/ds-code-input/ds-code-input-panel/index.ts +1 -0
  42. package/src/components/ds-code-input/ds-code-input.module.scss +25 -0
  43. package/src/components/ds-code-input/ds-code-input.tsx +401 -0
  44. package/src/components/ds-code-input/ds-code-input.types.ts +166 -0
  45. package/src/components/ds-code-input/ds-code-input.utils.ts +94 -0
  46. package/src/components/ds-code-input/ds-code-input.utils.unit.test.ts +166 -0
  47. package/src/components/ds-code-input/index.ts +2 -0
  48. package/src/components/ds-code-input/use-collapsed-viewport.ts +289 -0
  49. package/src/components/ds-code-input/use-editor-host.ts +87 -0
  50. package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +2 -0
  51. package/src/components/ds-empty-state/ds-empty-state.module.scss +16 -0
  52. package/src/components/ds-empty-state/ds-empty-state.tsx +45 -0
  53. package/src/components/ds-empty-state/ds-empty-state.types.ts +38 -0
  54. package/src/components/ds-empty-state/index.ts +3 -0
  55. package/src/components/ds-file-upload/ds-file-upload.tsx +4 -1
  56. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.ts +11 -0
  57. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.types.ts +41 -0
  58. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/index.ts +1 -0
  59. package/src/components/ds-filters-bar/components/ds-filters-bar-clear-all.ts +11 -0
  60. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/ds-filters-bar-conditions.ts +11 -0
  61. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/ds-filters-bar-conditions.types.ts +30 -0
  62. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/index.ts +1 -0
  63. package/src/components/ds-filters-bar/components/ds-filters-bar-pinned.ts +31 -0
  64. package/src/components/ds-filters-bar/components/ds-filters-bar-query/ds-filters-bar-query.ts +11 -0
  65. package/src/components/ds-filters-bar/components/ds-filters-bar-query/ds-filters-bar-query.types.ts +22 -0
  66. package/src/components/ds-filters-bar/components/ds-filters-bar-query/index.ts +1 -0
  67. package/src/components/ds-filters-bar/components/ds-filters-bar-saved-filters.ts +19 -0
  68. package/src/components/ds-filters-bar/components/ds-filters-bar-search.ts +11 -0
  69. package/src/components/ds-filters-bar/components/ds-filters-bar-summary.ts +11 -0
  70. package/src/components/ds-filters-bar/components/ds-filters-bar-toolbar.ts +11 -0
  71. package/src/components/ds-filters-bar/components/ds-filters-bar-view-switch.ts +11 -0
  72. package/src/components/ds-filters-bar/components/ds-filters-bar-view.ts +11 -0
  73. package/src/components/ds-filters-bar/components/index.ts +11 -0
  74. package/src/components/ds-filters-bar/ds-filters-bar.context.ts +69 -0
  75. package/src/components/ds-filters-bar/ds-filters-bar.tsx +120 -0
  76. package/src/components/ds-filters-bar/ds-filters-bar.types.ts +440 -0
  77. package/src/components/ds-filters-bar/ds-filters-bar.unit.test.ts +216 -0
  78. package/src/components/ds-filters-bar/ds-filters-bar.utils.ts +141 -0
  79. package/src/components/ds-filters-bar/index.ts +1 -0
  80. package/src/components/ds-form-control/ds-form-control.module.scss +18 -0
  81. package/src/components/ds-form-control/ds-form-control.tsx +61 -19
  82. package/src/components/ds-form-control/ds-form-control.types.ts +2 -1
  83. package/src/components/ds-icon/custom-icons/index.ts +2 -0
  84. package/src/components/ds-icon/custom-icons/special-adv-filters.tsx +28 -0
  85. package/src/components/ds-illustration/ds-illustration.figma.ts +34 -0
  86. package/src/components/ds-illustration/ds-illustration.module.scss +6 -0
  87. package/src/components/ds-illustration/ds-illustration.tsx +29 -0
  88. package/src/components/ds-illustration/ds-illustration.types.ts +45 -0
  89. package/src/components/ds-illustration/illustrations/configuration.tsx +51 -0
  90. package/src/components/ds-illustration/illustrations/device.tsx +133 -0
  91. package/src/components/ds-illustration/illustrations/done.tsx +107 -0
  92. package/src/components/{ds-catalog-layout/catalog-layout-empty-illustration.tsx → ds-illustration/illustrations/empty-inbox.tsx} +10 -14
  93. package/src/components/ds-illustration/illustrations/error.tsx +86 -0
  94. package/src/components/ds-illustration/illustrations/go-filter.tsx +51 -0
  95. package/src/components/ds-illustration/illustrations/illustration-clouds.tsx +10 -0
  96. package/src/components/ds-illustration/illustrations/index.ts +39 -0
  97. package/src/components/ds-illustration/illustrations/no-connection.tsx +54 -0
  98. package/src/components/ds-illustration/illustrations/no-credit-card.tsx +48 -0
  99. package/src/components/ds-illustration/illustrations/no-device.tsx +61 -0
  100. package/src/components/ds-illustration/illustrations/no-documents.tsx +52 -0
  101. package/src/components/ds-illustration/illustrations/no-gps.tsx +57 -0
  102. package/src/components/ds-illustration/illustrations/no-images.tsx +82 -0
  103. package/src/components/ds-illustration/illustrations/no-items-cart.tsx +86 -0
  104. package/src/components/ds-illustration/illustrations/no-messages.tsx +51 -0
  105. package/src/components/ds-illustration/illustrations/no-search-result.tsx +76 -0
  106. package/src/components/ds-illustration/illustrations/no-tasks.tsx +61 -0
  107. package/src/components/ds-illustration/illustrations/site.tsx +80 -0
  108. package/src/components/ds-illustration/index.ts +3 -0
  109. package/src/components/ds-main-menu/components/expanded-item/expanded-item.tsx +108 -0
  110. package/src/components/ds-main-menu/components/expanded-item/expanded-item.types.ts +8 -0
  111. package/src/components/ds-main-menu/components/expanded-item/index.ts +1 -0
  112. package/src/components/ds-main-menu/components/menu-graphic/index.ts +1 -0
  113. package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.tsx +16 -0
  114. package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.types.ts +7 -0
  115. package/src/components/ds-main-menu/components/tile-link/index.ts +1 -0
  116. package/src/components/ds-main-menu/components/tile-link/tile-link.tsx +85 -0
  117. package/src/components/ds-main-menu/components/tile-link/tile-link.types.ts +8 -0
  118. package/src/components/ds-main-menu/components/utility-link/index.ts +1 -0
  119. package/src/components/ds-main-menu/components/utility-link/utility-link.tsx +37 -0
  120. package/src/components/ds-main-menu/components/utility-link/utility-link.types.ts +7 -0
  121. package/src/components/ds-main-menu/ds-main-menu.figma.ts +17 -0
  122. package/src/components/ds-main-menu/ds-main-menu.module.scss +349 -0
  123. package/src/components/ds-main-menu/ds-main-menu.tsx +102 -0
  124. package/src/components/ds-main-menu/ds-main-menu.types.ts +86 -0
  125. package/src/components/ds-main-menu/ds-main-menu.utils.ts +40 -0
  126. package/src/components/ds-main-menu/index.ts +9 -0
  127. package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +1 -1
  128. package/src/components/ds-pin-toggle/ds-pin-toggle.figma.ts +27 -0
  129. package/src/components/ds-pin-toggle/ds-pin-toggle.module.scss +41 -0
  130. package/src/components/ds-pin-toggle/ds-pin-toggle.tsx +43 -0
  131. package/src/components/ds-pin-toggle/ds-pin-toggle.types.ts +29 -0
  132. package/src/components/ds-pin-toggle/index.ts +2 -0
  133. package/src/components/ds-popover/components/popover-trigger/index.ts +1 -0
  134. package/src/components/ds-popover/components/popover-trigger/popover-trigger.tsx +15 -0
  135. package/src/components/ds-popover/ds-popover.context.ts +18 -0
  136. package/src/components/ds-popover/ds-popover.hover-intent.ts +65 -0
  137. package/src/components/ds-popover/ds-popover.tsx +164 -41
  138. package/src/components/ds-popover/ds-popover.types.ts +65 -0
  139. package/src/components/ds-popover/ds-popover.utils.ts +60 -0
  140. package/src/components/ds-popover/ds-popover.utils.unit.test.ts +25 -0
  141. package/src/components/ds-saved-filters/components/ds-saved-filters-delete-confirm.tsx +53 -0
  142. package/src/components/ds-saved-filters/components/ds-saved-filters-name-entry.tsx +64 -0
  143. package/src/components/ds-saved-filters/components/ds-saved-filters-name-modal.tsx +50 -0
  144. package/src/components/ds-saved-filters/components/ds-saved-filters-row/ds-saved-filters-row.module.scss +68 -0
  145. package/src/components/ds-saved-filters/components/ds-saved-filters-row/ds-saved-filters-row.tsx +83 -0
  146. package/src/components/ds-saved-filters/components/ds-saved-filters-row/index.ts +1 -0
  147. package/src/components/ds-saved-filters/components/ds-saved-filters-save.tsx +164 -0
  148. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/ds-saved-filters-trigger.module.scss +21 -0
  149. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/ds-saved-filters-trigger.tsx +237 -0
  150. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/index.ts +1 -0
  151. package/src/components/ds-saved-filters/components/index.ts +2 -0
  152. package/src/components/ds-saved-filters/ds-saved-filters.ts +6 -0
  153. package/src/components/ds-saved-filters/ds-saved-filters.types.ts +106 -0
  154. package/src/components/ds-saved-filters/ds-saved-filters.utils.ts +35 -0
  155. package/src/components/ds-saved-filters/hooks/index.ts +1 -0
  156. package/src/components/ds-saved-filters/hooks/use-picker-outside-dismiss.ts +42 -0
  157. package/src/components/ds-saved-filters/index.ts +2 -0
  158. package/src/components/ds-segment-group/ds-segment-group.module.scss +65 -37
  159. package/src/components/ds-segment-group/ds-segment-group.tsx +17 -5
  160. package/src/components/ds-segment-group/ds-segment-group.types.ts +20 -5
  161. package/src/components/ds-segment-group/figma/ds-segment-group-v2-item.figma.ts +109 -0
  162. package/src/components/ds-segment-group/figma/ds-segment-group-v2.figma.ts +31 -0
  163. package/src/components/ds-site-node/ds-site-node.tsx +2 -2
  164. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +12 -2
  165. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +2 -3
  166. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +1 -6
  167. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +6 -3
  168. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +18 -7
  169. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +27 -21
  170. package/src/components/ds-table/components/ds-table-header-label.module.scss +12 -0
  171. package/src/components/ds-table/components/ds-table-header-label.tsx +28 -0
  172. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +0 -1
  173. package/src/components/ds-table/context/ds-table-context.tsx +8 -0
  174. package/src/components/ds-table/ds-table.module.scss +26 -16
  175. package/src/components/ds-table/ds-table.tsx +46 -14
  176. package/src/components/ds-table/ds-table.types.ts +33 -3
  177. package/src/components/ds-table/hooks/use-column-resize.ts +114 -11
  178. package/src/components/ds-table/hooks/use-scrollbar-spacer.ts +108 -0
  179. package/src/components/ds-table/hooks/use-scrollbar-spacer.unit.test.ts +26 -0
  180. package/src/components/ds-table/styles/_variables.scss +0 -2
  181. package/src/components/ds-table/utils/column-size.ts +274 -1
  182. package/src/components/ds-table/utils/column-size.unit.test.ts +157 -0
  183. package/src/components/ds-table/utils/constants.ts +13 -5
  184. package/src/components/ds-table/utils/table-columns.tsx +27 -13
  185. package/src/components/ds-table/utils/table-columns.unit.test.ts +44 -0
  186. package/src/components/ds-tag/ds-tag.figma.ts +23 -3
  187. package/src/components/ds-tag/ds-tag.module.scss +71 -7
  188. package/src/components/ds-tag/ds-tag.tsx +115 -14
  189. package/src/components/ds-tag/ds-tag.types.ts +70 -3
  190. package/src/components/ds-tag/ds-tag.unit.test.ts +23 -4
  191. package/src/components/ds-tag/index.ts +2 -2
  192. package/src/components/ds-text-input/ds-input-field-v02.figma.ts +2 -0
  193. package/src/components/ds-toggle/ds-toggle.module.scss +18 -0
  194. package/src/components/ds-toggle/ds-toggle.tsx +15 -9
  195. package/src/components/ds-toggle/ds-toggle.types.ts +6 -0
  196. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.module.scss +69 -0
  197. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.tsx +55 -0
  198. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.types.ts +43 -0
  199. package/src/components/ds-toggle-filter-data/index.ts +1 -0
  200. package/src/components/ds-tooltip/ds-tooltip.module.scss +8 -0
  201. package/src/components/ds-tooltip/ds-tooltip.tsx +7 -3
  202. package/src/components/ds-tooltip/ds-tooltip.types.ts +17 -0
  203. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.figma.ts +35 -0
  204. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.module.scss +150 -0
  205. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.tsx +161 -0
  206. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.types.ts +117 -0
  207. package/src/components/ds-top-bar-navigation/index.ts +2 -0
  208. package/src/components/ds-tree/ds-tree.tsx +10 -10
  209. package/src/components/ds-tree/ds-tree.types.ts +40 -7
  210. package/src/index.ts +8 -0
  211. package/src/utils/use-is-horizontally-overflowing.ts +35 -0
  212. package/src/components/ds-site-node/hooks/use-is-overflowing.ts +0 -31
  213. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +0 -112
  214. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +0 -67
  215. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +0 -41
  216. package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +0 -2
@@ -14,7 +14,7 @@ import {
14
14
  } from '@tanstack/react-table';
15
15
  import classnames from 'classnames';
16
16
  import { Table, TableBody, TableCell, TableRow } from './components/core-table';
17
- import { DsTableBulkActions } from './components/ds-table-bulk-actions';
17
+ import { DsBulkActions } from '../ds-bulk-actions';
18
18
  import { DsTableHeader } from './components/ds-table-header';
19
19
  import { DsTableResizeOverlay } from './components/ds-table-resize-overlay';
20
20
  import styles from './ds-table.module.scss';
@@ -26,7 +26,8 @@ import { useColumnResize } from './hooks/use-column-resize';
26
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 { EMPTY_TABLE_STATE_TEXT } from './utils/constants';
29
+ import { DsEmptyState } from '../ds-empty-state';
30
+ import { getUtilityColumnSizing } from './utils/column-size';
30
31
  import { createSkeletonRows, getAugmentedColumns, toSkeletonColumns } from './utils/table-columns';
31
32
  import { createTableApi } from './utils/table-api';
32
33
  import { areBodiesFrozen } from './utils/frozen-body';
@@ -75,6 +76,9 @@ const DsTable = <TData extends { id: string }, TValue>(props: DsDataTableProps<T
75
76
  resizableColumns,
76
77
  columnSizing,
77
78
  onColumnSizingChange,
79
+ selectableColumnWidth,
80
+ expandableColumnWidth,
81
+ reorderableColumnWidth,
78
82
  } = tableProps;
79
83
  const [data, setData] = React.useState(tableData);
80
84
  const [sorting, setSorting] = React.useState<SortingState>([]);
@@ -155,12 +159,32 @@ const DsTable = <TData extends { id: string }, TValue>(props: DsDataTableProps<T
155
159
  reorderable,
156
160
  virtualized,
157
161
  showSelectAllCheckbox,
162
+ selectableColumnWidth,
163
+ expandableColumnWidth,
164
+ reorderableColumnWidth,
158
165
  }),
159
- [columnsProp, hasSelectColumn, hasExpanderColumn, reorderable, virtualized, showSelectAllCheckbox],
166
+ [
167
+ columnsProp,
168
+ hasSelectColumn,
169
+ hasExpanderColumn,
170
+ reorderable,
171
+ virtualized,
172
+ showSelectAllCheckbox,
173
+ selectableColumnWidth,
174
+ expandableColumnWidth,
175
+ reorderableColumnWidth,
176
+ ],
160
177
  );
161
178
 
162
179
  const skeletonColumns = useMemo(() => toSkeletonColumns(columns), [columns]);
163
180
  const skeletonData = useMemo(() => createSkeletonRows<TData>(), []);
181
+ const utilityColumnSizing = useMemo(() => getUtilityColumnSizing(columns), [columns]);
182
+ const sourceDataLength = (reorderable ? data : tableData).length;
183
+ const inferredEmptyState = useMemo(
184
+ () => <DsEmptyState variant={sourceDataLength === 0 ? 'noData' : 'noMatches'} />,
185
+ [sourceDataLength],
186
+ );
187
+ const resolvedEmptyState = emptyState ?? inferredEmptyState;
164
188
 
165
189
  const resize = useColumnResize({
166
190
  enabled: resizableColumns,
@@ -168,6 +192,8 @@ const DsTable = <TData extends { id: string }, TValue>(props: DsDataTableProps<T
168
192
  onColumnSizingChange,
169
193
  columns,
170
194
  columnVisibility,
195
+ overflowKey: (reorderable ? data : tableData).length,
196
+ utilityColumnSizing,
171
197
  });
172
198
 
173
199
  const table = useReactTable({
@@ -238,7 +264,7 @@ const DsTable = <TData extends { id: string }, TValue>(props: DsDataTableProps<T
238
264
  {virtualized ? (
239
265
  <MemoizedDsTableBodyVirtualized
240
266
  table={table}
241
- emptyState={emptyState}
267
+ emptyState={resolvedEmptyState}
242
268
  estimateSize={virtualizedOptions?.estimateSize || ROW_SIZE_HEIGHT_MAP[rowSize]}
243
269
  overscan={virtualizedOptions?.overscan}
244
270
  onScroll={onScroll}
@@ -249,7 +275,7 @@ const DsTable = <TData extends { id: string }, TValue>(props: DsDataTableProps<T
249
275
  <MemoizedDsTableRowsBody
250
276
  table={table}
251
277
  rowSelection={rowSelection}
252
- emptyState={emptyState}
278
+ emptyState={resolvedEmptyState}
253
279
  SortableWrapper={SortableWrapper}
254
280
  />
255
281
  )}
@@ -260,14 +286,20 @@ const DsTable = <TData extends { id: string }, TValue>(props: DsDataTableProps<T
260
286
  ) : null}
261
287
  </div>
262
288
  {selectable && actions.length > 0 && (
263
- <DsTableBulkActions
264
- numSelectedRows={selectedRows.length}
265
- actions={actions.map((action) => ({
266
- ...action,
267
- onClick: () => action.onClick(selectedRows),
268
- }))}
289
+ <DsBulkActions
290
+ placement="floating"
291
+ selectedCount={selectedRows.length}
269
292
  onClearSelection={table.resetRowSelection}
270
- />
293
+ >
294
+ {actions.map((action, index) => (
295
+ <DsBulkActions.Item
296
+ key={`${action.label}-${String(index)}`}
297
+ icon={action.icon}
298
+ label={action.label}
299
+ onClick={() => action.onClick(selectedRows)}
300
+ />
301
+ ))}
302
+ </DsBulkActions>
271
303
  )}
272
304
  </div>
273
305
  </DsTableContextProvider>
@@ -296,9 +328,9 @@ const DsTableRowsBody = <TData extends { id: string }>({
296
328
  {rows.length ? (
297
329
  rows.map((row) => <DsTableRow key={row.id} row={row} isSelected={!!rowSelection[row.id]} />)
298
330
  ) : (
299
- <TableRow>
331
+ <TableRow className={styles.emptyStateRow}>
300
332
  <TableCell colSpan={columnCount} className={styles.emptyState}>
301
- {emptyState || EMPTY_TABLE_STATE_TEXT}
333
+ {emptyState}
302
334
  </TableCell>
303
335
  </TableRow>
304
336
  )}
@@ -296,7 +296,9 @@ export interface DsDataTableProps<TData, TValue> {
296
296
  activeRowId?: string | null;
297
297
 
298
298
  /**
299
- * Empty state of the table
299
+ * Replaces the inferred **Empty state**. When omitted, the table renders
300
+ * `DsEmptyState` as `noData` if `data` is empty, or `noMatches` if `data`
301
+ * has rows but filters hide all of them.
300
302
  */
301
303
  emptyState?: React.ReactNode;
302
304
 
@@ -340,14 +342,17 @@ export interface DsDataTableProps<TData, TValue> {
340
342
  * from {@link onColumnSizingChange}. Pass it (typically on the next mount) to
341
343
  * restore widths; columns absent from the map are measured and sized
342
344
  * automatically. This seeds widths — live resizing still updates them
343
- * internally — so it is not a fully controlled value.
345
+ * internally — so it is not a fully controlled value. Ids for the select,
346
+ * expander, and reorder utility columns are ignored; use the matching
347
+ * `*ColumnWidth` props instead.
344
348
  */
345
349
  columnSizing?: Record<string, number>;
346
350
 
347
351
  /**
348
352
  * Called with the next column id → width (px) map when a resize finishes
349
353
  * (drag end, double-click restore, or keyboard nudge). Use this to persist
350
- * widths across sessions.
354
+ * widths across sessions. Select, expander, and reorder utility columns are
355
+ * omitted from the map.
351
356
  */
352
357
  onColumnSizingChange?: (columnSizing: Record<string, number>) => void;
353
358
 
@@ -363,6 +368,14 @@ export interface DsDataTableProps<TData, TValue> {
363
368
  */
364
369
  expandable?: boolean | ((row: TData) => boolean);
365
370
 
371
+ /**
372
+ * Width in px of the expander utility column. Ignored when `expandable` is
373
+ * off. Non-finite or `<= 0` falls back to the default. The column is not
374
+ * user-resizable. Wins over a `columnSizing` entry for this column.
375
+ * @default 36
376
+ */
377
+ expandableColumnWidth?: number;
378
+
366
379
  /**
367
380
  * Function to render the expanded row
368
381
  */
@@ -394,6 +407,14 @@ export interface DsDataTableProps<TData, TValue> {
394
407
  */
395
408
  selectable?: boolean | ((rowData: TData) => boolean);
396
409
 
410
+ /**
411
+ * Width in px of the select utility column. Ignored when `selectable` is
412
+ * off. Non-finite or `<= 0` falls back to the default. The column is not
413
+ * user-resizable. Wins over a `columnSizing` entry for this column.
414
+ * @default 36
415
+ */
416
+ selectableColumnWidth?: number;
417
+
397
418
  /**
398
419
  * Whether to show the select/deselect all checkbox in the header
399
420
  * @default true
@@ -474,6 +495,15 @@ export interface DsDataTableProps<TData, TValue> {
474
495
  */
475
496
  reorderable?: boolean;
476
497
 
498
+ /**
499
+ * Width in px of the reorder utility column. Ignored when `reorderable` is
500
+ * off or the table is virtualized. Non-finite or `<= 0` falls back to the
501
+ * default. The column is not user-resizable. Wins over a `columnSizing`
502
+ * entry for this column.
503
+ * @default 60
504
+ */
505
+ reorderableColumnWidth?: number;
506
+
477
507
  /**
478
508
  * Callback when the order of rows changes via drag & drop
479
509
  */
@@ -19,12 +19,19 @@ import {
19
19
  collectMissingLeafSizing,
20
20
  getColumnSizeCssVars,
21
21
  getResizeOriginSize,
22
+ growFillLeavesToContainer,
23
+ isExplicitColumnWidth,
22
24
  measureLeafHeaderWidths,
23
25
  omitBuiltinColumnSizing,
26
+ shiftColumnTrack,
27
+ withUtilityColumnSizing,
24
28
  type ColumnSizeBoundsSource,
25
29
  } from '../utils/column-size';
26
- import { RESIZE_MIN_COLUMN_WIDTH } from '../utils/constants';
30
+ import { RESIZE_DIVIDER_WIDTH, RESIZE_MIN_COLUMN_WIDTH } from '../utils/constants';
27
31
  import type { DsTableResizePhase } from '../context/ds-table-context';
32
+ import { useScrollbarSpacer, type ScrollbarSpacerWidth } from './use-scrollbar-spacer';
33
+
34
+ const TRACK_MATCH_PX = 3;
28
35
 
29
36
  const INITIAL_COLUMN_SIZING_INFO: ColumnSizingInfoState = {
30
37
  startOffset: null,
@@ -35,6 +42,14 @@ const INITIAL_COLUMN_SIZING_INFO: ColumnSizingInfoState = {
35
42
  columnSizingStart: [],
36
43
  };
37
44
 
45
+ const clampResizeOverlayOffset = (offset: number, tableWidth: number): number => {
46
+ if (tableWidth <= RESIZE_DIVIDER_WIDTH) {
47
+ return offset;
48
+ }
49
+
50
+ return Math.min(offset, tableWidth - RESIZE_DIVIDER_WIDTH);
51
+ };
52
+
38
53
  export interface UseColumnResizeOptions {
39
54
  enabled: boolean;
40
55
  /**
@@ -49,6 +64,15 @@ export interface UseColumnResizeOptions {
49
64
  */
50
65
  columns: unknown;
51
66
  columnVisibility: VisibilityState;
67
+ /**
68
+ * Invalidates overflow measurement (row count). Read as a dependency only.
69
+ */
70
+ overflowKey: number;
71
+ /**
72
+ * Authored widths for injected utility leaves. Overlaid last so they win
73
+ * over persist seed and live internal state.
74
+ */
75
+ utilityColumnSizing?: ColumnSizingState;
52
76
  }
53
77
 
54
78
  export interface DsTableResizeContextSlice {
@@ -64,6 +88,10 @@ export interface DsTableResizeContextSlice {
64
88
  * the persist callback, mirroring drag end.
65
89
  */
66
90
  onResizeKeyboardNudge: (columnId: string, delta: number) => void;
91
+ /**
92
+ * Overflow-only **Scrollbar spacer** width (0 or rest scrollbar size).
93
+ */
94
+ scrollbarSpacerWidth: ScrollbarSpacerWidth;
67
95
  }
68
96
 
69
97
  export interface UseColumnResizeActiveResize {
@@ -92,6 +120,7 @@ export interface UseColumnResizeResult {
92
120
  columnSizingInfo: ColumnSizingInfoState;
93
121
  };
94
122
  bind: <TData>(table: Table<TData>) => UseColumnResizeBindResult;
123
+ scrollbarSpacerWidth: ScrollbarSpacerWidth;
95
124
  }
96
125
 
97
126
  /**
@@ -108,6 +137,8 @@ export const useColumnResize = ({
108
137
  onColumnSizingChange,
109
138
  columns,
110
139
  columnVisibility,
140
+ overflowKey,
141
+ utilityColumnSizing,
111
142
  }: UseColumnResizeOptions): UseColumnResizeResult => {
112
143
  const [columnResizeState, setColumnResizeState] = useState<{
113
144
  columnSizing: ColumnSizingState;
@@ -125,10 +156,11 @@ export const useColumnResize = ({
125
156
  // drag (or seed measurement) of a persisted column still takes effect. Passed
126
157
  // to TanStack and read for CSS vars / persist so restore, drag, and persist
127
158
  // all see the same widths.
128
- const columnSizing = useMemo<ColumnSizingState>(
129
- () => (columnSizingProp ? { ...columnSizingProp, ...internalColumnSizing } : internalColumnSizing),
130
- [columnSizingProp, internalColumnSizing],
131
- );
159
+ const columnSizing = useMemo<ColumnSizingState>(() => {
160
+ const merged = columnSizingProp ? { ...columnSizingProp, ...internalColumnSizing } : internalColumnSizing;
161
+
162
+ return withUtilityColumnSizing(merged, utilityColumnSizing ?? {});
163
+ }, [columnSizingProp, internalColumnSizing, utilityColumnSizing]);
132
164
  const [resizeHover, setResizeHover] = useState<{
133
165
  columnId: string;
134
166
  offset: number;
@@ -136,6 +168,10 @@ export const useColumnResize = ({
136
168
  const dragStartOffsetRef = useRef<number | null>(null);
137
169
  const resizeOriginSizingRef = useRef<ColumnSizingState>({});
138
170
  const containerRef = useRef<HTMLDivElement>(null);
171
+ const scrollbarSpacerWidth = useScrollbarSpacer(containerRef, overflowKey);
172
+ const spacerWidthRef = useRef(scrollbarSpacerWidth);
173
+ spacerWidthRef.current = scrollbarSpacerWidth;
174
+ const prevSpacerWidthRef = useRef<ScrollbarSpacerWidth | null>(null);
139
175
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- bind is generic; the ref only calls sizing APIs
140
176
  const tableRef = useRef<Table<any> | null>(null);
141
177
 
@@ -235,6 +271,8 @@ export const useColumnResize = ({
235
271
  );
236
272
 
237
273
  useLayoutEffect(() => {
274
+ prevSpacerWidthRef.current = null;
275
+
238
276
  if (!enabled) {
239
277
  return;
240
278
  }
@@ -258,6 +296,11 @@ export const useColumnResize = ({
258
296
  const leaves = table.getVisibleLeafColumns();
259
297
  const leafIds = leaves.map((column) => column.id);
260
298
  const measured = measureLeafHeaderWidths(tableEl, leafIds);
299
+ const fillableIds = leaves
300
+ .filter((column) => !isExplicitColumnWidth(column, {}))
301
+ .map((column) => column.id);
302
+ const trackWidth = container.clientWidth - spacerWidthRef.current;
303
+ const missing = collectMissingLeafSizing(measured, columnSizingRef.current, trackWidth, fillableIds);
261
304
 
262
305
  for (const column of leaves) {
263
306
  const measuredWidth = measured[column.id];
@@ -265,17 +308,31 @@ export const useColumnResize = ({
265
308
  continue;
266
309
  }
267
310
 
268
- resizeOriginSizingRef.current[column.id] = getResizeOriginSize(column, Math.round(measuredWidth));
311
+ resizeOriginSizingRef.current[column.id] = getResizeOriginSize(
312
+ column,
313
+ missing[column.id] ?? Math.round(measuredWidth),
314
+ );
269
315
  }
270
316
 
271
- const missing = collectMissingLeafSizing(measured, columnSizingRef.current);
272
317
  if (Object.keys(missing).length > 0) {
318
+ const bounds = leafSizeBoundsRef.current;
319
+ const clamped = clampColumnSizing(missing, bounds);
320
+ const grown = growFillLeavesToContainer(
321
+ clamped,
322
+ columnSizingRef.current,
323
+ Object.keys(measured),
324
+ trackWidth,
325
+ fillableIds,
326
+ bounds,
327
+ );
273
328
  handleColumnSizingChange((prev) => ({
274
- ...clampColumnSizing(missing, leafSizeBoundsRef.current),
329
+ ...grown,
275
330
  ...prev,
276
331
  }));
277
332
  }
278
333
 
334
+ prevSpacerWidthRef.current = spacerWidthRef.current;
335
+
279
336
  return true;
280
337
  };
281
338
 
@@ -293,6 +350,48 @@ export const useColumnResize = ({
293
350
  return () => observer.disconnect();
294
351
  }, [enabled, columns, columnVisibility, handleColumnSizingChange]);
295
352
 
353
+ // After seed, a vertical scrollbar can appear or disappear (Infinite Scroll,
354
+ // filter, shorter viewport). The **Scrollbar spacer** then toggles 0↔10px.
355
+ // Shrink or grow the column track by that delta so leaves + spacer still
356
+ // fill the container — internally only, never via onColumnSizingChange.
357
+ // Skip when the leaves were not filling the previous track (already
358
+ // overflowing horizontally) so authored-wide columns are left alone.
359
+ useLayoutEffect(() => {
360
+ if (!enabled) {
361
+ return;
362
+ }
363
+
364
+ const prevSpacerWidth = prevSpacerWidthRef.current;
365
+ if (prevSpacerWidth === null || prevSpacerWidth === scrollbarSpacerWidth) {
366
+ return;
367
+ }
368
+
369
+ const container = containerRef.current;
370
+ const table = tableRef.current;
371
+ if (!container || !table) {
372
+ return;
373
+ }
374
+
375
+ const leafIds = table.getVisibleLeafColumns().map((column) => column.id);
376
+ let leafTotal = 0;
377
+
378
+ for (const id of leafIds) {
379
+ leafTotal += columnSizingRef.current[id] ?? 0;
380
+ }
381
+
382
+ const prevTrack = container.clientWidth - prevSpacerWidth;
383
+ prevSpacerWidthRef.current = scrollbarSpacerWidth;
384
+
385
+ if (Math.abs(leafTotal - prevTrack) > TRACK_MATCH_PX) {
386
+ return;
387
+ }
388
+
389
+ const delta = prevSpacerWidth - scrollbarSpacerWidth;
390
+ handleColumnSizingChange(
391
+ shiftColumnTrack(columnSizingRef.current, leafIds, delta, leafSizeBoundsRef.current),
392
+ );
393
+ }, [enabled, scrollbarSpacerWidth, handleColumnSizingChange]);
394
+
296
395
  const tableOptions = useMemo<UseColumnResizeTableOptions>(
297
396
  () => ({
298
397
  enableColumnResizing: enabled,
@@ -319,7 +418,7 @@ export const useColumnResize = ({
319
418
  const columnSizeVars: CSSProperties | undefined = resizeSizingReady
320
419
  ? {
321
420
  ...getColumnSizeCssVars(table.getFlatHeaders()),
322
- width: table.getTotalSize(),
421
+ width: table.getTotalSize() + scrollbarSpacerWidth,
323
422
  }
324
423
  : undefined;
325
424
 
@@ -335,6 +434,7 @@ export const useColumnResize = ({
335
434
  onResizeDragStart: handleResizeDragStart,
336
435
  onResizeReset: handleResizeReset,
337
436
  onResizeKeyboardNudge: handleResizeKeyboardNudge,
437
+ scrollbarSpacerWidth,
338
438
  },
339
439
  };
340
440
  }
@@ -349,14 +449,15 @@ export const useColumnResize = ({
349
449
  resizingHeader && startSize !== null
350
450
  ? resizingHeader.getSize() - startSize
351
451
  : (columnSizingInfo.deltaOffset ?? 0);
452
+ const tableWidth = table.getTotalSize();
352
453
  const activeResize: UseColumnResizeActiveResize | null =
353
454
  isResizingColumn && dragStartOffsetRef.current !== null
354
455
  ? {
355
- offset: dragStartOffsetRef.current + clampedResizeDelta,
456
+ offset: clampResizeOverlayOffset(dragStartOffsetRef.current + clampedResizeDelta, tableWidth),
356
457
  phase: 'dragging',
357
458
  }
358
459
  : resizeHover
359
- ? { offset: resizeHover.offset, phase: 'hover' }
460
+ ? { offset: clampResizeOverlayOffset(resizeHover.offset, tableWidth), phase: 'hover' }
360
461
  : null;
361
462
 
362
463
  return {
@@ -370,6 +471,7 @@ export const useColumnResize = ({
370
471
  onResizeDragStart: handleResizeDragStart,
371
472
  onResizeReset: handleResizeReset,
372
473
  onResizeKeyboardNudge: handleResizeKeyboardNudge,
474
+ scrollbarSpacerWidth,
373
475
  },
374
476
  };
375
477
  };
@@ -381,5 +483,6 @@ export const useColumnResize = ({
381
483
  columnSizingInfo,
382
484
  },
383
485
  bind,
486
+ scrollbarSpacerWidth,
384
487
  };
385
488
  };
@@ -0,0 +1,108 @@
1
+ import { useLayoutEffect, useState, type RefObject } from 'react';
2
+ import { SCROLLBAR_SPACER_WIDTH } from '../utils/constants';
3
+
4
+ export type ScrollbarSpacerWidth = 0 | typeof SCROLLBAR_SPACER_WIDTH;
5
+
6
+ const BODY_SCROLLBAR_COMPENSATION_CSS_VAR = '--ds-table-body-scrollbar-compensation';
7
+
8
+ const bodyOverflowsVertically = (body: HTMLElement): boolean => body.scrollHeight > body.clientHeight;
9
+
10
+ const verticalScrollbarOccupancy = (body: HTMLElement): number =>
11
+ Math.max(0, body.offsetWidth - body.clientWidth);
12
+
13
+ /**
14
+ * Extra `padding-inline-end` on body rows so Fill columns match the header
15
+ * **Scrollbar spacer** when the vertical scrollbar occupies no layout (overlay).
16
+ * Classic scrollbars already inset `clientWidth`; do not add that occupancy twice.
17
+ */
18
+ export const getBodyScrollbarCompensation = (
19
+ occupancyPx: number,
20
+ spacerWidth: ScrollbarSpacerWidth,
21
+ ): number => Math.max(0, spacerWidth - occupancyPx);
22
+
23
+ const syncBodyScrollbarCompensation = (
24
+ container: HTMLElement,
25
+ body: HTMLElement | null,
26
+ spacerWidth: ScrollbarSpacerWidth,
27
+ ): void => {
28
+ if (!body) {
29
+ container.style.removeProperty(BODY_SCROLLBAR_COMPENSATION_CSS_VAR);
30
+ return;
31
+ }
32
+
33
+ container.style.setProperty(
34
+ BODY_SCROLLBAR_COMPENSATION_CSS_VAR,
35
+ `${String(getBodyScrollbarCompensation(verticalScrollbarOccupancy(body), spacerWidth))}px`,
36
+ );
37
+ };
38
+
39
+ /**
40
+ * Overflow-only **Scrollbar spacer** width: `SCROLLBAR_SPACER_WIDTH` while the
41
+ * body scroll container overflows vertically, otherwise 0.
42
+ *
43
+ * Observes container/tbody size and tbody children (rows added/removed). Does
44
+ * not watch `style` attributes — virtualized rows rewrite `transform` on scroll.
45
+ */
46
+ export const useScrollbarSpacer = (
47
+ containerRef: RefObject<HTMLElement | null>,
48
+ overflowKey: number,
49
+ ): ScrollbarSpacerWidth => {
50
+ const [width, setWidth] = useState<ScrollbarSpacerWidth>(0);
51
+
52
+ useLayoutEffect(() => {
53
+ const container = containerRef.current;
54
+ if (!container) {
55
+ return;
56
+ }
57
+
58
+ const read = () => {
59
+ const body = container.querySelector('tbody');
60
+ if (!(body instanceof HTMLElement)) {
61
+ setWidth(0);
62
+ syncBodyScrollbarCompensation(container, null, 0);
63
+ return;
64
+ }
65
+
66
+ const nextWidth = bodyOverflowsVertically(body) ? SCROLLBAR_SPACER_WIDTH : 0;
67
+ setWidth(nextWidth);
68
+ syncBodyScrollbarCompensation(container, body, nextWidth);
69
+ };
70
+
71
+ const resizeObserver = new ResizeObserver(read);
72
+ const bodyChildObserver = new MutationObserver(read);
73
+ resizeObserver.observe(container);
74
+
75
+ let observedBody: HTMLElement | null = null;
76
+
77
+ const bindBody = () => {
78
+ const body = container.querySelector('tbody');
79
+ if (!(body instanceof HTMLElement) || body === observedBody) {
80
+ return;
81
+ }
82
+
83
+ bodyChildObserver.disconnect();
84
+ observedBody = body;
85
+ resizeObserver.observe(body);
86
+ bodyChildObserver.observe(body, { childList: true });
87
+ read();
88
+ };
89
+
90
+ bindBody();
91
+ read();
92
+
93
+ const table = container.querySelector('table');
94
+ const tableChildObserver = new MutationObserver(bindBody);
95
+ if (table) {
96
+ tableChildObserver.observe(table, { childList: true });
97
+ }
98
+
99
+ return () => {
100
+ resizeObserver.disconnect();
101
+ bodyChildObserver.disconnect();
102
+ tableChildObserver.disconnect();
103
+ container.style.removeProperty(BODY_SCROLLBAR_COMPENSATION_CSS_VAR);
104
+ };
105
+ }, [containerRef, overflowKey]);
106
+
107
+ return width;
108
+ };
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { SCROLLBAR_SPACER_WIDTH } from '../utils/constants';
3
+ import { getBodyScrollbarCompensation } from './use-scrollbar-spacer';
4
+
5
+ describe('getBodyScrollbarCompensation', () => {
6
+ it('reserves the full spacer when the scrollbar occupies no layout (overlay)', () => {
7
+ expect(getBodyScrollbarCompensation(0, SCROLLBAR_SPACER_WIDTH)).toBe(SCROLLBAR_SPACER_WIDTH);
8
+ });
9
+
10
+ it('adds nothing when a classic scrollbar already occupies the spacer width', () => {
11
+ expect(getBodyScrollbarCompensation(SCROLLBAR_SPACER_WIDTH, SCROLLBAR_SPACER_WIDTH)).toBe(0);
12
+ });
13
+
14
+ it('fills only the gap when a thinner scrollbar occupies part of the spacer', () => {
15
+ expect(getBodyScrollbarCompensation(8, SCROLLBAR_SPACER_WIDTH)).toBe(2);
16
+ });
17
+
18
+ it('does not shrink the body when occupancy is wider than the spacer', () => {
19
+ expect(getBodyScrollbarCompensation(15, SCROLLBAR_SPACER_WIDTH)).toBe(0);
20
+ });
21
+
22
+ it('adds nothing when the spacer is collapsed', () => {
23
+ expect(getBodyScrollbarCompensation(0, 0)).toBe(0);
24
+ expect(getBodyScrollbarCompensation(SCROLLBAR_SPACER_WIDTH, 0)).toBe(0);
25
+ });
26
+ });
@@ -1,12 +1,10 @@
1
1
  $border-radius: 4px;
2
2
  $border-width: 1px;
3
3
  $group-border-width: 2px;
4
- $reorder-column-width: 60px;
5
4
  $container-margin-vertical: 1rem;
6
5
  $filter-input-gap: 0.5rem;
7
6
  $filter-input-max-width: 24rem;
8
7
  $header-sort-gap: 0.5rem;
9
- $empty-state-height: 6rem;
10
8
  $resize-handle-hit-width: 12px;
11
9
  $resize-divider-width: 2px;
12
10