@drivenets/design-system 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +201 -0
- package/dist/index.cjs +5394 -0
- package/dist/index.css +1 -0
- package/dist/index.d.cts +9157 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +9157 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5335 -0
- package/dist/index.js.map +1 -0
- package/package.json +104 -0
- package/src/components/ds-alert-banner/ds-alert-banner.module.scss +116 -0
- package/src/components/ds-alert-banner/ds-alert-banner.tsx +115 -0
- package/src/components/ds-alert-banner/ds-alert-banner.types.tsx +47 -0
- package/src/components/ds-alert-banner/index.tsx +2 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.module.scss +99 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.tsx +94 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.types.tsx +32 -0
- package/src/components/ds-breadcrumb/index.tsx +2 -0
- package/src/components/ds-button/ds-button.tsx +12 -0
- package/src/components/ds-button/ds-button.types.ts +6 -0
- package/src/components/ds-button/index.ts +2 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss +257 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx +34 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts +41 -0
- package/src/components/ds-button/versions/ds-button-legacy/index.ts +2 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +580 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +49 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +41 -0
- package/src/components/ds-button/versions/ds-button-new/index.ts +2 -0
- package/src/components/ds-checkbox/ds-checkbox.module.scss +130 -0
- package/src/components/ds-checkbox/ds-checkbox.tsx +55 -0
- package/src/components/ds-checkbox/ds-checkbox.types.tsx +27 -0
- package/src/components/ds-checkbox/index.tsx +2 -0
- package/src/components/ds-chip/ds-chip.module.scss +71 -0
- package/src/components/ds-chip/ds-chip.tsx +73 -0
- package/src/components/ds-chip/ds-chip.types.tsx +46 -0
- package/src/components/ds-chip/index.tsx +2 -0
- package/src/components/ds-chip-group/ds-chip-group.module.scss +59 -0
- package/src/components/ds-chip-group/ds-chip-group.tsx +122 -0
- package/src/components/ds-chip-group/ds-chip-group.types.ts +52 -0
- package/src/components/ds-chip-group/hooks/use-chip-row-calculation.ts +76 -0
- package/src/components/ds-chip-group/index.ts +2 -0
- package/src/components/ds-confirmation/ds-confirmation.module.scss +58 -0
- package/src/components/ds-confirmation/ds-confirmation.tsx +121 -0
- package/src/components/ds-confirmation/ds-confirmation.types.tsx +28 -0
- package/src/components/ds-confirmation/index.ts +2 -0
- package/src/components/ds-dialog/ds-dialog.module.scss +50 -0
- package/src/components/ds-dialog/ds-dialog.tsx +70 -0
- package/src/components/ds-dialog/ds-dialog.types.tsx +48 -0
- package/src/components/ds-dialog/index.tsx +2 -0
- package/src/components/ds-drawer/ds-drawer.module.scss +212 -0
- package/src/components/ds-drawer/ds-drawer.tsx +160 -0
- package/src/components/ds-drawer/ds-drawer.types.tsx +54 -0
- package/src/components/ds-drawer/index.ts +2 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss +123 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +396 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.types.tsx +262 -0
- package/src/components/ds-dropdown-menu/index.tsx +2 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.module.scss +70 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +106 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.types.tsx +19 -0
- package/src/components/ds-expandable-text-input/index.ts +2 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.module.scss +90 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.tsx +106 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.types.tsx +94 -0
- package/src/components/ds-file-upload/components/file-upload/index.ts +2 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.module.scss +125 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx +143 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.types.tsx +26 -0
- package/src/components/ds-file-upload/components/file-upload-item/index.ts +2 -0
- package/src/components/ds-file-upload/ds-file-upload-api.types.ts +38 -0
- package/src/components/ds-file-upload/ds-file-upload.tsx +46 -0
- package/src/components/ds-file-upload/ds-file-upload.types.tsx +54 -0
- package/src/components/ds-file-upload/errors/file-upload-errors.ts +27 -0
- package/src/components/ds-file-upload/hooks/index.ts +2 -0
- package/src/components/ds-file-upload/hooks/use-file-upload.ts +350 -0
- package/src/components/ds-file-upload/index.ts +7 -0
- package/src/components/ds-file-upload/types/accept-types.ts +106 -0
- package/src/components/ds-file-upload/utils/accept-utils.ts +25 -0
- package/src/components/ds-file-upload/utils/constants.ts +21 -0
- package/src/components/ds-file-upload/utils/file-factory.ts +18 -0
- package/src/components/ds-file-upload/utils/format-file-size.ts +15 -0
- package/src/components/ds-file-upload/utils/generate-helper-text.ts +39 -0
- package/src/components/ds-file-upload/utils/get-error-message.ts +17 -0
- package/src/components/ds-file-upload/utils/is-file-equal.ts +8 -0
- package/src/components/ds-form-control/ds-form-control.module.scss +135 -0
- package/src/components/ds-form-control/ds-form-control.tsx +94 -0
- package/src/components/ds-form-control/ds-form-control.types.tsx +58 -0
- package/src/components/ds-form-control/index.tsx +2 -0
- package/src/components/ds-grid/ds-grid.module.scss +0 -0
- package/src/components/ds-grid/ds-grid.tsx +43 -0
- package/src/components/ds-grid/ds-grid.types.ts +55 -0
- package/src/components/ds-grid/index.ts +2 -0
- package/src/components/ds-icon/ds-icon.module.scss +25 -0
- package/src/components/ds-icon/ds-icon.tsx +33 -0
- package/src/components/ds-icon/ds-icon.types.ts +53 -0
- package/src/components/ds-icon/index.ts +2 -0
- package/src/components/ds-icon/material-icons.ts +6203 -0
- package/src/components/ds-modal/ds-modal.module.scss +100 -0
- package/src/components/ds-modal/ds-modal.tsx +136 -0
- package/src/components/ds-modal/ds-modal.types.tsx +44 -0
- package/src/components/ds-modal/index.tsx +2 -0
- package/src/components/ds-number-input/ds-number-input.module.scss +15 -0
- package/src/components/ds-number-input/ds-number-input.tsx +74 -0
- package/src/components/ds-number-input/ds-number-input.types.tsx +76 -0
- package/src/components/ds-number-input/index.tsx +2 -0
- package/src/components/ds-panel/ds-panel.module.scss +95 -0
- package/src/components/ds-panel/ds-panel.tsx +46 -0
- package/src/components/ds-panel/ds-panel.types.ts +17 -0
- package/src/components/ds-panel/index.tsx +2 -0
- package/src/components/ds-password-input/ds-password-input.module.scss +10 -0
- package/src/components/ds-password-input/ds-password-input.tsx +67 -0
- package/src/components/ds-password-input/ds-password-input.types.tsx +62 -0
- package/src/components/ds-password-input/index.tsx +2 -0
- package/src/components/ds-popover/ds-popover.module.scss +23 -0
- package/src/components/ds-popover/ds-popover.tsx +33 -0
- package/src/components/ds-popover/ds-popover.types.tsx +24 -0
- package/src/components/ds-popover/index.tsx +2 -0
- package/src/components/ds-radio-group/ds-radio-group.module.scss +138 -0
- package/src/components/ds-radio-group/ds-radio-group.tsx +115 -0
- package/src/components/ds-radio-group/ds-radio-group.types.tsx +93 -0
- package/src/components/ds-radio-group/index.tsx +2 -0
- package/src/components/ds-select/ds-select.module.scss +66 -0
- package/src/components/ds-select/ds-select.tsx +239 -0
- package/src/components/ds-select/ds-select.types.tsx +80 -0
- package/src/components/ds-select/index.tsx +2 -0
- package/src/components/ds-select/select-items-chips.tsx +56 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.module.scss +69 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.tsx +113 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.types.tsx +74 -0
- package/src/components/ds-smart-tabs/index.tsx +2 -0
- package/src/components/ds-spinner/ds-spinner.module.scss +39 -0
- package/src/components/ds-spinner/ds-spinner.tsx +51 -0
- package/src/components/ds-spinner/ds-spinner.types.tsx +20 -0
- package/src/components/ds-spinner/index.tsx +2 -0
- package/src/components/ds-status-badge/ds-status-badge.module.scss +79 -0
- package/src/components/ds-status-badge/ds-status-badge.tsx +42 -0
- package/src/components/ds-status-badge/ds-status-badge.types.tsx +46 -0
- package/src/components/ds-status-badge/index.tsx +2 -0
- package/src/components/ds-stepper/components/ds-step-content.tsx +32 -0
- package/src/components/ds-stepper/components/ds-step-description.tsx +6 -0
- package/src/components/ds-stepper/components/ds-step-label.tsx +6 -0
- package/src/components/ds-stepper/components/ds-step-next-button.tsx +13 -0
- package/src/components/ds-stepper/components/ds-step.tsx +59 -0
- package/src/components/ds-stepper/ds-stepper.module.scss +134 -0
- package/src/components/ds-stepper/ds-stepper.tsx +34 -0
- package/src/components/ds-stepper/ds-stepper.types.ts +12 -0
- package/src/components/ds-stepper/hooks/use-stepper.ts +20 -0
- package/src/components/ds-stepper/hooks/use-steps.ts +18 -0
- package/src/components/ds-stepper/index.tsx +6 -0
- package/src/components/ds-system-status/ds-system-status.module.scss +87 -0
- package/src/components/ds-system-status/ds-system-status.tsx +33 -0
- package/src/components/ds-system-status/ds-system-status.types.tsx +37 -0
- package/src/components/ds-system-status/index.tsx +10 -0
- package/src/components/ds-table/components/core-table/core-table.module.scss +32 -0
- package/src/components/ds-table/components/core-table/core-table.tsx +83 -0
- package/src/components/ds-table/components/core-table/index.ts +1 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +115 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +70 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +41 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +53 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +95 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.types.ts +67 -0
- package/src/components/ds-table/components/ds-table-cell/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +81 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +99 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.types.ts +11 -0
- package/src/components/ds-table/components/ds-table-header/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +137 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +205 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +22 -0
- package/src/components/ds-table/components/ds-table-row/index.ts +2 -0
- package/src/components/ds-table/context/ds-table-context.tsx +39 -0
- package/src/components/ds-table/ds-table.module.scss +60 -0
- package/src/components/ds-table/ds-table.tsx +312 -0
- package/src/components/ds-table/ds-table.types.ts +377 -0
- package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +116 -0
- package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +171 -0
- package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +131 -0
- package/src/components/ds-table/filters/adapters/index.ts +12 -0
- package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +43 -0
- package/src/components/ds-table/filters/components/checkbox-filter/index.ts +2 -0
- package/src/components/ds-table/filters/components/range-filter/index.ts +2 -0
- package/src/components/ds-table/filters/components/range-filter/range-filter.module.scss +25 -0
- package/src/components/ds-table/filters/components/range-filter/range-filter.tsx +79 -0
- package/src/components/ds-table/filters/hooks/index.ts +1 -0
- package/src/components/ds-table/filters/hooks/use-table-filters.ts +234 -0
- package/src/components/ds-table/filters/index.ts +50 -0
- package/src/components/ds-table/filters/types/filter-adapter.types.ts +111 -0
- package/src/components/ds-table/hooks/use-drag-and-drop.tsx +107 -0
- package/src/components/ds-table/index.ts +4 -0
- package/src/components/ds-table/styles/_variables.scss +17 -0
- package/src/components/ds-table/styles/shared/ds-table-shared.module.scss +16 -0
- package/src/components/ds-table/utils/column-styling.ts +30 -0
- package/src/components/ds-table/utils/create-columns-grid-template.ts +23 -0
- package/src/components/ds-table/utils/merge-refs.ts +15 -0
- package/src/components/ds-table/utils/story-data-generator.ts +91 -0
- package/src/components/ds-text-input/ds-text-input.module.scss +27 -0
- package/src/components/ds-text-input/ds-text-input.tsx +75 -0
- package/src/components/ds-text-input/ds-text-input.types.tsx +110 -0
- package/src/components/ds-text-input/index.ts +2 -0
- package/src/components/ds-textarea/ds-textarea.module.scss +5 -0
- package/src/components/ds-textarea/ds-textarea.tsx +49 -0
- package/src/components/ds-textarea/ds-textarea.types.tsx +67 -0
- package/src/components/ds-textarea/index.tsx +2 -0
- package/src/components/ds-toast/ds-toast-context.tsx +79 -0
- package/src/components/ds-toast/ds-toast.module.scss +134 -0
- package/src/components/ds-toast/ds-toast.tsx +54 -0
- package/src/components/ds-toast/ds-toast.types.tsx +53 -0
- package/src/components/ds-toast/index.ts +3 -0
- package/src/components/ds-tooltip/ds-tooltip.module.scss +19 -0
- package/src/components/ds-tooltip/ds-tooltip.tsx +33 -0
- package/src/components/ds-tooltip/ds-tooltip.types.tsx +12 -0
- package/src/components/ds-tooltip/index.tsx +2 -0
- package/src/components/ds-typography/ds-typography.config.ts +32 -0
- package/src/components/ds-typography/ds-typography.module.scss +93 -0
- package/src/components/ds-typography/ds-typography.tsx +27 -0
- package/src/components/ds-typography/ds-typography.types.tsx +27 -0
- package/src/components/ds-typography/index.tsx +2 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.module.scss +60 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.tsx +80 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.types.ts +69 -0
- package/src/components/ds-vertical-tabs/index.ts +7 -0
- package/src/index.ts +34 -0
- package/src/styles/_colors.scss +65 -0
- package/src/styles/_grid.scss +111 -0
- package/src/styles/_mixin.scss +1 -0
- package/src/styles/_reset.scss +38 -0
- package/src/styles/_root.scss +390 -0
- package/src/styles/_scrollbars.scss +41 -0
- package/src/styles/_spacing.scss +47 -0
- package/src/styles/_typography.scss +301 -0
- package/src/styles/_utilities.scss +33 -0
- package/src/styles/_variables.scss +44 -0
- package/src/styles/mixins/_bounding-box.scss +53 -0
- package/src/styles/mixins/_list.scss +74 -0
- package/src/styles/shared/_dropdown.scss +89 -0
- package/src/styles/shared/_input.scss +365 -0
- package/src/styles/shared/_modal.scss +123 -0
- package/src/styles/styles.scss +12 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { Row } from '@tanstack/react-table';
|
|
3
|
+
import type { FilterAdapter } from '../types/filter-adapter.types';
|
|
4
|
+
import { type CheckboxFilterItem, CheckboxFilter } from '../components/checkbox-filter';
|
|
5
|
+
import { createFilterAdapter } from './create-filter-adapter';
|
|
6
|
+
|
|
7
|
+
export interface CheckboxFilterAdapterConfig<TData, TValue> {
|
|
8
|
+
/**
|
|
9
|
+
* Unique identifier (should match column accessorKey)
|
|
10
|
+
*/
|
|
11
|
+
id: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Display label for filter navigation
|
|
15
|
+
*/
|
|
16
|
+
label: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Available items to select from
|
|
20
|
+
*/
|
|
21
|
+
items: CheckboxFilterItem<TValue>[];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Optional custom renderer for each item
|
|
25
|
+
*/
|
|
26
|
+
renderer?: (item: CheckboxFilterItem<TValue>) => ReactNode;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Optional custom chip label generator
|
|
30
|
+
* @default (item) => `${label}: ${item.label}`
|
|
31
|
+
*/
|
|
32
|
+
chipLabelTemplate?: (item: CheckboxFilterItem<TValue>) => string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optional custom cell renderer for table column
|
|
36
|
+
*/
|
|
37
|
+
cellRenderer?: (value: TValue) => ReactNode;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* How to extract the value from a row for comparison
|
|
41
|
+
* @default (row) => row.getValue(id)
|
|
42
|
+
*/
|
|
43
|
+
getRowValue?: (row: Row<TData>) => TValue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
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
|
|
52
|
+
*/
|
|
53
|
+
export function createCheckboxFilterAdapter<TData, TValue = string>(
|
|
54
|
+
config: CheckboxFilterAdapterConfig<TData, TValue>,
|
|
55
|
+
): FilterAdapter<TData, CheckboxFilterItem<TValue>[], TValue> {
|
|
56
|
+
const {
|
|
57
|
+
id,
|
|
58
|
+
label,
|
|
59
|
+
items,
|
|
60
|
+
renderer,
|
|
61
|
+
chipLabelTemplate = (item) => `${label}: ${item.label}`,
|
|
62
|
+
cellRenderer,
|
|
63
|
+
getRowValue = (row) => row.getValue(id),
|
|
64
|
+
} = config;
|
|
65
|
+
|
|
66
|
+
return createFilterAdapter<TData, CheckboxFilterItem<TValue>[], TValue>({
|
|
67
|
+
id,
|
|
68
|
+
label,
|
|
69
|
+
initialValue: [], // Start with nothing selected (show all)
|
|
70
|
+
|
|
71
|
+
filterFn: (row, columnId, filterValue) => {
|
|
72
|
+
// Empty selection = no filter = show all
|
|
73
|
+
if (filterValue.length === 0) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Otherwise, show only selected items
|
|
78
|
+
const rowValue = getRowValue(row);
|
|
79
|
+
return filterValue.some((item) => item.value === rowValue);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
cellRenderer,
|
|
83
|
+
|
|
84
|
+
toChips: (selectedItems) => {
|
|
85
|
+
// Generate chips for all selected items
|
|
86
|
+
return selectedItems.map((item) => ({
|
|
87
|
+
id: `${id}_${String(item.value)}`,
|
|
88
|
+
label: chipLabelTemplate(item),
|
|
89
|
+
metadata: {
|
|
90
|
+
key: id,
|
|
91
|
+
value: item.value,
|
|
92
|
+
},
|
|
93
|
+
}));
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
fromChip: (chip, currentValue) => {
|
|
97
|
+
return currentValue.filter((item) => item.value !== chip.metadata?.value);
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
getActiveFiltersCount: (selectedItems) => {
|
|
101
|
+
// Count = number of selected items (0 means none selected)
|
|
102
|
+
return selectedItems.length;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
renderFilter: (value, onChange) => {
|
|
106
|
+
return (
|
|
107
|
+
<CheckboxFilter
|
|
108
|
+
items={items}
|
|
109
|
+
renderer={renderer}
|
|
110
|
+
selectedItems={value}
|
|
111
|
+
onSelectionChange={onChange}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { Row } from '@tanstack/react-table';
|
|
2
|
+
import type { ChipItem } from '../../../ds-chip-group';
|
|
3
|
+
import type { FilterAdapter } from '../types/filter-adapter.types';
|
|
4
|
+
import { RangeFilter } from '../components/range-filter';
|
|
5
|
+
import { createFilterAdapter } from './create-filter-adapter';
|
|
6
|
+
|
|
7
|
+
export interface RangeValue {
|
|
8
|
+
from?: number;
|
|
9
|
+
to?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DualRangeFilterValue {
|
|
13
|
+
[fieldName: string]: RangeValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DualRangeFilterAdapterConfig<TData> {
|
|
17
|
+
/**
|
|
18
|
+
* Unique identifier (should match column accessorKey)
|
|
19
|
+
*/
|
|
20
|
+
id: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Display label for filter navigation
|
|
24
|
+
*/
|
|
25
|
+
label: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Field configurations: { fieldKey: displayLabel }
|
|
29
|
+
* Example: { running: 'Running', completed: 'Completed' }
|
|
30
|
+
*/
|
|
31
|
+
fields: Record<string, string>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional formatter for chip display
|
|
35
|
+
* @default (num) => num.toLocaleString('en-US')
|
|
36
|
+
*/
|
|
37
|
+
formatNumber?: (num: number) => string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* How to extract the value object from a row
|
|
41
|
+
* @default (row) => row.getValue(id)
|
|
42
|
+
*/
|
|
43
|
+
getRowValue?: (row: Row<TData>) => Record<string, number>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Factory function to create a dual-range filter adapter
|
|
48
|
+
* Handles filtering on multiple numeric ranges within a single column
|
|
49
|
+
*/
|
|
50
|
+
export function createDualRangeFilterAdapter<TData>(
|
|
51
|
+
config: DualRangeFilterAdapterConfig<TData>,
|
|
52
|
+
): FilterAdapter<TData, DualRangeFilterValue, Record<string, number>> {
|
|
53
|
+
const {
|
|
54
|
+
id,
|
|
55
|
+
label,
|
|
56
|
+
fields,
|
|
57
|
+
formatNumber = (num) => num.toLocaleString('en-US'),
|
|
58
|
+
getRowValue = (row) => row.getValue(id),
|
|
59
|
+
} = config;
|
|
60
|
+
|
|
61
|
+
const initialValue: DualRangeFilterValue = {};
|
|
62
|
+
Object.keys(fields).forEach((key) => {
|
|
63
|
+
initialValue[key] = {};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return createFilterAdapter<TData, DualRangeFilterValue, Record<string, number>>({
|
|
67
|
+
id,
|
|
68
|
+
label,
|
|
69
|
+
initialValue,
|
|
70
|
+
|
|
71
|
+
filterFn: (row, columnId, filterValue) => {
|
|
72
|
+
const rowValue = getRowValue(row);
|
|
73
|
+
|
|
74
|
+
// Check each field's range
|
|
75
|
+
for (const [fieldKey, range] of Object.entries(filterValue)) {
|
|
76
|
+
const hasFilter = range.from !== undefined || range.to !== undefined;
|
|
77
|
+
|
|
78
|
+
if (hasFilter) {
|
|
79
|
+
const fieldValue = rowValue[fieldKey];
|
|
80
|
+
const matchesFrom = range.from === undefined || fieldValue >= range.from;
|
|
81
|
+
const matchesTo = range.to === undefined || fieldValue <= range.to;
|
|
82
|
+
|
|
83
|
+
if (!matchesFrom || !matchesTo) {
|
|
84
|
+
return false; // AND logic: all ranges must match
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
cellRenderer: undefined, // Let the column definition handle rendering
|
|
93
|
+
|
|
94
|
+
toChips: (value) => {
|
|
95
|
+
const chips: ChipItem[] = [];
|
|
96
|
+
|
|
97
|
+
Object.entries(value).forEach(([fieldKey, range]) => {
|
|
98
|
+
const hasFilter = range.from !== undefined || range.to !== undefined;
|
|
99
|
+
|
|
100
|
+
if (hasFilter) {
|
|
101
|
+
const fieldLabel = fields[fieldKey] || fieldKey;
|
|
102
|
+
const fromText = range.from !== undefined ? formatNumber(range.from) : '';
|
|
103
|
+
const toText = range.to !== undefined ? formatNumber(range.to) : '';
|
|
104
|
+
|
|
105
|
+
chips.push({
|
|
106
|
+
id: `${id}_${fieldKey}`,
|
|
107
|
+
label: `${fieldLabel}: From ${fromText} to ${toText}`,
|
|
108
|
+
metadata: {
|
|
109
|
+
key: id,
|
|
110
|
+
field: fieldKey,
|
|
111
|
+
from: range.from,
|
|
112
|
+
to: range.to,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return chips;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
fromChip: (chip, currentValue) => {
|
|
122
|
+
const fieldKey = chip.metadata?.field as string;
|
|
123
|
+
if (!fieldKey) {
|
|
124
|
+
return currentValue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
...currentValue,
|
|
129
|
+
[fieldKey]: {},
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
getActiveFiltersCount: (value) => {
|
|
134
|
+
let count = 0;
|
|
135
|
+
Object.values(value).forEach((range) => {
|
|
136
|
+
if (range.from !== undefined || range.to !== undefined) {
|
|
137
|
+
count++;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return count;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
renderFilter: (value, onChange) => {
|
|
144
|
+
return (
|
|
145
|
+
<div>
|
|
146
|
+
{Object.entries(fields).map(([fieldKey, fieldLabel]) => (
|
|
147
|
+
<RangeFilter
|
|
148
|
+
key={fieldKey}
|
|
149
|
+
label={fieldLabel}
|
|
150
|
+
// This is not necessarily a redundant condition. It depends on `noUncheckedIndexedAccess`.
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
152
|
+
value={value[fieldKey] || {}}
|
|
153
|
+
onChange={(rangeValue) =>
|
|
154
|
+
onChange({
|
|
155
|
+
...value,
|
|
156
|
+
[fieldKey]: rangeValue,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
onClear={() =>
|
|
160
|
+
onChange({
|
|
161
|
+
...value,
|
|
162
|
+
[fieldKey]: {},
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
/>
|
|
166
|
+
))}
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { FilterAdapter } from '../types/filter-adapter.types';
|
|
3
|
+
|
|
4
|
+
export interface FilterAdapterConfig<TData, TFilterValue, TCellValue = unknown> {
|
|
5
|
+
/**
|
|
6
|
+
* Unique identifier (should match column accessorKey)
|
|
7
|
+
*/
|
|
8
|
+
id: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Display label for filter navigation
|
|
12
|
+
*/
|
|
13
|
+
label: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initial/default value for the filter
|
|
17
|
+
*/
|
|
18
|
+
initialValue: TFilterValue;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Filter function - determines if row matches filter value
|
|
22
|
+
*/
|
|
23
|
+
filterFn: FilterAdapter<TData, TFilterValue, TCellValue>['columnFilterFn'];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert filter value to display chips
|
|
27
|
+
*/
|
|
28
|
+
toChips: FilterAdapter<TData, TFilterValue, TCellValue>['toChips'];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Remove chip effect from filter value
|
|
32
|
+
*/
|
|
33
|
+
fromChip: FilterAdapter<TData, TFilterValue, TCellValue>['fromChip'];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Calculate active filter count
|
|
37
|
+
*/
|
|
38
|
+
getActiveFiltersCount: FilterAdapter<TData, TFilterValue, TCellValue>['getActiveFiltersCount'];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Render the filter UI
|
|
42
|
+
*/
|
|
43
|
+
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
|
+
*/
|
|
49
|
+
cellRenderer?: (value: TCellValue) => ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
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.
|
|
115
|
+
*/
|
|
116
|
+
export function createFilterAdapter<TData, TFilterValue, TCellValue = unknown>(
|
|
117
|
+
config: FilterAdapterConfig<TData, TFilterValue, TCellValue>,
|
|
118
|
+
): FilterAdapter<TData, TFilterValue, TCellValue> {
|
|
119
|
+
return {
|
|
120
|
+
id: config.id,
|
|
121
|
+
label: config.label,
|
|
122
|
+
initialValue: config.initialValue,
|
|
123
|
+
columnFilterFn: config.filterFn,
|
|
124
|
+
cellRenderer: config.cellRenderer,
|
|
125
|
+
toChips: config.toChips,
|
|
126
|
+
fromChip: config.fromChip,
|
|
127
|
+
getActiveFiltersCount: config.getActiveFiltersCount,
|
|
128
|
+
reset: () => config.initialValue,
|
|
129
|
+
renderFilter: config.renderFilter,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { createFilterAdapter } from './create-filter-adapter';
|
|
2
|
+
export type { FilterAdapterConfig } from './create-filter-adapter';
|
|
3
|
+
|
|
4
|
+
export { createCheckboxFilterAdapter } from './create-checkbox-filter-adapter';
|
|
5
|
+
export type { CheckboxFilterAdapterConfig } from './create-checkbox-filter-adapter';
|
|
6
|
+
|
|
7
|
+
export { createDualRangeFilterAdapter } from './create-dual-range-filter-adapter';
|
|
8
|
+
export type {
|
|
9
|
+
RangeValue,
|
|
10
|
+
DualRangeFilterValue,
|
|
11
|
+
DualRangeFilterAdapterConfig,
|
|
12
|
+
} from './create-dual-range-filter-adapter';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { DsCheckbox } from '../../../../ds-checkbox';
|
|
3
|
+
|
|
4
|
+
export interface CheckboxFilterItem<TValue = string> {
|
|
5
|
+
value: TValue;
|
|
6
|
+
label: string;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CheckboxFilterProps<TValue> {
|
|
11
|
+
items: CheckboxFilterItem<TValue>[];
|
|
12
|
+
renderer?: (item: CheckboxFilterItem<TValue>) => ReactNode;
|
|
13
|
+
selectedItems: CheckboxFilterItem<TValue>[];
|
|
14
|
+
onSelectionChange: (selectedItems: CheckboxFilterItem<TValue>[]) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const CheckboxFilter = <TValue,>({
|
|
18
|
+
items,
|
|
19
|
+
renderer,
|
|
20
|
+
selectedItems,
|
|
21
|
+
onSelectionChange,
|
|
22
|
+
}: CheckboxFilterProps<TValue>) => {
|
|
23
|
+
const handleCheckedChange = (selected: CheckboxFilterItem<TValue>, checked: boolean) => {
|
|
24
|
+
if (checked) {
|
|
25
|
+
onSelectionChange([...selectedItems, selected]);
|
|
26
|
+
} else {
|
|
27
|
+
onSelectionChange(selectedItems.filter((item) => item.value !== selected.value));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return items.map((item) => {
|
|
32
|
+
const label = renderer ? renderer(item) : item.label;
|
|
33
|
+
const checked = selectedItems.findIndex((selectedItem) => selectedItem.value === item.value) > -1;
|
|
34
|
+
return (
|
|
35
|
+
<DsCheckbox
|
|
36
|
+
key={JSON.stringify(item.value)}
|
|
37
|
+
label={label}
|
|
38
|
+
checked={checked}
|
|
39
|
+
onCheckedChange={(newState) => handleCheckedChange(item, newState === true)}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: 10px;
|
|
5
|
+
padding-bottom: 16px;
|
|
6
|
+
border-bottom: 1px solid var(--color-border);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.header {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 8px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.inputs {
|
|
16
|
+
display: flex;
|
|
17
|
+
gap: 16px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.inputGroup {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
flex: 1;
|
|
24
|
+
gap: 2px;
|
|
25
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { DsTypography } from '../../../../ds-typography';
|
|
3
|
+
import { DsButton } from '../../../../ds-button';
|
|
4
|
+
import { DsTextInput } from '../../../../ds-text-input';
|
|
5
|
+
import styles from './range-filter.module.scss';
|
|
6
|
+
|
|
7
|
+
export interface RangeFilterValue {
|
|
8
|
+
from?: number;
|
|
9
|
+
to?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface RangeFilterProps {
|
|
13
|
+
label: string;
|
|
14
|
+
value: RangeFilterValue;
|
|
15
|
+
onChange: (value: RangeFilterValue) => void;
|
|
16
|
+
onClear?: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const RangeFilter = ({ label, value, onChange, onClear }: RangeFilterProps) => {
|
|
20
|
+
const [localFrom, setLocalFrom] = useState(value.from?.toString() || '');
|
|
21
|
+
const [localTo, setLocalTo] = useState(value.to?.toString() || '');
|
|
22
|
+
|
|
23
|
+
const handleFromChange = (val: string) => {
|
|
24
|
+
setLocalFrom(val);
|
|
25
|
+
const numVal = val ? parseInt(val, 10) : undefined;
|
|
26
|
+
onChange({ from: numVal, to: value.to });
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleToChange = (val: string) => {
|
|
30
|
+
setLocalTo(val);
|
|
31
|
+
const numVal = val ? parseInt(val, 10) : undefined;
|
|
32
|
+
onChange({ from: value.from, to: numVal });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleClear = () => {
|
|
36
|
+
setLocalFrom('');
|
|
37
|
+
setLocalTo('');
|
|
38
|
+
if (onClear) {
|
|
39
|
+
onClear();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const hasValue = value.from !== undefined || value.to !== undefined;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className={styles.container}>
|
|
47
|
+
<div className={styles.header}>
|
|
48
|
+
<DsTypography variant="body-md-md">{label}</DsTypography>
|
|
49
|
+
{hasValue && onClear && (
|
|
50
|
+
<DsButton design="v1.2" buttonType="tertiary" size="small" onClick={handleClear}>
|
|
51
|
+
Clear
|
|
52
|
+
</DsButton>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
<div className={styles.inputs}>
|
|
56
|
+
<div className={styles.inputGroup}>
|
|
57
|
+
<DsTypography variant="body-sm-reg">From</DsTypography>
|
|
58
|
+
<DsTextInput
|
|
59
|
+
type="number"
|
|
60
|
+
placeholder=""
|
|
61
|
+
value={localFrom}
|
|
62
|
+
onValueChange={handleFromChange}
|
|
63
|
+
size="default"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<div className={styles.inputGroup}>
|
|
67
|
+
<DsTypography variant="body-sm-reg">To</DsTypography>
|
|
68
|
+
<DsTextInput
|
|
69
|
+
type="number"
|
|
70
|
+
placeholder=""
|
|
71
|
+
value={localTo}
|
|
72
|
+
onValueChange={handleToChange}
|
|
73
|
+
size="default"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { UseTableFiltersResult } from './use-table-filters';
|