@drivenets/design-system 0.12.0 → 0.13.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 (88) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.cjs +1652 -455
  3. package/dist/index.d.cts +1226 -989
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +1226 -989
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1643 -456
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +31 -31
  11. package/src/components/ds-catalog-layout/catalog-layout-empty-illustration.tsx +57 -0
  12. package/src/components/ds-catalog-layout/ds-catalog-layout.context.ts +19 -0
  13. package/src/components/ds-catalog-layout/ds-catalog-layout.module.scss +167 -0
  14. package/src/components/ds-catalog-layout/ds-catalog-layout.tsx +130 -0
  15. package/src/components/ds-catalog-layout/ds-catalog-layout.types.ts +62 -0
  16. package/src/components/ds-catalog-layout/index.ts +9 -0
  17. package/src/components/ds-date-input/ds-date-input.utils.ts +2 -2
  18. package/src/components/ds-drawer/ds-drawer.tsx +2 -0
  19. package/src/components/ds-form-control/ds-form-control.tsx +14 -11
  20. package/src/components/ds-form-control/ds-form-control.types.ts +4 -0
  21. package/src/components/ds-grid/ds-grid.tsx +1 -2
  22. package/src/components/ds-modal/ds-modal.tsx +3 -0
  23. package/src/components/ds-smart-tabs/ds-smart-tabs.module.scss +1 -0
  24. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +21 -2
  25. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +14 -11
  26. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +1 -6
  27. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +2 -2
  28. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +2 -0
  29. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +42 -11
  30. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +24 -4
  31. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +22 -1
  32. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +22 -0
  33. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +1 -0
  34. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +19 -0
  35. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +2 -1
  36. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +1 -1
  37. package/src/components/ds-table/components/edit/cell-editors/cell-editors.module.scss +3 -0
  38. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +27 -0
  39. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +50 -0
  40. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +51 -0
  41. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +36 -0
  42. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +50 -0
  43. package/src/components/ds-table/components/edit/cell-editors/index.ts +14 -0
  44. package/src/components/ds-table/components/edit/cell-editors/table-edit-form-control.tsx +31 -0
  45. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.module.scss +28 -0
  46. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.tsx +39 -0
  47. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.types.ts +7 -0
  48. package/src/components/ds-table/components/edit/ds-table-editable-cell/index.ts +2 -0
  49. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.module.scss +48 -0
  50. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +76 -0
  51. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.types.ts +5 -0
  52. package/src/components/ds-table/components/edit/ds-table-editing-cell/index.ts +2 -0
  53. package/src/components/ds-table/context/ds-table-context.ts +79 -1
  54. package/src/components/ds-table/ds-table-column.types.ts +12 -0
  55. package/src/components/ds-table/ds-table.module.scss +100 -10
  56. package/src/components/ds-table/ds-table.tsx +18 -19
  57. package/src/components/ds-table/ds-table.types.ts +52 -0
  58. package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +24 -40
  59. package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +10 -31
  60. package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +6 -98
  61. package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.module.scss +36 -0
  62. package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +92 -13
  63. package/src/components/ds-table/filters/components/checkbox-filter/index.ts +1 -1
  64. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.module.scss +42 -0
  65. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.tsx +89 -0
  66. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.types.ts +25 -0
  67. package/src/components/ds-table/filters/components/column-filter-popover/index.ts +5 -0
  68. package/src/components/ds-table/filters/hooks/use-table-filters.ts +113 -78
  69. package/src/components/ds-table/filters/index.ts +2 -44
  70. package/src/components/ds-table/filters/types/column-meta.augmentation.ts +19 -0
  71. package/src/components/ds-table/filters/types/filter-adapter.types.ts +48 -81
  72. package/src/components/ds-table/hooks/use-cell-editor.ts +81 -0
  73. package/src/components/ds-table/index.ts +29 -0
  74. package/src/components/ds-table/utils/column-size.ts +9 -8
  75. package/src/components/ds-tag/ds-tag.tsx +2 -2
  76. package/src/components/ds-workspace-layout/ds-workspace-layout.context.ts +19 -0
  77. package/src/components/ds-workspace-layout/ds-workspace-layout.module.scss +166 -0
  78. package/src/components/ds-workspace-layout/ds-workspace-layout.tsx +135 -0
  79. package/src/components/ds-workspace-layout/ds-workspace-layout.types.ts +52 -0
  80. package/src/components/ds-workspace-layout/index.ts +11 -0
  81. package/src/index.ts +2 -1
  82. package/src/styles/_scrollbars.scss +37 -0
  83. package/src/utils/restore-document-body-modal-styles.ts +44 -0
  84. package/src/utils/use-ark-dialog-body-lock-cleanup.ts +36 -0
  85. package/src/components/ds-workspace/ds-workspace.module.scss +0 -55
  86. package/src/components/ds-workspace/ds-workspace.tsx +0 -37
  87. package/src/components/ds-workspace/ds-workspace.types.ts +0 -17
  88. package/src/components/ds-workspace/index.ts +0 -8
@@ -1,9 +1,28 @@
1
+ @use '../../../../styles/scrollbars' as scrollbars;
2
+
1
3
  .body {
2
- display: grid;
4
+ display: block;
5
+ position: relative;
6
+ overflow-y: auto;
7
+ flex: 1;
8
+ min-height: 0;
9
+ scrollbar-gutter: stable;
10
+
11
+ @include scrollbars.scrollbar-on-hover;
12
+ }
13
+
14
+ // 1px row pinned at `top: <totalSize>` (set inline) so absolutely-positioned
15
+ // rows extend the parent's scrollable overflow area.
16
+ .sentinel {
17
+ position: absolute;
18
+ left: 0;
19
+ width: 1px;
20
+ height: 1px;
21
+ pointer-events: none;
22
+ visibility: hidden;
3
23
  }
4
24
 
5
25
  .emptyState {
6
26
  position: absolute;
7
27
  inset: 0;
8
- top: var(--ds-table-header-height);
9
28
  }
@@ -10,7 +10,6 @@ import { useInfiniteScroll } from './use-infinite-scroll';
10
10
 
11
11
  export const DsTableBodyVirtualized = <TData,>({
12
12
  table,
13
- tableContainerRef,
14
13
  emptyState,
15
14
  estimateSize,
16
15
  overscan,
@@ -20,6 +19,7 @@ export const DsTableBodyVirtualized = <TData,>({
20
19
  }: DsTableBodyVirtualizedProps<TData>) => {
21
20
  const rowsMapRef = useRef(new Map<string, HTMLTableRowElement>());
22
21
  const rowHeightsMapRef = useRef(new Map<string, number>());
22
+ const tbodyRef = useRef<HTMLTableSectionElement>(null);
23
23
 
24
24
  const { rows } = table.getRowModel();
25
25
 
@@ -32,16 +32,16 @@ export const DsTableBodyVirtualized = <TData,>({
32
32
  return item ? `${item.row.id}${item.isExpandedRowContent ? '-expanded-content' : ''}` : String(index);
33
33
  };
34
34
 
35
- const { loadDataIfNeeded } = useInfiniteScroll(tableContainerRef, rows.length, infiniteScroll);
35
+ const { loadDataIfNeeded } = useInfiniteScroll(tbodyRef, rows.length, infiniteScroll);
36
36
 
37
- const rowVirtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
37
+ const rowVirtualizer = useVirtualizer<HTMLTableSectionElement, HTMLTableRowElement>({
38
38
  count: rowsAndExpandedRowContent.length,
39
39
  estimateSize: (index) => {
40
40
  const cachedHeight = rowHeightsMapRef.current.get(getItemKey(index));
41
41
  return cachedHeight || estimateSize;
42
42
  }, // estimate row height for accurate scrollbar dragging
43
43
  getItemKey,
44
- getScrollElement: () => tableContainerRef.current,
44
+ getScrollElement: () => tbodyRef.current,
45
45
  // measure dynamic row height, except in firefox because it measures table border height incorrectly
46
46
  measureElement:
47
47
  typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1
@@ -86,6 +86,7 @@ export const DsTableBodyVirtualized = <TData,>({
86
86
 
87
87
  return (
88
88
  <DsTableBody
89
+ ref={tbodyRef}
89
90
  rowsMapRef={rowsMapRef}
90
91
  rowHeightsMapRef={rowHeightsMapRef}
91
92
  rowVirtualizer={rowVirtualizer}
@@ -97,7 +98,8 @@ export const DsTableBodyVirtualized = <TData,>({
97
98
  };
98
99
 
99
100
  interface DsTableBodyProps<TData> {
100
- rowVirtualizer: Virtualizer<HTMLDivElement, HTMLTableRowElement>;
101
+ ref: RefObject<HTMLTableSectionElement | null>;
102
+ rowVirtualizer: Virtualizer<HTMLTableSectionElement, HTMLTableRowElement>;
101
103
  rowsMapRef: RefObject<Map<string, HTMLTableRowElement>>;
102
104
  rowHeightsMapRef: RefObject<Map<string, number>>;
103
105
  rowsAndExpandedRowContent: {
@@ -109,6 +111,7 @@ interface DsTableBodyProps<TData> {
109
111
  }
110
112
 
111
113
  function DsTableBody<TData>({
114
+ ref,
112
115
  rowVirtualizer,
113
116
  rowsMapRef,
114
117
  rowHeightsMapRef,
@@ -117,14 +120,10 @@ function DsTableBody<TData>({
117
120
  rowSelection,
118
121
  }: DsTableBodyProps<TData>) {
119
122
  const virtualRows = rowVirtualizer.getVirtualItems();
123
+ const totalSize = rowVirtualizer.getTotalSize();
120
124
 
121
125
  return (
122
- <TableBody
123
- className={styles.body}
124
- style={{
125
- height: `${rowVirtualizer.getTotalSize().toString()}px`, // tells scrollbar how big the table is
126
- }}
127
- >
126
+ <TableBody ref={ref} className={styles.body}>
128
127
  {virtualRows.length > 0 ? (
129
128
  virtualRows.map((virtualRow) => {
130
129
  const row = rowsAndExpandedRowContent[virtualRow.index];
@@ -150,6 +149,10 @@ function DsTableBody<TData>({
150
149
  <TableCell className={styles.emptyState}>{emptyState || EMPTY_TABLE_STATE_TEXT}</TableCell>
151
150
  </TableRow>
152
151
  )}
152
+
153
+ <tr aria-hidden className={styles.sentinel} style={{ top: `${totalSize.toString()}px` }}>
154
+ <td />
155
+ </tr>
153
156
  </TableBody>
154
157
  );
155
158
  }
@@ -1,4 +1,4 @@
1
- import { type RefObject, type ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  import { type RowSelectionState, type Table } from '@tanstack/react-table';
3
3
 
4
4
  export interface DsTableBodyVirtualizedProps<TData> {
@@ -7,11 +7,6 @@ export interface DsTableBodyVirtualizedProps<TData> {
7
7
  * and column state.
8
8
  */
9
9
  table: Table<TData>;
10
- /**
11
- * Ref to the scrollable container that hosts the virtualized rows. Required by
12
- * the virtualizer to measure viewport size and listen for scroll events.
13
- */
14
- tableContainerRef: RefObject<HTMLDivElement | null>;
15
10
  /**
16
11
  * Optional content rendered in place of rows when the table has no data.
17
12
  */
@@ -1,14 +1,14 @@
1
1
  @use '../../../../styles/typography';
2
2
 
3
3
  .bulkActionsContainer {
4
- position: fixed;
4
+ position: absolute;
5
5
  bottom: 20px;
6
6
  left: 50%;
7
7
  transform: translateX(-50%);
8
8
  background-color: var(--color-dap-gray-050);
9
9
  border-radius: 8px;
10
10
  box-shadow: 0 4px 12px #0445cc66;
11
- z-index: 1000;
11
+ z-index: 1;
12
12
  display: flex;
13
13
  align-items: center;
14
14
  border-radius: 8px;
@@ -1,6 +1,8 @@
1
1
  @use '../../../../styles/typography';
2
2
 
3
3
  .tableCellEllipsis {
4
+ flex: 1;
5
+ min-width: 0;
4
6
  display: -webkit-box;
5
7
  -webkit-line-clamp: 2;
6
8
  -webkit-box-orient: vertical;
@@ -2,31 +2,31 @@ import { type Cell, flexRender } from '@tanstack/react-table';
2
2
  import classnames from 'classnames';
3
3
  import { DsIcon } from '../../../ds-icon';
4
4
  import { DsDropdownMenu } from '../../../ds-dropdown-menu';
5
+ import { useDsTableContext } from '../../context/ds-table-context';
6
+ import { DsTableEditableCell } from '../edit/ds-table-editable-cell';
7
+ import { DsTableEditingCell } from '../edit/ds-table-editing-cell';
5
8
  import styles from './ds-table-cell.module.scss';
6
9
  import type { DsTableCellProps } from './ds-table-cell.types';
7
10
 
8
- const DsDefaultTableCell = <TData, TValue>({ cell }: { cell: Cell<TData, TValue> }) => {
9
- return (
10
- <div className={styles.tableCellEllipsis}>
11
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
12
- </div>
13
- );
14
- };
15
-
16
11
  export const DsTableCell = <TData, TValue>({
17
12
  row,
18
13
  cell,
19
14
  primaryRowActions = [],
20
15
  secondaryRowActions = [],
21
16
  }: DsTableCellProps<TData, TValue>) => {
17
+ const { editing } = useDsTableContext<TData, TValue>();
18
+ const isCellBeingEdited =
19
+ editing !== null && editing.cell.row.id === row.id && editing.cell.column.id === cell.column.id;
20
+
22
21
  const visiblePrimary = primaryRowActions.filter((action) => !action.hidden?.(row.original));
23
22
  const visibleSecondary = secondaryRowActions.filter((action) => !action.hidden?.(row.original));
24
23
 
25
- if (visiblePrimary.length || visibleSecondary.length) {
24
+ if (!isCellBeingEdited && (visiblePrimary.length || visibleSecondary.length)) {
26
25
  const hasSecondaryRowActions = visibleSecondary.length > 0;
26
+
27
27
  return (
28
28
  <div className={styles.lastCell}>
29
- <DsDefaultTableCell cell={cell} />
29
+ <CellContent cell={cell} isCellBeingEdited={isCellBeingEdited} />
30
30
  <div className={styles.cellActions}>
31
31
  {visiblePrimary.map((action, i) => {
32
32
  const isDisabled = action.disabled?.(row.original);
@@ -95,5 +95,36 @@ export const DsTableCell = <TData, TValue>({
95
95
  </div>
96
96
  );
97
97
  }
98
- return <DsDefaultTableCell cell={cell} />;
98
+
99
+ return <CellContent cell={cell} isCellBeingEdited={isCellBeingEdited} />;
100
+ };
101
+
102
+ interface CellContentProps<TData, TValue> {
103
+ cell: Cell<TData, TValue>;
104
+ isCellBeingEdited: boolean;
105
+ }
106
+
107
+ const CellContent = <TData, TValue>({ cell, isCellBeingEdited }: CellContentProps<TData, TValue>) => {
108
+ const columnDef = cell.column.columnDef;
109
+ const isEditable = typeof columnDef.editCell === 'function';
110
+
111
+ if (isCellBeingEdited) {
112
+ return <DsTableEditingCell cell={cell} />;
113
+ }
114
+
115
+ if (isEditable) {
116
+ return (
117
+ <DsTableEditableCell cell={cell}>
118
+ <div className={styles.tableCellEllipsis}>
119
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
120
+ </div>
121
+ </DsTableEditableCell>
122
+ );
123
+ }
124
+
125
+ return (
126
+ <div className={styles.tableCellEllipsis}>
127
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
128
+ </div>
129
+ );
99
130
  };
@@ -2,14 +2,11 @@
2
2
  @use '../../styles/variables' as vars;
3
3
 
4
4
  .stickyHeader {
5
- position: sticky;
6
- top: 0;
7
- z-index: 10;
8
5
  background-color: var(--background);
9
6
  }
10
7
 
11
8
  .virtualizedHeader {
12
- display: grid;
9
+ display: block;
13
10
  }
14
11
 
15
12
  .headerRow {
@@ -57,6 +54,7 @@
57
54
 
58
55
  .selectHeaderCell {
59
56
  padding: 0;
57
+ justify-content: center;
60
58
  }
61
59
 
62
60
  .headerSortContainer {
@@ -66,3 +64,25 @@
66
64
  gap: vars.$header-sort-gap;
67
65
  height: 100%;
68
66
  }
67
+
68
+ .filterTriggerContainer {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ margin-left: vars.$header-sort-gap;
72
+ }
73
+
74
+ .filterableHeader {
75
+ &:hover .filterTriggerContainer > * {
76
+ opacity: 1;
77
+ }
78
+
79
+ &:focus-within .filterTriggerContainer > * {
80
+ opacity: 1;
81
+ }
82
+ }
83
+
84
+ .filterActive {
85
+ .filterTriggerContainer > * {
86
+ opacity: 1;
87
+ }
88
+ }
@@ -9,6 +9,8 @@ import { useDsTableContext } from '../../context/ds-table-context';
9
9
  import { getColumnSizeStyle } from '../../utils/column-size';
10
10
  import { SELECT_COLUMN_ID } from '../../utils/constants';
11
11
  import { DsStack } from '../../../ds-stack';
12
+ import { DsTableColumnFilterPopover } from '../../filters/components/column-filter-popover';
13
+ import type { ResolvedColumnFilter } from '../../filters/types/filter-adapter.types';
12
14
 
13
15
  const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
14
16
  const { stickyHeader, bordered, virtualized } = useDsTableContext<TData, unknown>();
@@ -27,10 +29,15 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
27
29
  )}
28
30
  >
29
31
  {headerGroup.headers.map((header) => {
30
- const headerStyle = getColumnSizeStyle(header.column.getSize(), virtualized);
32
+ const headerStyle = getColumnSizeStyle(header.column.getSize());
31
33
  const canSort = header.column.getCanSort();
32
34
  const isSelectColumn = header.column.id === SELECT_COLUMN_ID;
33
35
 
36
+ const metaFilter = header.column.columnDef.meta?.filter;
37
+ const resolvedFilter: ResolvedColumnFilter | undefined =
38
+ metaFilter && 'render' in metaFilter ? metaFilter : undefined;
39
+ const hasActiveColumnFilter = resolvedFilter?.hasActiveFilter ?? false;
40
+
34
41
  return (
35
42
  <TableHead
36
43
  key={header.id}
@@ -38,6 +45,8 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
38
45
  styles.headerCell,
39
46
  canSort && styles.sortableHeader,
40
47
  isSelectColumn && styles.selectHeaderCell,
48
+ resolvedFilter && styles.filterableHeader,
49
+ hasActiveColumnFilter && styles.filterActive,
41
50
  )}
42
51
  onClick={header.column.getToggleSortingHandler()}
43
52
  style={headerStyle}
@@ -67,6 +76,18 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
67
76
  )}
68
77
  </div>
69
78
  )}
79
+ {resolvedFilter && (
80
+ <div className={styles.filterTriggerContainer}>
81
+ <DsTableColumnFilterPopover
82
+ hasActiveFilter={resolvedFilter.hasActiveFilter}
83
+ onApply={resolvedFilter.onApply}
84
+ onClear={resolvedFilter.onClear}
85
+ onCancel={resolvedFilter.onCancel}
86
+ >
87
+ {resolvedFilter.render()}
88
+ </DsTableColumnFilterPopover>
89
+ </div>
90
+ )}
70
91
  </div>
71
92
  )}
72
93
  </TableHead>
@@ -50,6 +50,8 @@
50
50
  background-color: var(--background-secondary);
51
51
 
52
52
  & > td {
53
+ flex: 1;
54
+ min-width: 0;
53
55
  padding: var(--standard);
54
56
  }
55
57
 
@@ -80,4 +82,24 @@
80
82
  .selectableCell {
81
83
  padding: 0;
82
84
  text-align: center;
85
+ justify-content: center;
86
+ }
87
+
88
+ .tableRow .editableCell {
89
+ padding: 1px;
90
+
91
+ &:hover {
92
+ border: 1px solid var(--border-secondary-hover);
93
+ }
94
+
95
+ &:focus-within {
96
+ border: 1px solid var(--border-action-primary);
97
+ }
98
+
99
+ &:hover,
100
+ &:focus-within {
101
+ padding: 0;
102
+ border-bottom-width: 2px;
103
+ background: var(--background);
104
+ }
83
105
  }
@@ -118,6 +118,7 @@ const DsTableRow = <TData,>({ ref, row, isSelected }: DsTableRowProps<TData>) =>
118
118
  styles.tableCell,
119
119
  cell.column.id === EXPANDER_COLUMN_ID && styles.expandableCell,
120
120
  cell.column.id === SELECT_COLUMN_ID && styles.selectableCell,
121
+ typeof cell.column.columnDef.editCell === 'function' && styles.editableCell,
121
122
  )}
122
123
  style={cellStyle}
123
124
  >
@@ -79,3 +79,22 @@
79
79
 
80
80
  @include table-mixins.expanded-row-border('bottom');
81
81
  }
82
+
83
+ .row td.editableCell {
84
+ padding: 1px;
85
+
86
+ &:hover {
87
+ border: 1px solid var(--border-secondary-hover);
88
+ }
89
+
90
+ &:focus-within {
91
+ border: 1px solid var(--border-action-primary);
92
+ }
93
+
94
+ &:hover,
95
+ &:focus-within {
96
+ padding: 0;
97
+ border-bottom-width: 2px;
98
+ background: var(--background);
99
+ }
100
+ }
@@ -67,7 +67,7 @@ export const DsTableRowVirtualized = <TData,>({
67
67
  <>
68
68
  {row.getVisibleCells().map((cell, idx) => {
69
69
  const isLastColumn = idx === row.getVisibleCells().length - 1;
70
- const cellStyle = getColumnSizeStyle(cell.column.getSize(), true);
70
+ const cellStyle = getColumnSizeStyle(cell.column.getSize());
71
71
 
72
72
  return (
73
73
  <TableCell
@@ -77,6 +77,7 @@ export const DsTableRowVirtualized = <TData,>({
77
77
  styles.cell,
78
78
  cell.column.id === EXPANDER_COLUMN_ID && styles.expandableCell,
79
79
  cell.column.id === SELECT_COLUMN_ID && styles.selectableCell,
80
+ typeof cell.column.columnDef.editCell === 'function' && styles.editableCell,
80
81
  )}
81
82
  >
82
83
  {isLastColumn ? (
@@ -25,7 +25,7 @@ export interface DsTableRowVirtualizedProps<TData> {
25
25
  /**
26
26
  * TanStack virtualizer driving the scroll window for the table body.
27
27
  */
28
- rowVirtualizer: Virtualizer<HTMLDivElement, HTMLTableRowElement>;
28
+ rowVirtualizer: Virtualizer<HTMLTableSectionElement, HTMLTableRowElement>;
29
29
  /**
30
30
  * Virtual item descriptor for this row (index, start, size) from the virtualizer.
31
31
  */
@@ -0,0 +1,27 @@
1
+ import type { CellContext, RowData } from '@tanstack/react-table';
2
+ import { DsCheckbox } from '../../../../ds-checkbox';
3
+ import { useCellEditor } from '../../../hooks/use-cell-editor';
4
+
5
+ export interface DsTableEditCellCheckboxProps<TData extends RowData> {
6
+ /** TanStack Table cell context from `ColumnDef.editCell`. */
7
+ cellContext: CellContext<TData, boolean>;
8
+ disabled?: boolean;
9
+ }
10
+
11
+ /** Checkbox editor for table cells. Render via `ColumnDef.editCell`. */
12
+ export const DsTableEditCellCheckbox = <TData extends RowData>({
13
+ cellContext,
14
+ disabled,
15
+ }: DsTableEditCellCheckboxProps<TData>) => {
16
+ const { value, setValue } = useCellEditor<TData, boolean>({
17
+ cellContext,
18
+ });
19
+
20
+ return (
21
+ <DsCheckbox
22
+ checked={value}
23
+ disabled={disabled}
24
+ onCheckedChange={(checked) => setValue(checked === true)}
25
+ />
26
+ );
27
+ };
@@ -0,0 +1,50 @@
1
+ import type { CellContext, RowData } from '@tanstack/react-table';
2
+ import { DsFormControl } from '../../../../ds-form-control';
3
+ import { DsIcon } from '../../../../ds-icon';
4
+ import { useCellEditor } from '../../../hooks/use-cell-editor';
5
+ import styles from './cell-editors.module.scss';
6
+ import { TableEditFormControl } from './table-edit-form-control';
7
+
8
+ export type DsTableEditCellDateValue = Date | null;
9
+
10
+ export interface DsTableEditCellDateProps<TData extends RowData> {
11
+ /** TanStack Table cell context from `ColumnDef.editCell`. */
12
+ cellContext: CellContext<TData, DsTableEditCellDateValue>;
13
+ min?: Date;
14
+ max?: Date;
15
+ placeholder?: string;
16
+ }
17
+
18
+ /** Date picker editor for table cells. Render via `ColumnDef.editCell`. */
19
+ export const DsTableEditCellDate = <TData extends RowData>({
20
+ cellContext,
21
+ min,
22
+ max,
23
+ placeholder,
24
+ }: DsTableEditCellDateProps<TData>) => {
25
+ const { value, setValue, error } = useCellEditor<TData, DsTableEditCellDateValue>({
26
+ cellContext,
27
+ });
28
+
29
+ return (
30
+ <TableEditFormControl cellContext={cellContext} error={error}>
31
+ <DsFormControl.DatePicker
32
+ className={styles.fullWidth}
33
+ value={value}
34
+ min={min}
35
+ max={max}
36
+ placeholder={placeholder}
37
+ onChange={setValue}
38
+ slotProps={
39
+ error
40
+ ? {
41
+ input: {
42
+ slots: { endAdornment: <DsIcon icon="error" size="tiny" /> },
43
+ },
44
+ }
45
+ : undefined
46
+ }
47
+ />
48
+ </TableEditFormControl>
49
+ );
50
+ };
@@ -0,0 +1,51 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import type { CellContext, RowData } from '@tanstack/react-table';
3
+ import { DsFormControl } from '../../../../ds-form-control';
4
+ import { useCellEditor } from '../../../hooks/use-cell-editor';
5
+ import styles from './cell-editors.module.scss';
6
+ import { TableEditFormControl } from './table-edit-form-control';
7
+
8
+ export interface DsTableEditCellNumberProps<TData extends RowData> {
9
+ /** TanStack Table cell context from `ColumnDef.editCell`. */
10
+ cellContext: CellContext<TData, number>;
11
+ min?: number;
12
+ max?: number;
13
+ step?: number;
14
+ }
15
+
16
+ /** Number input editor for table cells. Render via `ColumnDef.editCell`. */
17
+ export const DsTableEditCellNumber = <TData extends RowData>({
18
+ cellContext,
19
+ min,
20
+ max,
21
+ step,
22
+ }: DsTableEditCellNumberProps<TData>) => {
23
+ const { value, setValue, error } = useCellEditor<TData, number>({
24
+ cellContext,
25
+ });
26
+
27
+ const inputRef = useRef<HTMLInputElement>(null);
28
+
29
+ useEffect(() => {
30
+ const input = inputRef.current;
31
+ if (!input) {
32
+ return;
33
+ }
34
+ input.focus();
35
+ input.select();
36
+ }, []);
37
+
38
+ return (
39
+ <TableEditFormControl cellContext={cellContext} error={error}>
40
+ <DsFormControl.NumberInput
41
+ ref={inputRef}
42
+ className={styles.fullWidth}
43
+ value={value}
44
+ min={min}
45
+ max={max}
46
+ step={step}
47
+ onValueChange={setValue}
48
+ />
49
+ </TableEditFormControl>
50
+ );
51
+ };
@@ -0,0 +1,36 @@
1
+ import type { CellContext, RowData } from '@tanstack/react-table';
2
+ import { DsFormControl } from '../../../../ds-form-control';
3
+ import { type DsSelectOption } from '../../../../ds-select';
4
+ import { useCellEditor } from '../../../hooks/use-cell-editor';
5
+ import styles from './cell-editors.module.scss';
6
+ import { TableEditFormControl } from './table-edit-form-control';
7
+
8
+ export interface DsTableEditCellSelectProps<TData extends RowData> {
9
+ /** TanStack Table cell context from `ColumnDef.editCell`. */
10
+ cellContext: CellContext<TData, string>;
11
+ options: DsSelectOption[];
12
+ placeholder?: string;
13
+ }
14
+
15
+ /** Select editor for table cells. Render via `ColumnDef.editCell`. */
16
+ export const DsTableEditCellSelect = <TData extends RowData>({
17
+ cellContext,
18
+ options,
19
+ placeholder,
20
+ }: DsTableEditCellSelectProps<TData>) => {
21
+ const { value, setValue, error } = useCellEditor<TData, string>({
22
+ cellContext,
23
+ });
24
+
25
+ return (
26
+ <TableEditFormControl cellContext={cellContext} error={error}>
27
+ <DsFormControl.Select
28
+ className={styles.fullWidth}
29
+ value={value}
30
+ options={options}
31
+ placeholder={placeholder}
32
+ onValueChange={setValue}
33
+ />
34
+ </TableEditFormControl>
35
+ );
36
+ };
@@ -0,0 +1,50 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import type { CellContext, RowData } from '@tanstack/react-table';
3
+ import { DsFormControl } from '../../../../ds-form-control';
4
+ import { DsIcon } from '../../../../ds-icon';
5
+ import { useCellEditor } from '../../../hooks/use-cell-editor';
6
+ import styles from './cell-editors.module.scss';
7
+ import { TableEditFormControl } from './table-edit-form-control';
8
+
9
+ export interface DsTableEditCellTextProps<TData extends RowData> {
10
+ /** TanStack Table cell context from `ColumnDef.editCell`. */
11
+ cellContext: CellContext<TData, string>;
12
+ placeholder?: string;
13
+ maxLength?: number;
14
+ }
15
+
16
+ /** Text input editor for table cells. Render via `ColumnDef.editCell`. */
17
+ export const DsTableEditCellText = <TData extends RowData>({
18
+ cellContext,
19
+ placeholder,
20
+ maxLength,
21
+ }: DsTableEditCellTextProps<TData>) => {
22
+ const { value, setValue, error } = useCellEditor<TData, string>({
23
+ cellContext,
24
+ });
25
+
26
+ const inputRef = useRef<HTMLInputElement>(null);
27
+
28
+ useEffect(() => {
29
+ const input = inputRef.current;
30
+ if (!input) {
31
+ return;
32
+ }
33
+ input.focus();
34
+ input.select();
35
+ }, []);
36
+
37
+ return (
38
+ <TableEditFormControl cellContext={cellContext} error={error}>
39
+ <DsFormControl.TextInput
40
+ ref={inputRef}
41
+ className={styles.fullWidth}
42
+ value={value}
43
+ placeholder={placeholder}
44
+ maxLength={maxLength}
45
+ onValueChange={setValue}
46
+ slots={error ? { endAdornment: <DsIcon icon="error" size="tiny" /> } : undefined}
47
+ />
48
+ </TableEditFormControl>
49
+ );
50
+ };
@@ -0,0 +1,14 @@
1
+ export { DsTableEditCellText } from './ds-table-edit-cell-text';
2
+ export type { DsTableEditCellTextProps } from './ds-table-edit-cell-text';
3
+
4
+ export { DsTableEditCellNumber } from './ds-table-edit-cell-number';
5
+ export type { DsTableEditCellNumberProps } from './ds-table-edit-cell-number';
6
+
7
+ export { DsTableEditCellSelect } from './ds-table-edit-cell-select';
8
+ export type { DsTableEditCellSelectProps } from './ds-table-edit-cell-select';
9
+
10
+ export { DsTableEditCellDate } from './ds-table-edit-cell-date';
11
+ export type { DsTableEditCellDateProps, DsTableEditCellDateValue } from './ds-table-edit-cell-date';
12
+
13
+ export { DsTableEditCellCheckbox } from './ds-table-edit-cell-checkbox';
14
+ export type { DsTableEditCellCheckboxProps } from './ds-table-edit-cell-checkbox';