@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
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode, useState } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import styles from './checkbox-filter.module.scss';
|
|
2
4
|
import { DsCheckbox } from '../../../../ds-checkbox';
|
|
5
|
+
import { DsTextInput } from '../../../../ds-text-input';
|
|
6
|
+
import { DsIcon } from '../../../../ds-icon';
|
|
3
7
|
|
|
4
8
|
export interface CheckboxFilterItem<TValue = string> {
|
|
5
9
|
value: TValue;
|
|
@@ -7,11 +11,30 @@ export interface CheckboxFilterItem<TValue = string> {
|
|
|
7
11
|
metadata?: Record<string, unknown>;
|
|
8
12
|
}
|
|
9
13
|
|
|
14
|
+
export interface CheckboxFilterLocale {
|
|
15
|
+
/** @default 'All' */
|
|
16
|
+
allLabel?: string;
|
|
17
|
+
/** @default 'Search' */
|
|
18
|
+
searchPlaceholder?: string;
|
|
19
|
+
/** @default 'No matches' */
|
|
20
|
+
noMatchesLabel?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
export interface CheckboxFilterProps<TValue> {
|
|
11
24
|
items: CheckboxFilterItem<TValue>[];
|
|
25
|
+
/** Custom row renderer; defaults to `item.label`. */
|
|
12
26
|
renderer?: (item: CheckboxFilterItem<TValue>) => ReactNode;
|
|
13
27
|
selectedItems: CheckboxFilterItem<TValue>[];
|
|
14
28
|
onSelectionChange: (selectedItems: CheckboxFilterItem<TValue>[]) => void;
|
|
29
|
+
/** Show a case-insensitive substring search above the list. @default false */
|
|
30
|
+
searchable?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Show an "All" toggle. Checked when every visible item is selected,
|
|
33
|
+
* indeterminate when some are, unchecked otherwise. @default false
|
|
34
|
+
*/
|
|
35
|
+
selectAll?: boolean;
|
|
36
|
+
locale?: CheckboxFilterLocale;
|
|
37
|
+
className?: string;
|
|
15
38
|
}
|
|
16
39
|
|
|
17
40
|
export const CheckboxFilter = <TValue,>({
|
|
@@ -19,7 +42,19 @@ export const CheckboxFilter = <TValue,>({
|
|
|
19
42
|
renderer,
|
|
20
43
|
selectedItems,
|
|
21
44
|
onSelectionChange,
|
|
45
|
+
searchable = false,
|
|
46
|
+
selectAll = false,
|
|
47
|
+
locale,
|
|
48
|
+
className,
|
|
22
49
|
}: CheckboxFilterProps<TValue>) => {
|
|
50
|
+
const { allLabel = 'All', searchPlaceholder = 'Search', noMatchesLabel = 'No matches' } = locale ?? {};
|
|
51
|
+
|
|
52
|
+
const [query, setQuery] = useState('');
|
|
53
|
+
|
|
54
|
+
const visibleItems = searchable
|
|
55
|
+
? items.filter((item) => item.label.toLowerCase().includes(query.trim().toLowerCase()))
|
|
56
|
+
: items;
|
|
57
|
+
|
|
23
58
|
const handleCheckedChange = (selected: CheckboxFilterItem<TValue>, checked: boolean) => {
|
|
24
59
|
if (checked) {
|
|
25
60
|
onSelectionChange([...selectedItems, selected]);
|
|
@@ -28,16 +63,60 @@ export const CheckboxFilter = <TValue,>({
|
|
|
28
63
|
}
|
|
29
64
|
};
|
|
30
65
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
42
|
-
|
|
66
|
+
const visibleSelectedCount = visibleItems.filter((item) =>
|
|
67
|
+
selectedItems.some((selected) => selected.value === item.value),
|
|
68
|
+
).length;
|
|
69
|
+
|
|
70
|
+
const allVisibleSelected = visibleItems.length > 0 && visibleSelectedCount === visibleItems.length;
|
|
71
|
+
const someVisibleSelected = visibleSelectedCount > 0 && !allVisibleSelected;
|
|
72
|
+
|
|
73
|
+
const handleSelectAllChange = (next: boolean | 'indeterminate') => {
|
|
74
|
+
const shouldSelect = next === true;
|
|
75
|
+
const visibleValues = new Set(visibleItems.map((item) => item.value));
|
|
76
|
+
const withoutVisible = selectedItems.filter((item) => !visibleValues.has(item.value));
|
|
77
|
+
onSelectionChange(shouldSelect ? [...withoutVisible, ...visibleItems] : withoutVisible);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className={classNames(styles.root, className)}>
|
|
82
|
+
{searchable && (
|
|
83
|
+
<DsTextInput
|
|
84
|
+
value={query}
|
|
85
|
+
placeholder={searchPlaceholder}
|
|
86
|
+
onValueChange={setQuery}
|
|
87
|
+
slots={{
|
|
88
|
+
startAdornment: <DsIcon icon="search" size="tiny" />,
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
<div className={styles.list} role="group">
|
|
94
|
+
{selectAll && visibleItems.length > 0 && (
|
|
95
|
+
<DsCheckbox
|
|
96
|
+
className={styles.checkboxItem}
|
|
97
|
+
label={allLabel}
|
|
98
|
+
checked={allVisibleSelected ? true : someVisibleSelected ? 'indeterminate' : false}
|
|
99
|
+
onCheckedChange={handleSelectAllChange}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{visibleItems.length === 0 && <div className={styles.empty}>{noMatchesLabel}</div>}
|
|
104
|
+
|
|
105
|
+
{visibleItems.map((item) => {
|
|
106
|
+
const label = renderer ? renderer(item) : item.label;
|
|
107
|
+
const checked = selectedItems.some((selected) => selected.value === item.value);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<DsCheckbox
|
|
111
|
+
key={JSON.stringify(item.value)}
|
|
112
|
+
className={styles.checkboxItem}
|
|
113
|
+
label={label}
|
|
114
|
+
checked={checked}
|
|
115
|
+
onCheckedChange={(next) => handleCheckedChange(item, next === true)}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
43
122
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { CheckboxFilter } from './checkbox-filter';
|
|
2
|
-
export type { CheckboxFilterProps, CheckboxFilterItem } from './checkbox-filter';
|
|
2
|
+
export type { CheckboxFilterProps, CheckboxFilterItem, CheckboxFilterLocale } from './checkbox-filter';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.trigger {
|
|
2
|
+
min-width: var(--icon-width-tiny);
|
|
3
|
+
padding: 0;
|
|
4
|
+
opacity: 0;
|
|
5
|
+
transition: opacity 0.2s;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.triggerActive,
|
|
9
|
+
.triggerOpen {
|
|
10
|
+
opacity: 1;
|
|
11
|
+
color: var(--icon-information-main, var(--color-dap-primary-500));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.content {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
gap: var(--xs);
|
|
18
|
+
min-width: 280px;
|
|
19
|
+
max-width: 320px;
|
|
20
|
+
padding: var(--standard);
|
|
21
|
+
background: var(--background);
|
|
22
|
+
border: 1px solid var(--border-secondary);
|
|
23
|
+
border-radius: 4px;
|
|
24
|
+
box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.15);
|
|
25
|
+
z-index: 1000;
|
|
26
|
+
|
|
27
|
+
&[hidden] {
|
|
28
|
+
display: none;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.body {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.footer {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: flex-end;
|
|
41
|
+
gap: var(--xs);
|
|
42
|
+
}
|
package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type MouseEvent, useState } from 'react';
|
|
2
|
+
import { Popover } from '@ark-ui/react/popover';
|
|
3
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
import styles from './column-filter-popover.module.scss';
|
|
6
|
+
import type { DsTableColumnFilterPopoverProps } from './column-filter-popover.types';
|
|
7
|
+
import { DsIcon } from '../../../../ds-icon';
|
|
8
|
+
import { DsButtonV3 } from '../../../../ds-button-v3';
|
|
9
|
+
|
|
10
|
+
// React portals bubble events through the React tree, so clicks inside the
|
|
11
|
+
// popover would otherwise reach the parent `<TableHead onClick={sortHandler}>`
|
|
12
|
+
// and toggle column sorting when applying or clearing the filter.
|
|
13
|
+
const stopPropagation = (event: MouseEvent) => event.stopPropagation();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Per-column filter popover anchored to a header cell: funnel trigger,
|
|
17
|
+
* adapter UI as the body, Clear / Save footer wired to `useTableFilters`.
|
|
18
|
+
*/
|
|
19
|
+
export const DsTableColumnFilterPopover = ({
|
|
20
|
+
children,
|
|
21
|
+
hasActiveFilter,
|
|
22
|
+
onApply,
|
|
23
|
+
onClear,
|
|
24
|
+
onCancel,
|
|
25
|
+
locale,
|
|
26
|
+
className,
|
|
27
|
+
}: DsTableColumnFilterPopoverProps) => {
|
|
28
|
+
const { triggerAriaLabel = 'Filter column', clearLabel = 'Clear', saveLabel = 'Save' } = locale ?? {};
|
|
29
|
+
|
|
30
|
+
const [open, setOpen] = useState(false);
|
|
31
|
+
|
|
32
|
+
const handleOpenChange = (details: { open: boolean }) => {
|
|
33
|
+
if (open && !details.open) {
|
|
34
|
+
onCancel();
|
|
35
|
+
}
|
|
36
|
+
setOpen(details.open);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleSave = () => {
|
|
40
|
+
onApply();
|
|
41
|
+
setOpen(false);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleClear = () => {
|
|
45
|
+
onClear();
|
|
46
|
+
setOpen(false);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Popover.Root
|
|
51
|
+
open={open}
|
|
52
|
+
onOpenChange={handleOpenChange}
|
|
53
|
+
positioning={{ placement: 'bottom-end', gutter: 4 }}
|
|
54
|
+
>
|
|
55
|
+
<Popover.Trigger asChild>
|
|
56
|
+
<DsButtonV3
|
|
57
|
+
variant="tertiary"
|
|
58
|
+
size="tiny"
|
|
59
|
+
className={classNames(
|
|
60
|
+
styles.trigger,
|
|
61
|
+
hasActiveFilter && styles.triggerActive,
|
|
62
|
+
open && styles.triggerOpen,
|
|
63
|
+
)}
|
|
64
|
+
aria-label={triggerAriaLabel}
|
|
65
|
+
data-active={hasActiveFilter ? 'true' : undefined}
|
|
66
|
+
onClick={(event) => event.stopPropagation()}
|
|
67
|
+
>
|
|
68
|
+
<DsIcon icon="filter_alt" variant="outlined" filled={hasActiveFilter} size="tiny" />
|
|
69
|
+
</DsButtonV3>
|
|
70
|
+
</Popover.Trigger>
|
|
71
|
+
|
|
72
|
+
<Portal>
|
|
73
|
+
<Popover.Positioner>
|
|
74
|
+
<Popover.Content className={classNames(styles.content, className)} onClick={stopPropagation}>
|
|
75
|
+
<div className={styles.body}>{children}</div>
|
|
76
|
+
<div className={styles.footer}>
|
|
77
|
+
<DsButtonV3 variant="secondary" size="small" onClick={handleClear}>
|
|
78
|
+
{clearLabel}
|
|
79
|
+
</DsButtonV3>
|
|
80
|
+
<DsButtonV3 variant="primary" size="small" onClick={handleSave}>
|
|
81
|
+
{saveLabel}
|
|
82
|
+
</DsButtonV3>
|
|
83
|
+
</div>
|
|
84
|
+
</Popover.Content>
|
|
85
|
+
</Popover.Positioner>
|
|
86
|
+
</Portal>
|
|
87
|
+
</Popover.Root>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ColumnFilterPopoverLocale {
|
|
4
|
+
/** @default 'Filter column' */
|
|
5
|
+
triggerAriaLabel?: string;
|
|
6
|
+
/** @default 'Clear' */
|
|
7
|
+
clearLabel?: string;
|
|
8
|
+
/** @default 'Save' */
|
|
9
|
+
saveLabel?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DsTableColumnFilterPopoverProps {
|
|
13
|
+
/** Body rendered above the Clear / Save footer. */
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
/** Whether the column has an applied filter; drives the trigger's active state. */
|
|
16
|
+
hasActiveFilter: boolean;
|
|
17
|
+
/** Commit the draft for this column. */
|
|
18
|
+
onApply: () => void;
|
|
19
|
+
/** Clear the draft and the applied value for this column. */
|
|
20
|
+
onClear: () => void;
|
|
21
|
+
/** Discard the draft when the popover closes without Save. */
|
|
22
|
+
onCancel: () => void;
|
|
23
|
+
locale?: ColumnFilterPopoverLocale;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|