@drivenets/design-system 0.12.1 → 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 (81) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/index.cjs +1097 -308
  3. package/dist/index.d.cts +1118 -982
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +1118 -982
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1089 -309
  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-date-input/ds-date-input.utils.ts +2 -2
  12. package/src/components/ds-drawer/ds-drawer.tsx +2 -0
  13. package/src/components/ds-form-control/ds-form-control.tsx +14 -11
  14. package/src/components/ds-form-control/ds-form-control.types.ts +4 -0
  15. package/src/components/ds-grid/ds-grid.tsx +1 -2
  16. package/src/components/ds-modal/ds-modal.tsx +3 -0
  17. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +21 -2
  18. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +14 -11
  19. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +1 -6
  20. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +2 -2
  21. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +2 -0
  22. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +42 -11
  23. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +24 -4
  24. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +22 -1
  25. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +22 -0
  26. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +1 -0
  27. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +19 -0
  28. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +2 -1
  29. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +1 -1
  30. package/src/components/ds-table/components/edit/cell-editors/cell-editors.module.scss +3 -0
  31. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +27 -0
  32. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +50 -0
  33. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +51 -0
  34. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +36 -0
  35. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +50 -0
  36. package/src/components/ds-table/components/edit/cell-editors/index.ts +14 -0
  37. package/src/components/ds-table/components/edit/cell-editors/table-edit-form-control.tsx +31 -0
  38. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.module.scss +28 -0
  39. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.tsx +39 -0
  40. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.types.ts +7 -0
  41. package/src/components/ds-table/components/edit/ds-table-editable-cell/index.ts +2 -0
  42. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.module.scss +48 -0
  43. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +76 -0
  44. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.types.ts +5 -0
  45. package/src/components/ds-table/components/edit/ds-table-editing-cell/index.ts +2 -0
  46. package/src/components/ds-table/context/ds-table-context.ts +79 -1
  47. package/src/components/ds-table/ds-table-column.types.ts +12 -0
  48. package/src/components/ds-table/ds-table.module.scss +100 -10
  49. package/src/components/ds-table/ds-table.tsx +18 -19
  50. package/src/components/ds-table/ds-table.types.ts +52 -0
  51. package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +24 -40
  52. package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +10 -31
  53. package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +6 -98
  54. package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.module.scss +36 -0
  55. package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +92 -13
  56. package/src/components/ds-table/filters/components/checkbox-filter/index.ts +1 -1
  57. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.module.scss +42 -0
  58. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.tsx +89 -0
  59. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.types.ts +25 -0
  60. package/src/components/ds-table/filters/components/column-filter-popover/index.ts +5 -0
  61. package/src/components/ds-table/filters/hooks/use-table-filters.ts +113 -78
  62. package/src/components/ds-table/filters/index.ts +2 -44
  63. package/src/components/ds-table/filters/types/column-meta.augmentation.ts +19 -0
  64. package/src/components/ds-table/filters/types/filter-adapter.types.ts +48 -81
  65. package/src/components/ds-table/hooks/use-cell-editor.ts +81 -0
  66. package/src/components/ds-table/index.ts +29 -0
  67. package/src/components/ds-table/utils/column-size.ts +9 -8
  68. package/src/components/ds-tag/ds-tag.tsx +2 -2
  69. package/src/components/ds-workspace-layout/ds-workspace-layout.context.ts +19 -0
  70. package/src/components/ds-workspace-layout/ds-workspace-layout.module.scss +166 -0
  71. package/src/components/ds-workspace-layout/ds-workspace-layout.tsx +135 -0
  72. package/src/components/ds-workspace-layout/ds-workspace-layout.types.ts +52 -0
  73. package/src/components/ds-workspace-layout/index.ts +11 -0
  74. package/src/index.ts +1 -1
  75. package/src/styles/_scrollbars.scss +37 -0
  76. package/src/utils/restore-document-body-modal-styles.ts +44 -0
  77. package/src/utils/use-ark-dialog-body-lock-cleanup.ts +36 -0
  78. package/src/components/ds-workspace/ds-workspace.module.scss +0 -55
  79. package/src/components/ds-workspace/ds-workspace.tsx +0 -37
  80. package/src/components/ds-workspace/ds-workspace.types.ts +0 -17
  81. package/src/components/ds-workspace/index.ts +0 -8
@@ -1,5 +1,5 @@
1
1
  @use '../../styles/typography';
2
- @use '../../styles/utilities' as utils;
2
+ @use '../../styles/scrollbars' as scrollbars;
3
3
  @use './styles/variables' as vars;
4
4
 
5
5
  :root {
@@ -9,9 +9,10 @@
9
9
  --ds-table-header-height: 45px;
10
10
  }
11
11
 
12
- $bulk-actions-height: 60px;
12
+ $bulk-actions-reserved-space: 80px;
13
13
 
14
14
  .container {
15
+ position: relative;
15
16
  border-radius: vars.$border-radius;
16
17
  border-width: vars.$border-width;
17
18
  border-style: solid;
@@ -21,7 +22,51 @@ $bulk-actions-height: 60px;
21
22
 
22
23
  .dataTableContainer {
23
24
  max-height: 100%;
24
- overflow: auto;
25
+ overflow-x: auto;
26
+ overflow-y: hidden;
27
+ display: flex;
28
+ flex-direction: column;
29
+
30
+ @include scrollbars.scrollbar-on-hover;
31
+
32
+ > table {
33
+ display: flex;
34
+ flex-direction: column;
35
+ flex: 1;
36
+ min-height: 0;
37
+ width: max-content;
38
+ min-width: 100%;
39
+ }
40
+
41
+ thead {
42
+ display: block;
43
+ flex-shrink: 0;
44
+ overflow-y: scroll;
45
+ scrollbar-gutter: stable;
46
+
47
+ @include scrollbars.scrollbar-on-hover;
48
+ }
49
+
50
+ tbody {
51
+ display: block;
52
+ overflow-y: auto;
53
+ flex: 1;
54
+ min-height: 0;
55
+ scrollbar-gutter: stable;
56
+
57
+ @include scrollbars.scrollbar-on-hover;
58
+ }
59
+
60
+ thead tr,
61
+ tbody tr {
62
+ display: flex;
63
+ }
64
+
65
+ thead th,
66
+ tbody td {
67
+ display: flex;
68
+ align-items: center;
69
+ }
25
70
  }
26
71
 
27
72
  .table {
@@ -40,21 +85,66 @@ $bulk-actions-height: 60px;
40
85
  width: 100%;
41
86
  }
42
87
 
88
+ .bulkActionsVisible {
89
+ tbody {
90
+ padding-bottom: $bulk-actions-reserved-space;
91
+ }
92
+ }
93
+
43
94
  .emptyState {
95
+ // colSpan in markup isn't honored by flex row layout, so the cell stretches
96
+ // across the row; stretch the inner content so it occupies full table width
97
+ flex: 1;
98
+ min-width: 0;
44
99
  height: vars.$empty-state-height;
100
+ justify-content: center;
45
101
  text-align: center;
46
- }
47
102
 
48
- .virtualized {
49
- display: grid;
103
+ > * {
104
+ flex: 1;
105
+ }
50
106
  }
51
107
 
52
108
  .virtualizedContainer {
53
109
  position: relative;
54
110
  height: vars.$virtualized-height;
55
- overflow: auto;
56
- }
111
+ overflow-x: auto;
112
+ overflow-y: hidden;
113
+ display: flex;
114
+ flex-direction: column;
57
115
 
58
- .bulkActionsVisible {
59
- padding-bottom: $bulk-actions-height;
116
+ @include scrollbars.scrollbar-on-hover;
117
+
118
+ > table {
119
+ display: flex;
120
+ flex-direction: column;
121
+ flex: 1;
122
+ min-height: 0;
123
+ width: max-content;
124
+ min-width: 100%;
125
+ }
126
+
127
+ thead {
128
+ display: block;
129
+ flex-shrink: 0;
130
+ overflow-y: scroll;
131
+ scrollbar-gutter: stable;
132
+
133
+ @include scrollbars.scrollbar-on-hover;
134
+ }
135
+
136
+ tbody {
137
+ flex: 1;
138
+ min-height: 0;
139
+ scrollbar-gutter: stable;
140
+ }
141
+
142
+ thead tr {
143
+ display: flex;
144
+ }
145
+
146
+ thead th {
147
+ display: flex;
148
+ align-items: center;
149
+ }
60
150
  }
@@ -23,7 +23,7 @@ import { DsTableRowExpandableCell } from './components/ds-table-row-expandable-c
23
23
  import { DsTableRowSelectableCell } from './components/ds-table-row-selectable-cell';
24
24
  import { DsTableHeaderSelectableCell } from './components/ds-table-header-selectable-cell';
25
25
  import { useDragAndDrop } from './hooks/use-drag-and-drop';
26
- import { type DsTableContextType, DsTableContext } from './context/ds-table-context';
26
+ import { type DsTableContextType, DsTableContext, useEditingState } from './context/ds-table-context';
27
27
  import { DsTableBodyVirtualized } from './components/ds-table-body-virtualized';
28
28
  import {
29
29
  EMPTY_TABLE_STATE_TEXT,
@@ -77,6 +77,8 @@ const DsTable = <TData extends { id: string }, TValue>({
77
77
  onColumnVisibilityChange,
78
78
  activeRowId,
79
79
  infiniteScroll,
80
+ onCellEdit,
81
+ onCellValidate,
80
82
  }: DsDataTableProps<TData, TValue>) => {
81
83
  const [data, setData] = React.useState(tableData);
82
84
  const [sorting, setSorting] = React.useState<SortingState>([]);
@@ -267,6 +269,8 @@ const DsTable = <TData extends { id: string }, TValue>({
267
269
 
268
270
  const isBulkActionsVisible = selectable && actions.length > 0 && selectedRows.length > 0;
269
271
 
272
+ const editingState = useEditingState<TData, TValue>(onCellEdit, onCellValidate);
273
+
270
274
  const contextValue: DsTableContextType<TData, TValue> = {
271
275
  stickyHeader,
272
276
  bordered,
@@ -283,6 +287,8 @@ const DsTable = <TData extends { id: string }, TValue>({
283
287
  renderExpandedRow,
284
288
  virtualized,
285
289
  activeRowId,
290
+ onCellEdit,
291
+ ...editingState,
286
292
  };
287
293
 
288
294
  return (
@@ -298,18 +304,11 @@ const DsTable = <TData extends { id: string }, TValue>({
298
304
  )}
299
305
  >
300
306
  <DragWrapper>
301
- <Table
302
- className={classnames(
303
- fullWidth && styles.fullWidth,
304
- !bordered && styles.tableNoBorder,
305
- virtualized && styles.virtualized,
306
- )}
307
- >
307
+ <Table className={classnames(fullWidth && styles.fullWidth, !bordered && styles.tableNoBorder)}>
308
308
  <DsTableHeader table={table} />
309
309
  {virtualized ? (
310
310
  <DsTableBodyVirtualized
311
311
  table={table}
312
- tableContainerRef={tableContainerRef}
313
312
  emptyState={emptyState}
314
313
  estimateSize={virtualizedOptions?.estimateSize || ROW_SIZE_HEIGHT_MAP[rowSize]}
315
314
  overscan={virtualizedOptions?.overscan}
@@ -330,17 +329,17 @@ const DsTable = <TData extends { id: string }, TValue>({
330
329
  )}
331
330
  </Table>
332
331
  </DragWrapper>
332
+ {selectable && actions.length > 0 && (
333
+ <DsTableBulkActions
334
+ numSelectedRows={selectedRows.length}
335
+ actions={actions.map((action) => ({
336
+ ...action,
337
+ onClick: () => action.onClick(selectedRows),
338
+ }))}
339
+ onClearSelection={table.resetRowSelection}
340
+ />
341
+ )}
333
342
  </div>
334
- {selectable && actions.length > 0 && (
335
- <DsTableBulkActions
336
- numSelectedRows={selectedRows.length}
337
- actions={actions.map((action) => ({
338
- ...action,
339
- onClick: () => action.onClick(selectedRows),
340
- }))}
341
- onClearSelection={table.resetRowSelection}
342
- />
343
- )}
344
343
  </DsTableContext.Provider>
345
344
  );
346
345
  };
@@ -1,4 +1,5 @@
1
1
  import type React from 'react';
2
+ import './ds-table-column.types';
2
3
  import type {
3
4
  ColumnDef,
4
5
  ColumnFiltersState,
@@ -440,4 +441,55 @@ export interface DsDataTableProps<TData, TValue> {
440
441
  * Callback when column visibility changes
441
442
  */
442
443
  onColumnVisibilityChange?: (visibility: VisibilityState) => void;
444
+
445
+ /**
446
+ * Callback fired when an editable cell commits a new value via one of the
447
+ * `DsTableEditCell*` wrappers or the `useCellEditor` hook. The parent is
448
+ * expected to update the row data in response.
449
+ *
450
+ * Commits occur on confirm (check button or Enter). Opening another cell
451
+ * discards the previous draft without calling this callback.
452
+ *
453
+ * @param row - The row data of the cell that was edited
454
+ * @param columnId - The id of the column whose cell was edited
455
+ * @param value - The new committed value
456
+ *
457
+ * @example
458
+ * ```tsx
459
+ * const [people, setPeople] = useState(initialPeople);
460
+ *
461
+ * <DsTable
462
+ * data={people}
463
+ * columns={columns}
464
+ * onCellEdit={(row, columnId, value) => {
465
+ * setPeople((rows) =>
466
+ * rows.map((r) => (r.id === row.id ? { ...r, [columnId]: value } : r)),
467
+ * );
468
+ * }}
469
+ * />
470
+ * ```
471
+ */
472
+ onCellEdit?: (row: TData, columnId: string, value: TValue) => void;
473
+
474
+ /**
475
+ * Validates a cell value before it is committed. Return `null` to allow the
476
+ * commit (which fires `onCellEdit`), or an error message string to reject it
477
+ * and keep the cell in edit mode. Synchronous only.
478
+ *
479
+ *
480
+ * @param row - The row data of the cell being edited
481
+ * @param columnId - The id of the column whose cell is being edited
482
+ * @param value - The candidate value to validate
483
+ *
484
+ * @example
485
+ * ```tsx
486
+ * <DsTable
487
+ * data={people}
488
+ * columns={columns}
489
+ * onCellValidate={(row, columnId, value) => validateField(columnId)(value, row)}
490
+ * onCellEdit={...}
491
+ * />
492
+ * ```
493
+ */
494
+ onCellValidate?: (row: TData, columnId: string, value: TValue) => string | null;
443
495
  }
@@ -1,54 +1,36 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { Row } from '@tanstack/react-table';
3
3
  import type { FilterAdapter } from '../types/filter-adapter.types';
4
- import { type CheckboxFilterItem, CheckboxFilter } from '../components/checkbox-filter';
4
+ import {
5
+ type CheckboxFilterItem,
6
+ type CheckboxFilterLocale,
7
+ CheckboxFilter,
8
+ } from '../components/checkbox-filter';
5
9
  import { createFilterAdapter } from './create-filter-adapter';
6
10
 
7
11
  export interface CheckboxFilterAdapterConfig<TData, TValue> {
8
- /**
9
- * Unique identifier (should match column accessorKey)
10
- */
12
+ /** Unique id, should match the column `accessorKey`. */
11
13
  id: string;
12
-
13
- /**
14
- * Display label for filter navigation
15
- */
16
14
  label: string;
17
-
18
- /**
19
- * Available items to select from
20
- */
15
+ /** Selectable items. */
21
16
  items: CheckboxFilterItem<TValue>[];
22
-
23
- /**
24
- * Optional custom renderer for each item
25
- */
17
+ /** Custom row renderer; defaults to `item.label`. */
26
18
  renderer?: (item: CheckboxFilterItem<TValue>) => ReactNode;
27
-
28
- /**
29
- * Optional custom chip label generator
30
- * @default (item) => `${label}: ${item.label}`
31
- */
19
+ /** @default (item) => `${label}: ${item.label}` */
32
20
  chipLabelTemplate?: (item: CheckboxFilterItem<TValue>) => string;
33
-
34
- /**
35
- * Optional custom cell renderer for table column
36
- */
37
21
  cellRenderer?: (value: TValue) => ReactNode;
38
-
39
- /**
40
- * How to extract the value from a row for comparison
41
- * @default (row) => row.getValue(id)
42
- */
22
+ /** @default (row) => row.getValue(id) */
43
23
  getRowValue?: (row: Row<TData>) => TValue;
24
+ /** Show a search input above the list. @default false */
25
+ searchable?: boolean;
26
+ /** Show an "All" toggle that selects / clears every (visible) item. @default false */
27
+ selectAll?: boolean;
28
+ locale?: CheckboxFilterLocale;
44
29
  }
45
30
 
46
31
  /**
47
- * Factory function to create a checkbox filter adapter
48
- *
49
- * Handles multi-select checkbox filtering with inclusion model:
50
- * - Empty selection = show all data (no filter active)
51
- * - Selected items = show only those items
32
+ * Multi-select inclusion filter: empty selection shows every row, selected
33
+ * items show only those rows.
52
34
  */
53
35
  export function createCheckboxFilterAdapter<TData, TValue = string>(
54
36
  config: CheckboxFilterAdapterConfig<TData, TValue>,
@@ -61,20 +43,21 @@ export function createCheckboxFilterAdapter<TData, TValue = string>(
61
43
  chipLabelTemplate = (item) => `${label}: ${item.label}`,
62
44
  cellRenderer,
63
45
  getRowValue = (row) => row.getValue(id),
46
+ searchable = false,
47
+ selectAll = false,
48
+ locale,
64
49
  } = config;
65
50
 
66
51
  return createFilterAdapter<TData, CheckboxFilterItem<TValue>[], TValue>({
67
52
  id,
68
53
  label,
69
- initialValue: [], // Start with nothing selected (show all)
54
+ initialValue: [],
70
55
 
71
56
  filterFn: (row, columnId, filterValue) => {
72
- // Empty selection = no filter = show all
73
57
  if (filterValue.length === 0) {
74
58
  return true;
75
59
  }
76
60
 
77
- // Otherwise, show only selected items
78
61
  const rowValue = getRowValue(row);
79
62
  return filterValue.some((item) => item.value === rowValue);
80
63
  },
@@ -82,7 +65,6 @@ export function createCheckboxFilterAdapter<TData, TValue = string>(
82
65
  cellRenderer,
83
66
 
84
67
  toChips: (selectedItems) => {
85
- // Generate chips for all selected items
86
68
  return selectedItems.map((item) => ({
87
69
  id: `${id}_${String(item.value)}`,
88
70
  label: chipLabelTemplate(item),
@@ -98,7 +80,6 @@ export function createCheckboxFilterAdapter<TData, TValue = string>(
98
80
  },
99
81
 
100
82
  getActiveFiltersCount: (selectedItems) => {
101
- // Count = number of selected items (0 means none selected)
102
83
  return selectedItems.length;
103
84
  },
104
85
 
@@ -109,6 +90,9 @@ export function createCheckboxFilterAdapter<TData, TValue = string>(
109
90
  renderer={renderer}
110
91
  selectedItems={value}
111
92
  onSelectionChange={onChange}
93
+ searchable={searchable}
94
+ selectAll={selectAll}
95
+ locale={locale}
112
96
  />
113
97
  );
114
98
  },
@@ -1,5 +1,5 @@
1
1
  import type { Row } from '@tanstack/react-table';
2
- import type { ChipItem } from '../../../ds-chip-group';
2
+ import type { TagFilterItem } from '../../../ds-tag-filter';
3
3
  import type { FilterAdapter } from '../types/filter-adapter.types';
4
4
  import { RangeFilter } from '../components/range-filter';
5
5
  import { createFilterAdapter } from './create-filter-adapter';
@@ -14,39 +14,18 @@ export interface DualRangeFilterValue {
14
14
  }
15
15
 
16
16
  export interface DualRangeFilterAdapterConfig<TData> {
17
- /**
18
- * Unique identifier (should match column accessorKey)
19
- */
17
+ /** Unique id, should match the column `accessorKey`. */
20
18
  id: string;
21
-
22
- /**
23
- * Display label for filter navigation
24
- */
25
19
  label: string;
26
-
27
- /**
28
- * Field configurations: { fieldKey: displayLabel }
29
- * Example: { running: 'Running', completed: 'Completed' }
30
- */
20
+ /** Field key → display label, e.g. `{ running: 'Running' }`. */
31
21
  fields: Record<string, string>;
32
-
33
- /**
34
- * Optional formatter for chip display
35
- * @default (num) => num.toLocaleString('en-US')
36
- */
22
+ /** @default (num) => num.toLocaleString('en-US') */
37
23
  formatNumber?: (num: number) => string;
38
-
39
- /**
40
- * How to extract the value object from a row
41
- * @default (row) => row.getValue(id)
42
- */
24
+ /** @default (row) => row.getValue(id) */
43
25
  getRowValue?: (row: Row<TData>) => Record<string, number>;
44
26
  }
45
27
 
46
- /**
47
- * Factory function to create a dual-range filter adapter
48
- * Handles filtering on multiple numeric ranges within a single column
49
- */
28
+ /** Filter over multiple numeric ranges within one column (AND across fields). */
50
29
  export function createDualRangeFilterAdapter<TData>(
51
30
  config: DualRangeFilterAdapterConfig<TData>,
52
31
  ): FilterAdapter<TData, DualRangeFilterValue, Record<string, number>> {
@@ -71,7 +50,6 @@ export function createDualRangeFilterAdapter<TData>(
71
50
  filterFn: (row, columnId, filterValue) => {
72
51
  const rowValue = getRowValue(row);
73
52
 
74
- // Check each field's range
75
53
  for (const [fieldKey, range] of Object.entries(filterValue)) {
76
54
  const hasFilter = range.from !== undefined || range.to !== undefined;
77
55
  const fieldValue = rowValue[fieldKey];
@@ -80,8 +58,9 @@ export function createDualRangeFilterAdapter<TData>(
80
58
  const matchesFrom = range.from === undefined || fieldValue >= range.from;
81
59
  const matchesTo = range.to === undefined || fieldValue <= range.to;
82
60
 
61
+ // AND across fields: every range with a bound must match.
83
62
  if (!matchesFrom || !matchesTo) {
84
- return false; // AND logic: all ranges must match
63
+ return false;
85
64
  }
86
65
  }
87
66
  }
@@ -89,10 +68,10 @@ export function createDualRangeFilterAdapter<TData>(
89
68
  return true;
90
69
  },
91
70
 
92
- cellRenderer: undefined, // Let the column definition handle rendering
71
+ cellRenderer: undefined,
93
72
 
94
73
  toChips: (value) => {
95
- const chips: ChipItem[] = [];
74
+ const chips: TagFilterItem[] = [];
96
75
 
97
76
  Object.entries(value).forEach(([fieldKey, range]) => {
98
77
  const hasFilter = range.from !== undefined || range.to !== undefined;
@@ -2,116 +2,24 @@ import type { ReactNode } from 'react';
2
2
  import type { FilterAdapter } from '../types/filter-adapter.types';
3
3
 
4
4
  export interface FilterAdapterConfig<TData, TFilterValue, TCellValue = unknown> {
5
- /**
6
- * Unique identifier (should match column accessorKey)
7
- */
5
+ /** Unique id, should match the column `accessorKey`. */
8
6
  id: string;
9
-
10
- /**
11
- * Display label for filter navigation
12
- */
13
7
  label: string;
14
-
15
- /**
16
- * Initial/default value for the filter
17
- */
18
8
  initialValue: TFilterValue;
19
-
20
- /**
21
- * Filter function - determines if row matches filter value
22
- */
9
+ /** TanStack `filterFn` for this column. */
23
10
  filterFn: FilterAdapter<TData, TFilterValue, TCellValue>['columnFilterFn'];
24
-
25
- /**
26
- * Convert filter value to display chips
27
- */
28
11
  toChips: FilterAdapter<TData, TFilterValue, TCellValue>['toChips'];
29
-
30
- /**
31
- * Remove chip effect from filter value
32
- */
33
12
  fromChip: FilterAdapter<TData, TFilterValue, TCellValue>['fromChip'];
34
-
35
- /**
36
- * Calculate active filter count
37
- */
38
13
  getActiveFiltersCount: FilterAdapter<TData, TFilterValue, TCellValue>['getActiveFiltersCount'];
39
-
40
- /**
41
- * Render the filter UI
42
- */
43
14
  renderFilter: (value: TFilterValue, onChange: (value: TFilterValue) => void) => ReactNode;
44
-
45
- /**
46
- * Optional custom cell renderer for table cells
47
- * Note: This receives the cell value from the table, not the filter value
48
- */
15
+ /** Cell renderer; receives the cell value, not the filter value. */
49
16
  cellRenderer?: (value: TCellValue) => ReactNode;
50
17
  }
51
18
 
52
19
  /**
53
- * Base factory function to create a filter adapter
54
- *
55
- * This is the core function that all specialized filter adapters use internally.
56
- * Use this when checkbox or dual-range adapters don't fit your requirements.
57
- *
58
- * ## When to use directly:
59
- * - Complex filtering logic (e.g., editor + date range)
60
- * - Unique UI that doesn't fit generic patterns
61
- * - Combining multiple sub-filters
62
- * - Special chip generation requirements
63
- *
64
- * ## When to use specialized helpers instead:
65
- * - **Checkbox filters**: Use `createCheckboxFilterAdapter` for multi-select
66
- * - **Range filters**: Use `createDualRangeFilterAdapter` for numeric ranges
67
- *
68
- * ## Example:
69
- * ```typescript
70
- * interface MyFilterValue {
71
- * users: string[];
72
- * dateRange: { from?: Date; to?: Date };
73
- * }
74
- *
75
- * const myFilter = createFilterAdapter<DataType, MyFilterValue>({
76
- * id: 'myColumn',
77
- * label: 'My Filter',
78
- * initialValue: { users: [], dateRange: {} },
79
- *
80
- * filterFn: (row, columnId, filterValue) => {
81
- * // Your custom logic
82
- * return matchesUsers && matchesDateRange;
83
- * },
84
- *
85
- * toChips: (value) => {
86
- * // Generate chips from your filter value
87
- * return chips;
88
- * },
89
- *
90
- * fromChip: (chip, currentValue) => {
91
- * // Remove chip's effect from value
92
- * return updatedValue;
93
- * },
94
- *
95
- * getActiveFiltersCount: (value) => {
96
- * // Count active filters (0 means none active)
97
- * return count;
98
- * },
99
- *
100
- * renderFilter: (value, onChange) => (
101
- * <YourCustomFilterComponent
102
- * value={value}
103
- * onChange={onChange}
104
- * />
105
- * ),
106
- * });
107
- * ```
108
- *
109
- * ## Reference Implementation:
110
- * See `workflow-filters.config.tsx` for a complete example (lastEditedFilterAdapter).
111
- *
112
- * ## Note:
113
- * Most use cases are covered by `createCheckboxFilterAdapter` or `createDualRangeFilterAdapter`.
114
- * Only use this base function directly when you need completely custom filter logic.
20
+ * Base factory used by every specialized adapter. Reach for it only when
21
+ * `createCheckboxFilterAdapter` / `createDualRangeFilterAdapter` don't fit
22
+ * (custom UI, mixed sub-filters, bespoke chip logic).
115
23
  */
116
24
  export function createFilterAdapter<TData, TFilterValue, TCellValue = unknown>(
117
25
  config: FilterAdapterConfig<TData, TFilterValue, TCellValue>,
@@ -0,0 +1,36 @@
1
+ @use '../../../../../styles/typography';
2
+
3
+ .root {
4
+ display: flex;
5
+ flex-direction: column;
6
+ min-width: 248px;
7
+ }
8
+
9
+ .list {
10
+ display: flex;
11
+ flex-direction: column;
12
+ max-height: 280px;
13
+ overflow-y: auto;
14
+ }
15
+
16
+ .empty {
17
+ @include typography.body-sm-reg;
18
+ color: var(--font-secondary);
19
+ padding: var(--xs) var(--3xs);
20
+ text-align: center;
21
+ }
22
+
23
+ .checkboxItem {
24
+ display: flex;
25
+ width: 100%;
26
+ align-items: center;
27
+ gap: var(--xs);
28
+ padding: var(--xs);
29
+ min-height: 40px;
30
+ border-radius: 4px;
31
+
32
+ /* Highlight bg is the focus indicator (matches DsDropdownMenu items). */
33
+ &[data-focus-visible] {
34
+ outline: none;
35
+ }
36
+ }