@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,495 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useInfiniteQuery,
|
|
5
|
+
type InfiniteData,
|
|
6
|
+
type UseInfiniteQueryOptions,
|
|
7
|
+
} from '@tanstack/react-query';
|
|
8
|
+
import {
|
|
9
|
+
type ExpandedState,
|
|
10
|
+
type VisibilityState,
|
|
11
|
+
getCoreRowModel,
|
|
12
|
+
getExpandedRowModel,
|
|
13
|
+
useReactTable,
|
|
14
|
+
type ColumnDef,
|
|
15
|
+
type OnChangeFn,
|
|
16
|
+
type RowSelectionState,
|
|
17
|
+
type SortingState,
|
|
18
|
+
type Table,
|
|
19
|
+
} from '@tanstack/react-table';
|
|
20
|
+
|
|
21
|
+
import { useStableValue } from '@scalepad/ui-utils/hooks';
|
|
22
|
+
|
|
23
|
+
import { nestFlatRows, type WithSubRows } from '../utils/nestFlatRows';
|
|
24
|
+
|
|
25
|
+
import type { InfiniteScrollState } from '../components/InfiniteScrollTrigger';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generic list response type for paginated API endpoints.
|
|
29
|
+
* Shared contract between useServerTable and query options functions.
|
|
30
|
+
*/
|
|
31
|
+
export interface ListResponse<TData> {
|
|
32
|
+
data: TData[];
|
|
33
|
+
nextCursor?: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Options for the useServerTable hook
|
|
38
|
+
*/
|
|
39
|
+
export interface UseServerTableOptions<TData, TFilters extends object> {
|
|
40
|
+
/**
|
|
41
|
+
* Column definitions for TanStack Table
|
|
42
|
+
*/
|
|
43
|
+
columns: ColumnDef<TData, unknown>[];
|
|
44
|
+
/**
|
|
45
|
+
* Function that returns infinite query options given filters
|
|
46
|
+
* The filters will include converted sort params (sort_by, sort_order)
|
|
47
|
+
*/
|
|
48
|
+
queryOptions: (
|
|
49
|
+
filters: TFilters,
|
|
50
|
+
) => UseInfiniteQueryOptions<
|
|
51
|
+
ListResponse<TData>,
|
|
52
|
+
Error,
|
|
53
|
+
InfiniteData<ListResponse<TData>>,
|
|
54
|
+
readonly unknown[],
|
|
55
|
+
string
|
|
56
|
+
>;
|
|
57
|
+
/**
|
|
58
|
+
* Filter params to pass to the query (search, view mode, date range, etc.)
|
|
59
|
+
* Sort params will be added automatically based on table sorting state
|
|
60
|
+
*/
|
|
61
|
+
filters: TFilters;
|
|
62
|
+
/**
|
|
63
|
+
* Optional function to extract row ID from data
|
|
64
|
+
* @default (row) => row.id
|
|
65
|
+
*/
|
|
66
|
+
getRowId?: (row: TData) => string;
|
|
67
|
+
/**
|
|
68
|
+
* Optional field name mapping from TanStack Table column IDs to API sort field names
|
|
69
|
+
* @example { app_name: 'name', unique_users: 'users_count' }
|
|
70
|
+
*/
|
|
71
|
+
sortFieldMapping?: Record<string, string>;
|
|
72
|
+
/**
|
|
73
|
+
* Enable row selection
|
|
74
|
+
* @default false
|
|
75
|
+
*/
|
|
76
|
+
enableRowSelection?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Initial sorting state (e.g. [{ id: 'canonical_name', desc: false }]).
|
|
79
|
+
* Use when the API expects a default sort on first load.
|
|
80
|
+
*/
|
|
81
|
+
initialSorting?: SortingState;
|
|
82
|
+
/**
|
|
83
|
+
* Initial column visibility map keyed by column ID.
|
|
84
|
+
* Omitted IDs default to visible.
|
|
85
|
+
* @example { approval_status: false, discovered_date: false }
|
|
86
|
+
*/
|
|
87
|
+
initialColumnVisibility?: VisibilityState;
|
|
88
|
+
/**
|
|
89
|
+
* Opt in to tree rows (row grouping via expansion).
|
|
90
|
+
*
|
|
91
|
+
* Exactly one of `getSubRows` or `getParentId` must be provided:
|
|
92
|
+
* - `getSubRows`: server already returns nested data; the hook uses it
|
|
93
|
+
* directly.
|
|
94
|
+
* - `getParentId`: server returns a flat list with a parent reference on
|
|
95
|
+
* each row; the hook nests it via {@link nestFlatRows} before handing it
|
|
96
|
+
* to TanStack Table.
|
|
97
|
+
*
|
|
98
|
+
* Expand state can be either uncontrolled (seeded via `initialExpanded`)
|
|
99
|
+
* or controlled (`expanded` + `onExpandedChange`). Default is all
|
|
100
|
+
* collapsed.
|
|
101
|
+
*/
|
|
102
|
+
tree?: {
|
|
103
|
+
/**
|
|
104
|
+
* Return nested child rows for a given row. Mutually exclusive with
|
|
105
|
+
* `getParentId`.
|
|
106
|
+
*/
|
|
107
|
+
getSubRows?: (row: TData) => TData[] | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Return the `id` of this row's parent (or nullish for a root). Mutually
|
|
110
|
+
* exclusive with `getSubRows`. When present the hook nests the flat
|
|
111
|
+
* result of the infinite query via `nestFlatRows`.
|
|
112
|
+
*/
|
|
113
|
+
getParentId?: (row: TData) => string | null | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* How to handle rows that reference a parentId not present in the data.
|
|
116
|
+
* Only honored when using `getParentId`.
|
|
117
|
+
* @default "throw"
|
|
118
|
+
*/
|
|
119
|
+
onOrphan?: 'throw' | 'drop';
|
|
120
|
+
/**
|
|
121
|
+
* Initial expand state (uncontrolled mode only).
|
|
122
|
+
* - `"none"` (default): everything collapsed.
|
|
123
|
+
* - `"all"`: every row with children starts expanded.
|
|
124
|
+
* - An explicit `ExpandedState` for fine-grained control.
|
|
125
|
+
*/
|
|
126
|
+
initialExpanded?: 'all' | 'none' | ExpandedState;
|
|
127
|
+
/**
|
|
128
|
+
* Controlled expand state. When provided the hook does not maintain its
|
|
129
|
+
* own state; pair with `onExpandedChange`.
|
|
130
|
+
*/
|
|
131
|
+
expanded?: ExpandedState;
|
|
132
|
+
/**
|
|
133
|
+
* Called when the table wants to change expanded state. Required in
|
|
134
|
+
* controlled mode.
|
|
135
|
+
*/
|
|
136
|
+
onExpandedChange?: OnChangeFn<ExpandedState>;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Return type for the useServerTable hook
|
|
142
|
+
*/
|
|
143
|
+
export interface UseServerTableReturn<TData> {
|
|
144
|
+
/**
|
|
145
|
+
* TanStack Table instance (use for rendering)
|
|
146
|
+
*/
|
|
147
|
+
table: Table<TData>;
|
|
148
|
+
/**
|
|
149
|
+
* Flattened data array from all pages
|
|
150
|
+
*/
|
|
151
|
+
data: TData[];
|
|
152
|
+
/**
|
|
153
|
+
* True ONLY on first load (no cached data) -> show skeleton
|
|
154
|
+
*/
|
|
155
|
+
isInitialLoading: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* True when data exists but refetching (sort/filter change) -> show subtle indicator
|
|
158
|
+
*/
|
|
159
|
+
isRefetching: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* True when query failed
|
|
162
|
+
*/
|
|
163
|
+
isError: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* True when the data is empty (no results) and not loading
|
|
166
|
+
* Use this to disable controls or show "no data" UI
|
|
167
|
+
*/
|
|
168
|
+
hasNoData: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Pagination state for InfiniteScrollTrigger
|
|
171
|
+
*/
|
|
172
|
+
pagination: InfiniteScrollState;
|
|
173
|
+
/**
|
|
174
|
+
* Selected row IDs (when enableRowSelection is true)
|
|
175
|
+
*/
|
|
176
|
+
selectedIds: Set<string>;
|
|
177
|
+
/**
|
|
178
|
+
* Clear row selection
|
|
179
|
+
*/
|
|
180
|
+
clearSelection: () => void;
|
|
181
|
+
/**
|
|
182
|
+
* Set table sorting state (e.g. for external sort controls like a grid sort dropdown).
|
|
183
|
+
* Uses the same signature as TanStack Table's onSortingChange.
|
|
184
|
+
*/
|
|
185
|
+
setSorting: OnChangeFn<SortingState>;
|
|
186
|
+
/**
|
|
187
|
+
* Current expand state. Only meaningful when `tree` was configured.
|
|
188
|
+
* In uncontrolled mode this reflects the hook's internal state; in
|
|
189
|
+
* controlled mode it mirrors the `tree.expanded` prop.
|
|
190
|
+
*/
|
|
191
|
+
expanded: ExpandedState;
|
|
192
|
+
/**
|
|
193
|
+
* Programmatically set expand state (e.g. "expand all" / "collapse all"
|
|
194
|
+
* toolbar buttons). A no-op when `tree` was not configured.
|
|
195
|
+
*/
|
|
196
|
+
setExpanded: OnChangeFn<ExpandedState>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Flatten infinite query pages into a single array
|
|
201
|
+
*/
|
|
202
|
+
function flattenInfinitePages<TData>(
|
|
203
|
+
data: InfiniteData<ListResponse<TData>>,
|
|
204
|
+
): TData[] {
|
|
205
|
+
return data.pages.flatMap(page => page.data);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Convert TanStack Table sorting state to API query params
|
|
210
|
+
* @example [{ id: 'app_name', desc: true }] -> { sort_by: 'app_name', sort_order: 'desc' }
|
|
211
|
+
*/
|
|
212
|
+
function convertSortingToApiParams(
|
|
213
|
+
sorting: SortingState,
|
|
214
|
+
sortFieldMapping?: Record<string, string>,
|
|
215
|
+
): { sort_by?: string; sort_order?: 'asc' | 'desc' } {
|
|
216
|
+
if (sorting.length === 0) {
|
|
217
|
+
return { sort_by: undefined, sort_order: undefined };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// SortingState is an array (TanStack Table supports multi-column sorting),
|
|
221
|
+
// but our API only accepts a single sort_by/sort_order pair, so we use the first entry.
|
|
222
|
+
const sortState = sorting[0];
|
|
223
|
+
const apiFieldName = sortFieldMapping?.[sortState.id] ?? sortState.id;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
sort_by: apiFieldName,
|
|
227
|
+
sort_order: sortState.desc ? 'desc' : 'asc',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Hook for server-side table with TanStack Table + useInfiniteQuery
|
|
233
|
+
*
|
|
234
|
+
* Combines TanStack Table's state management with TanStack Query's data fetching
|
|
235
|
+
* to create a seamless server-side table experience with:
|
|
236
|
+
* - Declarative column definitions
|
|
237
|
+
* - Automatic sort state -> API params conversion
|
|
238
|
+
* - Clean loading state semantics (isInitialLoading vs isRefetching)
|
|
239
|
+
* - Smooth transitions with placeholderData
|
|
240
|
+
* - Built-in row selection (optional)
|
|
241
|
+
*
|
|
242
|
+
* @example Basic usage
|
|
243
|
+
* ```tsx
|
|
244
|
+
* const { table, ...states } = useServerTable({
|
|
245
|
+
* columns: appColumns,
|
|
246
|
+
* queryOptions: (filters) => appsInfiniteQueryOptions(clientId, filters),
|
|
247
|
+
* filters: { granularity, date, q: search },
|
|
248
|
+
* });
|
|
249
|
+
*
|
|
250
|
+
* <DataTable {...states} table={table} emptyState={...} />
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* @example With row selection
|
|
254
|
+
* ```tsx
|
|
255
|
+
* const { table, selectedIds, clearSelection, ...states } = useServerTable({
|
|
256
|
+
* columns: userColumns,
|
|
257
|
+
* queryOptions: (filters) => usersInfiniteQueryOptions(clientId, filters),
|
|
258
|
+
* filters: { q: search },
|
|
259
|
+
* enableRowSelection: true,
|
|
260
|
+
* });
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
export function useServerTable<TData, TFilters extends object>({
|
|
264
|
+
columns,
|
|
265
|
+
queryOptions,
|
|
266
|
+
filters,
|
|
267
|
+
getRowId = row => String((row as { id: string | number }).id),
|
|
268
|
+
sortFieldMapping,
|
|
269
|
+
enableRowSelection = false,
|
|
270
|
+
initialSorting,
|
|
271
|
+
initialColumnVisibility,
|
|
272
|
+
tree,
|
|
273
|
+
}: UseServerTableOptions<TData, TFilters>): UseServerTableReturn<TData> {
|
|
274
|
+
// TanStack Table state: sorting
|
|
275
|
+
const [sorting, setSorting] = useState<SortingState>(initialSorting ?? []);
|
|
276
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
|
277
|
+
initialColumnVisibility ?? {},
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// TanStack Table state: row selection (optional)
|
|
281
|
+
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
|
282
|
+
|
|
283
|
+
// Dev-time validation: exactly one of getSubRows / getParentId must be set
|
|
284
|
+
// when tree is enabled. We don't guard on NODE_ENV here because this is
|
|
285
|
+
// cheap and the hook is only used inside React.
|
|
286
|
+
if (tree) {
|
|
287
|
+
const hasSub = !!tree.getSubRows;
|
|
288
|
+
const hasParent = !!tree.getParentId;
|
|
289
|
+
if (hasSub === hasParent) {
|
|
290
|
+
console.warn(
|
|
291
|
+
'[useServerTable] `tree` requires exactly one of `getSubRows` or `getParentId`.',
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (tree.expanded !== undefined && tree.onExpandedChange === undefined) {
|
|
295
|
+
console.warn(
|
|
296
|
+
'[useServerTable] `tree.expanded` was provided without `tree.onExpandedChange`; expand toggling will be a no-op.',
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// TanStack Table state: expanded (only used when tree is enabled and
|
|
302
|
+
// consumer has not taken control). We always instantiate the state hook
|
|
303
|
+
// (rules of hooks) but only feed it into useReactTable when relevant.
|
|
304
|
+
// TanStack's `ExpandedState` supports `true` as a shorthand for
|
|
305
|
+
// "every expandable row is expanded", which is exactly what `'all'`
|
|
306
|
+
// means here.
|
|
307
|
+
const [internalExpanded, setInternalExpanded] = useState<ExpandedState>(
|
|
308
|
+
() => {
|
|
309
|
+
if (!tree) return {};
|
|
310
|
+
const initial = tree.initialExpanded;
|
|
311
|
+
if (initial === undefined || initial === 'none') return {};
|
|
312
|
+
if (initial === 'all') return true;
|
|
313
|
+
return initial;
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Stabilize filters object to prevent unnecessary refetches when parent components
|
|
318
|
+
// re-render with new filter object references but same values
|
|
319
|
+
const stableFilters = useStableValue(filters);
|
|
320
|
+
|
|
321
|
+
// Convert TanStack Table sorting to API params
|
|
322
|
+
const sortParams = useMemo(
|
|
323
|
+
() => convertSortingToApiParams(sorting, sortFieldMapping),
|
|
324
|
+
[sorting, sortFieldMapping],
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Merge sort params with user filters
|
|
328
|
+
const queryFilters = useMemo(
|
|
329
|
+
() => ({ ...stableFilters, ...sortParams }) as TFilters,
|
|
330
|
+
[stableFilters, sortParams],
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
// Fetch data with infinite query
|
|
334
|
+
const queryResult = useInfiniteQuery({
|
|
335
|
+
...queryOptions(queryFilters),
|
|
336
|
+
// Keep previous data visible while fetching new sorted data
|
|
337
|
+
// This makes sorting feel instant instead of showing a skeleton
|
|
338
|
+
// CRITICAL: Don't use placeholderData if we don't have data yet for THIS query
|
|
339
|
+
// (data might exist from a previous query with different filters)
|
|
340
|
+
placeholderData: previousData => {
|
|
341
|
+
// If there's no previous data, don't use placeholder
|
|
342
|
+
if (!previousData) return undefined;
|
|
343
|
+
// Use placeholder to keep UI smooth during refetches
|
|
344
|
+
return previousData;
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const {
|
|
349
|
+
data,
|
|
350
|
+
hasNextPage,
|
|
351
|
+
isFetchingNextPage,
|
|
352
|
+
fetchNextPage,
|
|
353
|
+
isLoading,
|
|
354
|
+
isError,
|
|
355
|
+
isFetching,
|
|
356
|
+
} = queryResult;
|
|
357
|
+
|
|
358
|
+
// Flatten pages into single array
|
|
359
|
+
const flattenedData = useMemo(
|
|
360
|
+
() => (data ? flattenInfinitePages(data) : []),
|
|
361
|
+
[data],
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
// When tree is enabled with `getParentId`, nest the flat result into a
|
|
365
|
+
// tree. When tree is enabled with `getSubRows`, the server already
|
|
366
|
+
// returns nested data and we pass it through untouched. When tree is
|
|
367
|
+
// disabled, pass the flat data through untouched.
|
|
368
|
+
const tableData = useMemo<TData[]>(() => {
|
|
369
|
+
if (!tree || !tree.getParentId) {
|
|
370
|
+
return flattenedData;
|
|
371
|
+
}
|
|
372
|
+
const nested = nestFlatRows(flattenedData, {
|
|
373
|
+
getId: getRowId,
|
|
374
|
+
getParentId: tree.getParentId,
|
|
375
|
+
onOrphan: tree.onOrphan ?? 'throw',
|
|
376
|
+
}) as (TData & WithSubRows<TData>)[];
|
|
377
|
+
return nested;
|
|
378
|
+
}, [flattenedData, tree, getRowId]);
|
|
379
|
+
|
|
380
|
+
// Pick the effective expand state + setter (controlled vs uncontrolled).
|
|
381
|
+
const expandedState: ExpandedState = tree
|
|
382
|
+
? (tree.expanded ?? internalExpanded)
|
|
383
|
+
: {};
|
|
384
|
+
const onExpandedChange: OnChangeFn<ExpandedState> = tree
|
|
385
|
+
? (tree.onExpandedChange ??
|
|
386
|
+
(setInternalExpanded as OnChangeFn<ExpandedState>))
|
|
387
|
+
: (setInternalExpanded as OnChangeFn<ExpandedState>);
|
|
388
|
+
|
|
389
|
+
// Resolve `getSubRows` for TanStack. Priority:
|
|
390
|
+
// 1. Consumer-provided `tree.getSubRows`.
|
|
391
|
+
// 2. When nesting via `getParentId`, read off the `subRows` field written
|
|
392
|
+
// by `nestFlatRows`.
|
|
393
|
+
// 3. Otherwise, undefined (flat table behavior).
|
|
394
|
+
const treeGetSubRows: ((row: TData) => TData[] | undefined) | undefined =
|
|
395
|
+
tree?.getSubRows ??
|
|
396
|
+
(tree?.getParentId
|
|
397
|
+
? (row: TData) => (row as WithSubRows<TData>).subRows
|
|
398
|
+
: undefined);
|
|
399
|
+
|
|
400
|
+
// Derive clean loading states
|
|
401
|
+
const isInitialLoading = isLoading;
|
|
402
|
+
const isRefetching = !isLoading && isFetching;
|
|
403
|
+
|
|
404
|
+
// Derive initial column pinning from column meta (meta.pinned: 'left' | 'right')
|
|
405
|
+
const initialColumnPinning = useMemo(() => {
|
|
406
|
+
const left: string[] = [];
|
|
407
|
+
const right: string[] = [];
|
|
408
|
+
for (const col of columns) {
|
|
409
|
+
const id = (col as ColumnDef<TData>).id;
|
|
410
|
+
const pinned = (col as ColumnDef<TData>).meta as
|
|
411
|
+
| { pinned?: 'left' | 'right' }
|
|
412
|
+
| undefined;
|
|
413
|
+
if (typeof id === 'string' && pinned?.pinned === 'left') left.push(id);
|
|
414
|
+
if (typeof id === 'string' && pinned?.pinned === 'right') right.push(id);
|
|
415
|
+
}
|
|
416
|
+
return { left, right };
|
|
417
|
+
}, [columns]);
|
|
418
|
+
|
|
419
|
+
// Create TanStack Table instance
|
|
420
|
+
const table = useReactTable({
|
|
421
|
+
data: tableData,
|
|
422
|
+
columns,
|
|
423
|
+
getCoreRowModel: getCoreRowModel(),
|
|
424
|
+
// Always provide getRowId so row.id is a stable entity ID (used as React key),
|
|
425
|
+
// not the default array index which causes reconciliation bugs on sort/page changes.
|
|
426
|
+
getRowId,
|
|
427
|
+
// Server-side sorting
|
|
428
|
+
manualSorting: true,
|
|
429
|
+
// Enable column sizing - columns without explicit size will use default 150px
|
|
430
|
+
defaultColumn: {
|
|
431
|
+
size: 150,
|
|
432
|
+
minSize: 40,
|
|
433
|
+
maxSize: 500,
|
|
434
|
+
},
|
|
435
|
+
initialState: {
|
|
436
|
+
columnPinning: initialColumnPinning,
|
|
437
|
+
},
|
|
438
|
+
state: {
|
|
439
|
+
sorting,
|
|
440
|
+
columnVisibility,
|
|
441
|
+
...(enableRowSelection && { rowSelection }),
|
|
442
|
+
...(tree && { expanded: expandedState }),
|
|
443
|
+
},
|
|
444
|
+
onSortingChange: setSorting as OnChangeFn<SortingState>,
|
|
445
|
+
onColumnVisibilityChange:
|
|
446
|
+
setColumnVisibility as OnChangeFn<VisibilityState>,
|
|
447
|
+
...(enableRowSelection && {
|
|
448
|
+
onRowSelectionChange: setRowSelection as OnChangeFn<RowSelectionState>,
|
|
449
|
+
enableRowSelection: true,
|
|
450
|
+
// Select descendants alongside a group row so checkbox + chevron can
|
|
451
|
+
// coexist meaningfully on grouped rows.
|
|
452
|
+
enableSubRowSelection: true,
|
|
453
|
+
}),
|
|
454
|
+
...(tree && {
|
|
455
|
+
getSubRows: treeGetSubRows,
|
|
456
|
+
getExpandedRowModel: getExpandedRowModel(),
|
|
457
|
+
onExpandedChange,
|
|
458
|
+
}),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Extract selected row IDs
|
|
462
|
+
const selectedIds = useMemo(() => {
|
|
463
|
+
if (!enableRowSelection) return new Set<string>();
|
|
464
|
+
return new Set(Object.keys(rowSelection));
|
|
465
|
+
}, [rowSelection, enableRowSelection]);
|
|
466
|
+
|
|
467
|
+
const clearSelection = useCallback(() => {
|
|
468
|
+
setRowSelection({});
|
|
469
|
+
}, []);
|
|
470
|
+
|
|
471
|
+
// Detect "no data" state - empty result set that's not due to loading.
|
|
472
|
+
// When nesting via `getParentId` we compare the root-level array length,
|
|
473
|
+
// so a fully-orphaned response still renders as empty.
|
|
474
|
+
const hasNoData = tableData.length === 0 && !isInitialLoading;
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
table,
|
|
478
|
+
data: flattenedData,
|
|
479
|
+
isInitialLoading,
|
|
480
|
+
isRefetching,
|
|
481
|
+
isError,
|
|
482
|
+
hasNoData,
|
|
483
|
+
pagination: {
|
|
484
|
+
hasNextPage,
|
|
485
|
+
isFetchingNextPage,
|
|
486
|
+
fetchNextPage,
|
|
487
|
+
totalPages: data?.pages.length ?? 0,
|
|
488
|
+
},
|
|
489
|
+
selectedIds,
|
|
490
|
+
clearSelection,
|
|
491
|
+
setSorting: setSorting as OnChangeFn<SortingState>,
|
|
492
|
+
expanded: expandedState,
|
|
493
|
+
setExpanded: onExpandedChange,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface UseTableSelectionReturn {
|
|
4
|
+
isSelectionMode: boolean;
|
|
5
|
+
selectedIds: Set<string>;
|
|
6
|
+
selectedCount: number;
|
|
7
|
+
toggleSelectionMode: () => void;
|
|
8
|
+
toggleRow: (id: string) => void;
|
|
9
|
+
toggleSelectAll: () => void;
|
|
10
|
+
isRowSelected: (id: string) => boolean;
|
|
11
|
+
isAllSelected: boolean;
|
|
12
|
+
clearSelection: () => void;
|
|
13
|
+
getRowProps: (id: string) => {
|
|
14
|
+
onClick: (() => void) | undefined;
|
|
15
|
+
className: string | undefined;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useTableSelection<T extends Record<string, unknown>>(
|
|
20
|
+
data: T[],
|
|
21
|
+
idKey: keyof T = 'id',
|
|
22
|
+
): UseTableSelectionReturn {
|
|
23
|
+
const [isSelectionMode, setIsSelectionMode] = useState(false);
|
|
24
|
+
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
25
|
+
|
|
26
|
+
// Use a ref to store the current selectedIds to avoid recreating callbacks
|
|
27
|
+
const selectedIdsRef = useRef(selectedIds);
|
|
28
|
+
selectedIdsRef.current = selectedIds;
|
|
29
|
+
|
|
30
|
+
// Toggle selection mode on/off (clears selections when turning off)
|
|
31
|
+
const toggleSelectionMode = useCallback(() => {
|
|
32
|
+
setIsSelectionMode(prev => !prev);
|
|
33
|
+
setSelectedIds(new Set());
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
// Toggle individual row selection
|
|
37
|
+
const toggleRow = useCallback((id: string) => {
|
|
38
|
+
setSelectedIds(prev => {
|
|
39
|
+
const next = new Set(prev);
|
|
40
|
+
if (next.has(id)) {
|
|
41
|
+
next.delete(id);
|
|
42
|
+
} else {
|
|
43
|
+
next.add(id);
|
|
44
|
+
}
|
|
45
|
+
return next;
|
|
46
|
+
});
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// Select/deselect all visible rows
|
|
50
|
+
const toggleSelectAll = useCallback(() => {
|
|
51
|
+
const allIds = data.map(item => String(item[idKey]));
|
|
52
|
+
|
|
53
|
+
setSelectedIds(prev => {
|
|
54
|
+
const allSelected = allIds.every(id => prev.has(id));
|
|
55
|
+
return allSelected ? new Set() : new Set(allIds);
|
|
56
|
+
});
|
|
57
|
+
}, [data, idKey]);
|
|
58
|
+
|
|
59
|
+
// Check if specific row is selected
|
|
60
|
+
// Uses ref to avoid recreating callback on every selection change
|
|
61
|
+
const isRowSelected = useCallback(
|
|
62
|
+
(id: string) => selectedIdsRef.current.has(id),
|
|
63
|
+
[],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Check if all visible rows are selected
|
|
67
|
+
const isAllSelected = useMemo(() => {
|
|
68
|
+
if (data.length === 0) return false;
|
|
69
|
+
return data.every(item => selectedIds.has(String(item[idKey])));
|
|
70
|
+
}, [data, idKey, selectedIds]);
|
|
71
|
+
|
|
72
|
+
// Clear all selections
|
|
73
|
+
const clearSelection = useCallback(() => {
|
|
74
|
+
setSelectedIds(new Set());
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
// Get props for table row (handles click + styling)
|
|
78
|
+
const getRowProps = useCallback(
|
|
79
|
+
(id: string) => ({
|
|
80
|
+
onClick: isSelectionMode ? () => toggleRow(id) : undefined,
|
|
81
|
+
className: isSelectionMode
|
|
82
|
+
? isRowSelected(id)
|
|
83
|
+
? 'selectable-row selected-row'
|
|
84
|
+
: 'selectable-row'
|
|
85
|
+
: undefined,
|
|
86
|
+
}),
|
|
87
|
+
[isSelectionMode, isRowSelected, toggleRow],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
isSelectionMode,
|
|
92
|
+
selectedIds,
|
|
93
|
+
selectedCount: selectedIds.size,
|
|
94
|
+
toggleSelectionMode,
|
|
95
|
+
toggleRow,
|
|
96
|
+
toggleSelectAll,
|
|
97
|
+
isRowSelected,
|
|
98
|
+
isAllSelected,
|
|
99
|
+
clearSelection,
|
|
100
|
+
getRowProps,
|
|
101
|
+
};
|
|
102
|
+
}
|