@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,234 @@
|
|
|
1
|
+
import { type ReactNode, useState } from 'react';
|
|
2
|
+
import type { CellContext, ColumnDef } from '@tanstack/react-table';
|
|
3
|
+
import type { ChipItem } from '../../../ds-chip-group';
|
|
4
|
+
import type {
|
|
5
|
+
AnyAdapter,
|
|
6
|
+
ColumnFilterState,
|
|
7
|
+
FilterAdapter,
|
|
8
|
+
FilterNavItem,
|
|
9
|
+
FilterState,
|
|
10
|
+
} from '../types/filter-adapter.types';
|
|
11
|
+
|
|
12
|
+
export interface UseTableFiltersResult<TData, TValue> {
|
|
13
|
+
/**
|
|
14
|
+
* Current filter state
|
|
15
|
+
*/
|
|
16
|
+
filterState: FilterState<TValue>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Column filters for TanStack Table
|
|
20
|
+
*/
|
|
21
|
+
columnFilters: ColumnFilterState<TValue>[];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Filter chips for display
|
|
25
|
+
*/
|
|
26
|
+
filterChips: ChipItem[];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Filter navigation items with counts
|
|
30
|
+
*/
|
|
31
|
+
filterNavItems: FilterNavItem[];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Enhanced column definitions with filter functions
|
|
35
|
+
*/
|
|
36
|
+
enhancedColumns: ColumnDef<TData>[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Handlers
|
|
40
|
+
*/
|
|
41
|
+
handlers: {
|
|
42
|
+
/**
|
|
43
|
+
* Update a specific filter's value
|
|
44
|
+
*/
|
|
45
|
+
updateFilter: (filterId: string, value: TValue) => void;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Apply all filters (converts to TanStack format)
|
|
49
|
+
*/
|
|
50
|
+
applyFilters: () => void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Clear all filters
|
|
54
|
+
*/
|
|
55
|
+
clearAll: () => void;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Delete a specific chip
|
|
59
|
+
*/
|
|
60
|
+
deleteChip: (chip: ChipItem) => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render function for filter modal content
|
|
65
|
+
*/
|
|
66
|
+
renderFilterContent: (selectedFilter: FilterNavItem) => ReactNode;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Hook to orchestrate table filtering with adapters
|
|
71
|
+
* Manages filter state, generates chips, handles column definitions
|
|
72
|
+
*
|
|
73
|
+
* @param filterAdapters Array of filter adapters
|
|
74
|
+
* @param baseColumns Base column definitions (optional)
|
|
75
|
+
* @returns Complete filter management system
|
|
76
|
+
*/
|
|
77
|
+
export function useTableFilters<TData, TValue, TCellValue>(
|
|
78
|
+
filterAdapters: FilterAdapter<TData, TValue, TCellValue>[] | AnyAdapter[],
|
|
79
|
+
baseColumns?: ColumnDef<TData>[],
|
|
80
|
+
): UseTableFiltersResult<TData, TValue> {
|
|
81
|
+
// Internal variable just to avoid assignment issues of `any`.
|
|
82
|
+
const _filterAdapters = filterAdapters as FilterAdapter<TData, TValue, TCellValue>[];
|
|
83
|
+
|
|
84
|
+
// Initialize filter state from adapters
|
|
85
|
+
const initialState: FilterState<TValue> = {};
|
|
86
|
+
_filterAdapters.forEach((adapter) => {
|
|
87
|
+
initialState[adapter.id] = adapter.initialValue;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const [filterState, setFilterState] = useState<FilterState<TValue>>(initialState);
|
|
91
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFilterState<TValue>[]>([]);
|
|
92
|
+
const [filterChips, setFilterChips] = useState<ChipItem[]>([]);
|
|
93
|
+
|
|
94
|
+
// Generate filter nav items with counts (updates as user changes filters in modal)
|
|
95
|
+
const filterNavItems: FilterNavItem[] = _filterAdapters.map((adapter) => ({
|
|
96
|
+
id: adapter.id,
|
|
97
|
+
label: adapter.label,
|
|
98
|
+
count: adapter.getActiveFiltersCount(filterState[adapter.id]),
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
// Enhance column definitions with filter functions and renderers
|
|
102
|
+
const enhancedColumns: ColumnDef<TData>[] = !baseColumns
|
|
103
|
+
? []
|
|
104
|
+
: baseColumns.map((col) => {
|
|
105
|
+
const adapter = _filterAdapters.find((a) => a.id === col.id);
|
|
106
|
+
|
|
107
|
+
if (adapter) {
|
|
108
|
+
const cellRenderer = adapter.cellRenderer;
|
|
109
|
+
return {
|
|
110
|
+
...col,
|
|
111
|
+
filterFn: adapter.columnFilterFn,
|
|
112
|
+
...(cellRenderer && {
|
|
113
|
+
cell: (info: CellContext<TData, TCellValue>) => cellRenderer(info.getValue()),
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return col;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Handlers
|
|
122
|
+
const updateFilter = (filterId: string, value: TValue) => {
|
|
123
|
+
setFilterState((prev) => ({
|
|
124
|
+
...prev,
|
|
125
|
+
[filterId]: value,
|
|
126
|
+
}));
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const applyFilters = () => {
|
|
130
|
+
const filters: ColumnFilterState<TValue>[] = [];
|
|
131
|
+
const chips: ChipItem[] = [];
|
|
132
|
+
|
|
133
|
+
_filterAdapters.forEach((adapter) => {
|
|
134
|
+
const value = filterState[adapter.id];
|
|
135
|
+
|
|
136
|
+
if (adapter.getActiveFiltersCount(value) > 0) {
|
|
137
|
+
filters.push({
|
|
138
|
+
id: adapter.id,
|
|
139
|
+
value,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Generate chips from current filter value
|
|
144
|
+
const adapterChips = adapter.toChips(value);
|
|
145
|
+
chips.push(...adapterChips);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
setColumnFilters(filters);
|
|
149
|
+
setFilterChips(chips);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const clearAll = () => {
|
|
153
|
+
const resetState: FilterState<TValue> = {};
|
|
154
|
+
_filterAdapters.forEach((adapter) => {
|
|
155
|
+
resetState[adapter.id] = adapter.reset();
|
|
156
|
+
});
|
|
157
|
+
setFilterState(resetState);
|
|
158
|
+
setColumnFilters([]);
|
|
159
|
+
setFilterChips([]);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const deleteChip = (chip: ChipItem) => {
|
|
163
|
+
const filterKey = typeof chip.metadata?.key === 'string' ? chip.metadata.key : undefined;
|
|
164
|
+
if (!filterKey) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const adapter = _filterAdapters.find((a) => a.id === filterKey);
|
|
169
|
+
if (!adapter) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const currentValue = filterState[filterKey];
|
|
174
|
+
const newValue = adapter.fromChip(chip, currentValue);
|
|
175
|
+
|
|
176
|
+
// Regenerate chips to check if this was the last one
|
|
177
|
+
const chips: ChipItem[] = [];
|
|
178
|
+
_filterAdapters.forEach((adapter) => {
|
|
179
|
+
const value = adapter.id === filterKey ? newValue : filterState[adapter.id];
|
|
180
|
+
const adapterChips = adapter.toChips(value);
|
|
181
|
+
chips.push(...adapterChips);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// If no chips left, clear all filters
|
|
185
|
+
if (chips.length === 0) {
|
|
186
|
+
clearAll();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Otherwise, update state and filters
|
|
191
|
+
setFilterState((prev) => ({
|
|
192
|
+
...prev,
|
|
193
|
+
[filterKey]: newValue,
|
|
194
|
+
}));
|
|
195
|
+
|
|
196
|
+
// Update column filters
|
|
197
|
+
if (adapter.getActiveFiltersCount(newValue) === 0) {
|
|
198
|
+
setColumnFilters((prev) => prev.filter((cf) => cf.id !== filterKey));
|
|
199
|
+
} else {
|
|
200
|
+
setColumnFilters((prev) =>
|
|
201
|
+
prev.map((cf) => (cf.id === filterKey ? { id: filterKey, value: newValue } : cf)),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
setFilterChips(chips);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const renderFilterContent = (selectedFilter: FilterNavItem) => {
|
|
209
|
+
const adapter = _filterAdapters.find((a) => a.id === selectedFilter.id);
|
|
210
|
+
if (!adapter) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const value = filterState[adapter.id];
|
|
215
|
+
const onChange = (newValue: TValue) => updateFilter(adapter.id, newValue);
|
|
216
|
+
|
|
217
|
+
return adapter.renderFilter(value, onChange);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
filterState,
|
|
222
|
+
columnFilters,
|
|
223
|
+
filterChips,
|
|
224
|
+
filterNavItems,
|
|
225
|
+
enhancedColumns,
|
|
226
|
+
handlers: {
|
|
227
|
+
updateFilter,
|
|
228
|
+
applyFilters,
|
|
229
|
+
clearAll,
|
|
230
|
+
deleteChip,
|
|
231
|
+
},
|
|
232
|
+
renderFilterContent,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
*/
|
|
45
|
+
|
|
46
|
+
export * from './adapters';
|
|
47
|
+
export * from './types/filter-adapter.types';
|
|
48
|
+
export * from './hooks';
|
|
49
|
+
export type { CheckboxFilterItem, CheckboxFilterProps } from './components/checkbox-filter';
|
|
50
|
+
export type { RangeFilterValue, RangeFilterProps } from './components/range-filter';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { Row } from '@tanstack/react-table';
|
|
3
|
+
import type { ChipItem } from '../../../ds-chip-group';
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
export type AnyAdapter = FilterAdapter<any, any, any>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base filter adapter interface that all filters must implement
|
|
10
|
+
* This provides a consistent contract for filter behavior
|
|
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)
|
|
15
|
+
*/
|
|
16
|
+
export interface FilterAdapter<TData, TFilterValue, TCellValue = unknown> {
|
|
17
|
+
/**
|
|
18
|
+
* Unique identifier for the filter (should match column accessorKey)
|
|
19
|
+
*/
|
|
20
|
+
id: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Display label for the filter in the navigation
|
|
24
|
+
*/
|
|
25
|
+
label: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initial/default value for the filter state
|
|
29
|
+
*/
|
|
30
|
+
initialValue: TFilterValue;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* TanStack Table filter function
|
|
34
|
+
* Determines if a row matches the current filter value
|
|
35
|
+
*/
|
|
36
|
+
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
|
+
*/
|
|
43
|
+
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
|
+
/**
|
|
58
|
+
* Calculate how many active filters are applied
|
|
59
|
+
* Used for the count indicator in filter navigation
|
|
60
|
+
* Can also be used to check if filter is active (count > 0)
|
|
61
|
+
*/
|
|
62
|
+
getActiveFiltersCount: (value: TFilterValue) => number;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reset filter to initial state
|
|
66
|
+
*/
|
|
67
|
+
reset: () => TFilterValue;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Render the filter UI component
|
|
71
|
+
* Receives current value and onChange callback
|
|
72
|
+
*/
|
|
73
|
+
renderFilter: (value: TFilterValue, onChange: (value: TFilterValue) => void) => ReactNode;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Filter navigation item used to render filter tabs in the UI
|
|
78
|
+
*/
|
|
79
|
+
export interface FilterNavItem {
|
|
80
|
+
/**
|
|
81
|
+
* Unique identifier for the filter (matches FilterAdapter.id)
|
|
82
|
+
*/
|
|
83
|
+
id: string;
|
|
84
|
+
/**
|
|
85
|
+
* Display label for the filter tab
|
|
86
|
+
*/
|
|
87
|
+
label: string;
|
|
88
|
+
/**
|
|
89
|
+
* Optional count badge to display active filters
|
|
90
|
+
*/
|
|
91
|
+
count?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Whether this filter is disabled
|
|
94
|
+
*/
|
|
95
|
+
disabled?: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Filter state managed by useTableFilters hook
|
|
100
|
+
*/
|
|
101
|
+
export interface FilterState<TValue> {
|
|
102
|
+
[filterId: string]: TValue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Column filter for TanStack Table
|
|
107
|
+
*/
|
|
108
|
+
export interface ColumnFilterState<TValue> {
|
|
109
|
+
id: string;
|
|
110
|
+
value: TValue;
|
|
111
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
type DragEndEvent,
|
|
4
|
+
type UniqueIdentifier,
|
|
5
|
+
closestCenter,
|
|
6
|
+
DndContext,
|
|
7
|
+
KeyboardSensor,
|
|
8
|
+
MouseSensor,
|
|
9
|
+
TouchSensor,
|
|
10
|
+
useSensor,
|
|
11
|
+
useSensors,
|
|
12
|
+
} from '@dnd-kit/core';
|
|
13
|
+
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
14
|
+
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
|
15
|
+
|
|
16
|
+
export interface UseDragAndDropResult {
|
|
17
|
+
DragWrapper: React.ComponentType<React.PropsWithChildren>;
|
|
18
|
+
SortableWrapper: React.ComponentType<React.PropsWithChildren>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UseDragAndDropOptions<T> {
|
|
22
|
+
isDragEnabled: boolean;
|
|
23
|
+
onOrderChange?: (newData: T[]) => void;
|
|
24
|
+
items: T[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type DragWrapperProps = React.PropsWithChildren<{
|
|
28
|
+
onDragEnd: (event: DragEndEvent) => void;
|
|
29
|
+
sensors: ReturnType<typeof useSensors>;
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
const DragWrapper = ({ children, onDragEnd, sensors }: DragWrapperProps) => (
|
|
33
|
+
<DndContext
|
|
34
|
+
collisionDetection={closestCenter}
|
|
35
|
+
modifiers={[restrictToVerticalAxis]}
|
|
36
|
+
onDragEnd={onDragEnd}
|
|
37
|
+
sensors={sensors}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</DndContext>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
type SortableWrapperProps = React.PropsWithChildren<{
|
|
44
|
+
items: UniqueIdentifier[];
|
|
45
|
+
}>;
|
|
46
|
+
|
|
47
|
+
const SortableWrapper = ({ children, items }: SortableWrapperProps) => (
|
|
48
|
+
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
|
49
|
+
{children}
|
|
50
|
+
</SortableContext>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export function useDragAndDrop<T extends { id: string }>({
|
|
54
|
+
isDragEnabled,
|
|
55
|
+
onOrderChange,
|
|
56
|
+
items,
|
|
57
|
+
}: UseDragAndDropOptions<T>): UseDragAndDropResult {
|
|
58
|
+
const dataIds = React.useMemo(() => items.map((row) => row.id), [items]);
|
|
59
|
+
|
|
60
|
+
const sensors = useSensors(
|
|
61
|
+
useSensor(MouseSensor, {}),
|
|
62
|
+
useSensor(TouchSensor, {}),
|
|
63
|
+
useSensor(KeyboardSensor, {}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const handleDragEnd = React.useCallback(
|
|
67
|
+
(event: DragEndEvent) => {
|
|
68
|
+
const { active, over } = event;
|
|
69
|
+
if (active.id !== over?.id) {
|
|
70
|
+
const oldIndex = items.findIndex((row) => row.id === active.id);
|
|
71
|
+
const newIndex = items.findIndex((row) => row.id === over?.id);
|
|
72
|
+
if (oldIndex !== -1 && newIndex !== -1) {
|
|
73
|
+
const newData = arrayMove(items, oldIndex, newIndex);
|
|
74
|
+
onOrderChange?.(newData);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[items, onOrderChange],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const DragWrapperComponent = React.useMemo(
|
|
82
|
+
() =>
|
|
83
|
+
isDragEnabled
|
|
84
|
+
? ({ children }: React.PropsWithChildren) => (
|
|
85
|
+
<DragWrapper onDragEnd={handleDragEnd} sensors={sensors}>
|
|
86
|
+
{children}
|
|
87
|
+
</DragWrapper>
|
|
88
|
+
)
|
|
89
|
+
: React.Fragment,
|
|
90
|
+
[isDragEnabled, handleDragEnd, sensors],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const SortableWrapperComponent = React.useMemo(
|
|
94
|
+
() =>
|
|
95
|
+
isDragEnabled
|
|
96
|
+
? ({ children }: React.PropsWithChildren) => (
|
|
97
|
+
<SortableWrapper items={dataIds}>{children}</SortableWrapper>
|
|
98
|
+
)
|
|
99
|
+
: React.Fragment,
|
|
100
|
+
[isDragEnabled, dataIds],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
DragWrapper: DragWrapperComponent,
|
|
105
|
+
SortableWrapper: SortableWrapperComponent,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
$border-radius: 4px;
|
|
2
|
+
$border-width: 1px;
|
|
3
|
+
$virtualized-height: 600px;
|
|
4
|
+
$expand-column-width: 40px;
|
|
5
|
+
$select-column-width: 40px;
|
|
6
|
+
$reorder-column-width: 60px;
|
|
7
|
+
$container-margin-vertical: 1rem;
|
|
8
|
+
$filter-input-gap: 0.5rem;
|
|
9
|
+
$filter-input-max-width: 24rem;
|
|
10
|
+
$header-sort-gap: 0.5rem;
|
|
11
|
+
$empty-state-height: 6rem;
|
|
12
|
+
|
|
13
|
+
$hover-bg-color: var(--color-background-secondary);
|
|
14
|
+
$hover-border-color: var(--color-border);
|
|
15
|
+
$expanded-row-bg-color: var(--color-background-secondary);
|
|
16
|
+
$expand-toggle-hover-bg-color: var(--color-background-action-hover-weak);
|
|
17
|
+
$expand-toggle-icon-color: var(--color-icon-information-main);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@use '../../../../styles/utilities' as utils;
|
|
2
|
+
@use '../variables' as vars;
|
|
3
|
+
|
|
4
|
+
.checkboxContainer {
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
width: fit-content;
|
|
9
|
+
margin: auto;
|
|
10
|
+
height: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.pageButtonIcon {
|
|
14
|
+
color: vars.$expand-toggle-icon-color;
|
|
15
|
+
@extend .rotate;
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import { defaultColumnSizing } from '@tanstack/react-table';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates the appropriate style object for a table column/cell based on its size and virtualization state
|
|
6
|
+
*
|
|
7
|
+
* @param columnSize - The size of the column
|
|
8
|
+
* @param virtualized - Whether the table is virtualized
|
|
9
|
+
* @param isLastColumn - Whether this is the last column in the row
|
|
10
|
+
* @returns Style object or undefined if no custom styling is needed
|
|
11
|
+
*/
|
|
12
|
+
export const getColumnStyle = (
|
|
13
|
+
columnSize: number,
|
|
14
|
+
virtualized?: boolean,
|
|
15
|
+
isLastColumn: boolean = false,
|
|
16
|
+
): CSSProperties | undefined => {
|
|
17
|
+
const hasCustomSize = columnSize !== defaultColumnSizing.size;
|
|
18
|
+
if (!hasCustomSize) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (virtualized) {
|
|
23
|
+
return {
|
|
24
|
+
flexBasis: `${String(columnSize)}px`,
|
|
25
|
+
flexGrow: isLastColumn ? 1 : 0,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { width: columnSize };
|
|
30
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ColumnDef } from '@tanstack/react-table';
|
|
2
|
+
|
|
3
|
+
type Args = {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
columns: ColumnDef<any, any>[];
|
|
6
|
+
selectable?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// In virtualized tables, the rows should all be positioned at the top with a `translateY` offset.
|
|
10
|
+
// We're using a Grid for this rather than `position: absolute` to ensure that each cell aligns with
|
|
11
|
+
// other cells in the same column (mimicking the native table behavior).
|
|
12
|
+
export function createColumnsGridTemplate({ columns, selectable }: Args): string {
|
|
13
|
+
return columns
|
|
14
|
+
.reduce<string[]>(
|
|
15
|
+
(acc, col) => {
|
|
16
|
+
acc.push(col.size ? `${String(col.size)}px` : 'auto');
|
|
17
|
+
|
|
18
|
+
return acc;
|
|
19
|
+
},
|
|
20
|
+
selectable ? ['max-content'] : [],
|
|
21
|
+
)
|
|
22
|
+
.join(' ');
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Ref, RefCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
export const mergeRefs = <T>(...refs: Array<Ref<T> | undefined>): RefCallback<T> => {
|
|
4
|
+
return (value) => {
|
|
5
|
+
refs
|
|
6
|
+
.filter((r) => !!r)
|
|
7
|
+
.forEach((ref) => {
|
|
8
|
+
if (typeof ref === 'function') {
|
|
9
|
+
ref(value);
|
|
10
|
+
} else {
|
|
11
|
+
ref.current = value;
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { SortingState } from '@tanstack/react-table';
|
|
2
|
+
import type { Status } from '../ds-table.stories';
|
|
3
|
+
|
|
4
|
+
export type Person = {
|
|
5
|
+
id: string;
|
|
6
|
+
firstName: string;
|
|
7
|
+
lastName: string;
|
|
8
|
+
age: number;
|
|
9
|
+
visits: number;
|
|
10
|
+
status: Status;
|
|
11
|
+
progress: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface GeneratedDataResult {
|
|
15
|
+
data: Person[];
|
|
16
|
+
meta: {
|
|
17
|
+
totalRowCount: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generates mock data for table stories with pagination support
|
|
23
|
+
*/
|
|
24
|
+
export const generatePersonData = (
|
|
25
|
+
start: number,
|
|
26
|
+
size: number,
|
|
27
|
+
sorting: SortingState,
|
|
28
|
+
totalRows: number = 10_000,
|
|
29
|
+
): GeneratedDataResult => {
|
|
30
|
+
const firstNames = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Henry', 'Ivy', 'Jack'];
|
|
31
|
+
const lastNames = [
|
|
32
|
+
'Smith',
|
|
33
|
+
'Johnson',
|
|
34
|
+
'Williams',
|
|
35
|
+
'Brown',
|
|
36
|
+
'Jones',
|
|
37
|
+
'Garcia',
|
|
38
|
+
'Miller',
|
|
39
|
+
'Davis',
|
|
40
|
+
'Rodriguez',
|
|
41
|
+
'Martinez',
|
|
42
|
+
];
|
|
43
|
+
const statuses: Status[] = ['single', 'relationship', 'complicated'];
|
|
44
|
+
|
|
45
|
+
// Generate all data first for sorting
|
|
46
|
+
const allData: Person[] = Array.from({ length: totalRows }).map((_, index) => {
|
|
47
|
+
const i = index + 1;
|
|
48
|
+
return {
|
|
49
|
+
id: i.toString(),
|
|
50
|
+
firstName: firstNames[i % firstNames.length],
|
|
51
|
+
lastName: lastNames[i % lastNames.length],
|
|
52
|
+
age: 20 + (i % 50),
|
|
53
|
+
visits: Math.floor(Math.random() * 500) + 1,
|
|
54
|
+
status: statuses[i % statuses.length],
|
|
55
|
+
progress: Math.floor(Math.random() * 100) + 1,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Apply sorting if provided
|
|
60
|
+
const sortedData = [...allData];
|
|
61
|
+
if (sorting.length) {
|
|
62
|
+
const sort = sorting[0];
|
|
63
|
+
const { id, desc } = sort;
|
|
64
|
+
sortedData.sort((a, b) => {
|
|
65
|
+
const aValue = a[id as keyof Person];
|
|
66
|
+
const bValue = b[id as keyof Person];
|
|
67
|
+
if (desc) {
|
|
68
|
+
return aValue < bValue ? 1 : -1;
|
|
69
|
+
}
|
|
70
|
+
return aValue > bValue ? 1 : -1;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Return paginated slice
|
|
75
|
+
return {
|
|
76
|
+
data: sortedData.slice(start, start + size),
|
|
77
|
+
meta: {
|
|
78
|
+
totalRowCount: sortedData.length,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Simulates an API call with network delay
|
|
85
|
+
*/
|
|
86
|
+
export const simulateApiCall = async <T>(dataGenerator: () => T, delayMs: number = 200): Promise<T> => {
|
|
87
|
+
await delay(delayMs);
|
|
88
|
+
return dataGenerator();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|