@drivenets/design-system 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/index.cjs +1797 -805
  3. package/dist/index.d.cts +117 -53
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +117 -53
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1799 -809
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +3 -3
  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-autocomplete/ds-autocomplete.module.scss +15 -4
  14. package/src/components/ds-autocomplete/ds-autocomplete.tsx +10 -3
  15. package/src/components/ds-autocomplete/ds-autocomplete.types.ts +8 -0
  16. package/src/components/ds-button-v3/ds-button-v3.figma.ts +5 -3
  17. package/src/components/ds-button-v3/ds-button-v3.module.scss +84 -17
  18. package/src/components/ds-button-v3/ds-button-v3.tsx +2 -0
  19. package/src/components/ds-button-v3/ds-button-v3.types.ts +11 -2
  20. package/src/components/ds-button-v3/ds-button-v4.figma.ts +121 -0
  21. package/src/components/ds-comment-bubble/ds-comment-bubble.figma.ts +52 -0
  22. package/src/components/ds-comment-card/ds-comment-card.figma.ts +36 -0
  23. package/src/components/ds-comment-indicator/ds-comment-indicator.figma.ts +32 -0
  24. package/src/components/ds-comments-drawer/ds-comments-drawer.figma.ts +19 -0
  25. package/src/components/ds-select/ds-select.figma.ts +2 -1
  26. package/src/components/ds-select/ds-select.tsx +6 -1
  27. package/src/components/ds-select/ds-select.types.ts +2 -1
  28. package/src/components/ds-slider/ds-slider.figma.ts +29 -0
  29. package/src/components/ds-slider/ds-slider.module.scss +104 -0
  30. package/src/components/ds-slider/ds-slider.tsx +118 -0
  31. package/src/components/ds-slider/ds-slider.types.ts +102 -0
  32. package/src/components/ds-slider/ds-slider.utils.ts +14 -0
  33. package/src/components/ds-slider/index.ts +11 -0
  34. package/src/components/ds-smart-tabs/figma/ds-smart-tabs-tab.figma.ts +83 -0
  35. package/src/components/ds-smart-tabs/figma/ds-smart-tabs.figma.ts +23 -0
  36. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +5 -1
  37. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +15 -3
  38. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +15 -1
  39. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +29 -6
  40. package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.module.scss +23 -0
  41. package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.tsx +218 -0
  42. package/src/components/ds-table/components/ds-table-resize-handle/ds-table-resize-handle.types.ts +14 -0
  43. package/src/components/ds-table/components/ds-table-resize-handle/index.ts +1 -0
  44. package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.module.scss +18 -0
  45. package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.tsx +16 -0
  46. package/src/components/ds-table/components/ds-table-resize-overlay/ds-table-resize-overlay.types.ts +13 -0
  47. package/src/components/ds-table/components/ds-table-resize-overlay/index.ts +1 -0
  48. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +1 -1
  49. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +9 -2
  50. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +2 -1
  51. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +9 -2
  52. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +2 -2
  53. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +2 -1
  54. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +2 -1
  55. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +2 -1
  56. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +2 -1
  57. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +12 -3
  58. package/src/components/ds-table/context/ds-table-context.tsx +208 -0
  59. package/src/components/ds-table/ds-table-defaults.ts +42 -0
  60. package/src/components/ds-table/ds-table.module.scss +4 -1
  61. package/src/components/ds-table/ds-table.tsx +139 -214
  62. package/src/components/ds-table/ds-table.types.ts +53 -47
  63. package/src/components/ds-table/grouping/utils/column-group.ts +7 -5
  64. package/src/components/ds-table/hooks/use-cell-editor.ts +5 -1
  65. package/src/components/ds-table/hooks/use-column-resize.ts +385 -0
  66. package/src/components/ds-table/hooks/use-editing-state.ts +190 -0
  67. package/src/components/ds-table/index.ts +1 -0
  68. package/src/components/ds-table/styles/_variables.scss +2 -1
  69. package/src/components/ds-table/utils/column-size-meta.augmentation.ts +16 -0
  70. package/src/components/ds-table/utils/column-size.ts +213 -10
  71. package/src/components/ds-table/utils/column-size.unit.test.ts +213 -0
  72. package/src/components/ds-table/utils/constants.ts +28 -0
  73. package/src/components/ds-table/utils/frozen-body.ts +33 -0
  74. package/src/components/ds-table/utils/frozen-body.unit.test.ts +44 -0
  75. package/src/components/ds-table/utils/table-api.ts +42 -0
  76. package/src/components/ds-table/utils/table-api.unit.test.ts +82 -0
  77. package/src/components/ds-table/utils/table-columns.tsx +119 -0
  78. package/src/components/ds-table/utils/table-columns.unit.test.ts +199 -0
  79. package/src/components/ds-tag/ds-tag.figma.ts +51 -0
  80. package/src/components/ds-tag-filter/ds-tag-filter.figma.ts +16 -0
  81. package/src/components/ds-tag-filter/ds-tag-filter.module.scss +7 -35
  82. package/src/components/ds-tag-filter/ds-tag-filter.tsx +34 -57
  83. package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -1
  84. package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +13 -18
  85. package/src/components/ds-text-input/ds-input-field-v02.figma.ts +29 -4
  86. package/src/components/ds-text-input/ds-text-input.figma.ts +3 -2
  87. package/src/components/ds-text-input/ds-text-input.tsx +1 -0
  88. package/src/components/ds-text-input/ds-text-input.types.ts +1 -1
  89. package/src/components/ds-tree/ds-tree.figma.ts +54 -0
  90. package/src/components/ds-workspace-layout/ds-workspace-layout.figma.ts +99 -0
  91. package/src/components/ds-workspace-layout/ds-workspace-layout.module.scss +1 -8
  92. package/src/index.ts +1 -0
  93. package/src/styles/_gradients.scss +19 -0
  94. package/src/styles/_root.scss +39 -15
  95. package/src/styles/_typography.scss +1 -0
  96. package/src/styles/shared/_input.scss +10 -0
  97. package/src/styles/styles.scss +1 -0
  98. package/src/components/ds-table/context/ds-table-context.ts +0 -112
@@ -3,6 +3,7 @@
3
3
 
4
4
  .header {
5
5
  background-color: var(--background-secondary);
6
+ overflow: hidden;
6
7
  }
7
8
 
8
9
  .virtualizedHeader {
@@ -28,6 +29,7 @@
28
29
 
29
30
  .headerCell {
30
31
  @include typography.body-sm-md;
32
+ position: relative;
31
33
  color: var(--font-main);
32
34
  text-align: left;
33
35
  padding: 0 var(--standard);
@@ -57,7 +59,7 @@
57
59
  justify-content: center;
58
60
  }
59
61
 
60
- .groupStart {
62
+ .groupStart:not(:first-child) {
61
63
  border-left: vars.$group-border-width solid var(--border);
62
64
  }
63
65
 
@@ -69,6 +71,18 @@
69
71
  height: 100%;
70
72
  }
71
73
 
74
+ .headerLabelStack {
75
+ min-width: 0;
76
+ overflow: hidden;
77
+ }
78
+
79
+ .headerLabel {
80
+ display: block;
81
+ overflow: hidden;
82
+ text-overflow: ellipsis;
83
+ white-space: nowrap;
84
+ }
85
+
72
86
  .filterTriggerContainer {
73
87
  display: inline-flex;
74
88
  align-items: center;
@@ -6,16 +6,17 @@ import styles from './ds-table-header.module.scss';
6
6
  import stylesShared from '../../styles/shared/ds-table-shared.module.scss';
7
7
  import type { DsTableHeaderProps } from './ds-table-header.types';
8
8
  import { useDsTableContext } from '../../context/ds-table-context';
9
- import { getColumnSizeStyle } from '../../utils/column-size';
9
+ import { getHeaderCellSizeStyle, isExplicitColumnWidth } from '../../utils/column-size';
10
10
  import { SELECT_COLUMN_ID } from '../../utils/constants';
11
11
  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 { DsTableResizeHandle } from '../ds-table-resize-handle';
15
16
  import { isFirstLeafColumnOfGroup } from '../../grouping';
16
17
 
17
18
  const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
18
- const { bordered, virtualized } = useDsTableContext<TData, unknown>();
19
+ const { bordered, virtualized, resizableColumns, resizeSizingReady } = useDsTableContext<TData, unknown>();
19
20
 
20
21
  return (
21
22
  <TableHeader className={classnames(styles.header, virtualized && styles.virtualizedHeader)}>
@@ -29,12 +30,19 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
29
30
  )}
30
31
  >
31
32
  {headerGroup.headers.map((header) => {
33
+ const columnSizing = table.getState().columnSizing;
34
+
32
35
  if (header.isPlaceholder) {
33
36
  return (
34
37
  <TableHead
35
38
  key={header.id}
36
39
  className={styles.headerCell}
37
- style={getColumnSizeStyle(header.getSize())}
40
+ style={getHeaderCellSizeStyle(
41
+ header.id,
42
+ header.getSize(),
43
+ isExplicitColumnWidth(header.column, resizeSizingReady ? columnSizing : {}),
44
+ !!resizeSizingReady,
45
+ )}
38
46
  />
39
47
  );
40
48
  }
@@ -43,8 +51,14 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
43
51
  return <DsTableGroupHeaderCell key={header.id} header={header} />;
44
52
  }
45
53
 
46
- const headerStyle = getColumnSizeStyle(header.column.getSize());
54
+ const headerStyle = getHeaderCellSizeStyle(
55
+ header.id,
56
+ header.column.getSize(),
57
+ isExplicitColumnWidth(header.column, resizeSizingReady ? columnSizing : {}),
58
+ !!resizeSizingReady,
59
+ );
47
60
  const canSort = header.column.getCanSort();
61
+ const canResize = resizableColumns && header.column.getCanResize();
48
62
  const isSelectColumn = header.column.id === SELECT_COLUMN_ID;
49
63
  const isGroupStart = isFirstLeafColumnOfGroup(header.column);
50
64
 
@@ -56,6 +70,7 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
56
70
  return (
57
71
  <TableHead
58
72
  key={header.id}
73
+ data-column-id={header.column.id}
59
74
  className={classnames(
60
75
  styles.headerCell,
61
76
  canSort && styles.sortableHeader,
@@ -68,8 +83,15 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
68
83
  style={headerStyle}
69
84
  >
70
85
  <div className={styles.headerSortContainer}>
71
- <DsStack direction="column" justifyContent="center" width="100%">
72
- {flexRender(header.column.columnDef.header, header.getContext())}
86
+ <DsStack
87
+ direction="column"
88
+ justifyContent="center"
89
+ width="100%"
90
+ className={styles.headerLabelStack}
91
+ >
92
+ <span className={styles.headerLabel}>
93
+ {flexRender(header.column.columnDef.header, header.getContext())}
94
+ </span>
73
95
  </DsStack>
74
96
  {canSort && (
75
97
  <div className={styles.pageButtonIconContainer}>
@@ -104,6 +126,7 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
104
126
  </div>
105
127
  )}
106
128
  </div>
129
+ {canResize && <DsTableResizeHandle header={header} />}
107
130
  </TableHead>
108
131
  );
109
132
  })}
@@ -0,0 +1,23 @@
1
+ @use '../../styles/variables' as vars;
2
+
3
+ .resizeHandle {
4
+ position: absolute;
5
+ top: 0;
6
+ // Overlay paints 2px from the cell right edge; center this hit target on that band.
7
+ right: vars.$resize-handle-hit-width * -0.5 + vars.$resize-divider-width * -0.5;
8
+ height: 100%;
9
+ width: vars.$resize-handle-hit-width;
10
+ padding: 0;
11
+ border: 0;
12
+ background: transparent;
13
+ cursor: col-resize;
14
+ touch-action: none;
15
+ z-index: 1;
16
+
17
+ // Focusable for keyboard resize (WCAG 2.1.1). Negative offset keeps the ring
18
+ // clear of the scroll container's overflow clip on the last column.
19
+ &:focus-visible {
20
+ outline: vars.$resize-divider-width solid var(--border-action-primary);
21
+ outline-offset: -1px;
22
+ }
23
+ }
@@ -0,0 +1,218 @@
1
+ import {
2
+ useEffect,
3
+ useRef,
4
+ type KeyboardEvent,
5
+ type MouseEvent,
6
+ type PointerEvent,
7
+ type TouchEvent,
8
+ } from 'react';
9
+ import { useDsTableContext } from '../../context/ds-table-context';
10
+ import { defaultDsTableLocale } from '../../ds-table.types';
11
+ import {
12
+ RESIZE_KEYBOARD_STEP,
13
+ RESIZE_KEYBOARD_STEP_LARGE,
14
+ RESIZE_MIN_COLUMN_WIDTH,
15
+ } from '../../utils/constants';
16
+ import type { DsTableResizeHandleProps } from './ds-table-resize-handle.types';
17
+ import styles from './ds-table-resize-handle.module.scss';
18
+
19
+ const clientPointFromEvent = (event: Event): { x: number; y: number } | null => {
20
+ if (event instanceof MouseEvent) {
21
+ return { x: event.clientX, y: event.clientY };
22
+ }
23
+
24
+ if (typeof TouchEvent !== 'undefined' && event instanceof TouchEvent) {
25
+ const touch = event.changedTouches[0];
26
+ return touch ? { x: touch.clientX, y: touch.clientY } : null;
27
+ }
28
+
29
+ return null;
30
+ };
31
+
32
+ /**
33
+ * Handle straddling the right edge of a resizable header cell.
34
+ *
35
+ * Drag is TanStack v8 `header.getResizeHandler()`. Hover drives the full-height
36
+ * overlay; double-click restores snapshot origin widths for this header's
37
+ * leaves. As a focusable `separator`, Arrow keys nudge the width (Shift for a
38
+ * larger step) for keyboard operability (WCAG 2.1.1).
39
+ */
40
+ export const DsTableResizeHandle = <TData, TValue>({ header }: DsTableResizeHandleProps<TData, TValue>) => {
41
+ const {
42
+ resizeContainerRef,
43
+ onResizeHover,
44
+ onResizeDragStart,
45
+ onResizeReset,
46
+ onResizeKeyboardNudge,
47
+ locale,
48
+ } = useDsTableContext<TData, TValue>();
49
+ const handleRef = useRef<HTMLDivElement>(null);
50
+ const isPointerOverRef = useRef(false);
51
+ const detachDragEndRef = useRef<(() => void) | null>(null);
52
+
53
+ useEffect(
54
+ () => () => {
55
+ detachDragEndRef.current?.();
56
+ },
57
+ [],
58
+ );
59
+
60
+ const label = { ...defaultDsTableLocale, ...locale }.resizeColumn;
61
+
62
+ const measureCellRightOffset = (): number | null => {
63
+ const containerEl = resizeContainerRef?.current;
64
+ const cellEl = handleRef.current?.closest<HTMLElement>('th');
65
+ if (!containerEl || !cellEl) {
66
+ return null;
67
+ }
68
+ // The overlay is absolutely positioned inside the horizontally scrollable
69
+ // container, so its offset must be in the container's scrolled content
70
+ // space — add `scrollLeft` to the viewport-relative distance.
71
+ return (
72
+ cellEl.getBoundingClientRect().right - containerEl.getBoundingClientRect().left + containerEl.scrollLeft
73
+ );
74
+ };
75
+
76
+ const reportHover = () => {
77
+ const offset = measureCellRightOffset();
78
+ if (offset === null) {
79
+ return;
80
+ }
81
+ onResizeHover?.(header.column.id, offset);
82
+ };
83
+
84
+ const clearHover = () => {
85
+ isPointerOverRef.current = false;
86
+ onResizeHover?.(null, null);
87
+ };
88
+
89
+ const syncHoverAtPoint = (clientX: number, clientY: number) => {
90
+ const handleEl = handleRef.current;
91
+ const doc = handleEl?.ownerDocument ?? document;
92
+ const hit = doc.elementFromPoint(clientX, clientY);
93
+ if (handleEl && hit && (hit === handleEl || handleEl.contains(hit))) {
94
+ isPointerOverRef.current = true;
95
+ reportHover();
96
+ return;
97
+ }
98
+
99
+ clearHover();
100
+ };
101
+
102
+ const attachDragEndListeners = () => {
103
+ detachDragEndRef.current?.();
104
+
105
+ const doc = handleRef.current?.ownerDocument ?? document;
106
+ const onDragEnd = (event: Event) => {
107
+ detachDragEndRef.current?.();
108
+ detachDragEndRef.current = null;
109
+
110
+ const point = clientPointFromEvent(event);
111
+ clearHover();
112
+
113
+ const frame = handleRef.current?.ownerDocument.defaultView ?? window;
114
+ frame.requestAnimationFrame(() => {
115
+ if (!point) {
116
+ return;
117
+ }
118
+
119
+ syncHoverAtPoint(point.x, point.y);
120
+ });
121
+ };
122
+
123
+ doc.addEventListener('mouseup', onDragEnd);
124
+ doc.addEventListener('touchend', onDragEnd);
125
+ detachDragEndRef.current = () => {
126
+ doc.removeEventListener('mouseup', onDragEnd);
127
+ doc.removeEventListener('touchend', onDragEnd);
128
+ };
129
+ };
130
+
131
+ const handlePointerEnter = () => {
132
+ isPointerOverRef.current = true;
133
+ reportHover();
134
+ };
135
+
136
+ const handlePointerLeave = () => {
137
+ clearHover();
138
+ };
139
+
140
+ const startDrag = () => {
141
+ const offset = measureCellRightOffset();
142
+ if (offset !== null) {
143
+ onResizeDragStart?.(offset);
144
+ }
145
+ };
146
+
147
+ const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {
148
+ if (event.pointerType === 'touch') {
149
+ return;
150
+ }
151
+
152
+ event.stopPropagation();
153
+ startDrag();
154
+ header.getResizeHandler(handleRef.current?.ownerDocument ?? document)(event);
155
+ attachDragEndListeners();
156
+ };
157
+
158
+ const handleTouchStart = (event: TouchEvent<HTMLDivElement>) => {
159
+ event.stopPropagation();
160
+ startDrag();
161
+ header.getResizeHandler(handleRef.current?.ownerDocument ?? document)(event);
162
+ attachDragEndListeners();
163
+ };
164
+
165
+ const handleReset = (event: MouseEvent<HTMLDivElement>) => {
166
+ event.preventDefault();
167
+ event.stopPropagation();
168
+ const leafIds = header.column.getLeafColumns().map((column) => column.id);
169
+ onResizeReset?.(leafIds);
170
+ if (isPointerOverRef.current) {
171
+ reportHover();
172
+ } else {
173
+ clearHover();
174
+ }
175
+ };
176
+
177
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
178
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {
179
+ return;
180
+ }
181
+
182
+ event.preventDefault();
183
+ event.stopPropagation();
184
+ const step = event.shiftKey ? RESIZE_KEYBOARD_STEP_LARGE : RESIZE_KEYBOARD_STEP;
185
+ const delta = event.key === 'ArrowRight' ? step : -step;
186
+ onResizeKeyboardNudge?.(header.column.id, delta);
187
+ };
188
+
189
+ // Only leaves expose a meaningful min; a group boundary's min/max would be
190
+ // misleading, so announce the current size but omit bounds for groups.
191
+ const isLeaf = header.subHeaders.length === 0;
192
+
193
+ // A focusable role="separator" with aria-valuenow/valuemin and arrow-key
194
+ // handling is the WAI-ARIA window-splitter pattern, which is interactive.
195
+ // jsx-a11y's role taxonomy does not model focusable separators, so its
196
+ // non-interactive rules misfire on this element.
197
+ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */
198
+ return (
199
+ <div
200
+ ref={handleRef}
201
+ role="separator"
202
+ aria-orientation="vertical"
203
+ aria-label={label}
204
+ tabIndex={0}
205
+ aria-valuenow={Math.round(header.getSize())}
206
+ aria-valuemin={isLeaf ? (header.column.columnDef.minSize ?? RESIZE_MIN_COLUMN_WIDTH) : undefined}
207
+ className={styles.resizeHandle}
208
+ onPointerDown={handlePointerDown}
209
+ onPointerEnter={handlePointerEnter}
210
+ onPointerLeave={handlePointerLeave}
211
+ onTouchStart={handleTouchStart}
212
+ onClick={(event) => event.stopPropagation()}
213
+ onDoubleClick={handleReset}
214
+ onKeyDown={handleKeyDown}
215
+ />
216
+ );
217
+ /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */
218
+ };
@@ -0,0 +1,14 @@
1
+ import type { Header } from '@tanstack/react-table';
2
+
3
+ /**
4
+ * Props for the column resize handle rendered on the right edge of a resizable
5
+ * header cell.
6
+ */
7
+ export interface DsTableResizeHandleProps<TData, TValue> {
8
+ /**
9
+ * The header (leaf or group) whose right edge this handle resizes. A group
10
+ * handle uses TanStack's resize handler, which scales that group's leaf
11
+ * columns.
12
+ */
13
+ header: Header<TData, TValue>;
14
+ }
@@ -0,0 +1 @@
1
+ export { DsTableResizeHandle } from './ds-table-resize-handle';
@@ -0,0 +1,18 @@
1
+ @use '../../styles/variables' as vars;
2
+
3
+ .resizeOverlay {
4
+ position: absolute;
5
+ top: 0;
6
+ bottom: 0;
7
+ width: vars.$resize-divider-width;
8
+ background: var(--border-light);
9
+ // `left` is the cell's right edge; nudge the divider right by 1px so its 2px
10
+ // band aligns flush with the 2px group border drawn from that edge rightward.
11
+ transform: translateX(calc(-50% + #{vars.$border-width}));
12
+ pointer-events: none;
13
+ z-index: 2;
14
+
15
+ &[data-resize-phase='dragging'] {
16
+ background: var(--border-action-primary);
17
+ }
18
+ }
@@ -0,0 +1,16 @@
1
+ import type { DsTableResizeOverlayProps } from './ds-table-resize-overlay.types';
2
+ import styles from './ds-table-resize-overlay.module.scss';
3
+
4
+ /**
5
+ * Full-height divider that tracks the active column's right edge during hover
6
+ * and drag resize.
7
+ */
8
+ export const DsTableResizeOverlay = ({ offset, phase }: DsTableResizeOverlayProps) => (
9
+ <div
10
+ className={styles.resizeOverlay}
11
+ style={{ left: offset }}
12
+ data-resize-overlay
13
+ data-resize-phase={phase}
14
+ aria-hidden
15
+ />
16
+ );
@@ -0,0 +1,13 @@
1
+ export type DsTableResizeOverlayPhase = 'hover' | 'dragging';
2
+
3
+ /**
4
+ * Props for the full-height column resize divider overlay.
5
+ */
6
+ export interface DsTableResizeOverlayProps {
7
+ /**
8
+ * Distance from the table container's left edge to the column's right edge,
9
+ * in pixels.
10
+ */
11
+ offset: number;
12
+ phase: DsTableResizeOverlayPhase;
13
+ }
@@ -0,0 +1 @@
1
+ export { DsTableResizeOverlay } from './ds-table-resize-overlay';
@@ -85,7 +85,7 @@
85
85
  justify-content: center;
86
86
  }
87
87
 
88
- .groupStart {
88
+ .groupStart:not(:first-child) {
89
89
  border-left: vars.$group-border-width solid var(--border);
90
90
  }
91
91
 
@@ -9,7 +9,7 @@ import type { DsTableRowProps } from './ds-table-row.types';
9
9
  import styles from './ds-table-row.module.scss';
10
10
  import { useDsTableContext } from '../../context/ds-table-context';
11
11
  import { mergeRefs } from '../../../../utils/merge-refs';
12
- import { getColumnSizeStyle } from '../../utils/column-size';
12
+ import { getBodyCellSizeStyle, isExplicitColumnWidth } from '../../utils/column-size';
13
13
  import { isFirstLeafColumnOfGroup } from '../../grouping';
14
14
  import { EXPANDER_COLUMN_ID, REORDER_COLUMN_ID, SELECT_COLUMN_ID } from '../../utils/constants';
15
15
 
@@ -52,6 +52,7 @@ const DsTableRow = <TData,>({ ref, row, isSelected }: DsTableRowProps<TData>) =>
52
52
  secondaryRowActions,
53
53
  activeRowId,
54
54
  loading,
55
+ resizeSizingReady,
55
56
  } = useDsTableContext<TData, unknown>();
56
57
  const isExpanded = row.getIsExpanded();
57
58
  const isActive = activeRowId === row.id;
@@ -98,7 +99,13 @@ const DsTableRow = <TData,>({ ref, row, isSelected }: DsTableRowProps<TData>) =>
98
99
  onDoubleClick={() => onRowDoubleClick?.(row.original)}
99
100
  >
100
101
  {row.getVisibleCells().map((cell, idx) => {
101
- const cellStyle = getColumnSizeStyle(cell.column.getSize());
102
+ const columnSizing = cell.getContext().table.getState().columnSizing;
103
+ const cellStyle = getBodyCellSizeStyle(
104
+ cell.column.id,
105
+ cell.column.getSize(),
106
+ isExplicitColumnWidth(cell.column, resizeSizingReady ? columnSizing : {}),
107
+ !!resizeSizingReady,
108
+ );
102
109
 
103
110
  if (cell.column.id === REORDER_COLUMN_ID) {
104
111
  return (
@@ -64,9 +64,10 @@
64
64
  .selectableCell {
65
65
  padding: 0;
66
66
  justify-content: center;
67
+ text-align: center;
67
68
  }
68
69
 
69
- .groupStart {
70
+ .groupStart:not(:first-child) {
70
71
  border-left: vars.$group-border-width solid var(--border);
71
72
  }
72
73
 
@@ -1,7 +1,7 @@
1
1
  import classnames from 'classnames';
2
2
  import styles from './ds-table-row-virtualized.module.scss';
3
3
  import { useDsTableContext } from '../../context/ds-table-context';
4
- import { getColumnSizeStyle } from '../../utils/column-size';
4
+ import { getBodyCellSizeStyle, isExplicitColumnWidth } from '../../utils/column-size';
5
5
  import { isFirstLeafColumnOfGroup } from '../../grouping';
6
6
  import { TableCell, TableRow } from '../core-table';
7
7
  import type { DsTableRowVirtualizedProps } from './ds-table-row-virtualized.types';
@@ -27,6 +27,7 @@ export const DsTableRowVirtualized = <TData,>({
27
27
  onRowClick,
28
28
  onRowDoubleClick,
29
29
  loading,
30
+ resizeSizingReady,
30
31
  } = useDsTableContext<TData, unknown>();
31
32
  const isExpanded = row.getIsExpanded();
32
33
  const isActive = activeRowId === row.id;
@@ -70,7 +71,13 @@ export const DsTableRowVirtualized = <TData,>({
70
71
  <>
71
72
  {row.getVisibleCells().map((cell, idx) => {
72
73
  const isLastColumn = idx === row.getVisibleCells().length - 1;
73
- const cellStyle = getColumnSizeStyle(cell.column.getSize());
74
+ const columnSizing = cell.getContext().table.getState().columnSizing;
75
+ const cellStyle = getBodyCellSizeStyle(
76
+ cell.column.id,
77
+ cell.column.getSize(),
78
+ isExplicitColumnWidth(cell.column, resizeSizingReady ? columnSizing : {}),
79
+ !!resizeSizingReady,
80
+ );
74
81
 
75
82
  return (
76
83
  <TableCell
@@ -13,14 +13,14 @@ export const DsTableEditCellCheckbox = <TData extends RowData>({
13
13
  cellContext,
14
14
  disabled,
15
15
  }: DsTableEditCellCheckboxProps<TData>) => {
16
- const { value, setValue } = useCellEditor<TData, boolean>({
16
+ const { value, setValue, isPending } = useCellEditor<TData, boolean>({
17
17
  cellContext,
18
18
  });
19
19
 
20
20
  return (
21
21
  <DsCheckbox
22
22
  checked={value}
23
- disabled={disabled}
23
+ disabled={disabled || isPending}
24
24
  onCheckedChange={(checked) => setValue(checked === true)}
25
25
  />
26
26
  );
@@ -22,7 +22,7 @@ export const DsTableEditCellDate = <TData extends RowData>({
22
22
  max,
23
23
  placeholder,
24
24
  }: DsTableEditCellDateProps<TData>) => {
25
- const { value, setValue, error } = useCellEditor<TData, DsTableEditCellDateValue>({
25
+ const { value, setValue, error, isPending } = useCellEditor<TData, DsTableEditCellDateValue>({
26
26
  cellContext,
27
27
  });
28
28
 
@@ -34,6 +34,7 @@ export const DsTableEditCellDate = <TData extends RowData>({
34
34
  min={min}
35
35
  max={max}
36
36
  placeholder={placeholder}
37
+ disabled={isPending}
37
38
  onChange={setValue}
38
39
  slotProps={
39
40
  error
@@ -20,7 +20,7 @@ export const DsTableEditCellNumber = <TData extends RowData>({
20
20
  max,
21
21
  step,
22
22
  }: DsTableEditCellNumberProps<TData>) => {
23
- const { value, setValue, error } = useCellEditor<TData, number>({
23
+ const { value, setValue, error, isPending } = useCellEditor<TData, number>({
24
24
  cellContext,
25
25
  });
26
26
 
@@ -44,6 +44,7 @@ export const DsTableEditCellNumber = <TData extends RowData>({
44
44
  min={min}
45
45
  max={max}
46
46
  step={step}
47
+ disabled={isPending}
47
48
  onValueChange={setValue}
48
49
  />
49
50
  </TableEditFormControl>
@@ -18,7 +18,7 @@ export const DsTableEditCellSelect = <TData extends RowData>({
18
18
  options,
19
19
  placeholder,
20
20
  }: DsTableEditCellSelectProps<TData>) => {
21
- const { value, setValue, error } = useCellEditor<TData, string>({
21
+ const { value, setValue, error, isPending } = useCellEditor<TData, string>({
22
22
  cellContext,
23
23
  });
24
24
 
@@ -29,6 +29,7 @@ export const DsTableEditCellSelect = <TData extends RowData>({
29
29
  value={value}
30
30
  options={options}
31
31
  placeholder={placeholder}
32
+ disabled={isPending}
32
33
  onValueChange={setValue}
33
34
  />
34
35
  </TableEditFormControl>
@@ -19,7 +19,7 @@ export const DsTableEditCellText = <TData extends RowData>({
19
19
  placeholder,
20
20
  maxLength,
21
21
  }: DsTableEditCellTextProps<TData>) => {
22
- const { value, setValue, error } = useCellEditor<TData, string>({
22
+ const { value, setValue, error, isPending } = useCellEditor<TData, string>({
23
23
  cellContext,
24
24
  });
25
25
 
@@ -42,6 +42,7 @@ export const DsTableEditCellText = <TData extends RowData>({
42
42
  value={value}
43
43
  placeholder={placeholder}
44
44
  maxLength={maxLength}
45
+ disabled={isPending}
45
46
  onValueChange={setValue}
46
47
  slots={error ? { endAdornment: <DsIcon icon="error" size="tiny" /> } : undefined}
47
48
  />
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, type FormEvent } from 'react';
1
+ import { useEffect, useRef, type SubmitEvent } from 'react';
2
2
  import { flexRender } from '@tanstack/react-table';
3
3
  import { DsButtonV3 } from '../../../../ds-button-v3';
4
4
  import { DsStack } from '../../../../ds-stack';
@@ -7,13 +7,20 @@ import styles from './ds-table-editing-cell.module.scss';
7
7
  import type { DsTableEditingCellProps } from './ds-table-editing-cell.types';
8
8
 
9
9
  export const DsTableEditingCell = <TData, TValue>({ cell }: DsTableEditingCellProps<TData, TValue>) => {
10
- const { commit, cancel } = useDsTableContext<TData, TValue>();
10
+ const { commit, cancel, editing } = useDsTableContext<TData, TValue>();
11
11
  const columnDef = cell.column.columnDef;
12
12
  const editAnchorRef = useRef<HTMLDivElement>(null);
13
13
 
14
- const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
14
+ const isActiveCell = editing?.cell.id === cell.id;
15
+ const isPending = isActiveCell && editing.pending;
16
+ const hasError = isActiveCell && editing.error !== null;
17
+
18
+ const handleSubmit = (event: SubmitEvent<HTMLFormElement>) => {
15
19
  event.preventDefault();
16
20
  event.stopPropagation();
21
+ if (isPending || hasError) {
22
+ return;
23
+ }
17
24
  commit();
18
25
  };
19
26
 
@@ -62,6 +69,8 @@ export const DsTableEditingCell = <TData, TValue>({ cell }: DsTableEditingCellPr
62
69
  variant="tertiary"
63
70
  size="small"
64
71
  icon="check"
72
+ loading={isPending}
73
+ disabled={hasError}
65
74
  aria-label="Confirm edit"
66
75
  onClick={(event) => {
67
76
  event.stopPropagation();