@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.
- package/CHANGELOG.md +15 -0
- package/dist/index.cjs +1097 -308
- package/dist/index.d.cts +1118 -982
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1118 -982
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1089 -309
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +31 -31
- package/src/components/ds-date-input/ds-date-input.utils.ts +2 -2
- package/src/components/ds-drawer/ds-drawer.tsx +2 -0
- package/src/components/ds-form-control/ds-form-control.tsx +14 -11
- package/src/components/ds-form-control/ds-form-control.types.ts +4 -0
- package/src/components/ds-grid/ds-grid.tsx +1 -2
- package/src/components/ds-modal/ds-modal.tsx +3 -0
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +21 -2
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +14 -11
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +1 -6
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +2 -2
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +2 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +42 -11
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +24 -4
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +22 -1
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +22 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +1 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +19 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +2 -1
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +1 -1
- package/src/components/ds-table/components/edit/cell-editors/cell-editors.module.scss +3 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +27 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +50 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +51 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +36 -0
- package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +50 -0
- package/src/components/ds-table/components/edit/cell-editors/index.ts +14 -0
- package/src/components/ds-table/components/edit/cell-editors/table-edit-form-control.tsx +31 -0
- package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.module.scss +28 -0
- package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.tsx +39 -0
- package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.types.ts +7 -0
- package/src/components/ds-table/components/edit/ds-table-editable-cell/index.ts +2 -0
- package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.module.scss +48 -0
- package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +76 -0
- package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.types.ts +5 -0
- package/src/components/ds-table/components/edit/ds-table-editing-cell/index.ts +2 -0
- package/src/components/ds-table/context/ds-table-context.ts +79 -1
- package/src/components/ds-table/ds-table-column.types.ts +12 -0
- package/src/components/ds-table/ds-table.module.scss +100 -10
- package/src/components/ds-table/ds-table.tsx +18 -19
- package/src/components/ds-table/ds-table.types.ts +52 -0
- package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +24 -40
- package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +10 -31
- package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +6 -98
- package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.module.scss +36 -0
- package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +92 -13
- package/src/components/ds-table/filters/components/checkbox-filter/index.ts +1 -1
- package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.module.scss +42 -0
- package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.tsx +89 -0
- package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.types.ts +25 -0
- package/src/components/ds-table/filters/components/column-filter-popover/index.ts +5 -0
- package/src/components/ds-table/filters/hooks/use-table-filters.ts +113 -78
- package/src/components/ds-table/filters/index.ts +2 -44
- package/src/components/ds-table/filters/types/column-meta.augmentation.ts +19 -0
- package/src/components/ds-table/filters/types/filter-adapter.types.ts +48 -81
- package/src/components/ds-table/hooks/use-cell-editor.ts +81 -0
- package/src/components/ds-table/index.ts +29 -0
- package/src/components/ds-table/utils/column-size.ts +9 -8
- package/src/components/ds-tag/ds-tag.tsx +2 -2
- package/src/components/ds-workspace-layout/ds-workspace-layout.context.ts +19 -0
- package/src/components/ds-workspace-layout/ds-workspace-layout.module.scss +166 -0
- package/src/components/ds-workspace-layout/ds-workspace-layout.tsx +135 -0
- package/src/components/ds-workspace-layout/ds-workspace-layout.types.ts +52 -0
- package/src/components/ds-workspace-layout/index.ts +11 -0
- package/src/index.ts +1 -1
- package/src/styles/_scrollbars.scss +37 -0
- package/src/utils/restore-document-body-modal-styles.ts +44 -0
- package/src/utils/use-ark-dialog-body-lock-cleanup.ts +36 -0
- package/src/components/ds-workspace/ds-workspace.module.scss +0 -55
- package/src/components/ds-workspace/ds-workspace.tsx +0 -37
- package/src/components/ds-workspace/ds-workspace.types.ts +0 -17
- package/src/components/ds-workspace/index.ts +0 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@use '../../styles/typography';
|
|
2
|
-
@use '../../styles/
|
|
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-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
.
|
|
59
|
-
|
|
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 {
|
|
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
|
-
*
|
|
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: [],
|
|
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 {
|
|
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;
|
|
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,
|
|
71
|
+
cellRenderer: undefined,
|
|
93
72
|
|
|
94
73
|
toChips: (value) => {
|
|
95
|
-
const chips:
|
|
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
|
|
54
|
-
*
|
|
55
|
-
*
|
|
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>,
|
package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.module.scss
ADDED
|
@@ -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
|
+
}
|