@drivenets/design-system 0.18.1 → 0.19.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 (122) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/index.cjs +17138 -12075
  3. package/dist/index.d.cts +783 -238
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +783 -238
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +17123 -12076
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +15 -15
  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-dropdown-menu/ds-dropdown-menu.tsx +2 -0
  33. package/src/components/ds-empty-state/ds-empty-state.module.scss +16 -0
  34. package/src/components/ds-empty-state/ds-empty-state.tsx +45 -0
  35. package/src/components/ds-empty-state/ds-empty-state.types.ts +38 -0
  36. package/src/components/ds-empty-state/index.ts +3 -0
  37. package/src/components/ds-file-upload/ds-file-upload.tsx +4 -1
  38. package/src/components/ds-form-control/ds-form-control.tsx +18 -10
  39. package/src/components/ds-illustration/ds-illustration.figma.ts +34 -0
  40. package/src/components/ds-illustration/ds-illustration.module.scss +6 -0
  41. package/src/components/ds-illustration/ds-illustration.tsx +29 -0
  42. package/src/components/ds-illustration/ds-illustration.types.ts +45 -0
  43. package/src/components/ds-illustration/illustrations/configuration.tsx +51 -0
  44. package/src/components/ds-illustration/illustrations/device.tsx +133 -0
  45. package/src/components/ds-illustration/illustrations/done.tsx +107 -0
  46. package/src/components/{ds-catalog-layout/catalog-layout-empty-illustration.tsx → ds-illustration/illustrations/empty-inbox.tsx} +10 -14
  47. package/src/components/ds-illustration/illustrations/error.tsx +86 -0
  48. package/src/components/ds-illustration/illustrations/go-filter.tsx +51 -0
  49. package/src/components/ds-illustration/illustrations/illustration-clouds.tsx +10 -0
  50. package/src/components/ds-illustration/illustrations/index.ts +39 -0
  51. package/src/components/ds-illustration/illustrations/no-connection.tsx +54 -0
  52. package/src/components/ds-illustration/illustrations/no-credit-card.tsx +48 -0
  53. package/src/components/ds-illustration/illustrations/no-device.tsx +61 -0
  54. package/src/components/ds-illustration/illustrations/no-documents.tsx +52 -0
  55. package/src/components/ds-illustration/illustrations/no-gps.tsx +57 -0
  56. package/src/components/ds-illustration/illustrations/no-images.tsx +82 -0
  57. package/src/components/ds-illustration/illustrations/no-items-cart.tsx +86 -0
  58. package/src/components/ds-illustration/illustrations/no-messages.tsx +51 -0
  59. package/src/components/ds-illustration/illustrations/no-search-result.tsx +76 -0
  60. package/src/components/ds-illustration/illustrations/no-tasks.tsx +61 -0
  61. package/src/components/ds-illustration/illustrations/site.tsx +80 -0
  62. package/src/components/ds-illustration/index.ts +3 -0
  63. package/src/components/ds-main-menu/components/expanded-item/expanded-item.tsx +108 -0
  64. package/src/components/ds-main-menu/components/expanded-item/expanded-item.types.ts +8 -0
  65. package/src/components/ds-main-menu/components/expanded-item/index.ts +1 -0
  66. package/src/components/ds-main-menu/components/menu-graphic/index.ts +1 -0
  67. package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.tsx +16 -0
  68. package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.types.ts +7 -0
  69. package/src/components/ds-main-menu/components/tile-link/index.ts +1 -0
  70. package/src/components/ds-main-menu/components/tile-link/tile-link.tsx +85 -0
  71. package/src/components/ds-main-menu/components/tile-link/tile-link.types.ts +8 -0
  72. package/src/components/ds-main-menu/components/utility-link/index.ts +1 -0
  73. package/src/components/ds-main-menu/components/utility-link/utility-link.tsx +37 -0
  74. package/src/components/ds-main-menu/components/utility-link/utility-link.types.ts +7 -0
  75. package/src/components/ds-main-menu/ds-main-menu.figma.ts +17 -0
  76. package/src/components/ds-main-menu/ds-main-menu.module.scss +349 -0
  77. package/src/components/ds-main-menu/ds-main-menu.tsx +102 -0
  78. package/src/components/ds-main-menu/ds-main-menu.types.ts +86 -0
  79. package/src/components/ds-main-menu/ds-main-menu.utils.ts +40 -0
  80. package/src/components/ds-main-menu/index.ts +9 -0
  81. package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +1 -1
  82. package/src/components/ds-popover/ds-popover.tsx +2 -1
  83. package/src/components/ds-popover/ds-popover.types.ts +7 -0
  84. package/src/components/ds-site-node/ds-site-node.tsx +2 -2
  85. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +12 -2
  86. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +2 -3
  87. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +1 -6
  88. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +6 -3
  89. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +18 -7
  90. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +27 -21
  91. package/src/components/ds-table/components/ds-table-header-label.module.scss +12 -0
  92. package/src/components/ds-table/components/ds-table-header-label.tsx +28 -0
  93. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +0 -1
  94. package/src/components/ds-table/context/ds-table-context.tsx +8 -0
  95. package/src/components/ds-table/ds-table.module.scss +26 -16
  96. package/src/components/ds-table/ds-table.tsx +46 -14
  97. package/src/components/ds-table/ds-table.types.ts +33 -3
  98. package/src/components/ds-table/hooks/use-column-resize.ts +114 -11
  99. package/src/components/ds-table/hooks/use-scrollbar-spacer.ts +108 -0
  100. package/src/components/ds-table/hooks/use-scrollbar-spacer.unit.test.ts +26 -0
  101. package/src/components/ds-table/styles/_variables.scss +0 -2
  102. package/src/components/ds-table/utils/column-size.ts +274 -1
  103. package/src/components/ds-table/utils/column-size.unit.test.ts +157 -0
  104. package/src/components/ds-table/utils/constants.ts +13 -5
  105. package/src/components/ds-table/utils/table-columns.tsx +27 -13
  106. package/src/components/ds-table/utils/table-columns.unit.test.ts +44 -0
  107. package/src/components/ds-toggle/ds-toggle.module.scss +18 -0
  108. package/src/components/ds-toggle/ds-toggle.tsx +15 -13
  109. package/src/components/ds-tooltip/ds-tooltip.tsx +6 -2
  110. package/src/components/ds-tooltip/ds-tooltip.types.ts +17 -0
  111. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.figma.ts +35 -0
  112. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.module.scss +150 -0
  113. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.tsx +161 -0
  114. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.types.ts +117 -0
  115. package/src/components/ds-top-bar-navigation/index.ts +2 -0
  116. package/src/index.ts +6 -0
  117. package/src/utils/use-is-horizontally-overflowing.ts +35 -0
  118. package/src/components/ds-site-node/hooks/use-is-overflowing.ts +0 -31
  119. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +0 -112
  120. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +0 -67
  121. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +0 -41
  122. package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +0 -2
@@ -5,6 +5,7 @@ import { useDsTableContext } from '../../context/ds-table-context';
5
5
  import { getGroupColumnSizeStyle } from '../../grouping';
6
6
  import { getResizableHeaderStyle } from '../../utils/column-size';
7
7
  import { defaultDsTableLocale } from '../../ds-table.types';
8
+ import { DsTableHeaderLabel } from '../ds-table-header-label';
8
9
  import { DsTableResizeHandle } from '../ds-table-resize-handle';
9
10
  import type { DsTableGroupHeaderCellProps } from './ds-table-group-header-cell.types';
10
11
  import styles from './ds-table-group-header-cell.module.scss';
@@ -29,6 +30,8 @@ export const DsTableGroupHeaderCell = <TData, TValue>({
29
30
  const toggleLabel = isCollapsed ? mergedLocale.expandColumnGroup : mergedLocale.collapseColumnGroup;
30
31
 
31
32
  const handleToggle = () => onToggleColumnGroup?.(groupId);
33
+ const headerDef = header.column.columnDef.header;
34
+ const tooltipText = typeof headerDef === 'string' ? headerDef : undefined;
32
35
 
33
36
  return (
34
37
  <TableHead
@@ -38,9 +41,9 @@ export const DsTableGroupHeaderCell = <TData, TValue>({
38
41
  colSpan={header.colSpan}
39
42
  >
40
43
  <div className={styles.groupHeaderContent}>
41
- <span className={styles.groupLabel}>
42
- {flexRender(header.column.columnDef.header, header.getContext())}
43
- </span>
44
+ <DsTableHeaderLabel tooltipText={tooltipText}>
45
+ {flexRender(headerDef, header.getContext())}
46
+ </DsTableHeaderLabel>
44
47
  {isCollapsible && (
45
48
  <DsButtonV3
46
49
  variant="secondary"
@@ -15,6 +15,10 @@
15
15
  border-bottom: 1px solid var(--color-dap-gray-400);
16
16
  }
17
17
 
18
+ .scrollbarSpacer:not(.scrollbarSpacerOccupied) {
19
+ border-bottom: none;
20
+ }
21
+
18
22
  &.headerRowNoBorder {
19
23
  .headerCell {
20
24
  border-bottom: none;
@@ -42,6 +46,7 @@
42
46
 
43
47
  .pageButtonIconContainer {
44
48
  display: flex;
49
+ flex-shrink: 0;
45
50
  align-items: center;
46
51
  justify-content: center;
47
52
  width: 24px;
@@ -54,9 +59,18 @@
54
59
  }
55
60
  }
56
61
 
62
+ .scrollbarSpacer {
63
+ flex-shrink: 0;
64
+ padding: 0;
65
+ overflow: hidden;
66
+ border-left: none;
67
+ pointer-events: none;
68
+ }
69
+
57
70
  .selectHeaderCell {
58
71
  padding: 0;
59
72
  justify-content: center;
73
+ text-align: center;
60
74
  }
61
75
 
62
76
  .groupStart:not(:first-child) {
@@ -68,7 +82,10 @@
68
82
  align-items: center;
69
83
  justify-content: space-between;
70
84
  gap: vars.$header-sort-gap;
85
+ width: 100%;
86
+ min-width: 0;
71
87
  height: 100%;
88
+ overflow: hidden;
72
89
  }
73
90
 
74
91
  .headerLabelStack {
@@ -76,15 +93,9 @@
76
93
  overflow: hidden;
77
94
  }
78
95
 
79
- .headerLabel {
80
- display: block;
81
- overflow: hidden;
82
- text-overflow: ellipsis;
83
- white-space: nowrap;
84
- }
85
-
86
96
  .filterTriggerContainer {
87
97
  display: inline-flex;
98
+ flex-shrink: 0;
88
99
  align-items: center;
89
100
  margin-left: vars.$header-sort-gap;
90
101
  }
@@ -12,11 +12,13 @@ import { DsStack } from '../../../ds-stack';
12
12
  import { DsTableColumnFilterPopover } from '../../filters/components/column-filter-popover';
13
13
  import type { ResolvedColumnFilter } from '../../filters/types/filter-adapter.types';
14
14
  import { DsTableGroupHeaderCell } from '../ds-table-group-header-cell';
15
+ import { DsTableHeaderLabel } from '../ds-table-header-label';
15
16
  import { DsTableResizeHandle } from '../ds-table-resize-handle';
16
17
  import { isFirstLeafColumnOfGroup } from '../../grouping';
17
18
 
18
19
  const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
19
- const { bordered, virtualized, resizableColumns, resizeSizingReady } = useDsTableContext<TData, unknown>();
20
+ const { bordered, virtualized, resizableColumns, resizeSizingReady, scrollbarSpacerWidth } =
21
+ useDsTableContext<TData, unknown>();
20
22
 
21
23
  return (
22
24
  <TableHeader className={classnames(styles.header, virtualized && styles.virtualizedHeader)}>
@@ -58,6 +60,7 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
58
60
  !!resizeSizingReady,
59
61
  );
60
62
  const canSort = header.column.getCanSort();
63
+ const sortDirection = header.column.getIsSorted();
61
64
  const canResize = resizableColumns && header.column.getCanResize();
62
65
  const isSelectColumn = header.column.id === SELECT_COLUMN_ID;
63
66
  const isGroupStart = isFirstLeafColumnOfGroup(header.column);
@@ -66,6 +69,8 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
66
69
  const resolvedFilter: ResolvedColumnFilter | undefined =
67
70
  metaFilter && 'render' in metaFilter ? metaFilter : undefined;
68
71
  const hasActiveColumnFilter = resolvedFilter?.hasActiveFilter ?? false;
72
+ const headerDef = header.column.columnDef.header;
73
+ const tooltipText = typeof headerDef === 'string' ? headerDef : undefined;
69
74
 
70
75
  return (
71
76
  <TableHead
@@ -89,28 +94,16 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
89
94
  width="100%"
90
95
  className={styles.headerLabelStack}
91
96
  >
92
- <span className={styles.headerLabel}>
93
- {flexRender(header.column.columnDef.header, header.getContext())}
94
- </span>
97
+ <DsTableHeaderLabel tooltipText={tooltipText}>
98
+ {flexRender(headerDef, header.getContext())}
99
+ </DsTableHeaderLabel>
95
100
  </DsStack>
96
- {canSort && (
101
+ {canSort && sortDirection && (
97
102
  <div className={styles.pageButtonIconContainer}>
98
- {{
99
- asc: (
100
- <DsIcon
101
- icon="arrow_drop_up"
102
- className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
103
- />
104
- ),
105
- desc: (
106
- <DsIcon
107
- icon="arrow_drop_down"
108
- className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
109
- />
110
- ),
111
- }[header.column.getIsSorted() as string] ?? (
112
- <div className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)} />
113
- )}
103
+ <DsIcon
104
+ icon={sortDirection === 'asc' ? 'arrow_drop_up' : 'arrow_drop_down'}
105
+ className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
106
+ />
114
107
  </div>
115
108
  )}
116
109
  {resolvedFilter && (
@@ -130,6 +123,19 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
130
123
  </TableHead>
131
124
  );
132
125
  })}
126
+ <TableHead
127
+ aria-hidden
128
+ className={classnames(
129
+ styles.headerCell,
130
+ styles.scrollbarSpacer,
131
+ scrollbarSpacerWidth > 0 && styles.scrollbarSpacerOccupied,
132
+ )}
133
+ style={{
134
+ width: scrollbarSpacerWidth,
135
+ minWidth: scrollbarSpacerWidth,
136
+ flexShrink: 0,
137
+ }}
138
+ />
133
139
  </TableRow>
134
140
  ))}
135
141
  </TableHeader>
@@ -0,0 +1,12 @@
1
+ @use '../../../styles/typography';
2
+
3
+ .headerLabel {
4
+ display: block;
5
+ min-width: 0;
6
+ }
7
+
8
+ .headerLabelInner {
9
+ display: block;
10
+ min-width: 0;
11
+ @include typography.ellipsis;
12
+ }
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from 'react';
2
+ import classnames from 'classnames';
3
+ import { DsTooltip } from '../../ds-tooltip';
4
+ import { useIsHorizontallyOverflowing } from '../../../utils/use-is-horizontally-overflowing';
5
+ import styles from './ds-table-header-label.module.scss';
6
+
7
+ type DsTableHeaderLabelProps = {
8
+ children: ReactNode;
9
+ tooltipText?: string;
10
+ className?: string;
11
+ };
12
+
13
+ export const DsTableHeaderLabel = ({ className, children, tooltipText }: DsTableHeaderLabelProps) => {
14
+ const { ref, isOverflowing } = useIsHorizontallyOverflowing<HTMLSpanElement>(tooltipText);
15
+ const title = isOverflowing ? tooltipText : undefined;
16
+
17
+ return (
18
+ <span className={classnames(styles.headerLabel, className)}>
19
+ <DsTooltip content={title} placement="top">
20
+ <span ref={ref} className={styles.headerLabelInner}>
21
+ {children}
22
+ </span>
23
+ </DsTooltip>
24
+ </span>
25
+ );
26
+ };
27
+
28
+ DsTableHeaderLabel.displayName = 'DsTableHeaderLabel';
@@ -59,7 +59,6 @@
59
59
  }
60
60
 
61
61
  .cellReorder {
62
- width: vars.$reorder-column-width;
63
62
  text-align: center;
64
63
 
65
64
  &.isDragging {
@@ -2,6 +2,7 @@ import { createContext, useContext, useMemo, type ReactNode, type RefObject } fr
2
2
  import type { DsDataTableProps, DsTableRowSize } from '../ds-table.types';
3
3
  import type { DsTablePropsWithDefaults } from '../ds-table-defaults';
4
4
  import type { DsTableResizeContextSlice } from '../hooks/use-column-resize';
5
+ import type { ScrollbarSpacerWidth } from '../hooks/use-scrollbar-spacer';
5
6
  import { useEditingState, type UseEditingStateResult } from '../hooks/use-editing-state';
6
7
 
7
8
  /** Visual phase of the full-height column-resize divider. */
@@ -56,6 +57,10 @@ export interface DsTableContextType<TData, TValue>
56
57
  * boundary header's column (leaf or group). Clamps and fires persist.
57
58
  */
58
59
  onResizeKeyboardNudge?: (columnId: string, delta: number) => void;
60
+ /**
61
+ * Overflow-only **Scrollbar spacer** width (0 or rest scrollbar size).
62
+ */
63
+ scrollbarSpacerWidth: ScrollbarSpacerWidth;
59
64
  }
60
65
 
61
66
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -124,6 +129,7 @@ export const DsTableContextProvider = <TData, TValue>({
124
129
  onResizeDragStart,
125
130
  onResizeReset,
126
131
  onResizeKeyboardNudge,
132
+ scrollbarSpacerWidth,
127
133
  } = derived.resize;
128
134
 
129
135
  const { editing, beginEdit, setDraft, commit, cancel } = useEditingState<TData, TValue>(
@@ -160,6 +166,7 @@ export const DsTableContextProvider = <TData, TValue>({
160
166
  onResizeDragStart,
161
167
  onResizeReset,
162
168
  onResizeKeyboardNudge,
169
+ scrollbarSpacerWidth,
163
170
  editing,
164
171
  beginEdit,
165
172
  setDraft,
@@ -194,6 +201,7 @@ export const DsTableContextProvider = <TData, TValue>({
194
201
  onResizeDragStart,
195
202
  onResizeReset,
196
203
  onResizeKeyboardNudge,
204
+ scrollbarSpacerWidth,
197
205
  editing,
198
206
  beginEdit,
199
207
  setDraft,
@@ -58,10 +58,7 @@ $bulk-actions-reserved-space: 80px;
58
58
  thead {
59
59
  display: block;
60
60
  flex-shrink: 0;
61
- overflow-y: scroll;
62
- scrollbar-gutter: stable;
63
-
64
- @include scrollbars.scrollbar-on-hover;
61
+ overflow-y: hidden;
65
62
  }
66
63
 
67
64
  tbody {
@@ -70,14 +67,24 @@ $bulk-actions-reserved-space: 80px;
70
67
  overflow-x: hidden;
71
68
  flex: 1;
72
69
  min-height: 0;
73
- scrollbar-gutter: stable;
70
+ scrollbar-gutter: auto;
74
71
 
75
72
  @include scrollbars.scrollbar-on-hover;
73
+
74
+ &:has(.emptyState) {
75
+ display: flex;
76
+ flex-direction: column;
77
+ }
76
78
  }
77
79
 
78
80
  thead tr,
79
81
  tbody tr {
80
82
  display: flex;
83
+ width: 100%;
84
+ }
85
+
86
+ tbody tr {
87
+ padding-inline-end: var(--ds-table-body-scrollbar-compensation, 0px);
81
88
  }
82
89
 
83
90
  thead th,
@@ -109,18 +116,19 @@ $bulk-actions-reserved-space: 80px;
109
116
  }
110
117
  }
111
118
 
119
+ .emptyStateRow {
120
+ flex: 1;
121
+ align-items: center;
122
+ }
123
+
112
124
  .emptyState {
113
125
  // colSpan in markup isn't honored by flex row layout, so the cell stretches
114
- // across the row; stretch the inner content so it occupies full table width
126
+ // across the row; center the Empty state in the remaining body
115
127
  flex: 1;
116
128
  min-width: 0;
117
- height: vars.$empty-state-height;
118
129
  justify-content: center;
130
+ align-items: center;
119
131
  text-align: center;
120
-
121
- > * {
122
- flex: 1;
123
- }
124
132
  }
125
133
 
126
134
  .virtualizedContainer {
@@ -145,20 +153,22 @@ $bulk-actions-reserved-space: 80px;
145
153
  thead {
146
154
  display: block;
147
155
  flex-shrink: 0;
148
- overflow-y: scroll;
149
- scrollbar-gutter: stable;
150
-
151
- @include scrollbars.scrollbar-on-hover;
156
+ overflow-y: hidden;
152
157
  }
153
158
 
154
159
  tbody {
155
160
  flex: 1;
156
161
  min-height: 0;
157
- scrollbar-gutter: stable;
162
+ scrollbar-gutter: auto;
158
163
  }
159
164
 
160
165
  thead tr {
161
166
  display: flex;
167
+ width: 100%;
168
+ }
169
+
170
+ tbody tr {
171
+ padding-inline-end: var(--ds-table-body-scrollbar-compensation, 0px);
162
172
  }
163
173
 
164
174
  thead th {
@@ -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
  */