@scalepad/ui 0.1.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/.ai/rules/date-handling.md +39 -0
- package/.ai/rules/figma-design-system.md +372 -0
- package/.ai/rules/figma-lm-design-system-keys.md +680 -0
- package/.ai/rules/file-extensions.md +13 -0
- package/.ai/rules/modal-confirmation-mutation.md +56 -0
- package/.ai/rules/react-hooks.md +29 -0
- package/.ai/rules/styling.md +83 -0
- package/AGENTS.md +37 -0
- package/README.md +125 -0
- package/figma.config.json +9 -0
- package/package.json +127 -0
- package/scripts/install-ai-rules.mjs +136 -0
- package/src/ThemeProvider.tsx +57 -0
- package/src/charts.ts +32 -0
- package/src/components/ActionCard/ActionCard.css.ts +60 -0
- package/src/components/ActionCard/ActionCard.tsx +154 -0
- package/src/components/ActionCard/index.ts +2 -0
- package/src/components/Anchor/Anchor.tsx +47 -0
- package/src/components/Anchor/index.ts +2 -0
- package/src/components/AppliedFiltersManagerBar/AppliedFiltersManagerBar.tsx +105 -0
- package/src/components/AppliedFiltersManagerBar/FilterBadge.css.ts +23 -0
- package/src/components/AppliedFiltersManagerBar/FilterBadge.tsx +50 -0
- package/src/components/AppliedFiltersManagerBar/index.ts +5 -0
- package/src/components/Badge/Badge.css.ts +72 -0
- package/src/components/Badge/Badge.figma.tsx +43 -0
- package/src/components/Badge/Badge.tsx +159 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/BreadCrumb/BreadCrumb.tsx +62 -0
- package/src/components/BreadCrumb/index.ts +2 -0
- package/src/components/BulkActionBar/BulkActionBar.css.ts +26 -0
- package/src/components/BulkActionBar/BulkActionBar.tsx +164 -0
- package/src/components/BulkActionBar/index.ts +2 -0
- package/src/components/Button/Button.css.ts +272 -0
- package/src/components/Button/Button.figma.tsx +74 -0
- package/src/components/Button/Button.tsx +84 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Charts/ChartTooltip.figma.tsx +33 -0
- package/src/components/Charts/ChartTooltip.tsx +101 -0
- package/src/components/Charts/MiniBarSparkline.tsx +75 -0
- package/src/components/Charts/StackedPatternBarChart.tsx +494 -0
- package/src/components/Charts/TrendAreaChart.css.ts +23 -0
- package/src/components/Charts/TrendAreaChart.tsx +210 -0
- package/src/components/Charts/index.ts +12 -0
- package/src/components/CodePanel/CodePanel.css.ts +113 -0
- package/src/components/CodePanel/CodePanel.tsx +121 -0
- package/src/components/CodePanel/index.ts +2 -0
- package/src/components/CommentComposer/CommentComposer.css.ts +60 -0
- package/src/components/CommentComposer/CommentComposer.tsx +181 -0
- package/src/components/CommentComposer/index.ts +2 -0
- package/src/components/ConfirmationModal/ConfirmationModal.tsx +149 -0
- package/src/components/ConfirmationModal/index.ts +2 -0
- package/src/components/ConfirmationTooltip/ConfirmationTooltip.tsx +132 -0
- package/src/components/ConfirmationTooltip/index.ts +2 -0
- package/src/components/DataDialog.figma.tsx +33 -0
- package/src/components/DataDialog.tsx +46 -0
- package/src/components/DataTable/DataTable.tsx +1042 -0
- package/src/components/DataTable/RowExpandToggle.tsx +105 -0
- package/src/components/DataTable/RowGroupHeader.tsx +190 -0
- package/src/components/DataTable/createActionsColumn.tsx +86 -0
- package/src/components/DataTable/index.ts +25 -0
- package/src/components/DatePicker/CustomRangePicker.tsx +59 -0
- package/src/components/DatePicker/DateInput.tsx +329 -0
- package/src/components/DatePicker/DateNavigator.tsx +486 -0
- package/src/components/DatePicker/DatePicker.tsx +242 -0
- package/src/components/DatePicker/MonthlyRangePicker.tsx +231 -0
- package/src/components/DatePicker/QuarterlyRangePicker.tsx +224 -0
- package/src/components/DatePicker/QuickPicksSidebar.tsx +242 -0
- package/src/components/DatePicker/YearlyRangePicker.tsx +171 -0
- package/src/components/DatePicker/index.ts +7 -0
- package/src/components/DatePicker/types.ts +12 -0
- package/src/components/DesignSystemPrimitives/FluidGrid.tsx +44 -0
- package/src/components/DesignSystemPrimitives/InteractivePrimitives.tsx +177 -0
- package/src/components/DesignSystemPrimitives/LayoutPrimitives.tsx +220 -0
- package/src/components/DesignSystemPrimitives/LayoutPrimitives.types.tsx +15 -0
- package/src/components/DesignSystemPrimitives/SurfacePrimitives.tsx +46 -0
- package/src/components/DesignSystemPrimitives/index.ts +55 -0
- package/src/components/Details/Details.css.ts +74 -0
- package/src/components/Details/Details.tsx +140 -0
- package/src/components/Details/index.ts +2 -0
- package/src/components/DownloadCard/DownloadCard.css.ts +22 -0
- package/src/components/DownloadCard/DownloadCard.tsx +63 -0
- package/src/components/DownloadCard/index.ts +2 -0
- package/src/components/Drawer/Drawer.css.ts +32 -0
- package/src/components/Drawer/Drawer.tsx +236 -0
- package/src/components/Drawer/hooks/useDetailDrawer.ts +61 -0
- package/src/components/Drawer/hooks/useDetailDrawerNavigation.ts +125 -0
- package/src/components/Drawer/hooks/useDetailDrawerNavigationContext.ts +66 -0
- package/src/components/EditableRichText/EditableRichText.css.ts +72 -0
- package/src/components/EditableRichText/EditableRichText.tsx +324 -0
- package/src/components/EditableRichText/index.ts +2 -0
- package/src/components/EditableSelect/EditableSelect.css.ts +62 -0
- package/src/components/EditableSelect/EditableSelect.tsx +224 -0
- package/src/components/EditableSelect/index.ts +2 -0
- package/src/components/EditableText/EditableText.tsx +377 -0
- package/src/components/EditableText/index.ts +2 -0
- package/src/components/EmptyState/EmptyState.figma.tsx +33 -0
- package/src/components/EmptyState/EmptyState.tsx +230 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/ErrorBoundary.tsx +135 -0
- package/src/components/ErrorState/ErrorState.tsx +197 -0
- package/src/components/ErrorState/index.ts +2 -0
- package/src/components/FeatureCard.tsx +42 -0
- package/src/components/FilterMenu/FilterMenu.figma.tsx +30 -0
- package/src/components/FilterMenu/FilterMenu.tsx +198 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/BooleanFilterSubmenu.tsx +46 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/SearchableFilterSubmenu.tsx +239 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/index.ts +8 -0
- package/src/components/FilterMenu/defaultFilterSchemas.ts +63 -0
- package/src/components/FilterMenu/helpers.ts +115 -0
- package/src/components/FilterMenu/index.ts +35 -0
- package/src/components/FilterMenu/types.ts +101 -0
- package/src/components/IconButton/IconButton.css.ts +272 -0
- package/src/components/IconButton/IconButton.figma.tsx +47 -0
- package/src/components/IconButton/IconButton.tsx +72 -0
- package/src/components/IconButton/README.md +230 -0
- package/src/components/IconButton/index.ts +2 -0
- package/src/components/InfiniteScrollSentinel.tsx +86 -0
- package/src/components/InfiniteScrollTrigger.tsx +78 -0
- package/src/components/InfoCard.figma.tsx +47 -0
- package/src/components/InfoCard.tsx +216 -0
- package/src/components/KbdHint/KbdHint.tsx +23 -0
- package/src/components/KbdHint/index.ts +2 -0
- package/src/components/LabeledField/LabeledField.tsx +21 -0
- package/src/components/LabeledField/index.ts +2 -0
- package/src/components/LookupSelect/LookupSelect.css.ts +149 -0
- package/src/components/LookupSelect/LookupSelect.tsx +325 -0
- package/src/components/LookupSelect/index.ts +2 -0
- package/src/components/Menu/Menu.css.ts +89 -0
- package/src/components/Menu/Menu.tsx +105 -0
- package/src/components/Menu/index.ts +2 -0
- package/src/components/MessageBox/MessageBox.tsx +168 -0
- package/src/components/MessageBox/index.ts +2 -0
- package/src/components/MetricDisplay/MetricDisplay.tsx +55 -0
- package/src/components/MetricDisplay/index.ts +1 -0
- package/src/components/MultiSelect/MultiSelect.tsx +278 -0
- package/src/components/MultiSelect/index.ts +2 -0
- package/src/components/Notifications/Notifications.tsx +12 -0
- package/src/components/Notifications/README.md +93 -0
- package/src/components/Notifications/index.ts +4 -0
- package/src/components/Notifications/showToast.tsx +100 -0
- package/src/components/PropertyRow/PropertyRow.tsx +96 -0
- package/src/components/PropertyRow/index.ts +2 -0
- package/src/components/RadioTile/RadioTile.tsx +253 -0
- package/src/components/RadioTile/index.ts +2 -0
- package/src/components/RichText/FormattingToolbar.css.ts +69 -0
- package/src/components/RichText/FormattingToolbar.tsx +112 -0
- package/src/components/RichText/RichTextInline.css.ts +54 -0
- package/src/components/RichText/RichTextInline.tsx +318 -0
- package/src/components/RichText/formattingCommands.ts +181 -0
- package/src/components/RichText/formattingTypes.ts +34 -0
- package/src/components/RichText/index.ts +49 -0
- package/src/components/RichText/richTextExtensions.ts +111 -0
- package/src/components/RichText/richTextHelpers.ts +65 -0
- package/src/components/RichText/richTextImage.ts +253 -0
- package/src/components/RichText/richTextImageHandlers.ts +244 -0
- package/src/components/RichText/richTextProse.css.ts +261 -0
- package/src/components/RichTextEditor/RichTextEditor.css.ts +82 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +204 -0
- package/src/components/RichTextEditor/index.ts +2 -0
- package/src/components/RichTextView/RichTextView.css.ts +11 -0
- package/src/components/RichTextView/RichTextView.tsx +114 -0
- package/src/components/RichTextView/index.ts +2 -0
- package/src/components/Schedule/Schedule.tsx +35 -0
- package/src/components/SchedulePicker/SchedulePicker.css.ts +42 -0
- package/src/components/SchedulePicker/SchedulePicker.tsx +130 -0
- package/src/components/SchedulePicker/index.ts +2 -0
- package/src/components/SearchableList/types.ts +30 -0
- package/src/components/SearchableSubMenu/SearchableSubMenu.css.ts +25 -0
- package/src/components/SearchableSubMenu/SearchableSubMenu.tsx +139 -0
- package/src/components/SearchableSubMenu/index.ts +2 -0
- package/src/components/Select/README.md +114 -0
- package/src/components/Select/Select.css.ts +110 -0
- package/src/components/Select/Select.tsx +133 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/SelectCreatable/SelectCreatable.css.ts +16 -0
- package/src/components/SelectCreatable/SelectCreatable.tsx +203 -0
- package/src/components/SelectCreatable/index.ts +2 -0
- package/src/components/SettingsCard/SettingsCard.tsx +98 -0
- package/src/components/SettingsCard/index.ts +2 -0
- package/src/components/Sidebar/Sidebar.css.ts +91 -0
- package/src/components/Sidebar/Sidebar.tsx +129 -0
- package/src/components/Sidebar/index.ts +5 -0
- package/src/components/SimpleList/SimpleList.css.ts +12 -0
- package/src/components/SimpleList/SimpleList.tsx +44 -0
- package/src/components/SimpleList/index.ts +2 -0
- package/src/components/SimpleTable/SimpleTable.tsx +296 -0
- package/src/components/SimpleTable/index.ts +2 -0
- package/src/components/SlashRichTextEditor/SelectionBubbleMenu.css.ts +62 -0
- package/src/components/SlashRichTextEditor/SelectionBubbleMenu.tsx +85 -0
- package/src/components/SlashRichTextEditor/SlashCommandMenu.css.ts +124 -0
- package/src/components/SlashRichTextEditor/SlashCommandMenu.tsx +168 -0
- package/src/components/SlashRichTextEditor/SlashRichTextEditor.css.ts +81 -0
- package/src/components/SlashRichTextEditor/SlashRichTextEditor.tsx +538 -0
- package/src/components/SlashRichTextEditor/SlashSuggestionExtension.ts +48 -0
- package/src/components/SlashRichTextEditor/index.ts +13 -0
- package/src/components/SlashRichTextEditor/types.ts +48 -0
- package/src/components/StatCard/StatCard.css.ts +70 -0
- package/src/components/StatCard/StatCard.tsx +201 -0
- package/src/components/StatCard/index.ts +1 -0
- package/src/components/StatusBadge/StatusBadge.tsx +70 -0
- package/src/components/StatusBadge/index.ts +2 -0
- package/src/components/StatusIndicator/StatusIndicator.tsx +67 -0
- package/src/components/StatusIndicator/index.ts +6 -0
- package/src/components/SubNavigation/SubNavigation.css.ts +72 -0
- package/src/components/SubNavigation/SubNavigation.tsx +104 -0
- package/src/components/SubNavigation/index.ts +2 -0
- package/src/components/SuspenseLoader.tsx +22 -0
- package/src/components/Table/SortableColumnHeader.tsx +99 -0
- package/src/components/Table/TableSkeletonRows.figma.tsx +22 -0
- package/src/components/Table/TableSkeletonRows.tsx +113 -0
- package/src/components/Table/index.ts +9 -0
- package/src/components/TableActionsMenu.tsx +58 -0
- package/src/components/TableCard.tsx +29 -0
- package/src/components/TableContainer/TableContainer.tsx +86 -0
- package/src/components/TableContainer/index.ts +2 -0
- package/src/components/TableControlBar/TableControlBar.tsx +156 -0
- package/src/components/TableControlBar/TableSelectionButton.tsx +57 -0
- package/src/components/TableControlBar/index.ts +13 -0
- package/src/components/TableControlBar/useTableControlBar.tsx +314 -0
- package/src/components/TableSelection/TableSelection.tsx +43 -0
- package/src/components/TableSelection/index.ts +5 -0
- package/src/components/Tabs/README.md +76 -0
- package/src/components/Tabs/Tabs.css.ts +54 -0
- package/src/components/Tabs/Tabs.figma.tsx +47 -0
- package/src/components/Tabs/Tabs.tsx +96 -0
- package/src/components/Tabs/index.ts +8 -0
- package/src/components/TextInput/README.md +98 -0
- package/src/components/TextInput/SearchTextInput.figma.tsx +22 -0
- package/src/components/TextInput/SearchTextInput.tsx +150 -0
- package/src/components/TextInput/TextInput.figma.tsx +44 -0
- package/src/components/TextInput/TextInput.tsx +42 -0
- package/src/components/TextInput/index.ts +4 -0
- package/src/components/ThemeSwitcher.figma.tsx +28 -0
- package/src/components/ThemeSwitcher.tsx +69 -0
- package/src/components/TrendBadge/TrendBadge.tsx +76 -0
- package/src/components/TrendBadge/index.ts +2 -0
- package/src/components/TruncatedText.tsx +115 -0
- package/src/components/Typography/Text.tsx +74 -0
- package/src/components/Typography/Title.tsx +100 -0
- package/src/components/Typography/index.ts +4 -0
- package/src/geist-fonts.ts +48 -0
- package/src/hooks/index.ts +31 -0
- package/src/hooks/useFilters.ts +152 -0
- package/src/hooks/useInfiniteScroll.ts +62 -0
- package/src/hooks/usePlatform.ts +33 -0
- package/src/hooks/useServerTable.ts +495 -0
- package/src/hooks/useTableSelection.ts +102 -0
- package/src/hooks/useTableSort.ts +259 -0
- package/src/index.ts +483 -0
- package/src/mantine.ts +25 -0
- package/src/theme/mantineVars.ts +12 -0
- package/src/theme/themeContract.css.ts +131 -0
- package/src/theme/themeVars.ts +31 -0
- package/src/theme.ts +168 -0
- package/src/tokens/color-types.ts +107 -0
- package/src/tokens/colors.ts +243 -0
- package/src/tokens/index.ts +14 -0
- package/src/tokens/radius.ts +17 -0
- package/src/tokens/semantic-colors.ts +224 -0
- package/src/tokens/semantic-tokens-css.ts +53 -0
- package/src/tokens/shadows.ts +11 -0
- package/src/tokens/spacing.ts +20 -0
- package/src/tokens/text-styles.ts +179 -0
- package/src/tokens/typography.ts +40 -0
- package/src/tokens/zIndex.ts +27 -0
- package/src/types/mantine-theme.d.ts +17 -0
- package/src/types/tanstack-table.d.ts +22 -0
- package/src/utils/avatar.ts +150 -0
- package/src/utils/chartHelpers.ts +53 -0
- package/src/utils/color-props.ts +77 -0
- package/src/utils/createDesignComponent.tsx +104 -0
- package/src/utils/nestFlatRows.ts +111 -0
- package/src/utils/withStaticComponents.ts +6 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for the useTableSort hook (client-side sorting)
|
|
5
|
+
*/
|
|
6
|
+
export interface UseTableSortOptionsClientSide<TData, TSortField> {
|
|
7
|
+
/**
|
|
8
|
+
* Data array to sort (for client-side sorting)
|
|
9
|
+
*/
|
|
10
|
+
data: TData[];
|
|
11
|
+
/**
|
|
12
|
+
* Custom comparator function for sorting
|
|
13
|
+
* @param a - First item to compare
|
|
14
|
+
* @param b - Second item to compare
|
|
15
|
+
* @param field - Field to sort by
|
|
16
|
+
* @param order - Sort order ('asc' or 'desc')
|
|
17
|
+
* @returns Negative if a < b, positive if a > b, 0 if equal
|
|
18
|
+
*/
|
|
19
|
+
sortComparator: (
|
|
20
|
+
a: TData,
|
|
21
|
+
b: TData,
|
|
22
|
+
field: TSortField,
|
|
23
|
+
order: 'asc' | 'desc',
|
|
24
|
+
) => number;
|
|
25
|
+
/**
|
|
26
|
+
* Whether to use client-side sorting (must be true for this overload)
|
|
27
|
+
*/
|
|
28
|
+
enableClientSort: true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options for the useTableSort hook (server-side sorting)
|
|
33
|
+
*/
|
|
34
|
+
export interface UseTableSortOptionsServerSide {
|
|
35
|
+
/**
|
|
36
|
+
* Whether to use client-side sorting (must be false for this overload)
|
|
37
|
+
*/
|
|
38
|
+
enableClientSort: false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Legacy options interface for backward compatibility
|
|
43
|
+
*/
|
|
44
|
+
export interface UseTableSortOptions<TData, TSortField> {
|
|
45
|
+
/**
|
|
46
|
+
* Data array to sort (for client-side sorting)
|
|
47
|
+
*/
|
|
48
|
+
data?: TData[];
|
|
49
|
+
/**
|
|
50
|
+
* Custom comparator function for sorting
|
|
51
|
+
* @param a - First item to compare
|
|
52
|
+
* @param b - Second item to compare
|
|
53
|
+
* @param field - Field to sort by
|
|
54
|
+
* @param order - Sort order ('asc' or 'desc')
|
|
55
|
+
* @returns Negative if a < b, positive if a > b, 0 if equal
|
|
56
|
+
*/
|
|
57
|
+
sortComparator?: (
|
|
58
|
+
a: TData,
|
|
59
|
+
b: TData,
|
|
60
|
+
field: TSortField,
|
|
61
|
+
order: 'asc' | 'desc',
|
|
62
|
+
) => number;
|
|
63
|
+
/**
|
|
64
|
+
* Whether to use client-side sorting
|
|
65
|
+
* - true: Sort data in-memory (instant, but only works with complete dataset)
|
|
66
|
+
* - false: Return sort state for API queries (works with pagination)
|
|
67
|
+
* @default false
|
|
68
|
+
*/
|
|
69
|
+
enableClientSort?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Return type for the useTableSort hook
|
|
74
|
+
*/
|
|
75
|
+
export interface UseTableSortReturn<TData, TSortField> {
|
|
76
|
+
/**
|
|
77
|
+
* Current sort field (null if no sort applied)
|
|
78
|
+
*/
|
|
79
|
+
sortBy: TSortField | null;
|
|
80
|
+
/**
|
|
81
|
+
* Current sort order (null if no sort applied)
|
|
82
|
+
*/
|
|
83
|
+
sortOrder: 'asc' | 'desc' | null;
|
|
84
|
+
/**
|
|
85
|
+
* Toggle sort for a field
|
|
86
|
+
* Cycles through: null → 'asc' → 'desc' → null
|
|
87
|
+
*/
|
|
88
|
+
toggleSort: (field: TSortField) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Get props to spread on column header element
|
|
91
|
+
* Provides onClick handler and aria-sort attribute
|
|
92
|
+
*/
|
|
93
|
+
getSortProps: (field: TSortField) => {
|
|
94
|
+
onClick: () => void;
|
|
95
|
+
'aria-sort': 'ascending' | 'descending' | 'none';
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Sorted data array
|
|
99
|
+
* - If enableClientSort is true: returns sorted data
|
|
100
|
+
* - If enableClientSort is false: returns original data (API handles sorting)
|
|
101
|
+
*/
|
|
102
|
+
sortedData: TData[];
|
|
103
|
+
/**
|
|
104
|
+
* Sort state for API queries (server-side sorting)
|
|
105
|
+
* Pass these to your API query options when enableClientSort is false
|
|
106
|
+
*/
|
|
107
|
+
sortState: {
|
|
108
|
+
sort_by: TSortField | null;
|
|
109
|
+
sort_order: 'asc' | 'desc' | null;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Hook for managing table sorting with hybrid client/server-side support
|
|
115
|
+
*
|
|
116
|
+
* This hook provides a smart sorting strategy:
|
|
117
|
+
* - Client-side sorting: Instant sorting when all data is loaded
|
|
118
|
+
* - Server-side sorting: API-based sorting when data is paginated
|
|
119
|
+
*
|
|
120
|
+
* @example Client-side sorting (all data loaded)
|
|
121
|
+
* ```tsx
|
|
122
|
+
* const sort = useTableSort({
|
|
123
|
+
* data: users,
|
|
124
|
+
* sortComparator: (a, b, field, order) => {
|
|
125
|
+
* const result = a[field] - b[field];
|
|
126
|
+
* return order === 'desc' ? -result : result;
|
|
127
|
+
* },
|
|
128
|
+
* enableClientSort: true
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* // Use sorted data
|
|
132
|
+
* return <Table data={sort.sortedData} />
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @example Server-side sorting (paginated data)
|
|
136
|
+
* ```tsx
|
|
137
|
+
* const sort = useTableSort<'username' | 'email'>({
|
|
138
|
+
* enableClientSort: false
|
|
139
|
+
* });
|
|
140
|
+
*
|
|
141
|
+
* // Pass sort state to API query
|
|
142
|
+
* const { data } = useQuery(
|
|
143
|
+
* usersQueryOptions(clientId, {
|
|
144
|
+
* sort_by: sort.sortState.sort_by,
|
|
145
|
+
* sort_order: sort.sortState.sort_order
|
|
146
|
+
* })
|
|
147
|
+
* );
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function useTableSort<TData, TSortField extends string>(
|
|
151
|
+
options: UseTableSortOptionsClientSide<TData, TSortField>,
|
|
152
|
+
): UseTableSortReturn<TData, TSortField>;
|
|
153
|
+
export function useTableSort<TSortField extends string>(
|
|
154
|
+
options: UseTableSortOptionsServerSide,
|
|
155
|
+
): UseTableSortReturn<never, TSortField>;
|
|
156
|
+
export function useTableSort<TData, TSortField extends string>({
|
|
157
|
+
data,
|
|
158
|
+
sortComparator,
|
|
159
|
+
enableClientSort = false,
|
|
160
|
+
}: UseTableSortOptions<TData, TSortField>): UseTableSortReturn<
|
|
161
|
+
TData,
|
|
162
|
+
TSortField
|
|
163
|
+
> {
|
|
164
|
+
const [sortBy, setSortBy] = useState<TSortField | null>(null);
|
|
165
|
+
const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null);
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Toggle sort for a field
|
|
169
|
+
* Cycles through: null → 'asc' → 'desc' → null
|
|
170
|
+
*/
|
|
171
|
+
const toggleSort = useCallback(
|
|
172
|
+
(field: TSortField) => {
|
|
173
|
+
// If clicking a different field, start with ascending
|
|
174
|
+
if (sortBy !== field) {
|
|
175
|
+
setSortBy(field);
|
|
176
|
+
setSortOrder('asc');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// If clicking the same field, cycle through states
|
|
181
|
+
if (sortOrder === null) {
|
|
182
|
+
// null → asc
|
|
183
|
+
setSortOrder('asc');
|
|
184
|
+
} else if (sortOrder === 'asc') {
|
|
185
|
+
// asc → desc
|
|
186
|
+
setSortOrder('desc');
|
|
187
|
+
} else {
|
|
188
|
+
// desc → null (clear sort)
|
|
189
|
+
setSortBy(null);
|
|
190
|
+
setSortOrder(null);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
[sortBy, sortOrder],
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get props to spread on column header
|
|
198
|
+
*/
|
|
199
|
+
const getSortProps = useCallback(
|
|
200
|
+
(field: TSortField) => ({
|
|
201
|
+
onClick: () => toggleSort(field),
|
|
202
|
+
'aria-sort': (sortBy === field
|
|
203
|
+
? sortOrder === 'asc'
|
|
204
|
+
? 'ascending'
|
|
205
|
+
: sortOrder === 'desc'
|
|
206
|
+
? 'descending'
|
|
207
|
+
: 'none'
|
|
208
|
+
: 'none') as 'ascending' | 'descending' | 'none',
|
|
209
|
+
}),
|
|
210
|
+
[sortBy, sortOrder, toggleSort],
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Sort data client-side if enabled and sort is active
|
|
215
|
+
*/
|
|
216
|
+
const sortedData = useMemo(() => {
|
|
217
|
+
// If client-side sorting is disabled, return empty array (not used in server-side mode)
|
|
218
|
+
if (!enableClientSort) {
|
|
219
|
+
return (data ?? []) as TData[];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If no data provided, return empty array
|
|
223
|
+
if (!data) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// If no sort applied, return original data
|
|
228
|
+
if (sortBy === null || sortOrder === null) {
|
|
229
|
+
return data;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Sort data using the provided comparator
|
|
233
|
+
if (!sortComparator) {
|
|
234
|
+
return data;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return [...data].sort((a, b) => sortComparator(a, b, sortBy, sortOrder));
|
|
238
|
+
}, [data, sortBy, sortOrder, enableClientSort, sortComparator]);
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Sort state for API queries (server-side sorting)
|
|
242
|
+
*/
|
|
243
|
+
const sortState = useMemo(
|
|
244
|
+
() => ({
|
|
245
|
+
sort_by: sortBy,
|
|
246
|
+
sort_order: sortOrder,
|
|
247
|
+
}),
|
|
248
|
+
[sortBy, sortOrder],
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
sortBy,
|
|
253
|
+
sortOrder,
|
|
254
|
+
toggleSort,
|
|
255
|
+
getSortProps,
|
|
256
|
+
sortedData,
|
|
257
|
+
sortState,
|
|
258
|
+
};
|
|
259
|
+
}
|