@drivenets/design-system 0.12.1 → 0.14.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 +31 -0
- package/dist/index.cjs +1989 -735
- package/dist/index.d.cts +1378 -998
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1378 -998
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1978 -735
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +43 -41
- package/src/components/ds-button-v3/ds-button-v3.figma.ts +99 -0
- package/src/components/ds-button-v3/ds-button-v3.module.scss +22 -22
- package/src/components/ds-card/figma/ds-card-content.figma.ts +15 -0
- package/src/components/ds-card/figma/ds-card-footer.figma.ts +15 -0
- package/src/components/ds-card/figma/ds-card-header.figma.ts +15 -0
- package/src/components/ds-card/figma/ds-card.figma.ts +72 -0
- package/src/components/ds-date-input/ds-date-input.utils.ts +2 -2
- package/src/components/ds-date-picker/ds-date-picker.figma.ts +19 -0
- package/src/components/ds-drawer/ds-drawer.tsx +2 -0
- package/src/components/ds-form-control/ds-form-control.figma.ts +76 -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-icon/figma/ds-icon-material.figma.batch.ts +10 -0
- package/src/components/ds-icon/figma/ds-icon-special.figma.batch.ts +18 -0
- package/src/components/ds-icon/figma/ds-icon.figma.batch.json +2873 -0
- package/src/components/ds-modal/ds-modal.tsx +3 -0
- package/src/components/ds-modal/figma/ds-modal-content.figma.ts +18 -0
- package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +19 -0
- package/src/components/ds-modal/figma/ds-modal-header.figma.ts +21 -0
- package/src/components/ds-modal/figma/ds-modal.figma.ts +36 -0
- package/src/components/ds-popover/ds-popover.module.scss +58 -16
- package/src/components/ds-popover/ds-popover.tsx +148 -24
- package/src/components/ds-popover/ds-popover.types.ts +95 -12
- package/src/components/ds-popover/index.ts +1 -1
- package/src/components/ds-select/ds-select.figma.ts +53 -0
- package/src/components/ds-split-button/ds-split-button.figma.ts +95 -0
- package/src/components/ds-status-badge-v2/ds-status-badge-v2.figma.ts +41 -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 +22 -14
- 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-group-header-cell/ds-table-group-header-cell.module.scss +31 -0
- package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +48 -0
- package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.types.ts +12 -0
- package/src/components/ds-table/components/ds-table-group-header-cell/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +28 -4
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +65 -28
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +26 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +3 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +23 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +4 -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 +83 -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 +38 -21
- package/src/components/ds-table/ds-table.types.ts +107 -0
- package/src/components/ds-table/figma/ds-table-expandable.figma.ts +17 -0
- package/src/components/ds-table/figma/ds-table.figma.ts +16 -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/grouping/hooks/use-column-groups.ts +73 -0
- package/src/components/ds-table/grouping/index.ts +5 -0
- package/src/components/ds-table/grouping/types/column-group-meta.augmentation.ts +26 -0
- package/src/components/ds-table/grouping/types/column-group.types.ts +35 -0
- package/src/components/ds-table/grouping/utils/column-group.ts +119 -0
- package/src/components/ds-table/hooks/use-cell-editor.ts +81 -0
- package/src/components/ds-table/index.ts +32 -0
- package/src/components/ds-table/styles/_variables.scss +1 -0
- package/src/components/ds-table/utils/column-size.ts +9 -8
- package/src/components/ds-tag/ds-tag.module.scss +22 -1
- package/src/components/ds-tag/ds-tag.tsx +18 -5
- package/src/components/ds-tag/ds-tag.types.ts +22 -8
- package/src/components/ds-tag/ds-tag.unit.test.ts +32 -0
- package/src/components/ds-tag-filter/ds-tag-filter.tsx +14 -22
- package/src/components/ds-text-input/ds-text-input.figma.ts +60 -0
- package/src/components/ds-time-picker/ds-time-picker.figma.ts +18 -0
- 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/_root_new.scss +2 -0
- package/src/styles/_scrollbars.scss +36 -22
- 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,107 +1,77 @@
|
|
|
1
1
|
import { type ReactNode, useState } from 'react';
|
|
2
2
|
import type { CellContext, ColumnDef } from '@tanstack/react-table';
|
|
3
|
-
import type {
|
|
3
|
+
import type { TagFilterItem } from '../../../ds-tag-filter';
|
|
4
4
|
import type {
|
|
5
5
|
AnyAdapter,
|
|
6
|
+
ColumnFilterMeta,
|
|
6
7
|
ColumnFilterState,
|
|
7
8
|
FilterAdapter,
|
|
8
9
|
FilterNavItem,
|
|
9
10
|
FilterState,
|
|
11
|
+
ResolvedColumnFilter,
|
|
10
12
|
} from '../types/filter-adapter.types';
|
|
11
13
|
|
|
14
|
+
const resolveColumnAdapterId = (
|
|
15
|
+
meta: ColumnFilterMeta | ResolvedColumnFilter | undefined,
|
|
16
|
+
): string | undefined => {
|
|
17
|
+
if (!meta) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if ('render' in meta) {
|
|
21
|
+
return meta.filterId;
|
|
22
|
+
}
|
|
23
|
+
if (meta.adapter) {
|
|
24
|
+
return meta.adapter.id;
|
|
25
|
+
}
|
|
26
|
+
return meta.adapterId;
|
|
27
|
+
};
|
|
28
|
+
|
|
12
29
|
export interface UseTableFiltersOptions<TData, TValue, TCellValue> {
|
|
13
|
-
/**
|
|
14
|
-
* Array of filter adapters that define filter behavior
|
|
15
|
-
*/
|
|
16
30
|
filterAdapters: FilterAdapter<TData, TValue, TCellValue>[] | AnyAdapter[];
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Base column definitions
|
|
20
|
-
*/
|
|
21
31
|
baseColumns?: ColumnDef<TData>[];
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Controlled mode: External applied filters (source of truth).
|
|
25
|
-
* When provided with onFiltersChange, the hook operates in controlled mode.
|
|
26
|
-
* Keys must match adapter IDs.
|
|
27
|
-
*/
|
|
32
|
+
/** Controlled applied filters, keyed by adapter id. Pair with `onFiltersChange`. */
|
|
28
33
|
appliedFilters?: FilterState<TValue>;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Controlled mode: Callback when filters change.
|
|
32
|
-
* Called by applyFilters(), deleteChip(), clearAll().
|
|
33
|
-
*/
|
|
34
|
+
/** Fires from `applyFilters`, `deleteChip`, and `clearAll` in controlled mode. */
|
|
34
35
|
onFiltersChange?: (filters: FilterState<TValue>) => void;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export interface UseTableFiltersResult<TData, TValue> {
|
|
38
|
-
/**
|
|
39
|
-
* Current draft filter state (uncommitted user selections)
|
|
40
|
-
*/
|
|
39
|
+
/** Draft filter state (uncommitted). */
|
|
41
40
|
filterState: FilterState<TValue>;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Column filters (derived from applied state)
|
|
45
|
-
*/
|
|
41
|
+
/** Applied filters mapped to TanStack's column-filters shape. */
|
|
46
42
|
columnFilters: ColumnFilterState<TValue>[];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*/
|
|
51
|
-
filterChips: ChipItem[];
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Filter navigation items with active counts
|
|
55
|
-
*/
|
|
43
|
+
/** Chips for `DsTagFilter`, derived from applied state. */
|
|
44
|
+
filterChips: TagFilterItem[];
|
|
45
|
+
/** Filter-nav items with active counts. */
|
|
56
46
|
filterNavItems: FilterNavItem[];
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Enhanced column definitions with filter functions
|
|
60
|
-
*/
|
|
47
|
+
/** `baseColumns` with `filterFn`, cell renderer and per-column meta wired in. */
|
|
61
48
|
enhancedColumns: ColumnDef<TData>[];
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Handlers for filter operations
|
|
65
|
-
*/
|
|
66
49
|
handlers: {
|
|
67
|
-
/** Update a
|
|
50
|
+
/** Update a filter's draft value. */
|
|
68
51
|
updateFilter: (filterId: string, value: TValue) => void;
|
|
69
|
-
/** Commit
|
|
52
|
+
/** Commit all drafts to applied state. */
|
|
70
53
|
applyFilters: () => void;
|
|
71
|
-
/** Clear all filters */
|
|
54
|
+
/** Clear all filters. */
|
|
72
55
|
clearAll: () => void;
|
|
73
|
-
/**
|
|
74
|
-
deleteChip: (chip:
|
|
56
|
+
/** Remove a chip's effect from applied state. */
|
|
57
|
+
deleteChip: (chip: TagFilterItem) => void;
|
|
58
|
+
/** Commit one column's draft (per-column popover Save). */
|
|
59
|
+
applyColumnFilter: (filterId: string) => void;
|
|
60
|
+
/** Discard one column's draft without committing. */
|
|
61
|
+
cancelColumnFilter: (filterId: string) => void;
|
|
62
|
+
/** Clear one applied filter (per-column popover Clear). */
|
|
63
|
+
clearColumnFilter: (filterId: string) => void;
|
|
75
64
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*/
|
|
65
|
+
/** Renders the adapter UI for a column id, or `null` if no match. */
|
|
66
|
+
renderColumnFilter: (filterId: string) => ReactNode;
|
|
67
|
+
/** Renders the adapter UI for a `FilterNavItem`. */
|
|
80
68
|
renderFilterContent: (selectedFilter: FilterNavItem) => ReactNode;
|
|
81
69
|
}
|
|
82
70
|
|
|
83
71
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* **Uncontrolled mode** (default): Filters are managed internally.
|
|
88
|
-
* **Controlled mode**: Filters are managed externally via appliedFilters/onFiltersChange.
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* // Uncontrolled mode
|
|
92
|
-
* const { filterChips, handlers } = useTableFilters({
|
|
93
|
-
* filterAdapters: adapters,
|
|
94
|
-
* baseColumns: columns,
|
|
95
|
-
* });
|
|
96
|
-
*
|
|
97
|
-
* @example
|
|
98
|
-
* // Controlled mode (e.g., URL-driven filtering)
|
|
99
|
-
* const { filterChips, handlers } = useTableFilters({
|
|
100
|
-
* filterAdapters: adapters,
|
|
101
|
-
* baseColumns: columns,
|
|
102
|
-
* appliedFilters: filtersFromUrl,
|
|
103
|
-
* onFiltersChange: (filters) => updateUrlAndRefetch(filters),
|
|
104
|
-
* });
|
|
72
|
+
* Orchestrates table filtering: drafts → applied state, chips, nav counts, and
|
|
73
|
+
* column wiring. Uncontrolled by default; pass `appliedFilters` +
|
|
74
|
+
* `onFiltersChange` for URL-driven / external state.
|
|
105
75
|
*/
|
|
106
76
|
export function useTableFilters<TData, TValue, TCellValue>({
|
|
107
77
|
filterAdapters,
|
|
@@ -109,7 +79,6 @@ export function useTableFilters<TData, TValue, TCellValue>({
|
|
|
109
79
|
appliedFilters: externalAppliedFilters,
|
|
110
80
|
onFiltersChange: setExternalAppliedFilters,
|
|
111
81
|
}: UseTableFiltersOptions<TData, TValue, TCellValue>): UseTableFiltersResult<TData, TValue> {
|
|
112
|
-
// Internal variable just to avoid assignment issues of `any`.
|
|
113
82
|
const _filterAdapters = filterAdapters as FilterAdapter<TData, TValue, TCellValue>[];
|
|
114
83
|
|
|
115
84
|
const isControlled = externalAppliedFilters !== undefined && setExternalAppliedFilters !== undefined;
|
|
@@ -148,18 +117,41 @@ export function useTableFilters<TData, TValue, TCellValue>({
|
|
|
148
117
|
const enhancedColumns: ColumnDef<TData>[] = !baseColumns
|
|
149
118
|
? []
|
|
150
119
|
: baseColumns.map((col) => {
|
|
151
|
-
const
|
|
120
|
+
const metaFilter = col.meta?.filter as ColumnFilterMeta | undefined;
|
|
121
|
+
const adapterIdFromMeta = resolveColumnAdapterId(metaFilter);
|
|
122
|
+
const adapter =
|
|
123
|
+
(adapterIdFromMeta && _filterAdapters.find((a) => a.id === adapterIdFromMeta)) ||
|
|
124
|
+
_filterAdapters.find((a) => a.id === col.id);
|
|
125
|
+
|
|
152
126
|
if (!adapter) {
|
|
153
127
|
return col;
|
|
154
128
|
}
|
|
155
129
|
|
|
156
130
|
const cellRenderer = adapter.cellRenderer;
|
|
131
|
+
const appliedValue = appliedFilters[adapter.id];
|
|
132
|
+
const resolvedFilter: ResolvedColumnFilter | undefined = metaFilter
|
|
133
|
+
? {
|
|
134
|
+
filterId: adapter.id,
|
|
135
|
+
hasActiveFilter: appliedValue !== undefined && adapter.getActiveFiltersCount(appliedValue) > 0,
|
|
136
|
+
render: () => renderForId(adapter.id),
|
|
137
|
+
onApply: () => applyColumnFilter(adapter.id),
|
|
138
|
+
onClear: () => clearColumnFilter(adapter.id),
|
|
139
|
+
onCancel: () => cancelColumnFilter(adapter.id),
|
|
140
|
+
}
|
|
141
|
+
: undefined;
|
|
142
|
+
|
|
157
143
|
return {
|
|
158
144
|
...col,
|
|
159
145
|
filterFn: adapter.columnFilterFn,
|
|
160
146
|
...(cellRenderer && {
|
|
161
147
|
cell: (info: CellContext<TData, TCellValue>) => cellRenderer(info.getValue()),
|
|
162
148
|
}),
|
|
149
|
+
...(resolvedFilter && {
|
|
150
|
+
meta: {
|
|
151
|
+
...col.meta,
|
|
152
|
+
filter: resolvedFilter,
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
163
155
|
};
|
|
164
156
|
});
|
|
165
157
|
|
|
@@ -185,7 +177,43 @@ export function useTableFilters<TData, TValue, TCellValue>({
|
|
|
185
177
|
setAppliedFilters({});
|
|
186
178
|
};
|
|
187
179
|
|
|
188
|
-
const
|
|
180
|
+
const omitKey = (source: FilterState<TValue>, filterId: string): FilterState<TValue> =>
|
|
181
|
+
Object.fromEntries(Object.entries(source).filter(([key]) => key !== filterId));
|
|
182
|
+
|
|
183
|
+
const applyColumnFilter = (filterId: string) => {
|
|
184
|
+
const adapter = _filterAdapters.find((a) => a.id === filterId);
|
|
185
|
+
if (!adapter) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const draftValue = draftFilters[filterId] as TValue;
|
|
190
|
+
const hasActive = adapter.getActiveFiltersCount(draftValue) > 0;
|
|
191
|
+
const next = hasActive
|
|
192
|
+
? { ...appliedFilters, [filterId]: draftValue }
|
|
193
|
+
: omitKey(appliedFilters, filterId);
|
|
194
|
+
|
|
195
|
+
setAppliedFilters(next);
|
|
196
|
+
setPendingFilters((prev) => omitKey(prev, filterId));
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const cancelColumnFilter = (filterId: string) => {
|
|
200
|
+
setPendingFilters((prev) => omitKey(prev, filterId));
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const clearColumnFilter = (filterId: string) => {
|
|
204
|
+
const adapter = _filterAdapters.find((a) => a.id === filterId);
|
|
205
|
+
if (!adapter) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
setPendingFilters((prev) => ({ ...omitKey(prev, filterId), [filterId]: adapter.initialValue }));
|
|
210
|
+
|
|
211
|
+
if (appliedFilters[filterId] !== undefined) {
|
|
212
|
+
setAppliedFilters(omitKey(appliedFilters, filterId));
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const deleteChip = (chip: TagFilterItem) => {
|
|
189
217
|
const filterKey = typeof chip.metadata?.key === 'string' ? chip.metadata.key : undefined;
|
|
190
218
|
if (!filterKey) {
|
|
191
219
|
return;
|
|
@@ -211,8 +239,8 @@ export function useTableFilters<TData, TValue, TCellValue>({
|
|
|
211
239
|
setAppliedFilters(newFilters);
|
|
212
240
|
};
|
|
213
241
|
|
|
214
|
-
const
|
|
215
|
-
const adapter = _filterAdapters.find((a) => a.id ===
|
|
242
|
+
const renderForId = (filterId: string): ReactNode => {
|
|
243
|
+
const adapter = _filterAdapters.find((a) => a.id === filterId);
|
|
216
244
|
if (!adapter) {
|
|
217
245
|
return null;
|
|
218
246
|
}
|
|
@@ -223,6 +251,9 @@ export function useTableFilters<TData, TValue, TCellValue>({
|
|
|
223
251
|
return adapter.renderFilter(value, onChange);
|
|
224
252
|
};
|
|
225
253
|
|
|
254
|
+
const renderFilterContent = (selectedFilter: FilterNavItem) => renderForId(selectedFilter.id);
|
|
255
|
+
const renderColumnFilter = (filterId: string) => renderForId(filterId);
|
|
256
|
+
|
|
226
257
|
return {
|
|
227
258
|
filterState: draftFilters,
|
|
228
259
|
columnFilters,
|
|
@@ -234,7 +265,11 @@ export function useTableFilters<TData, TValue, TCellValue>({
|
|
|
234
265
|
applyFilters,
|
|
235
266
|
clearAll,
|
|
236
267
|
deleteChip,
|
|
268
|
+
applyColumnFilter,
|
|
269
|
+
cancelColumnFilter,
|
|
270
|
+
clearColumnFilter,
|
|
237
271
|
},
|
|
238
272
|
renderFilterContent,
|
|
273
|
+
renderColumnFilter,
|
|
239
274
|
};
|
|
240
275
|
}
|
|
@@ -1,50 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
* Table Filters - Filter Adapter Pattern
|
|
3
|
-
*
|
|
4
|
-
* A plug-and-play filter system that eliminates boilerplate and centralizes filter logic.
|
|
5
|
-
*
|
|
6
|
-
* ## Quick Start:
|
|
7
|
-
*
|
|
8
|
-
* ```typescript
|
|
9
|
-
* // 1. Create filter adapters in a config file
|
|
10
|
-
* import { createCheckboxFilterAdapter, createDualRangeFilterAdapter } from '../filters';
|
|
11
|
-
*
|
|
12
|
-
* export const statusFilter = createCheckboxFilterAdapter({
|
|
13
|
-
* id: 'status',
|
|
14
|
-
* label: 'Status',
|
|
15
|
-
* items: [{ value: 'active', label: 'Active' }],
|
|
16
|
-
* });
|
|
17
|
-
*
|
|
18
|
-
* export const myFilters = [statusFilter];
|
|
19
|
-
*
|
|
20
|
-
* // 2. Use in component with useTableFilters hook
|
|
21
|
-
* import { useTableFilters } from '../filters';
|
|
22
|
-
*
|
|
23
|
-
* const { columnFilters, filterChips, enhancedColumns, handlers } =
|
|
24
|
-
* useTableFilters(myFilters, columns);
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* ## Available Exports:
|
|
28
|
-
*
|
|
29
|
-
* ### Factory Functions:
|
|
30
|
-
* - `createCheckboxFilterAdapter` - Multi-select checkbox filters
|
|
31
|
-
* - `createDualRangeFilterAdapter` - Numeric range filters (with multiple fields)
|
|
32
|
-
* - `createFilterAdapter` - Base function for custom filters (full control)
|
|
33
|
-
*
|
|
34
|
-
* ### Hook:
|
|
35
|
-
* - `useTableFilters` - Orchestrates multiple filters, manages state
|
|
36
|
-
*
|
|
37
|
-
* ### Types:
|
|
38
|
-
* - `FilterAdapter` - Core adapter interface
|
|
39
|
-
* - `CheckboxFilterItem` - Item type for checkbox filters
|
|
40
|
-
* - `RangeFilterValue` - Value type for range filters
|
|
41
|
-
*
|
|
42
|
-
* ## Example:
|
|
43
|
-
* See `stories/filters-panel.stories.tsx` and `stories/filters-panel/workflow-filters.config.tsx`
|
|
44
|
-
*/
|
|
1
|
+
import './types/column-meta.augmentation';
|
|
45
2
|
|
|
46
3
|
export * from './adapters';
|
|
47
4
|
export * from './types/filter-adapter.types';
|
|
48
5
|
export * from './hooks';
|
|
49
6
|
export type { CheckboxFilterItem, CheckboxFilterProps } from './components/checkbox-filter';
|
|
50
7
|
export type { RangeFilterValue, RangeFilterProps } from './components/range-filter';
|
|
8
|
+
export * from './components/column-filter-popover';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import '@tanstack/react-table';
|
|
2
|
+
import type { ColumnFilterMeta, ResolvedColumnFilter } from './filter-adapter.types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Adds `column.meta.filter`. Consumers set `ColumnFilterMeta`;
|
|
6
|
+
* `useTableFilters` swaps it for `ResolvedColumnFilter` on `enhancedColumns`
|
|
7
|
+
* so the header can render the trigger + popover. The header narrows the
|
|
8
|
+
* union by checking for `render`.
|
|
9
|
+
*/
|
|
10
|
+
declare module '@tanstack/react-table' {
|
|
11
|
+
// `TData` / `TValue` must mirror the original `ColumnMeta` signature for
|
|
12
|
+
// the augmentation to merge.
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
14
|
+
interface ColumnMeta<TData, TValue> {
|
|
15
|
+
filter?: ColumnFilterMeta | ResolvedColumnFilter;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export {};
|
|
@@ -1,117 +1,84 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { Row } from '@tanstack/react-table';
|
|
3
|
-
import type {
|
|
3
|
+
import type { TagFilterItem } from '../../../ds-tag-filter';
|
|
4
4
|
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
6
|
export type AnyAdapter = FilterAdapter<any, any, any>;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @template TData - The row data type
|
|
13
|
-
* @template TFilterValue - The filter state value type
|
|
14
|
-
* @template TCellValue - The actual cell value type from the table (defaults to unknown)
|
|
9
|
+
* Contract every filter implements. `TData` is the row type, `TFilterValue`
|
|
10
|
+
* the adapter's state, `TCellValue` the cell value returned by the column.
|
|
15
11
|
*/
|
|
16
12
|
export interface FilterAdapter<TData, TFilterValue, TCellValue = unknown> {
|
|
17
|
-
/**
|
|
18
|
-
* Unique identifier for the filter (should match column accessorKey)
|
|
19
|
-
*/
|
|
13
|
+
/** Unique id, should match the column `accessorKey`. */
|
|
20
14
|
id: string;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Display label for the filter in the navigation
|
|
24
|
-
*/
|
|
15
|
+
/** Display label used in the filter navigation. */
|
|
25
16
|
label: string;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Initial/default value for the filter state
|
|
29
|
-
*/
|
|
17
|
+
/** Initial / reset value. */
|
|
30
18
|
initialValue: TFilterValue;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* TanStack Table filter function
|
|
34
|
-
* Determines if a row matches the current filter value
|
|
35
|
-
*/
|
|
19
|
+
/** TanStack `filterFn` — returns whether a row matches the current value. */
|
|
36
20
|
columnFilterFn: (row: Row<TData>, columnId: string, filterValue: TFilterValue) => boolean;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Optional custom cell renderer for the table column
|
|
40
|
-
* Receives the actual cell value from the table, not the filter value
|
|
41
|
-
* If not provided, default rendering will be used
|
|
42
|
-
*/
|
|
21
|
+
/** Optional cell renderer; receives the cell value, not the filter value. */
|
|
43
22
|
cellRenderer?: (value: TCellValue) => ReactNode;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Convert filter value to filter chips for display
|
|
47
|
-
* Returns empty array if no chips should be shown
|
|
48
|
-
*/
|
|
49
|
-
toChips: (value: TFilterValue) => ChipItem[];
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Remove a chip from the filter value
|
|
53
|
-
* Returns updated filter value with the chip's effect removed
|
|
54
|
-
*/
|
|
55
|
-
fromChip: (chip: ChipItem, currentValue: TFilterValue) => TFilterValue;
|
|
56
|
-
|
|
57
23
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* Can also be used to check if filter is active (count > 0)
|
|
24
|
+
* Convert the filter value into chips for `DsTagFilter`. Each chip's
|
|
25
|
+
* `metadata.key` / `metadata.value` must let `fromChip` undo it.
|
|
61
26
|
*/
|
|
27
|
+
toChips: (value: TFilterValue) => TagFilterItem[];
|
|
28
|
+
/** Remove a chip's effect from the current value. */
|
|
29
|
+
fromChip: (chip: TagFilterItem, currentValue: TFilterValue) => TFilterValue;
|
|
30
|
+
/** Number of active sub-filters; `0` means the filter is inactive. */
|
|
62
31
|
getActiveFiltersCount: (value: TFilterValue) => number;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Reset filter to initial state
|
|
66
|
-
*/
|
|
32
|
+
/** Return the initial value. */
|
|
67
33
|
reset: () => TFilterValue;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Render the filter UI component
|
|
71
|
-
* Receives current value and onChange callback
|
|
72
|
-
*/
|
|
34
|
+
/** Render the filter UI for a given value + onChange. */
|
|
73
35
|
renderFilter: (value: TFilterValue, onChange: (value: TFilterValue) => void) => ReactNode;
|
|
74
36
|
}
|
|
75
37
|
|
|
76
|
-
/**
|
|
77
|
-
* Filter navigation item used to render filter tabs in the UI
|
|
78
|
-
*/
|
|
79
38
|
export interface FilterNavItem {
|
|
80
|
-
/**
|
|
81
|
-
* Unique identifier for the filter (matches FilterAdapter.id)
|
|
82
|
-
*/
|
|
39
|
+
/** Matches `FilterAdapter.id`. */
|
|
83
40
|
id: string;
|
|
84
|
-
/**
|
|
85
|
-
* Display label for the filter tab
|
|
86
|
-
*/
|
|
87
41
|
label: string;
|
|
88
|
-
/**
|
|
89
|
-
* Optional count badge to display active filters
|
|
90
|
-
*/
|
|
42
|
+
/** Active filter count badge. */
|
|
91
43
|
count?: number;
|
|
92
|
-
/**
|
|
93
|
-
* Whether this filter is disabled
|
|
94
|
-
*/
|
|
95
44
|
disabled?: boolean;
|
|
96
45
|
}
|
|
97
46
|
|
|
98
|
-
/**
|
|
99
|
-
* Filter state managed by useTableFilters hook
|
|
100
|
-
*/
|
|
47
|
+
/** Map of filter id → adapter value. */
|
|
101
48
|
export interface FilterState<TValue> {
|
|
102
49
|
[filterId: string]: TValue;
|
|
103
50
|
}
|
|
104
51
|
|
|
105
|
-
/**
|
|
106
|
-
* Column filter for TanStack Table
|
|
107
|
-
*/
|
|
108
52
|
export interface ColumnFilterState<TValue> {
|
|
109
|
-
/**
|
|
110
|
-
* Column identifier this filter applies to (matches the column `accessorKey`).
|
|
111
|
-
*/
|
|
53
|
+
/** Column id (matches the column `accessorKey`). */
|
|
112
54
|
id: string;
|
|
113
|
-
/**
|
|
114
|
-
* Current filter value, passed to the column's filter function.
|
|
115
|
-
*/
|
|
55
|
+
/** Value passed to the column's filter function. */
|
|
116
56
|
value: TValue;
|
|
117
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Set by the consumer on `column.meta.filter` to opt a column into the
|
|
61
|
+
* per-column filter popover. Provide an `adapter` inline or just `adapterId`
|
|
62
|
+
* to resolve against `useTableFilters({ filterAdapters })`.
|
|
63
|
+
*/
|
|
64
|
+
export interface ColumnFilterMeta {
|
|
65
|
+
/** Mutually exclusive with `adapterId`. */
|
|
66
|
+
adapter?: AnyAdapter;
|
|
67
|
+
/** Mutually exclusive with `adapter`. */
|
|
68
|
+
adapterId?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* What `useTableFilters` injects onto `column.meta.filter` of each enhanced
|
|
73
|
+
* column. Consumers don't build this — they set `ColumnFilterMeta` and the
|
|
74
|
+
* hook swaps it for this resolved shape on `enhancedColumns`.
|
|
75
|
+
*/
|
|
76
|
+
export interface ResolvedColumnFilter {
|
|
77
|
+
filterId: string;
|
|
78
|
+
hasActiveFilter: boolean;
|
|
79
|
+
/** Renders the adapter's filter UI for this column. */
|
|
80
|
+
render: () => ReactNode;
|
|
81
|
+
onApply: () => void;
|
|
82
|
+
onClear: () => void;
|
|
83
|
+
onCancel: () => void;
|
|
84
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { ColumnDef, VisibilityState } from '@tanstack/react-table';
|
|
3
|
+
import { useControlled } from '../../../../utils/use-controlled';
|
|
4
|
+
import { getCollapsedColumnVisibility, getInitialCollapsedGroups } from '../utils/column-group';
|
|
5
|
+
|
|
6
|
+
interface UseColumnGroupsParams<TData, TValue> {
|
|
7
|
+
/**
|
|
8
|
+
* Consumer-provided columns (before utility columns are injected). Group defs
|
|
9
|
+
* nest child columns via `columns`.
|
|
10
|
+
*/
|
|
11
|
+
columns: ColumnDef<TData, TValue>[];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Controlled list of collapsed group ids. When provided, the table no longer
|
|
15
|
+
* manages collapse state internally.
|
|
16
|
+
*/
|
|
17
|
+
collapsedColumnGroups?: string[];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Called with the next list of collapsed group ids whenever a group is toggled.
|
|
21
|
+
*/
|
|
22
|
+
onCollapsedColumnGroupsChange?: (collapsedGroupIds: string[]) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface UseColumnGroupsResult {
|
|
26
|
+
/**
|
|
27
|
+
* The effective list of collapsed group ids (controlled or internal).
|
|
28
|
+
*/
|
|
29
|
+
collapsedColumnGroups: string[];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Toggles a single group's collapsed state.
|
|
33
|
+
*/
|
|
34
|
+
toggleColumnGroup: (groupId: string) => void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Visibility overrides derived from the collapsed groups, to be layered on top
|
|
38
|
+
* of the consumer's own column visibility.
|
|
39
|
+
*/
|
|
40
|
+
collapsedVisibility: VisibilityState;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Manages column-group collapse state. Supports both controlled
|
|
45
|
+
* (`collapsedColumnGroups` + `onCollapsedColumnGroupsChange`) and uncontrolled
|
|
46
|
+
* usage (seeded from `meta.group.defaultCollapsed`).
|
|
47
|
+
*/
|
|
48
|
+
export const useColumnGroups = <TData, TValue>({
|
|
49
|
+
columns,
|
|
50
|
+
collapsedColumnGroups,
|
|
51
|
+
onCollapsedColumnGroupsChange,
|
|
52
|
+
}: UseColumnGroupsParams<TData, TValue>): UseColumnGroupsResult => {
|
|
53
|
+
const [collapsed, setCollapsed] = useControlled(
|
|
54
|
+
collapsedColumnGroups,
|
|
55
|
+
onCollapsedColumnGroupsChange,
|
|
56
|
+
getInitialCollapsedGroups(columns),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const toggleColumnGroup = (groupId: string) => {
|
|
60
|
+
const next = collapsed.includes(groupId)
|
|
61
|
+
? collapsed.filter((id) => id !== groupId)
|
|
62
|
+
: [...collapsed, groupId];
|
|
63
|
+
|
|
64
|
+
setCollapsed(next);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const collapsedVisibility = useMemo(
|
|
68
|
+
() => getCollapsedColumnVisibility(columns, collapsed),
|
|
69
|
+
[columns, collapsed],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return { collapsedColumnGroups: collapsed, toggleColumnGroup, collapsedVisibility };
|
|
73
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import './types/column-group-meta.augmentation';
|
|
2
|
+
|
|
3
|
+
export type { DsColumnGroupMeta } from './types/column-group.types';
|
|
4
|
+
export { useColumnGroups } from './hooks/use-column-groups';
|
|
5
|
+
export { getGroupColumnSizeStyle, isFirstLeafColumnOfGroup } from './utils/column-group';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import '@tanstack/react-table';
|
|
2
|
+
import type { DsColumnGroupMeta } from './column-group.types';
|
|
3
|
+
|
|
4
|
+
declare module '@tanstack/react-table' {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6
|
+
interface ColumnMeta<TData, TValue> {
|
|
7
|
+
/**
|
|
8
|
+
* Set on a grouping column def (one that nests child `columns`) to configure
|
|
9
|
+
* the group header and its collapse behavior.
|
|
10
|
+
*/
|
|
11
|
+
group?: DsColumnGroupMeta;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Set on a leaf column to keep it visible while its parent group is collapsed
|
|
15
|
+
* (e.g. a key identifier column that should always stay on screen).
|
|
16
|
+
*
|
|
17
|
+
* Only affects the collapsed state. When `false` or omitted (the default),
|
|
18
|
+
* the column is hidden while the group is collapsed and shown again when the
|
|
19
|
+
* group is expanded — it is never permanently hidden by this flag.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
keepVisibleWhenCollapsed?: boolean;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for a grouping column, set on a column definition that nests
|
|
3
|
+
* child columns via TanStack's `columns` array. Lives under `meta.group`.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* const columns: ColumnDef<Row>[] = [
|
|
8
|
+
* {
|
|
9
|
+
* id: 'equipment',
|
|
10
|
+
* header: 'Equipment',
|
|
11
|
+
* meta: { group: { collapsible: true } },
|
|
12
|
+
* columns: [
|
|
13
|
+
* { accessorKey: 'cabinetType', header: 'Cabinet Type', meta: { keepVisibleWhenCollapsed: true } },
|
|
14
|
+
* { accessorKey: 'shelf', header: 'Shelf#' },
|
|
15
|
+
* ],
|
|
16
|
+
* },
|
|
17
|
+
* ];
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export interface DsColumnGroupMeta {
|
|
21
|
+
/**
|
|
22
|
+
* Renders a collapse/expand toggle in the group header. When collapsed, every
|
|
23
|
+
* leaf column in the group is hidden except those marked
|
|
24
|
+
* `meta.keepVisibleWhenCollapsed`.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
collapsible?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* When the table manages collapse state internally (uncontrolled), start this
|
|
31
|
+
* group collapsed. Ignored once `collapsedColumnGroups` is provided.
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
defaultCollapsed?: boolean;
|
|
35
|
+
}
|