@datum-cloud/datum-ui 0.3.0-alpha.9d90881 → 0.3.0-alpha.bdce331
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/dist/autocomplete/index.mjs +4 -4
- package/dist/{autocomplete-e33EmvBu.mjs → autocomplete-B9bCkXtz.mjs} +3 -3
- package/dist/avatar-stack/index.mjs +2 -2
- package/dist/{avatar-stack-Ci0cnjxv.mjs → avatar-stack-Bh-tLz0X.mjs} +1 -1
- package/dist/{calendar-date-picker-BBAg78Lg.mjs → calendar-date-picker-mlbzp3xR.mjs} +2 -2
- package/dist/checkbox/index.mjs +1 -2
- package/dist/checkbox-LG1OKTpG.mjs +34 -0
- package/dist/collapsible/index.mjs +1 -1
- package/dist/command/index.mjs +2 -2
- package/dist/{command-DQlO6uTL.mjs → command-s0Yv3abE.mjs} +1 -1
- package/dist/components/features/data-table/adapters/nuqs-adapter.d.ts +13 -12
- package/dist/components/features/data-table/adapters/nuqs-adapter.d.ts.map +1 -1
- package/dist/components/features/data-table/components/active-filters.d.ts +5 -0
- package/dist/components/features/data-table/components/active-filters.d.ts.map +1 -0
- package/dist/components/features/data-table/components/bulk-actions.d.ts.map +1 -1
- package/dist/components/features/data-table/components/column-header.d.ts.map +1 -1
- package/dist/components/features/data-table/components/content.d.ts.map +1 -1
- package/dist/components/features/data-table/components/pagination.d.ts.map +1 -1
- package/dist/components/features/data-table/components/search.d.ts.map +1 -1
- package/dist/components/features/data-table/core/client-provider.d.ts +9 -2
- package/dist/components/features/data-table/core/client-provider.d.ts.map +1 -1
- package/dist/components/features/data-table/core/data-table-context.d.ts +22 -3
- package/dist/components/features/data-table/core/data-table-context.d.ts.map +1 -1
- package/dist/components/features/data-table/core/filter-engine.d.ts +16 -0
- package/dist/components/features/data-table/core/filter-engine.d.ts.map +1 -0
- package/dist/components/features/data-table/core/server-provider.d.ts +9 -2
- package/dist/components/features/data-table/core/server-provider.d.ts.map +1 -1
- package/dist/components/features/data-table/core/store.d.ts +3 -0
- package/dist/components/features/data-table/core/store.d.ts.map +1 -0
- package/dist/components/features/data-table/data-table.d.ts +1 -0
- package/dist/components/features/data-table/data-table.d.ts.map +1 -1
- package/dist/components/features/data-table/filters/checkbox-filter.d.ts.map +1 -1
- package/dist/components/features/data-table/filters/date-picker-filter.d.ts.map +1 -1
- package/dist/components/features/data-table/filters/select-filter.d.ts.map +1 -1
- package/dist/components/features/data-table/hooks/index.d.ts +1 -1
- package/dist/components/features/data-table/hooks/index.d.ts.map +1 -1
- package/dist/components/features/data-table/hooks/use-data-table-client.d.ts +17 -20
- package/dist/components/features/data-table/hooks/use-data-table-client.d.ts.map +1 -1
- package/dist/components/features/data-table/hooks/use-data-table-server.d.ts +27 -19
- package/dist/components/features/data-table/hooks/use-data-table-server.d.ts.map +1 -1
- package/dist/components/features/data-table/hooks/use-is-client.d.ts +8 -0
- package/dist/components/features/data-table/hooks/use-is-client.d.ts.map +1 -0
- package/dist/components/features/data-table/hooks/use-selectors.d.ts +50 -0
- package/dist/components/features/data-table/hooks/use-selectors.d.ts.map +1 -0
- package/dist/components/features/data-table/index.d.ts +3 -4
- package/dist/components/features/data-table/index.d.ts.map +1 -1
- package/dist/components/features/data-table/types.d.ts +68 -0
- package/dist/components/features/data-table/types.d.ts.map +1 -1
- package/dist/data-table/index.mjs +1039 -550
- package/dist/date-picker/index.mjs +5 -5
- package/dist/dialog/index.mjs +2 -2
- package/dist/{dialog-B2EZJW-q.mjs → dialog-bnMMf9GD.mjs} +2 -2
- package/dist/dropdown/index.mjs +1 -1
- package/dist/dropzone/index.mjs +1 -1
- package/dist/empty-content/index.mjs +1 -1
- package/dist/form/index.mjs +12 -12
- package/dist/grid/index.mjs +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/hover-card/index.mjs +1 -1
- package/dist/icons/index.mjs +1 -1
- package/dist/index.mjs +50 -52
- package/dist/input/index.mjs +2 -2
- package/dist/{input-D241oNEm.mjs → input-fzXBheCN.mjs} +1 -1
- package/dist/input-group/index.mjs +3 -3
- package/dist/{input-group-uobp64zr.mjs → input-group-CPaFSTEV.mjs} +2 -2
- package/dist/input-number/index.mjs +2 -2
- package/dist/{input-number-CEMgBk8-.mjs → input-number-D9ydFith.mjs} +1 -1
- package/dist/input-with-addons/index.mjs +1 -1
- package/dist/label/index.mjs +1 -2
- package/dist/{label-byipFGok.mjs → label-_ste_Re3.mjs} +12 -1
- package/dist/loader-overlay/index.mjs +1 -1
- package/dist/map/index.mjs +8 -8
- package/dist/{map-DupFPkJT.mjs → map-ClJD-qxm.mjs} +5 -5
- package/dist/more-actions/index.mjs +2 -2
- package/dist/{more-actions-D6OyqZQS.mjs → more-actions-DbC8dyed.mjs} +2 -2
- package/dist/page-title/index.mjs +1 -1
- package/dist/popover/index.mjs +1 -1
- package/dist/radio-group/index.mjs +1 -1
- package/dist/select/index.mjs +1 -1
- package/dist/{select-BznmyqBr.mjs → select-CwVIFWFO.mjs} +1 -1
- package/dist/sheet/index.mjs +2 -2
- package/dist/{sheet-Bmayi68h.mjs → sheet-mx5XjyEY.mjs} +2 -2
- package/dist/sidebar/index.mjs +6 -6
- package/dist/{sidebar-D2zE7rPy.mjs → sidebar-C4NqSr4r.mjs} +5 -5
- package/dist/skeleton/index.mjs +1 -1
- package/dist/spinner/index.mjs +1 -1
- package/dist/stepper/index.mjs +1 -1
- package/dist/switch/index.mjs +1 -1
- package/dist/table/index.mjs +1 -1
- package/dist/tabs/index.mjs +1 -1
- package/dist/tag-input/index.mjs +2 -2
- package/dist/{tag-input-BI8IRBDH.mjs → tag-input-BfHaKoMF.mjs} +1 -1
- package/dist/task-queue/index.mjs +4 -4
- package/dist/{task-queue-dropdown-D6k067_W.mjs → task-queue-dropdown-fo3TX58Q.mjs} +4 -4
- package/dist/textarea/index.mjs +2 -2
- package/dist/{textarea-BZ85VFsJ.mjs → textarea-X4OjkqLJ.mjs} +1 -1
- package/dist/theme/index.mjs +1 -1
- package/dist/{to-api-format-CXQ7knV4.mjs → to-api-format-zI26rEBI.mjs} +3 -3
- package/dist/toast/index.mjs +1 -1
- package/dist/tooltip/index.mjs +1 -1
- package/dist/typography/index.mjs +1 -1
- package/dist/{use-copy-to-clipboard-CC2hhyYI.mjs → use-copy-to-clipboard-C7xqNxBX.mjs} +1 -1
- package/dist/{use-stepper-CU75TdjZ.mjs → use-stepper-CB1injte.mjs} +14 -14
- package/dist/{use-toast-BLBGnOC3.mjs → use-toast-DN-fZBzJ.mjs} +1 -1
- package/dist/visually-hidden/index.mjs +1 -1
- package/package.json +3 -3
- package/dist/checkbox-DB5_3E_l.mjs +0 -22
- package/dist/checkbox-DMC1Mhaw.mjs +0 -17
- package/dist/components/features/data-table/hooks/use-data-table-context.d.ts +0 -2
- package/dist/components/features/data-table/hooks/use-data-table-context.d.ts.map +0 -1
- package/dist/components/features/data-table/hooks/use-inline-contents.d.ts +0 -7
- package/dist/components/features/data-table/hooks/use-inline-contents.d.ts.map +0 -1
- package/dist/label-ClzLBWRT.mjs +0 -16
- /package/dist/{close.icon-D2r5q3bj.mjs → close.icon-CMNMoXM_.mjs} +0 -0
- /package/dist/{col-Cg_2sTDA.mjs → col-RfO7d6AR.mjs} +0 -0
- /package/dist/{collapsible-Dw71o2um.mjs → collapsible-Bt9UYfv3.mjs} +0 -0
- /package/dist/{dialog-Bm4trnic.mjs → dialog-DXBaT9gA.mjs} +0 -0
- /package/dist/{dropdown-DLZXinlT.mjs → dropdown-Cs7Xr8w7.mjs} +0 -0
- /package/dist/{dropdown-menu-Xahj42Gr.mjs → dropdown-menu-DAFyO-qD.mjs} +0 -0
- /package/dist/{dropzone-CGyjGnER.mjs → dropzone-BT5fEDEF.mjs} +0 -0
- /package/dist/{empty-content-ByvwjHUs.mjs → empty-content-iDu3NUqG.mjs} +0 -0
- /package/dist/{hover-card-BNrHtWy6.mjs → hover-card-CUPfFUqE.mjs} +0 -0
- /package/dist/{input-C-ZmsHkk.mjs → input-DuyjEKEW.mjs} +0 -0
- /package/dist/{input-with-addons-DzuyGa6G.mjs → input-with-addons-CdgiUQce.mjs} +0 -0
- /package/dist/{loader-overlay-CbxcjyHV.mjs → loader-overlay-D83QeQNj.mjs} +0 -0
- /package/dist/{map-leaflet-imports-CgEyVRnp.mjs → map-leaflet-imports-CdzvEnzY.mjs} +0 -0
- /package/dist/{page-title-CrYQ091u.mjs → page-title-SGchAF6Y.mjs} +0 -0
- /package/dist/{popover-CYzXdp9q.mjs → popover-Ds9624qY.mjs} +0 -0
- /package/dist/{radio-group-WZCIDQCH.mjs → radio-group-B9Hm77LQ.mjs} +0 -0
- /package/dist/{sheet-b9V9soz8.mjs → sheet-Cemwh78x.mjs} +0 -0
- /package/dist/{skeleton-D3qW_KvG.mjs → skeleton-Cs6Q5GQc.mjs} +0 -0
- /package/dist/{spinner-CKTGKv5n.mjs → spinner-earfjpJs.mjs} +0 -0
- /package/dist/{stepper-B07hPGG7.mjs → stepper-BG9DIzN5.mjs} +0 -0
- /package/dist/{switch-CujyyOi6.mjs → switch-B2VVauH6.mjs} +0 -0
- /package/dist/{table-fZEvpdD-.mjs → table-Dc3HfbM4.mjs} +0 -0
- /package/dist/{tabs-B7cW59gB.mjs → tabs-Ccb4uqbe.mjs} +0 -0
- /package/dist/{textarea-BSkDKiej.mjs → textarea-QYRcDEpK.mjs} +0 -0
- /package/dist/{theme.provider-BG3cS9xe.mjs → theme.provider-Nun_O9-O.mjs} +0 -0
- /package/dist/{tooltip-CbCWKEzu.mjs → tooltip-DZFG1iMs.mjs} +0 -0
- /package/dist/{typography-DdrxIJMd.mjs → typography-T7WgvO77.mjs} +0 -0
- /package/dist/{use-debounce-Dc95PFRX.mjs → use-debounce-Ctljs3MB.mjs} +0 -0
- /package/dist/{visuallyhidden-CfBnXfvh.mjs → visuallyhidden-CgkVhApW.mjs} +0 -0
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { t as cn } from "../
|
|
2
|
-
import { t as
|
|
3
|
-
import
|
|
4
|
-
import "../
|
|
5
|
-
import
|
|
6
|
-
import { t as
|
|
7
|
-
import
|
|
8
|
-
import { i as
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import { t as
|
|
12
|
-
import {
|
|
13
|
-
import { t as
|
|
14
|
-
import {
|
|
1
|
+
import { t as cn } from "../cn-DWCc1QRE.mjs";
|
|
2
|
+
import { t as Badge } from "../badge-bFgeYceE.mjs";
|
|
3
|
+
import "../utils-Bfgoe-Gm.mjs";
|
|
4
|
+
import { t as Button } from "../button-C1wRfGtT.mjs";
|
|
5
|
+
import "../button-AzpnV-WB.mjs";
|
|
6
|
+
import { t as Checkbox } from "../checkbox-LG1OKTpG.mjs";
|
|
7
|
+
import "../dialog-DXBaT9gA.mjs";
|
|
8
|
+
import { a as CommandInput, i as CommandGroup, o as CommandItem, r as CommandEmpty, s as CommandList, t as Command } from "../command-s0Yv3abE.mjs";
|
|
9
|
+
import "../input-DuyjEKEW.mjs";
|
|
10
|
+
import { t as Input } from "../input-fzXBheCN.mjs";
|
|
11
|
+
import { t as Label } from "../label-_ste_Re3.mjs";
|
|
12
|
+
import { i as DropdownMenuItem, l as DropdownMenuTrigger, r as DropdownMenuContent, t as DropdownMenu } from "../dropdown-menu-DAFyO-qD.mjs";
|
|
13
|
+
import { i as PopoverTrigger, r as PopoverContent, t as Popover } from "../popover-Ds9624qY.mjs";
|
|
14
|
+
import { i as SelectItem, l as SelectTrigger, n as SelectContent, t as Select, u as SelectValue } from "../select-CwVIFWFO.mjs";
|
|
15
|
+
import { t as Skeleton } from "../skeleton-Cs6Q5GQc.mjs";
|
|
16
|
+
import { c as TableRow, i as TableCell, n as TableBody, o as TableHead, s as TableHeader, t as Table } from "../table-Dc3HfbM4.mjs";
|
|
17
|
+
import { t as CalendarDatePicker } from "../calendar-date-picker-mlbzp3xR.mjs";
|
|
15
18
|
import { ArrowDown, ArrowUp, ArrowUpDown, Check, ChevronDown, ChevronLeft, ChevronRight, MoreHorizontal, X } from "lucide-react";
|
|
16
|
-
import { createContext, use, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
17
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
18
|
-
import { parseAsInteger,
|
|
19
|
-
import { flexRender, getCoreRowModel,
|
|
19
|
+
import { createContext, memo, use, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
20
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
21
|
+
import { parseAsInteger, parseAsString, useQueryStates } from "nuqs";
|
|
22
|
+
import { flexRender, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
|
20
23
|
|
|
21
24
|
//#region src/components/features/data-table/constants.ts
|
|
22
25
|
const DEFAULT_PAGE_SIZE = 20;
|
|
@@ -32,15 +35,32 @@ const DEFAULT_LOADING_ROWS = 5;
|
|
|
32
35
|
//#endregion
|
|
33
36
|
//#region src/components/features/data-table/adapters/nuqs-adapter.ts
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
38
|
+
* Serialize SortingState to URL-friendly string.
|
|
39
|
+
* Format: "name" (asc), "-name" (desc), comma-separated for multi-sort.
|
|
40
|
+
* Example: "-department,name" → [{id:"department",desc:true},{id:"name",desc:false}]
|
|
37
41
|
*/
|
|
38
|
-
function
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
42
|
+
function serializeSorting(sorting) {
|
|
43
|
+
if (sorting.length === 0) return "";
|
|
44
|
+
return sorting.map((s) => s.desc ? `-${s.id}` : s.id).join(",");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse URL sort string back to SortingState.
|
|
48
|
+
*/
|
|
49
|
+
function parseSorting(value) {
|
|
50
|
+
if (!value) return [];
|
|
51
|
+
return value.split(",").filter(Boolean).map((part) => {
|
|
52
|
+
if (part.startsWith("-")) return {
|
|
53
|
+
id: part.slice(1),
|
|
54
|
+
desc: true
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
id: part,
|
|
58
|
+
desc: false
|
|
59
|
+
};
|
|
60
|
+
});
|
|
41
61
|
}
|
|
42
62
|
const coreSearchParams = {
|
|
43
|
-
sort:
|
|
63
|
+
sort: parseAsString.withDefault(""),
|
|
44
64
|
q: parseAsString.withDefault(""),
|
|
45
65
|
page: parseAsInteger.withDefault(0),
|
|
46
66
|
size: parseAsInteger.withDefault(DEFAULT_PAGE_SIZE)
|
|
@@ -48,22 +68,21 @@ const coreSearchParams = {
|
|
|
48
68
|
/**
|
|
49
69
|
* Hook that creates a StateAdapter backed by nuqs URL query state.
|
|
50
70
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
71
|
+
* URL format:
|
|
72
|
+
* - `?sort=name` (asc) or `?sort=-name` (desc), comma-separated for multi-sort
|
|
73
|
+
* - `?q=search` for search text
|
|
74
|
+
* - `?page=0&size=20` for pagination
|
|
75
|
+
* - Custom filter keys as declared in options
|
|
53
76
|
*
|
|
54
77
|
* Requires `nuqs` to be installed in the consumer app.
|
|
55
78
|
*
|
|
56
79
|
* @example
|
|
57
80
|
* ```tsx
|
|
58
|
-
* // Zero config — all defaults synced to URL
|
|
59
|
-
* const stateAdapter = useNuqsAdapter()
|
|
60
|
-
* const tableState = useDataTableClient({ data, columns, stateAdapter })
|
|
61
|
-
*
|
|
62
|
-
* // With custom filters synced to URL
|
|
63
|
-
* import { parseAsString } from 'nuqs'
|
|
64
|
-
*
|
|
65
81
|
* const stateAdapter = useNuqsAdapter({
|
|
66
|
-
* filters: {
|
|
82
|
+
* filters: {
|
|
83
|
+
* status: parseAsString.withDefault(''),
|
|
84
|
+
* department: parseAsArrayOf(parseAsString).withDefault([]),
|
|
85
|
+
* },
|
|
67
86
|
* })
|
|
68
87
|
* const tableState = useDataTableClient({ data, columns, stateAdapter })
|
|
69
88
|
* ```
|
|
@@ -75,7 +94,7 @@ function useNuqsAdapter(options = {}) {
|
|
|
75
94
|
const [filterState, setFilterState] = useQueryStates(hasFilters ? filterParsers : { _dt: parseAsString.withDefault("") });
|
|
76
95
|
return useMemo(() => ({
|
|
77
96
|
read: () => ({
|
|
78
|
-
sorting: coreState.sort,
|
|
97
|
+
sorting: parseSorting(coreState.sort),
|
|
79
98
|
search: coreState.q,
|
|
80
99
|
pageIndex: coreState.page,
|
|
81
100
|
pageSize: coreState.size,
|
|
@@ -83,19 +102,24 @@ function useNuqsAdapter(options = {}) {
|
|
|
83
102
|
}),
|
|
84
103
|
write: (state) => {
|
|
85
104
|
setCoreState({
|
|
86
|
-
sort: state.sorting,
|
|
105
|
+
sort: serializeSorting(state.sorting),
|
|
87
106
|
q: state.search,
|
|
88
107
|
page: state.pageIndex ?? 0,
|
|
89
108
|
size: state.pageSize ?? DEFAULT_PAGE_SIZE
|
|
90
109
|
});
|
|
91
|
-
if (hasFilters)
|
|
110
|
+
if (hasFilters && filterParsers) {
|
|
111
|
+
const update = {};
|
|
112
|
+
for (const key of Object.keys(filterParsers)) update[key] = state.filters?.[key] ?? null;
|
|
113
|
+
setFilterState(update);
|
|
114
|
+
}
|
|
92
115
|
}
|
|
93
116
|
}), [
|
|
94
117
|
coreState,
|
|
95
118
|
filterState,
|
|
96
119
|
hasFilters,
|
|
97
120
|
setCoreState,
|
|
98
|
-
setFilterState
|
|
121
|
+
setFilterState,
|
|
122
|
+
filterParsers
|
|
99
123
|
]);
|
|
100
124
|
}
|
|
101
125
|
|
|
@@ -131,22 +155,587 @@ function withSelectionColumn(columns, options = {}) {
|
|
|
131
155
|
return [createSelectionColumn(options), ...columns];
|
|
132
156
|
}
|
|
133
157
|
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/components/features/data-table/core/filter-engine.ts
|
|
160
|
+
const FILTER_STRATEGIES = {
|
|
161
|
+
"checkbox": (cellValue, filterValue) => {
|
|
162
|
+
if (filterValue == null) return true;
|
|
163
|
+
if (Array.isArray(filterValue) && filterValue.length === 0) return true;
|
|
164
|
+
if (!Array.isArray(filterValue)) return cellValue === filterValue;
|
|
165
|
+
if (Array.isArray(cellValue)) return cellValue.some((v) => filterValue.includes(v));
|
|
166
|
+
return filterValue.includes(cellValue);
|
|
167
|
+
},
|
|
168
|
+
"select": (cellValue, filterValue) => {
|
|
169
|
+
if (filterValue == null || filterValue === "") return true;
|
|
170
|
+
return cellValue === filterValue;
|
|
171
|
+
},
|
|
172
|
+
"date-gte": (cellValue, filterValue) => {
|
|
173
|
+
if (!filterValue) return true;
|
|
174
|
+
const cell = new Date(cellValue);
|
|
175
|
+
const filter = new Date(filterValue);
|
|
176
|
+
if (Number.isNaN(cell.getTime()) || Number.isNaN(filter.getTime())) return true;
|
|
177
|
+
return cell >= filter;
|
|
178
|
+
},
|
|
179
|
+
"date-lte": (cellValue, filterValue) => {
|
|
180
|
+
if (!filterValue) return true;
|
|
181
|
+
const cell = new Date(cellValue);
|
|
182
|
+
const filter = new Date(filterValue);
|
|
183
|
+
if (Number.isNaN(cell.getTime()) || Number.isNaN(filter.getTime())) return true;
|
|
184
|
+
return cell <= filter;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
function resolveStrategy(strategy) {
|
|
188
|
+
if (!strategy) return void 0;
|
|
189
|
+
if (typeof strategy === "function") return strategy;
|
|
190
|
+
return FILTER_STRATEGIES[strategy];
|
|
191
|
+
}
|
|
192
|
+
function applyFilters(data, filters, search, registeredFilters, customFilterFns, searchConfig) {
|
|
193
|
+
const hasFilters = Object.keys(filters).length > 0;
|
|
194
|
+
const hasSearch = search.length > 0;
|
|
195
|
+
if (!hasFilters && !hasSearch) return data;
|
|
196
|
+
return data.filter((row) => {
|
|
197
|
+
if (hasFilters) for (const [column, value] of Object.entries(filters)) {
|
|
198
|
+
const fn = customFilterFns[column] ?? resolveStrategy(registeredFilters.get(column));
|
|
199
|
+
if (!fn) {
|
|
200
|
+
console.warn(`[DataTable] No filter strategy registered for column "${column}". Filter ignored.`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const cellValue = row[column];
|
|
204
|
+
if (!fn(cellValue, value)) return false;
|
|
205
|
+
}
|
|
206
|
+
if (hasSearch) {
|
|
207
|
+
const query = search.toLowerCase();
|
|
208
|
+
if (searchConfig.searchFn) return searchConfig.searchFn(row, search);
|
|
209
|
+
if (searchConfig.searchableColumns && searchConfig.searchableColumns.length > 0) return searchConfig.searchableColumns.some((col) => {
|
|
210
|
+
const cellValue = row[col];
|
|
211
|
+
return cellValue != null && String(cellValue).toLowerCase().includes(query);
|
|
212
|
+
});
|
|
213
|
+
return Object.values(row).some((val) => {
|
|
214
|
+
if (val == null) return false;
|
|
215
|
+
return String(val).toLowerCase().includes(query);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/components/features/data-table/core/store.ts
|
|
224
|
+
function createDataTableStore(options) {
|
|
225
|
+
let registeredFilters = /* @__PURE__ */ new Map();
|
|
226
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
227
|
+
function computeFilteredData(s) {
|
|
228
|
+
if (s.mode === "server") return s.data;
|
|
229
|
+
return applyFilters(s.data, s.filters, s.search, registeredFilters, options.filterFns ?? {}, {
|
|
230
|
+
searchFn: options.searchFn,
|
|
231
|
+
searchableColumns: options.searchableColumns
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
let state = {
|
|
235
|
+
data: options.data,
|
|
236
|
+
filteredData: options.data,
|
|
237
|
+
sorting: options.defaultSort ?? [],
|
|
238
|
+
filters: options.defaultFilters ?? {},
|
|
239
|
+
search: "",
|
|
240
|
+
rowSelection: {},
|
|
241
|
+
pageIndex: 0,
|
|
242
|
+
pageSize: options.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
243
|
+
mode: options.mode,
|
|
244
|
+
isLoading: false,
|
|
245
|
+
error: null,
|
|
246
|
+
inlineContents: [],
|
|
247
|
+
_version: 0
|
|
248
|
+
};
|
|
249
|
+
if (options.defaultFilters && Object.keys(options.defaultFilters).length > 0) state = {
|
|
250
|
+
...state,
|
|
251
|
+
filteredData: computeFilteredData(state)
|
|
252
|
+
};
|
|
253
|
+
function notify() {
|
|
254
|
+
for (const listener of listeners) listener();
|
|
255
|
+
}
|
|
256
|
+
function setState(next) {
|
|
257
|
+
state = {
|
|
258
|
+
...next,
|
|
259
|
+
_version: state._version + 1
|
|
260
|
+
};
|
|
261
|
+
notify();
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
getSnapshot: () => state,
|
|
265
|
+
subscribe: (listener) => {
|
|
266
|
+
listeners.add(listener);
|
|
267
|
+
return () => listeners.delete(listener);
|
|
268
|
+
},
|
|
269
|
+
setData: (data) => {
|
|
270
|
+
const next = {
|
|
271
|
+
...state,
|
|
272
|
+
data,
|
|
273
|
+
pageIndex: 0,
|
|
274
|
+
rowSelection: {}
|
|
275
|
+
};
|
|
276
|
+
setState({
|
|
277
|
+
...next,
|
|
278
|
+
filteredData: computeFilteredData(next)
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
setServerData: (data) => {
|
|
282
|
+
setState({
|
|
283
|
+
...state,
|
|
284
|
+
data,
|
|
285
|
+
filteredData: data
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
setTable: (_t) => {},
|
|
289
|
+
setSorting: (sorting) => {
|
|
290
|
+
setState({
|
|
291
|
+
...state,
|
|
292
|
+
sorting,
|
|
293
|
+
rowSelection: {}
|
|
294
|
+
});
|
|
295
|
+
},
|
|
296
|
+
setFilter: (key, value) => {
|
|
297
|
+
const next = {
|
|
298
|
+
...state,
|
|
299
|
+
filters: {
|
|
300
|
+
...state.filters,
|
|
301
|
+
[key]: value
|
|
302
|
+
},
|
|
303
|
+
rowSelection: {},
|
|
304
|
+
pageIndex: 0
|
|
305
|
+
};
|
|
306
|
+
setState({
|
|
307
|
+
...next,
|
|
308
|
+
filteredData: computeFilteredData(next)
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
clearFilter: (key) => {
|
|
312
|
+
const filters = Object.fromEntries(Object.entries(state.filters).filter(([k]) => k !== key));
|
|
313
|
+
const next = {
|
|
314
|
+
...state,
|
|
315
|
+
filters,
|
|
316
|
+
rowSelection: {},
|
|
317
|
+
pageIndex: 0
|
|
318
|
+
};
|
|
319
|
+
setState({
|
|
320
|
+
...next,
|
|
321
|
+
filteredData: computeFilteredData(next)
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
clearAllFilters: () => {
|
|
325
|
+
const next = {
|
|
326
|
+
...state,
|
|
327
|
+
filters: {},
|
|
328
|
+
rowSelection: {},
|
|
329
|
+
pageIndex: 0
|
|
330
|
+
};
|
|
331
|
+
setState({
|
|
332
|
+
...next,
|
|
333
|
+
filteredData: computeFilteredData(next)
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
setSearch: (search) => {
|
|
337
|
+
const next = {
|
|
338
|
+
...state,
|
|
339
|
+
search,
|
|
340
|
+
rowSelection: {},
|
|
341
|
+
pageIndex: 0
|
|
342
|
+
};
|
|
343
|
+
setState({
|
|
344
|
+
...next,
|
|
345
|
+
filteredData: computeFilteredData(next)
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
clearSearch: () => {
|
|
349
|
+
const next = {
|
|
350
|
+
...state,
|
|
351
|
+
search: "",
|
|
352
|
+
rowSelection: {},
|
|
353
|
+
pageIndex: 0
|
|
354
|
+
};
|
|
355
|
+
setState({
|
|
356
|
+
...next,
|
|
357
|
+
filteredData: computeFilteredData(next)
|
|
358
|
+
});
|
|
359
|
+
},
|
|
360
|
+
setRowSelection: (rowSelection) => {
|
|
361
|
+
setState({
|
|
362
|
+
...state,
|
|
363
|
+
rowSelection
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
setPageIndex: (pageIndex) => {
|
|
367
|
+
if (!Number.isFinite(pageIndex) || pageIndex < 0) return;
|
|
368
|
+
setState({
|
|
369
|
+
...state,
|
|
370
|
+
pageIndex: Math.floor(pageIndex),
|
|
371
|
+
rowSelection: {}
|
|
372
|
+
});
|
|
373
|
+
},
|
|
374
|
+
setPageSize: (pageSize) => {
|
|
375
|
+
if (!Number.isFinite(pageSize) || pageSize < 1) return;
|
|
376
|
+
setState({
|
|
377
|
+
...state,
|
|
378
|
+
pageSize: Math.floor(pageSize),
|
|
379
|
+
pageIndex: 0,
|
|
380
|
+
rowSelection: {}
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
setPagination: (pageIndex, pageSize) => {
|
|
384
|
+
const safeIndex = Number.isFinite(pageIndex) ? Math.max(0, Math.floor(pageIndex)) : state.pageIndex;
|
|
385
|
+
const safeSize = Number.isFinite(pageSize) ? Math.max(1, Math.floor(pageSize)) : state.pageSize;
|
|
386
|
+
setState({
|
|
387
|
+
...state,
|
|
388
|
+
pageIndex: safeIndex,
|
|
389
|
+
pageSize: safeSize,
|
|
390
|
+
rowSelection: {}
|
|
391
|
+
});
|
|
392
|
+
},
|
|
393
|
+
setLoading: (isLoading) => {
|
|
394
|
+
setState({
|
|
395
|
+
...state,
|
|
396
|
+
isLoading
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
setError: (error) => {
|
|
400
|
+
setState({
|
|
401
|
+
...state,
|
|
402
|
+
error
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
registerFilter: (column, strategy) => {
|
|
406
|
+
const next = new Map(registeredFilters);
|
|
407
|
+
next.set(column, strategy);
|
|
408
|
+
registeredFilters = next;
|
|
409
|
+
const filteredData = computeFilteredData(state);
|
|
410
|
+
setState({
|
|
411
|
+
...state,
|
|
412
|
+
filteredData
|
|
413
|
+
});
|
|
414
|
+
},
|
|
415
|
+
unregisterFilter: (column) => {
|
|
416
|
+
const next = new Map(registeredFilters);
|
|
417
|
+
next.delete(column);
|
|
418
|
+
registeredFilters = next;
|
|
419
|
+
if (column in state.filters) {
|
|
420
|
+
const filteredData = computeFilteredData(state);
|
|
421
|
+
setState({
|
|
422
|
+
...state,
|
|
423
|
+
filteredData
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
registerInlineContent: (entry) => {
|
|
428
|
+
const existing = state.inlineContents.findIndex((e) => e.id === entry.id);
|
|
429
|
+
const inlineContents = existing >= 0 ? state.inlineContents.map((e, i) => i === existing ? entry : e) : [...state.inlineContents, entry];
|
|
430
|
+
setState({
|
|
431
|
+
...state,
|
|
432
|
+
inlineContents
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
unregisterInlineContent: (id) => {
|
|
436
|
+
const inlineContents = state.inlineContents.filter((e) => e.id !== id);
|
|
437
|
+
setState({
|
|
438
|
+
...state,
|
|
439
|
+
inlineContents
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
134
445
|
//#endregion
|
|
135
446
|
//#region src/components/features/data-table/core/data-table-context.tsx
|
|
447
|
+
const DataTableStoreContext = createContext(null);
|
|
448
|
+
const TableInstanceContext = createContext(null);
|
|
449
|
+
/**
|
|
450
|
+
* Monotonic counter that increments on every store mutation.
|
|
451
|
+
* Table-dependent hooks consume this context to force re-renders
|
|
452
|
+
* through React's {children} composition boundary, since the
|
|
453
|
+
* mutable table singleton (stable ref) cannot trigger context updates.
|
|
454
|
+
*/
|
|
455
|
+
const DataTableRenderKeyContext = createContext(0);
|
|
456
|
+
/**
|
|
457
|
+
* Forces a re-render when the store changes. Used by table-dependent hooks.
|
|
458
|
+
* Uses useContext (not use()) because use() does not reliably register
|
|
459
|
+
* context subscriptions in SSR/hydration scenarios (React Router SSR),
|
|
460
|
+
* preventing re-renders when the context value changes.
|
|
461
|
+
*/
|
|
462
|
+
function useRenderKey() {
|
|
463
|
+
return useContext(DataTableRenderKeyContext);
|
|
464
|
+
}
|
|
136
465
|
const DataTableContext = createContext(null);
|
|
137
|
-
function
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
140
|
-
return
|
|
466
|
+
function useDataTableStore() {
|
|
467
|
+
const store = use(DataTableStoreContext);
|
|
468
|
+
if (!store) throw new Error("useDataTableStore must be used within a <DataTable.Client> or <DataTable.Server> provider");
|
|
469
|
+
return store;
|
|
470
|
+
}
|
|
471
|
+
function useTableInstance() {
|
|
472
|
+
const table = use(TableInstanceContext);
|
|
473
|
+
if (!table) throw new Error("useTableInstance must be used within a <DataTable.Client> or <DataTable.Server> provider");
|
|
474
|
+
return table;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/components/features/data-table/hooks/use-selectors.ts
|
|
479
|
+
function shallowEqual(a, b) {
|
|
480
|
+
const keysA = Object.keys(a);
|
|
481
|
+
const keysB = Object.keys(b);
|
|
482
|
+
if (keysA.length !== keysB.length) return false;
|
|
483
|
+
for (const key of keysA) {
|
|
484
|
+
const va = a[key];
|
|
485
|
+
const vb = b[key];
|
|
486
|
+
if (va === vb) continue;
|
|
487
|
+
if (Array.isArray(va) && Array.isArray(vb)) {
|
|
488
|
+
if (va.length !== vb.length) return false;
|
|
489
|
+
for (let i = 0; i < va.length; i++) if (va[i] !== vb[i]) return false;
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
function useSliceSelector(selector) {
|
|
497
|
+
const store = useDataTableStore();
|
|
498
|
+
const cachedRef = useRef(null);
|
|
499
|
+
const getSnapshot = useCallback(() => {
|
|
500
|
+
const next = selector(store.getSnapshot());
|
|
501
|
+
if (cachedRef.current && shallowEqual(cachedRef.current, next)) return cachedRef.current;
|
|
502
|
+
cachedRef.current = next;
|
|
503
|
+
return next;
|
|
504
|
+
}, [store, selector]);
|
|
505
|
+
return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
|
|
506
|
+
}
|
|
507
|
+
function useDataTableFilters() {
|
|
508
|
+
const store = useDataTableStore();
|
|
509
|
+
return useSliceSelector(useCallback((state) => ({
|
|
510
|
+
filters: state.filters,
|
|
511
|
+
setFilter: store.setFilter,
|
|
512
|
+
clearFilter: store.clearFilter,
|
|
513
|
+
clearAllFilters: store.clearAllFilters,
|
|
514
|
+
registerFilter: store.registerFilter,
|
|
515
|
+
unregisterFilter: store.unregisterFilter
|
|
516
|
+
}), [store]));
|
|
517
|
+
}
|
|
518
|
+
function useDataTableSearch() {
|
|
519
|
+
const store = useDataTableStore();
|
|
520
|
+
return useSliceSelector(useCallback((state) => ({
|
|
521
|
+
search: state.search,
|
|
522
|
+
setSearch: store.setSearch,
|
|
523
|
+
clearSearch: store.clearSearch
|
|
524
|
+
}), [store]));
|
|
525
|
+
}
|
|
526
|
+
function useDataTableSorting() {
|
|
527
|
+
const store = useDataTableStore();
|
|
528
|
+
return useSliceSelector(useCallback((state) => ({
|
|
529
|
+
sorting: state.sorting,
|
|
530
|
+
setSorting: store.setSorting
|
|
531
|
+
}), [store]));
|
|
532
|
+
}
|
|
533
|
+
function useDataTableSelection() {
|
|
534
|
+
useRenderKey();
|
|
535
|
+
const store = useDataTableStore();
|
|
536
|
+
const table = useTableInstance();
|
|
537
|
+
return {
|
|
538
|
+
rowSelection: store.getSnapshot().rowSelection,
|
|
539
|
+
setRowSelection: store.setRowSelection,
|
|
540
|
+
selectedRows: table.getFilteredSelectedRowModel().rows.map((r) => r.original)
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function useDataTablePagination() {
|
|
544
|
+
useRenderKey();
|
|
545
|
+
const store = useDataTableStore();
|
|
546
|
+
const table = useTableInstance();
|
|
547
|
+
const state = store.getSnapshot();
|
|
548
|
+
const nextPage = useCallback(() => table.nextPage(), [table]);
|
|
549
|
+
const prevPage = useCallback(() => table.previousPage(), [table]);
|
|
550
|
+
return {
|
|
551
|
+
canNextPage: table.getCanNextPage(),
|
|
552
|
+
canPrevPage: table.getCanPreviousPage(),
|
|
553
|
+
nextPage,
|
|
554
|
+
prevPage,
|
|
555
|
+
pageIndex: state.pageIndex,
|
|
556
|
+
pageCount: table.getPageCount(),
|
|
557
|
+
setPageIndex: store.setPageIndex,
|
|
558
|
+
pageSize: state.pageSize,
|
|
559
|
+
setPageSize: store.setPageSize,
|
|
560
|
+
totalRows: state.filteredData.length
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function useDataTableRows() {
|
|
564
|
+
useRenderKey();
|
|
565
|
+
const table = useTableInstance();
|
|
566
|
+
return {
|
|
567
|
+
rows: table.getRowModel().rows,
|
|
568
|
+
headerGroups: table.getHeaderGroups(),
|
|
569
|
+
totalColumns: table.getAllColumns().length
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function useDataTableLoading() {
|
|
573
|
+
return useSliceSelector(useCallback((state) => ({
|
|
574
|
+
isLoading: state.isLoading,
|
|
575
|
+
error: state.error
|
|
576
|
+
}), []));
|
|
577
|
+
}
|
|
578
|
+
function useDataTableInlineContents() {
|
|
579
|
+
const store = useDataTableStore();
|
|
580
|
+
return useSliceSelector(useCallback((state) => ({
|
|
581
|
+
inlineContents: state.inlineContents,
|
|
582
|
+
registerInlineContent: store.registerInlineContent,
|
|
583
|
+
unregisterInlineContent: store.unregisterInlineContent
|
|
584
|
+
}), [store]));
|
|
141
585
|
}
|
|
142
586
|
|
|
587
|
+
//#endregion
|
|
588
|
+
//#region src/components/features/data-table/components/active-filters.tsx
|
|
589
|
+
function formatValue(column, value, formatter) {
|
|
590
|
+
if (formatter) {
|
|
591
|
+
const result = formatter(column, value);
|
|
592
|
+
if (result !== void 0) return result;
|
|
593
|
+
}
|
|
594
|
+
return String(value);
|
|
595
|
+
}
|
|
596
|
+
function FilterGroup({ label, children, className }) {
|
|
597
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
598
|
+
className: cn("flex items-center gap-2 rounded-md border px-2 py-1", className),
|
|
599
|
+
"data-slot": "dt-filter-group",
|
|
600
|
+
"data-testid": "dt-filter-group",
|
|
601
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
602
|
+
className: "text-muted-foreground border-r pr-2 text-xs",
|
|
603
|
+
children: label
|
|
604
|
+
}), children]
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const EMPTY_LABELS = {};
|
|
608
|
+
function ActiveFiltersInner({ label = "Selected Filters", filterLabels = EMPTY_LABELS, formatFilterValue: formatter, clearAll = "icon", clearAllLabel = "Clear all", className, groupClassName, badgeClassName }) {
|
|
609
|
+
const { filters, setFilter, clearFilter, clearAllFilters } = useDataTableFilters();
|
|
610
|
+
const { search, clearSearch } = useDataTableSearch();
|
|
611
|
+
const activeFilterEntries = Object.entries(filters).filter(([, value]) => value != null && value !== "" && !(Array.isArray(value) && value.length === 0));
|
|
612
|
+
const hasSearch = search.length > 0;
|
|
613
|
+
const hasFilters = activeFilterEntries.length > 0;
|
|
614
|
+
if (!hasSearch && !hasFilters) return null;
|
|
615
|
+
const totalGroups = activeFilterEntries.length + (hasSearch ? 1 : 0);
|
|
616
|
+
const removeArrayItem = (column, items, item) => {
|
|
617
|
+
const remaining = items.filter((v) => v !== item);
|
|
618
|
+
if (remaining.length > 0) setFilter(column, remaining);
|
|
619
|
+
else clearFilter(column);
|
|
620
|
+
};
|
|
621
|
+
const handleClearAll = () => {
|
|
622
|
+
clearAllFilters();
|
|
623
|
+
if (hasSearch) clearSearch();
|
|
624
|
+
};
|
|
625
|
+
const badgeCn = cn("flex items-center gap-1.5 px-2 py-0.5 text-xs", badgeClassName);
|
|
626
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
627
|
+
className: cn("flex flex-wrap items-center gap-2", className),
|
|
628
|
+
"data-slot": "dt-active-filters",
|
|
629
|
+
"data-testid": "dt-active-filters",
|
|
630
|
+
children: [
|
|
631
|
+
label !== null && /* @__PURE__ */ jsx("span", {
|
|
632
|
+
className: "text-sm text-muted-foreground",
|
|
633
|
+
"data-slot": "dt-active-filters-label",
|
|
634
|
+
children: label
|
|
635
|
+
}),
|
|
636
|
+
hasSearch && /* @__PURE__ */ jsx(FilterGroup, {
|
|
637
|
+
label: "Search",
|
|
638
|
+
className: groupClassName,
|
|
639
|
+
children: /* @__PURE__ */ jsxs(Badge, {
|
|
640
|
+
type: "muted",
|
|
641
|
+
theme: "solid",
|
|
642
|
+
className: badgeCn,
|
|
643
|
+
children: [/* @__PURE__ */ jsx("span", { children: search }), /* @__PURE__ */ jsx(Button, {
|
|
644
|
+
theme: "borderless",
|
|
645
|
+
size: "small",
|
|
646
|
+
"aria-label": "Clear search",
|
|
647
|
+
className: "h-auto p-0 text-muted-foreground hover:text-foreground",
|
|
648
|
+
onClick: clearSearch,
|
|
649
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
650
|
+
className: "size-2.5",
|
|
651
|
+
"aria-hidden": "true"
|
|
652
|
+
})
|
|
653
|
+
})]
|
|
654
|
+
})
|
|
655
|
+
}),
|
|
656
|
+
activeFilterEntries.map(([column, value]) => {
|
|
657
|
+
const groupLabel = filterLabels[column] ?? column;
|
|
658
|
+
if (Array.isArray(value)) return /* @__PURE__ */ jsx(FilterGroup, {
|
|
659
|
+
label: groupLabel,
|
|
660
|
+
className: groupClassName,
|
|
661
|
+
children: value.map((item) => /* @__PURE__ */ jsxs(Badge, {
|
|
662
|
+
type: "muted",
|
|
663
|
+
theme: "solid",
|
|
664
|
+
className: badgeCn,
|
|
665
|
+
children: [/* @__PURE__ */ jsx("span", { children: formatValue(column, item, formatter) }), /* @__PURE__ */ jsx(Button, {
|
|
666
|
+
theme: "borderless",
|
|
667
|
+
size: "small",
|
|
668
|
+
"aria-label": `Remove ${formatValue(column, item, formatter)} from ${groupLabel}`,
|
|
669
|
+
className: "h-auto p-0 text-muted-foreground hover:text-foreground",
|
|
670
|
+
onClick: () => removeArrayItem(column, value, item),
|
|
671
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
672
|
+
className: "size-2.5",
|
|
673
|
+
"aria-hidden": "true"
|
|
674
|
+
})
|
|
675
|
+
})]
|
|
676
|
+
}, item))
|
|
677
|
+
}, column);
|
|
678
|
+
return /* @__PURE__ */ jsx(FilterGroup, {
|
|
679
|
+
label: groupLabel,
|
|
680
|
+
className: groupClassName,
|
|
681
|
+
children: /* @__PURE__ */ jsxs(Badge, {
|
|
682
|
+
type: "muted",
|
|
683
|
+
theme: "solid",
|
|
684
|
+
className: badgeCn,
|
|
685
|
+
children: [/* @__PURE__ */ jsx("span", { children: formatValue(column, value, formatter) }), /* @__PURE__ */ jsx(Button, {
|
|
686
|
+
theme: "borderless",
|
|
687
|
+
size: "small",
|
|
688
|
+
"aria-label": `Clear ${groupLabel} filter`,
|
|
689
|
+
className: "h-auto p-0 text-muted-foreground hover:text-foreground",
|
|
690
|
+
onClick: () => clearFilter(column),
|
|
691
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
692
|
+
className: "size-2.5",
|
|
693
|
+
"aria-hidden": "true"
|
|
694
|
+
})
|
|
695
|
+
})]
|
|
696
|
+
})
|
|
697
|
+
}, column);
|
|
698
|
+
}),
|
|
699
|
+
totalGroups > 1 && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
700
|
+
clearAll === "icon" && /* @__PURE__ */ jsx(Button, {
|
|
701
|
+
theme: "borderless",
|
|
702
|
+
size: "small",
|
|
703
|
+
"aria-label": clearAllLabel,
|
|
704
|
+
title: clearAllLabel,
|
|
705
|
+
className: "h-auto p-1 text-muted-foreground hover:text-foreground",
|
|
706
|
+
"data-slot": "dt-clear-all-filters",
|
|
707
|
+
"data-testid": "dt-clear-all-filters",
|
|
708
|
+
onClick: handleClearAll,
|
|
709
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
710
|
+
}),
|
|
711
|
+
clearAll === "button" && /* @__PURE__ */ jsxs(Button, {
|
|
712
|
+
theme: "outline",
|
|
713
|
+
size: "small",
|
|
714
|
+
className: "h-auto px-2 py-1 text-xs",
|
|
715
|
+
"data-slot": "dt-clear-all-filters",
|
|
716
|
+
"data-testid": "dt-clear-all-filters",
|
|
717
|
+
onClick: handleClearAll,
|
|
718
|
+
children: [/* @__PURE__ */ jsx(X, { className: "size-3 mr-1" }), clearAllLabel]
|
|
719
|
+
}),
|
|
720
|
+
clearAll === "text" && /* @__PURE__ */ jsx(Button, {
|
|
721
|
+
theme: "borderless",
|
|
722
|
+
size: "small",
|
|
723
|
+
className: "h-auto px-1 py-0.5 text-xs text-muted-foreground hover:text-foreground",
|
|
724
|
+
"data-slot": "dt-clear-all-filters",
|
|
725
|
+
"data-testid": "dt-clear-all-filters",
|
|
726
|
+
onClick: handleClearAll,
|
|
727
|
+
children: clearAllLabel
|
|
728
|
+
})
|
|
729
|
+
] })
|
|
730
|
+
]
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
const DataTableActiveFilters = memo(ActiveFiltersInner);
|
|
734
|
+
|
|
143
735
|
//#endregion
|
|
144
736
|
//#region src/components/features/data-table/components/bulk-actions.tsx
|
|
145
737
|
function DataTableBulkActions({ children, className }) {
|
|
146
|
-
const {
|
|
147
|
-
const selectedRows = useMemo(() => {
|
|
148
|
-
return table.getFilteredSelectedRowModel().rows.map((row) => row.original);
|
|
149
|
-
}, [table, rowSelection]);
|
|
738
|
+
const { selectedRows } = useDataTableSelection();
|
|
150
739
|
if (selectedRows.length === 0) return null;
|
|
151
740
|
return /* @__PURE__ */ jsx("div", {
|
|
152
741
|
"data-slot": "dt-bulk-actions",
|
|
@@ -170,7 +759,8 @@ function DataTableColumnHeader({ column, title, className }) {
|
|
|
170
759
|
children: /* @__PURE__ */ jsxs("button", {
|
|
171
760
|
type: "button",
|
|
172
761
|
className: "flex items-center gap-1 hover:text-foreground -ml-3 h-8 px-3 cursor-pointer",
|
|
173
|
-
onClick:
|
|
762
|
+
onClick: column.getToggleSortingHandler(),
|
|
763
|
+
"aria-label": `Sort by ${title}${sorted === "asc" ? ", sorted ascending" : sorted === "desc" ? ", sorted descending" : ""}`,
|
|
174
764
|
children: [/* @__PURE__ */ jsx("span", { children: title }), sorted === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "size-4" }) : sorted === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-4" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "size-4" })]
|
|
175
765
|
})
|
|
176
766
|
});
|
|
@@ -197,10 +787,10 @@ function renderInlineContentRow(entry, colSpan, rows) {
|
|
|
197
787
|
}, entry.id);
|
|
198
788
|
}
|
|
199
789
|
function DataTableContent({ emptyMessage, className, tableClassName, headerClassName, headerRowClassName, headerCellClassName, bodyClassName, rowClassName, cellClassName }) {
|
|
200
|
-
const {
|
|
201
|
-
const
|
|
790
|
+
const { rows, headerGroups, totalColumns } = useDataTableRows();
|
|
791
|
+
const { inlineContents } = useDataTableInlineContents();
|
|
202
792
|
const openInlineContents = useMemo(() => inlineContents.filter((e) => e.open), [inlineContents]);
|
|
203
|
-
const colSpan =
|
|
793
|
+
const colSpan = totalColumns;
|
|
204
794
|
return /* @__PURE__ */ jsx("div", {
|
|
205
795
|
className: cn("datum-ui-data-table", className),
|
|
206
796
|
"data-slot": "dt",
|
|
@@ -211,7 +801,7 @@ function DataTableContent({ emptyMessage, className, tableClassName, headerClass
|
|
|
211
801
|
children: [/* @__PURE__ */ jsx(TableHeader, {
|
|
212
802
|
className: cn(headerClassName),
|
|
213
803
|
"data-slot": "dt-header",
|
|
214
|
-
children:
|
|
804
|
+
children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx(TableRow, {
|
|
215
805
|
className: cn(headerRowClassName),
|
|
216
806
|
"data-slot": "dt-header-row",
|
|
217
807
|
children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(TableHead, {
|
|
@@ -228,6 +818,7 @@ function DataTableContent({ emptyMessage, className, tableClassName, headerClass
|
|
|
228
818
|
if (rowEntry) return renderInlineContentRow(rowEntry, colSpan, rows);
|
|
229
819
|
return /* @__PURE__ */ jsx(TableRow, {
|
|
230
820
|
className: cn(resolveClassName(rowClassName, row)),
|
|
821
|
+
style: { transitionProperty: "none" },
|
|
231
822
|
"data-slot": "dt-row",
|
|
232
823
|
"data-state": row.getIsSelected() ? "selected" : void 0,
|
|
233
824
|
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
|
|
@@ -254,7 +845,7 @@ function DataTableContent({ emptyMessage, className, tableClassName, headerClass
|
|
|
254
845
|
//#region src/components/features/data-table/components/inline-content.tsx
|
|
255
846
|
function DataTableInlineContent({ position, rowId, open, onClose, className, children }) {
|
|
256
847
|
const id = useId();
|
|
257
|
-
const { registerInlineContent, unregisterInlineContent } =
|
|
848
|
+
const { registerInlineContent, unregisterInlineContent } = useDataTableInlineContents();
|
|
258
849
|
const initialRender = useRef(true);
|
|
259
850
|
useEffect(() => {
|
|
260
851
|
registerInlineContent({
|
|
@@ -325,57 +916,121 @@ function DataTableLoading({ rows = DEFAULT_LOADING_ROWS, columns = 4, className
|
|
|
325
916
|
|
|
326
917
|
//#endregion
|
|
327
918
|
//#region src/components/features/data-table/components/pagination.tsx
|
|
919
|
+
/**
|
|
920
|
+
* Generates page numbers with ellipsis for large page counts.
|
|
921
|
+
* Shows up to 7 items: first, last, current +/- 1 neighbor, and ellipsis gaps.
|
|
922
|
+
*/
|
|
923
|
+
function getPageNumbers(currentPage, totalPages) {
|
|
924
|
+
if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1);
|
|
925
|
+
const pages = [1];
|
|
926
|
+
const current = currentPage + 1;
|
|
927
|
+
if (current <= 4) {
|
|
928
|
+
for (let i = 2; i <= 5; i++) pages.push(i);
|
|
929
|
+
pages.push("...");
|
|
930
|
+
pages.push(totalPages);
|
|
931
|
+
} else if (current >= totalPages - 3) {
|
|
932
|
+
pages.push("...");
|
|
933
|
+
for (let i = totalPages - 4; i <= totalPages; i++) pages.push(i);
|
|
934
|
+
} else {
|
|
935
|
+
pages.push("...");
|
|
936
|
+
for (let i = current - 1; i <= current + 1; i++) pages.push(i);
|
|
937
|
+
pages.push("...");
|
|
938
|
+
pages.push(totalPages);
|
|
939
|
+
}
|
|
940
|
+
return pages;
|
|
941
|
+
}
|
|
328
942
|
function DataTablePagination({ pageSizes = DEFAULT_PAGE_SIZES, className }) {
|
|
329
|
-
const {
|
|
943
|
+
const { canNextPage, canPrevPage, nextPage, prevPage, pageIndex, pageCount, setPageIndex, pageSize, setPageSize, totalRows } = useDataTablePagination();
|
|
944
|
+
const isClientMode = pageCount > 0;
|
|
945
|
+
const startRow = pageIndex * pageSize + 1;
|
|
946
|
+
const endRow = Math.min((pageIndex + 1) * pageSize, totalRows);
|
|
947
|
+
const pageNumbers = useMemo(() => getPageNumbers(pageIndex, pageCount), [pageIndex, pageCount]);
|
|
330
948
|
return /* @__PURE__ */ jsxs("div", {
|
|
331
|
-
className: cn("flex items-center justify-between px-2 py-4", className),
|
|
949
|
+
className: cn("flex flex-col-reverse items-center justify-between gap-4 px-2 py-4 sm:flex-row", className),
|
|
332
950
|
"data-slot": "dt-pagination",
|
|
333
951
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
334
|
-
className: "flex items-center gap-
|
|
335
|
-
children: [/* @__PURE__ */
|
|
336
|
-
className: "text-sm text-muted-foreground",
|
|
337
|
-
children:
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
952
|
+
className: "flex items-center gap-4",
|
|
953
|
+
children: [isClientMode && totalRows > 0 && /* @__PURE__ */ jsxs("span", {
|
|
954
|
+
className: "text-sm text-muted-foreground whitespace-nowrap",
|
|
955
|
+
children: [
|
|
956
|
+
"Showing",
|
|
957
|
+
" ",
|
|
958
|
+
startRow,
|
|
959
|
+
" ",
|
|
960
|
+
"to",
|
|
961
|
+
" ",
|
|
962
|
+
endRow,
|
|
963
|
+
" ",
|
|
964
|
+
"of",
|
|
965
|
+
" ",
|
|
966
|
+
totalRows,
|
|
967
|
+
" ",
|
|
968
|
+
"rows"
|
|
969
|
+
]
|
|
970
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
971
|
+
className: "flex items-center gap-2",
|
|
972
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
973
|
+
className: "text-sm text-muted-foreground whitespace-nowrap",
|
|
974
|
+
children: "Rows per page"
|
|
975
|
+
}), /* @__PURE__ */ jsxs(Select, {
|
|
976
|
+
value: String(pageSize),
|
|
977
|
+
onValueChange: (value) => setPageSize(Number(value)),
|
|
978
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
979
|
+
className: "h-8 w-[70px]",
|
|
980
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: String(pageSize) })
|
|
981
|
+
}), /* @__PURE__ */ jsx(SelectContent, {
|
|
982
|
+
side: "top",
|
|
983
|
+
children: pageSizes.map((size) => /* @__PURE__ */ jsx(SelectItem, {
|
|
984
|
+
value: String(size),
|
|
985
|
+
children: size
|
|
986
|
+
}, size))
|
|
987
|
+
})]
|
|
350
988
|
})]
|
|
351
989
|
})]
|
|
352
|
-
}), /* @__PURE__ */ jsxs("
|
|
353
|
-
|
|
990
|
+
}), /* @__PURE__ */ jsxs("nav", {
|
|
991
|
+
"aria-label": "Table pagination",
|
|
992
|
+
className: "flex items-center gap-1",
|
|
354
993
|
children: [
|
|
355
|
-
|
|
356
|
-
|
|
994
|
+
/* @__PURE__ */ jsx(Button, {
|
|
995
|
+
theme: "outline",
|
|
996
|
+
size: "icon",
|
|
997
|
+
className: "size-8",
|
|
998
|
+
onClick: prevPage,
|
|
999
|
+
disabled: !canPrevPage,
|
|
1000
|
+
"aria-label": "Previous page",
|
|
1001
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
|
|
1002
|
+
}),
|
|
1003
|
+
isClientMode && pageCount > 1 ? pageNumbers.map((page, index) => {
|
|
1004
|
+
if (page === "...") return /* @__PURE__ */ jsx("span", {
|
|
1005
|
+
className: "px-2 text-sm text-muted-foreground",
|
|
1006
|
+
children: "..."
|
|
1007
|
+
}, `ellipsis-${index}`);
|
|
1008
|
+
const isActive = page === pageIndex + 1;
|
|
1009
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
1010
|
+
theme: isActive ? "solid" : "outline",
|
|
1011
|
+
size: "small",
|
|
1012
|
+
className: cn("h-8 min-w-8 px-2", isActive && "pointer-events-none font-semibold"),
|
|
1013
|
+
onClick: () => setPageIndex(page - 1),
|
|
1014
|
+
"aria-disabled": isActive || void 0,
|
|
1015
|
+
"aria-label": `Page ${page}`,
|
|
1016
|
+
"aria-current": isActive ? "page" : void 0,
|
|
1017
|
+
children: page
|
|
1018
|
+
}, page);
|
|
1019
|
+
}) : !isClientMode && /* @__PURE__ */ jsxs("span", {
|
|
1020
|
+
className: "px-2 text-sm text-muted-foreground",
|
|
357
1021
|
children: [
|
|
358
1022
|
"Page",
|
|
359
1023
|
" ",
|
|
360
|
-
|
|
361
|
-
" ",
|
|
362
|
-
"of",
|
|
363
|
-
" ",
|
|
364
|
-
pagination.pageCount
|
|
1024
|
+
pageIndex + 1
|
|
365
1025
|
]
|
|
366
1026
|
}),
|
|
367
1027
|
/* @__PURE__ */ jsx(Button, {
|
|
368
|
-
|
|
369
|
-
size: "
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
/* @__PURE__ */ jsx(Button, {
|
|
375
|
-
variant: "outline",
|
|
376
|
-
size: "sm",
|
|
377
|
-
onClick: pagination.nextPage,
|
|
378
|
-
disabled: !pagination.canNextPage,
|
|
1028
|
+
theme: "outline",
|
|
1029
|
+
size: "icon",
|
|
1030
|
+
className: "size-8",
|
|
1031
|
+
onClick: nextPage,
|
|
1032
|
+
disabled: !canNextPage,
|
|
1033
|
+
"aria-label": "Next page",
|
|
379
1034
|
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
|
|
380
1035
|
})
|
|
381
1036
|
]
|
|
@@ -395,8 +1050,8 @@ function DataTableRowActions({ row, actions, isLoading = false, className }) {
|
|
|
395
1050
|
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
396
1051
|
asChild: true,
|
|
397
1052
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
398
|
-
|
|
399
|
-
size: "
|
|
1053
|
+
theme: "borderless",
|
|
1054
|
+
size: "small",
|
|
400
1055
|
className,
|
|
401
1056
|
disabled: isLoading,
|
|
402
1057
|
"data-slot": "dt-row-actions",
|
|
@@ -421,7 +1076,7 @@ function DataTableRowActions({ row, actions, isLoading = false, className }) {
|
|
|
421
1076
|
//#endregion
|
|
422
1077
|
//#region src/components/features/data-table/components/search.tsx
|
|
423
1078
|
function DataTableSearch({ placeholder = "Search...", debounceMs = DEFAULT_DEBOUNCE_MS, className }) {
|
|
424
|
-
const { search, setSearch } =
|
|
1079
|
+
const { search, setSearch } = useDataTableSearch();
|
|
425
1080
|
const [inputValue, setInputValue] = useState(search);
|
|
426
1081
|
useEffect(() => {
|
|
427
1082
|
setInputValue(search);
|
|
@@ -442,262 +1097,331 @@ function DataTableSearch({ placeholder = "Search...", debounceMs = DEFAULT_DEBOU
|
|
|
442
1097
|
value: inputValue,
|
|
443
1098
|
onChange: (e) => setInputValue(e.target.value),
|
|
444
1099
|
className,
|
|
1100
|
+
"aria-label": placeholder,
|
|
445
1101
|
"data-slot": "dt-search"
|
|
446
1102
|
});
|
|
447
1103
|
}
|
|
448
1104
|
|
|
449
1105
|
//#endregion
|
|
450
|
-
//#region src/components/features/data-table/hooks/use-
|
|
451
|
-
function
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}, [])
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
//#endregion
|
|
473
|
-
//#region src/components/features/data-table/core/client-provider.tsx
|
|
474
|
-
function ClientProvider({ data, columns, sorting, setSorting, filters, setFilter, clearFilter, clearAllFilters, search, setSearch, clearSearch, searchableColumns, searchFn, rowSelection, setRowSelection, pagination, getRowId, enableRowSelection = false, className, children }) {
|
|
475
|
-
const columnFilters = useMemo(() => Object.entries(filters).map(([id, value]) => ({
|
|
476
|
-
id,
|
|
477
|
-
value
|
|
478
|
-
})), [filters]);
|
|
479
|
-
const arrayIncludesFilter = useMemo(() => (row, columnId, filterValue) => {
|
|
480
|
-
if (filterValue == null) return true;
|
|
481
|
-
if (Array.isArray(filterValue) && filterValue.length === 0) return true;
|
|
482
|
-
const cellValue = row.getValue(columnId);
|
|
483
|
-
if (Array.isArray(filterValue)) {
|
|
484
|
-
if (Array.isArray(cellValue)) return cellValue.some((v) => filterValue.includes(v));
|
|
485
|
-
return filterValue.includes(cellValue);
|
|
1106
|
+
//#region src/components/features/data-table/hooks/use-data-table-client.ts
|
|
1107
|
+
function useDataTableClient(options) {
|
|
1108
|
+
const { data, columns, pageSize, getRowId, enableRowSelection = false, defaultSort, defaultFilters, searchableColumns, searchFn, filterFns, stateAdapter } = options;
|
|
1109
|
+
const store = useMemo(() => createDataTableStore({
|
|
1110
|
+
data,
|
|
1111
|
+
mode: "client",
|
|
1112
|
+
defaultSort,
|
|
1113
|
+
defaultFilters,
|
|
1114
|
+
pageSize,
|
|
1115
|
+
searchableColumns,
|
|
1116
|
+
searchFn,
|
|
1117
|
+
filterFns
|
|
1118
|
+
}), []);
|
|
1119
|
+
const isInitialRender = useRef(true);
|
|
1120
|
+
useEffect(() => {
|
|
1121
|
+
if (isInitialRender.current) {
|
|
1122
|
+
isInitialRender.current = false;
|
|
1123
|
+
return;
|
|
486
1124
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}, []);
|
|
490
|
-
const
|
|
491
|
-
if (searchFn) return (row, _columnId, filterValue) => searchFn(row.original, String(filterValue));
|
|
492
|
-
if (searchableColumns && searchableColumns.length > 0) return (row, _columnId, filterValue) => {
|
|
493
|
-
const query = String(filterValue).toLowerCase();
|
|
494
|
-
return searchableColumns.some((col) => {
|
|
495
|
-
const cellValue = row.getValue(col);
|
|
496
|
-
return cellValue != null && String(cellValue).toLowerCase().includes(query);
|
|
497
|
-
});
|
|
498
|
-
};
|
|
499
|
-
}, [searchFn, searchableColumns]);
|
|
1125
|
+
store.setData(data);
|
|
1126
|
+
}, [data, store]);
|
|
1127
|
+
const resolvedColumns = useMemo(() => enableRowSelection ? withSelectionColumn(columns, typeof enableRowSelection === "object" ? enableRowSelection : {}) : columns, [columns, enableRowSelection]);
|
|
1128
|
+
const { filteredData, sorting, rowSelection, pageIndex, pageSize: storePageSize, filters, search } = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
500
1129
|
const table = useReactTable({
|
|
501
|
-
data,
|
|
502
|
-
columns,
|
|
1130
|
+
data: filteredData,
|
|
1131
|
+
columns: resolvedColumns,
|
|
503
1132
|
state: {
|
|
504
1133
|
sorting,
|
|
505
1134
|
rowSelection,
|
|
506
|
-
globalFilter: search,
|
|
507
|
-
columnFilters,
|
|
508
1135
|
pagination: {
|
|
509
|
-
pageIndex
|
|
510
|
-
pageSize:
|
|
1136
|
+
pageIndex,
|
|
1137
|
+
pageSize: storePageSize
|
|
511
1138
|
}
|
|
512
1139
|
},
|
|
513
|
-
filterFns: { arrayIncludes: arrayIncludesFilter },
|
|
514
|
-
defaultColumn: { filterFn: arrayIncludesFilter },
|
|
515
|
-
...globalFilterFn ? { globalFilterFn } : {},
|
|
516
1140
|
onSortingChange: (updater) => {
|
|
517
|
-
|
|
1141
|
+
const next = typeof updater === "function" ? updater(sorting) : updater;
|
|
1142
|
+
store.setSorting(next);
|
|
518
1143
|
},
|
|
519
1144
|
onRowSelectionChange: (updater) => {
|
|
520
|
-
|
|
1145
|
+
const next = typeof updater === "function" ? updater(rowSelection) : updater;
|
|
1146
|
+
store.setRowSelection(next);
|
|
521
1147
|
},
|
|
522
1148
|
onPaginationChange: (updater) => {
|
|
523
1149
|
const next = typeof updater === "function" ? updater({
|
|
524
|
-
pageIndex
|
|
525
|
-
pageSize:
|
|
1150
|
+
pageIndex,
|
|
1151
|
+
pageSize: storePageSize
|
|
526
1152
|
}) : updater;
|
|
527
|
-
|
|
528
|
-
pagination.setPageSize(next.pageSize);
|
|
1153
|
+
store.setPagination(next.pageIndex, next.pageSize);
|
|
529
1154
|
},
|
|
530
1155
|
getCoreRowModel: getCoreRowModel(),
|
|
531
1156
|
getSortedRowModel: getSortedRowModel(),
|
|
532
|
-
getFilteredRowModel: getFilteredRowModel(),
|
|
533
1157
|
getPaginationRowModel: getPaginationRowModel(),
|
|
534
1158
|
getRowId,
|
|
535
1159
|
enableRowSelection: !!enableRowSelection
|
|
536
1160
|
});
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
setFilter,
|
|
560
|
-
clearFilter,
|
|
561
|
-
clearAllFilters,
|
|
562
|
-
setSearch,
|
|
563
|
-
clearSearch,
|
|
564
|
-
setRowSelection,
|
|
565
|
-
pagination: paginationState,
|
|
566
|
-
inlineContents,
|
|
567
|
-
registerInlineContent,
|
|
568
|
-
unregisterInlineContent
|
|
569
|
-
}), [
|
|
570
|
-
table,
|
|
1161
|
+
const hydratedRef = useRef(false);
|
|
1162
|
+
useEffect(() => {
|
|
1163
|
+
if (stateAdapter && !hydratedRef.current) {
|
|
1164
|
+
hydratedRef.current = true;
|
|
1165
|
+
const persisted = stateAdapter.read();
|
|
1166
|
+
if (persisted.sorting && persisted.sorting.length > 0) store.setSorting(persisted.sorting);
|
|
1167
|
+
if (persisted.filters) {
|
|
1168
|
+
for (const [key, value] of Object.entries(persisted.filters)) if (value != null) store.setFilter(key, value);
|
|
1169
|
+
}
|
|
1170
|
+
if (persisted.search) store.setSearch(persisted.search);
|
|
1171
|
+
if (persisted.pageIndex != null && persisted.pageIndex > 0) store.setPageIndex(persisted.pageIndex);
|
|
1172
|
+
if (persisted.pageSize != null) store.setPageSize(persisted.pageSize);
|
|
1173
|
+
}
|
|
1174
|
+
}, []);
|
|
1175
|
+
const isFirstWrite = useRef(true);
|
|
1176
|
+
useEffect(() => {
|
|
1177
|
+
if (!stateAdapter) return;
|
|
1178
|
+
if (isFirstWrite.current) {
|
|
1179
|
+
isFirstWrite.current = false;
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
stateAdapter.write({
|
|
571
1183
|
sorting,
|
|
572
1184
|
filters,
|
|
573
1185
|
search,
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1186
|
+
pageIndex,
|
|
1187
|
+
pageSize: storePageSize
|
|
1188
|
+
});
|
|
1189
|
+
}, [
|
|
1190
|
+
sorting,
|
|
1191
|
+
filters,
|
|
1192
|
+
search,
|
|
1193
|
+
pageIndex,
|
|
1194
|
+
storePageSize,
|
|
1195
|
+
stateAdapter
|
|
1196
|
+
]);
|
|
1197
|
+
return {
|
|
1198
|
+
store,
|
|
1199
|
+
table
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
//#endregion
|
|
1204
|
+
//#region src/components/features/data-table/hooks/use-is-client.ts
|
|
1205
|
+
/**
|
|
1206
|
+
* Returns `false` during SSR and `true` after hydration.
|
|
1207
|
+
* Used to gate components that depend on client-only APIs
|
|
1208
|
+
* (e.g. TanStack Table's internal useSyncExternalStore call
|
|
1209
|
+
* which omits the getServerSnapshot argument).
|
|
1210
|
+
*/
|
|
1211
|
+
function useIsClient() {
|
|
1212
|
+
const [isClient, setIsClient] = useState(false);
|
|
1213
|
+
useEffect(() => setIsClient(true), []);
|
|
1214
|
+
return isClient;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
//#endregion
|
|
1218
|
+
//#region src/components/features/data-table/core/client-provider.tsx
|
|
1219
|
+
/**
|
|
1220
|
+
* Inner component that calls useDataTableClient.
|
|
1221
|
+
* Only rendered on the client (gated by ClientProvider).
|
|
1222
|
+
*/
|
|
1223
|
+
function ClientProviderInner({ className, children, ssrFallback: _ssrFallback, ...options }) {
|
|
1224
|
+
const { store, table } = useDataTableClient(options);
|
|
1225
|
+
return /* @__PURE__ */ jsx(DataTableStoreContext, {
|
|
1226
|
+
value: store,
|
|
1227
|
+
children: /* @__PURE__ */ jsx(TableInstanceContext, {
|
|
1228
|
+
value: table,
|
|
1229
|
+
children: /* @__PURE__ */ jsx(DataTableRenderKeyContext, {
|
|
1230
|
+
value: store.getSnapshot()._version,
|
|
1231
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1232
|
+
className,
|
|
1233
|
+
children
|
|
1234
|
+
})
|
|
1235
|
+
})
|
|
590
1236
|
})
|
|
591
1237
|
});
|
|
592
1238
|
}
|
|
1239
|
+
function ClientProvider(props) {
|
|
1240
|
+
if (!useIsClient()) return /* @__PURE__ */ jsx(Fragment$1, { children: props.ssrFallback ?? null });
|
|
1241
|
+
return /* @__PURE__ */ jsx(ClientProviderInner, { ...props });
|
|
1242
|
+
}
|
|
593
1243
|
|
|
594
1244
|
//#endregion
|
|
595
|
-
//#region src/components/features/data-table/
|
|
596
|
-
function
|
|
1245
|
+
//#region src/components/features/data-table/hooks/use-data-table-server.ts
|
|
1246
|
+
function useDataTableServer(options) {
|
|
1247
|
+
const { columns, fetchFn, transform, limit = 20, getRowId, enableRowSelection = false, defaultSort, defaultFilters, stateAdapter } = options;
|
|
1248
|
+
const fetchRef = useRef(fetchFn);
|
|
1249
|
+
const transformRef = useRef(transform);
|
|
1250
|
+
useEffect(() => {
|
|
1251
|
+
fetchRef.current = fetchFn;
|
|
1252
|
+
}, [fetchFn]);
|
|
1253
|
+
useEffect(() => {
|
|
1254
|
+
transformRef.current = transform;
|
|
1255
|
+
}, [transform]);
|
|
1256
|
+
const cursorMapRef = useRef(/* @__PURE__ */ new Map());
|
|
1257
|
+
const hasNextPageRef = useRef(false);
|
|
1258
|
+
const store = useMemo(() => createDataTableStore({
|
|
1259
|
+
data: [],
|
|
1260
|
+
mode: "server",
|
|
1261
|
+
defaultSort,
|
|
1262
|
+
defaultFilters,
|
|
1263
|
+
pageSize: limit
|
|
1264
|
+
}), []);
|
|
1265
|
+
const { sorting, filters, search, rowSelection, pageSize, pageIndex } = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
1266
|
+
const prevQueryRef = useRef({
|
|
1267
|
+
sorting,
|
|
1268
|
+
filters,
|
|
1269
|
+
search,
|
|
1270
|
+
pageSize
|
|
1271
|
+
});
|
|
1272
|
+
useEffect(() => {
|
|
1273
|
+
const prev = prevQueryRef.current;
|
|
1274
|
+
if (prev.sorting !== sorting || prev.filters !== filters || prev.search !== search || prev.pageSize !== pageSize) {
|
|
1275
|
+
cursorMapRef.current = /* @__PURE__ */ new Map();
|
|
1276
|
+
hasNextPageRef.current = false;
|
|
1277
|
+
if (pageIndex !== 0) {
|
|
1278
|
+
prevQueryRef.current = {
|
|
1279
|
+
sorting,
|
|
1280
|
+
filters,
|
|
1281
|
+
search,
|
|
1282
|
+
pageSize
|
|
1283
|
+
};
|
|
1284
|
+
store.setPageIndex(0);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
prevQueryRef.current = {
|
|
1289
|
+
sorting,
|
|
1290
|
+
filters,
|
|
1291
|
+
search,
|
|
1292
|
+
pageSize
|
|
1293
|
+
};
|
|
1294
|
+
let cancelled = false;
|
|
1295
|
+
store.setLoading(true);
|
|
1296
|
+
const cursor = cursorMapRef.current.get(pageIndex);
|
|
1297
|
+
fetchRef.current({
|
|
1298
|
+
sorting,
|
|
1299
|
+
filters,
|
|
1300
|
+
search,
|
|
1301
|
+
cursor,
|
|
1302
|
+
limit: pageSize
|
|
1303
|
+
}).then((response) => {
|
|
1304
|
+
if (cancelled) return;
|
|
1305
|
+
const result = transformRef.current(response);
|
|
1306
|
+
store.setServerData(result.data);
|
|
1307
|
+
store.setError(null);
|
|
1308
|
+
if (result.nextCursor) cursorMapRef.current.set(pageIndex + 1, result.nextCursor);
|
|
1309
|
+
hasNextPageRef.current = result.hasNextPage;
|
|
1310
|
+
}).catch((error) => {
|
|
1311
|
+
if (cancelled) return;
|
|
1312
|
+
store.setServerData([]);
|
|
1313
|
+
store.setError(error instanceof Error ? error : new Error(String(error)));
|
|
1314
|
+
hasNextPageRef.current = false;
|
|
1315
|
+
}).finally(() => {
|
|
1316
|
+
if (!cancelled) store.setLoading(false);
|
|
1317
|
+
});
|
|
1318
|
+
return () => {
|
|
1319
|
+
cancelled = true;
|
|
1320
|
+
};
|
|
1321
|
+
}, [
|
|
1322
|
+
sorting,
|
|
1323
|
+
filters,
|
|
1324
|
+
search,
|
|
1325
|
+
pageSize,
|
|
1326
|
+
pageIndex,
|
|
1327
|
+
store
|
|
1328
|
+
]);
|
|
1329
|
+
const resolvedColumns = useMemo(() => enableRowSelection ? withSelectionColumn(columns, typeof enableRowSelection === "object" ? enableRowSelection : {}) : columns, [columns, enableRowSelection]);
|
|
597
1330
|
const table = useReactTable({
|
|
598
|
-
data,
|
|
599
|
-
columns,
|
|
1331
|
+
data: store.getSnapshot().data,
|
|
1332
|
+
columns: resolvedColumns,
|
|
600
1333
|
state: {
|
|
601
1334
|
sorting,
|
|
602
|
-
rowSelection
|
|
1335
|
+
rowSelection,
|
|
1336
|
+
pagination: {
|
|
1337
|
+
pageIndex,
|
|
1338
|
+
pageSize
|
|
1339
|
+
}
|
|
603
1340
|
},
|
|
1341
|
+
manualPagination: true,
|
|
1342
|
+
manualSorting: true,
|
|
1343
|
+
manualFiltering: true,
|
|
1344
|
+
pageCount: hasNextPageRef.current ? pageIndex + 2 : pageIndex + 1,
|
|
1345
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1346
|
+
getRowId,
|
|
1347
|
+
enableRowSelection: !!enableRowSelection,
|
|
604
1348
|
onSortingChange: (updater) => {
|
|
605
|
-
|
|
1349
|
+
const next = typeof updater === "function" ? updater(sorting) : updater;
|
|
1350
|
+
store.setSorting(next);
|
|
606
1351
|
},
|
|
607
1352
|
onRowSelectionChange: (updater) => {
|
|
608
|
-
|
|
1353
|
+
const next = typeof updater === "function" ? updater(rowSelection) : updater;
|
|
1354
|
+
store.setRowSelection(next);
|
|
609
1355
|
},
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1356
|
+
onPaginationChange: (updater) => {
|
|
1357
|
+
const next = typeof updater === "function" ? updater({
|
|
1358
|
+
pageIndex,
|
|
1359
|
+
pageSize
|
|
1360
|
+
}) : updater;
|
|
1361
|
+
store.setPagination(next.pageIndex, next.pageSize);
|
|
1362
|
+
}
|
|
616
1363
|
});
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
canNextPage: pagination.hasNextPage,
|
|
620
|
-
canPrevPage: pagination.hasPrevPage,
|
|
621
|
-
nextPage: pagination.nextPage,
|
|
622
|
-
prevPage: pagination.prevPage,
|
|
623
|
-
pageSize: pagination.limit,
|
|
624
|
-
setPageSize: pagination.setLimit
|
|
625
|
-
}), [pagination]);
|
|
626
|
-
return /* @__PURE__ */ jsx(DataTableContext, {
|
|
627
|
-
value: useMemo(() => ({
|
|
628
|
-
table,
|
|
629
|
-
mode: "server",
|
|
630
|
-
sorting,
|
|
631
|
-
filters,
|
|
632
|
-
search,
|
|
633
|
-
rowSelection,
|
|
634
|
-
isLoading,
|
|
635
|
-
setSorting,
|
|
636
|
-
setFilter,
|
|
637
|
-
clearFilter,
|
|
638
|
-
clearAllFilters,
|
|
639
|
-
setSearch,
|
|
640
|
-
clearSearch,
|
|
641
|
-
setRowSelection,
|
|
642
|
-
pagination: paginationState,
|
|
643
|
-
inlineContents,
|
|
644
|
-
registerInlineContent,
|
|
645
|
-
unregisterInlineContent
|
|
646
|
-
}), [
|
|
647
|
-
table,
|
|
1364
|
+
useEffect(() => {
|
|
1365
|
+
if (stateAdapter) stateAdapter.write({
|
|
648
1366
|
sorting,
|
|
649
1367
|
filters,
|
|
650
1368
|
search,
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
]),
|
|
665
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
666
|
-
className,
|
|
667
|
-
children
|
|
668
|
-
})
|
|
669
|
-
});
|
|
1369
|
+
pageSize
|
|
1370
|
+
});
|
|
1371
|
+
}, [
|
|
1372
|
+
sorting,
|
|
1373
|
+
filters,
|
|
1374
|
+
search,
|
|
1375
|
+
pageSize,
|
|
1376
|
+
stateAdapter
|
|
1377
|
+
]);
|
|
1378
|
+
return {
|
|
1379
|
+
store,
|
|
1380
|
+
table
|
|
1381
|
+
};
|
|
670
1382
|
}
|
|
671
1383
|
|
|
672
1384
|
//#endregion
|
|
673
|
-
//#region
|
|
1385
|
+
//#region src/components/features/data-table/core/server-provider.tsx
|
|
674
1386
|
/**
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
* For Datum-specific variants (sunglow, butter), import from @/modules/datum-ui
|
|
1387
|
+
* Inner component that calls useDataTableServer.
|
|
1388
|
+
* Only rendered on the client (gated by ServerProvider).
|
|
678
1389
|
*/
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1390
|
+
function ServerProviderInner({ className, children, ssrFallback: _ssrFallback, ...options }) {
|
|
1391
|
+
const { store, table } = useDataTableServer(options);
|
|
1392
|
+
return /* @__PURE__ */ jsx(DataTableStoreContext, {
|
|
1393
|
+
value: store,
|
|
1394
|
+
children: /* @__PURE__ */ jsx(TableInstanceContext, {
|
|
1395
|
+
value: table,
|
|
1396
|
+
children: /* @__PURE__ */ jsx(DataTableRenderKeyContext, {
|
|
1397
|
+
value: store.getSnapshot()._version,
|
|
1398
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1399
|
+
className,
|
|
1400
|
+
children
|
|
1401
|
+
})
|
|
1402
|
+
})
|
|
1403
|
+
})
|
|
692
1404
|
});
|
|
693
1405
|
}
|
|
1406
|
+
function ServerProvider(props) {
|
|
1407
|
+
if (!useIsClient()) return /* @__PURE__ */ jsx(Fragment$1, { children: props.ssrFallback ?? null });
|
|
1408
|
+
return /* @__PURE__ */ jsx(ServerProviderInner, { ...props });
|
|
1409
|
+
}
|
|
694
1410
|
|
|
695
1411
|
//#endregion
|
|
696
1412
|
//#region src/components/features/data-table/filters/checkbox-filter.tsx
|
|
697
1413
|
const MAX_VISIBLE_BADGES = 2;
|
|
698
1414
|
function CheckboxFilter({ column, label, options, className, checkboxPopoverClassName }) {
|
|
699
|
-
const { filters, setFilter, clearFilter } =
|
|
1415
|
+
const { filters, setFilter, clearFilter, registerFilter, unregisterFilter } = useDataTableFilters();
|
|
700
1416
|
const [open, setOpen] = useState(false);
|
|
1417
|
+
useEffect(() => {
|
|
1418
|
+
registerFilter(column, "checkbox");
|
|
1419
|
+
return () => unregisterFilter(column);
|
|
1420
|
+
}, [
|
|
1421
|
+
column,
|
|
1422
|
+
registerFilter,
|
|
1423
|
+
unregisterFilter
|
|
1424
|
+
]);
|
|
701
1425
|
const selectedValues = filters[column] ?? [];
|
|
702
1426
|
const updateValues = (newValues) => {
|
|
703
1427
|
if (newValues.length > 0) setFilter(column, newValues);
|
|
@@ -717,7 +1441,7 @@ function CheckboxFilter({ column, label, options, className, checkboxPopoverClas
|
|
|
717
1441
|
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
718
1442
|
asChild: true,
|
|
719
1443
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
720
|
-
|
|
1444
|
+
theme: "outline",
|
|
721
1445
|
className: cn("justify-between gap-1", className),
|
|
722
1446
|
"data-slot": "dt-filter",
|
|
723
1447
|
"data-testid": "dt-filter-trigger",
|
|
@@ -726,7 +1450,8 @@ function CheckboxFilter({ column, label, options, className, checkboxPopoverClas
|
|
|
726
1450
|
children: [visibleBadges.map((val) => {
|
|
727
1451
|
const opt = options.find((o) => o.value === val);
|
|
728
1452
|
return /* @__PURE__ */ jsxs(Badge, {
|
|
729
|
-
|
|
1453
|
+
type: "secondary",
|
|
1454
|
+
theme: "light",
|
|
730
1455
|
className: "text-xs px-1.5 py-0",
|
|
731
1456
|
children: [opt?.label ?? val, /* @__PURE__ */ jsx("span", {
|
|
732
1457
|
role: "button",
|
|
@@ -766,8 +1491,8 @@ function CheckboxFilter({ column, label, options, className, checkboxPopoverClas
|
|
|
766
1491
|
className: "text-sm font-medium",
|
|
767
1492
|
children: label
|
|
768
1493
|
}), selectedValues.length > 0 && /* @__PURE__ */ jsx(Button, {
|
|
769
|
-
|
|
770
|
-
size: "
|
|
1494
|
+
theme: "borderless",
|
|
1495
|
+
size: "small",
|
|
771
1496
|
className: "h-auto p-1 text-xs",
|
|
772
1497
|
onClick: () => clearFilter(column),
|
|
773
1498
|
children: "Clear"
|
|
@@ -797,8 +1522,16 @@ function CheckboxFilter({ column, label, options, className, checkboxPopoverClas
|
|
|
797
1522
|
//#endregion
|
|
798
1523
|
//#region src/components/features/data-table/filters/date-picker-filter.tsx
|
|
799
1524
|
function DatePickerFilter({ column, label, className, datePickerPopoverClassName, disableFuture, disablePast, minDate, maxDate }) {
|
|
800
|
-
const { filters, setFilter, clearFilter } =
|
|
1525
|
+
const { filters, setFilter, clearFilter, registerFilter, unregisterFilter } = useDataTableFilters();
|
|
801
1526
|
const rawValue = filters[column];
|
|
1527
|
+
useEffect(() => {
|
|
1528
|
+
registerFilter(column, "date-gte");
|
|
1529
|
+
return () => unregisterFilter(column);
|
|
1530
|
+
}, [
|
|
1531
|
+
column,
|
|
1532
|
+
registerFilter,
|
|
1533
|
+
unregisterFilter
|
|
1534
|
+
]);
|
|
802
1535
|
const dateRange = useMemo(() => {
|
|
803
1536
|
const date = rawValue ? new Date(rawValue) : void 0;
|
|
804
1537
|
return {
|
|
@@ -831,9 +1564,17 @@ function DatePickerFilter({ column, label, className, datePickerPopoverClassName
|
|
|
831
1564
|
//#endregion
|
|
832
1565
|
//#region src/components/features/data-table/filters/select-filter.tsx
|
|
833
1566
|
function SelectFilter({ column, label, options, placeholder, searchable = true, className, selectPopoverClassName }) {
|
|
834
|
-
const { filters, setFilter, clearFilter } =
|
|
1567
|
+
const { filters, setFilter, clearFilter, registerFilter, unregisterFilter } = useDataTableFilters();
|
|
835
1568
|
const [open, setOpen] = useState(false);
|
|
836
1569
|
const value = filters[column];
|
|
1570
|
+
useEffect(() => {
|
|
1571
|
+
registerFilter(column, "select");
|
|
1572
|
+
return () => unregisterFilter(column);
|
|
1573
|
+
}, [
|
|
1574
|
+
column,
|
|
1575
|
+
registerFilter,
|
|
1576
|
+
unregisterFilter
|
|
1577
|
+
]);
|
|
837
1578
|
const selectedOption = options.find((o) => o.value === value);
|
|
838
1579
|
return /* @__PURE__ */ jsxs(Popover, {
|
|
839
1580
|
open,
|
|
@@ -841,7 +1582,7 @@ function SelectFilter({ column, label, options, placeholder, searchable = true,
|
|
|
841
1582
|
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
842
1583
|
asChild: true,
|
|
843
1584
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
844
|
-
|
|
1585
|
+
theme: "outline",
|
|
845
1586
|
role: "combobox",
|
|
846
1587
|
"aria-expanded": open,
|
|
847
1588
|
className: cn("justify-between", className),
|
|
@@ -892,6 +1633,7 @@ function SelectFilter({ column, label, options, placeholder, searchable = true,
|
|
|
892
1633
|
const DataTable = {
|
|
893
1634
|
Client: ClientProvider,
|
|
894
1635
|
Server: ServerProvider,
|
|
1636
|
+
ActiveFilters: DataTableActiveFilters,
|
|
895
1637
|
Content: DataTableContent,
|
|
896
1638
|
InlineContent: DataTableInlineContent,
|
|
897
1639
|
ColumnHeader: DataTableColumnHeader,
|
|
@@ -906,257 +1648,4 @@ const DataTable = {
|
|
|
906
1648
|
};
|
|
907
1649
|
|
|
908
1650
|
//#endregion
|
|
909
|
-
|
|
910
|
-
function useDataTableClient(options) {
|
|
911
|
-
const { data, columns, pageSize: initialPageSize = DEFAULT_PAGE_SIZE, getRowId, enableRowSelection = false, defaultSort = [], defaultFilters = {}, searchableColumns, searchFn, stateAdapter } = options;
|
|
912
|
-
const adapterState = useMemo(() => stateAdapter?.read() ?? {}, []);
|
|
913
|
-
const [sorting, setSorting] = useState(adapterState.sorting ?? defaultSort);
|
|
914
|
-
const [filters, setFilters] = useState(adapterState.filters ?? defaultFilters);
|
|
915
|
-
const [search, setSearch] = useState(adapterState.search ?? "");
|
|
916
|
-
const [rowSelection, setRowSelection] = useState({});
|
|
917
|
-
const [pageIndex, setPageIndex] = useState(adapterState.pageIndex ?? 0);
|
|
918
|
-
const [pageSize, setPageSize] = useState(adapterState.pageSize ?? initialPageSize);
|
|
919
|
-
const setFilter = useCallback((key, value) => {
|
|
920
|
-
setFilters((prev) => ({
|
|
921
|
-
...prev,
|
|
922
|
-
[key]: value
|
|
923
|
-
}));
|
|
924
|
-
}, []);
|
|
925
|
-
const clearFilter = useCallback((key) => {
|
|
926
|
-
setFilters((prev) => {
|
|
927
|
-
const { [key]: _, ...rest } = prev;
|
|
928
|
-
return rest;
|
|
929
|
-
});
|
|
930
|
-
}, []);
|
|
931
|
-
const clearAllFilters = useCallback(() => {
|
|
932
|
-
setFilters({});
|
|
933
|
-
}, []);
|
|
934
|
-
const clearSearch = useCallback(() => {
|
|
935
|
-
setSearch("");
|
|
936
|
-
}, []);
|
|
937
|
-
useEffect(() => {
|
|
938
|
-
setRowSelection({});
|
|
939
|
-
}, [
|
|
940
|
-
sorting,
|
|
941
|
-
filters,
|
|
942
|
-
search,
|
|
943
|
-
pageIndex,
|
|
944
|
-
pageSize
|
|
945
|
-
]);
|
|
946
|
-
useEffect(() => {
|
|
947
|
-
stateAdapter?.write({
|
|
948
|
-
sorting,
|
|
949
|
-
filters,
|
|
950
|
-
search,
|
|
951
|
-
pageIndex,
|
|
952
|
-
pageSize
|
|
953
|
-
});
|
|
954
|
-
}, [
|
|
955
|
-
stateAdapter,
|
|
956
|
-
sorting,
|
|
957
|
-
filters,
|
|
958
|
-
search,
|
|
959
|
-
pageIndex,
|
|
960
|
-
pageSize
|
|
961
|
-
]);
|
|
962
|
-
const pagination = useMemo(() => ({
|
|
963
|
-
pageIndex,
|
|
964
|
-
pageSize,
|
|
965
|
-
setPageIndex,
|
|
966
|
-
setPageSize
|
|
967
|
-
}), [pageIndex, pageSize]);
|
|
968
|
-
const selectionEnabled = !!enableRowSelection;
|
|
969
|
-
const selectionOptions = typeof enableRowSelection === "object" ? enableRowSelection : void 0;
|
|
970
|
-
return {
|
|
971
|
-
data,
|
|
972
|
-
columns: useMemo(() => selectionEnabled ? withSelectionColumn(columns, selectionOptions) : columns, [
|
|
973
|
-
selectionEnabled,
|
|
974
|
-
columns,
|
|
975
|
-
selectionOptions
|
|
976
|
-
]),
|
|
977
|
-
sorting,
|
|
978
|
-
setSorting,
|
|
979
|
-
filters,
|
|
980
|
-
setFilter,
|
|
981
|
-
clearFilter,
|
|
982
|
-
clearAllFilters,
|
|
983
|
-
search,
|
|
984
|
-
setSearch,
|
|
985
|
-
clearSearch,
|
|
986
|
-
rowSelection,
|
|
987
|
-
setRowSelection,
|
|
988
|
-
pagination,
|
|
989
|
-
getRowId,
|
|
990
|
-
enableRowSelection: selectionEnabled,
|
|
991
|
-
searchableColumns,
|
|
992
|
-
searchFn
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
//#endregion
|
|
997
|
-
//#region src/components/features/data-table/hooks/use-data-table-server.ts
|
|
998
|
-
function useDataTableServer(options) {
|
|
999
|
-
const { columns, fetchFn, transform, limit: initialLimit = DEFAULT_PAGE_SIZE, getRowId, enableRowSelection = false, defaultSort = [], defaultFilters = {}, stateAdapter } = options;
|
|
1000
|
-
const adapterState = useMemo(() => stateAdapter?.read() ?? {}, []);
|
|
1001
|
-
const [data, setData] = useState([]);
|
|
1002
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
1003
|
-
const [sorting, setSorting] = useState(adapterState.sorting ?? defaultSort);
|
|
1004
|
-
const [filters, setFilters] = useState(adapterState.filters ?? defaultFilters);
|
|
1005
|
-
const [search, setSearch] = useState(adapterState.search ?? "");
|
|
1006
|
-
const [rowSelection, setRowSelection] = useState({});
|
|
1007
|
-
const [limit, setLimit] = useState(adapterState.limit ?? initialLimit);
|
|
1008
|
-
const [cursor, setCursor] = useState(void 0);
|
|
1009
|
-
const [nextCursor, setNextCursor] = useState(void 0);
|
|
1010
|
-
const [hasNextPage, setHasNextPage] = useState(false);
|
|
1011
|
-
const [cursorHistory, setCursorHistory] = useState([]);
|
|
1012
|
-
const fetchFnRef = useRef(fetchFn);
|
|
1013
|
-
fetchFnRef.current = fetchFn;
|
|
1014
|
-
const transformRef = useRef(transform);
|
|
1015
|
-
transformRef.current = transform;
|
|
1016
|
-
const setFilter = useCallback((key, value) => {
|
|
1017
|
-
setFilters((prev) => ({
|
|
1018
|
-
...prev,
|
|
1019
|
-
[key]: value
|
|
1020
|
-
}));
|
|
1021
|
-
}, []);
|
|
1022
|
-
const clearFilter = useCallback((key) => {
|
|
1023
|
-
setFilters((prev) => {
|
|
1024
|
-
const { [key]: _, ...rest } = prev;
|
|
1025
|
-
return rest;
|
|
1026
|
-
});
|
|
1027
|
-
}, []);
|
|
1028
|
-
const clearAllFilters = useCallback(() => {
|
|
1029
|
-
setFilters({});
|
|
1030
|
-
}, []);
|
|
1031
|
-
const clearSearch = useCallback(() => {
|
|
1032
|
-
setSearch("");
|
|
1033
|
-
}, []);
|
|
1034
|
-
const resetPagination = useCallback(() => {
|
|
1035
|
-
setCursor(void 0);
|
|
1036
|
-
setNextCursor(void 0);
|
|
1037
|
-
setCursorHistory([]);
|
|
1038
|
-
setHasNextPage(false);
|
|
1039
|
-
setRowSelection({});
|
|
1040
|
-
}, []);
|
|
1041
|
-
useEffect(() => {
|
|
1042
|
-
resetPagination();
|
|
1043
|
-
}, [
|
|
1044
|
-
sorting,
|
|
1045
|
-
filters,
|
|
1046
|
-
search,
|
|
1047
|
-
limit,
|
|
1048
|
-
resetPagination
|
|
1049
|
-
]);
|
|
1050
|
-
useEffect(() => {
|
|
1051
|
-
let cancelled = false;
|
|
1052
|
-
setIsLoading(true);
|
|
1053
|
-
fetchFnRef.current({
|
|
1054
|
-
cursor,
|
|
1055
|
-
limit,
|
|
1056
|
-
sorting,
|
|
1057
|
-
filters,
|
|
1058
|
-
search
|
|
1059
|
-
}).then((response) => {
|
|
1060
|
-
if (cancelled) return;
|
|
1061
|
-
const result = transformRef.current(response);
|
|
1062
|
-
setData(result.data);
|
|
1063
|
-
setNextCursor(result.cursor);
|
|
1064
|
-
setHasNextPage(result.hasNextPage);
|
|
1065
|
-
}).catch(() => {
|
|
1066
|
-
if (cancelled) return;
|
|
1067
|
-
setData([]);
|
|
1068
|
-
setNextCursor(void 0);
|
|
1069
|
-
setHasNextPage(false);
|
|
1070
|
-
}).finally(() => {
|
|
1071
|
-
if (!cancelled) setIsLoading(false);
|
|
1072
|
-
});
|
|
1073
|
-
return () => {
|
|
1074
|
-
cancelled = true;
|
|
1075
|
-
};
|
|
1076
|
-
}, [
|
|
1077
|
-
cursor,
|
|
1078
|
-
limit,
|
|
1079
|
-
sorting,
|
|
1080
|
-
filters,
|
|
1081
|
-
search
|
|
1082
|
-
]);
|
|
1083
|
-
useEffect(() => {
|
|
1084
|
-
stateAdapter?.write({
|
|
1085
|
-
sorting,
|
|
1086
|
-
filters,
|
|
1087
|
-
search,
|
|
1088
|
-
limit
|
|
1089
|
-
});
|
|
1090
|
-
}, [
|
|
1091
|
-
stateAdapter,
|
|
1092
|
-
sorting,
|
|
1093
|
-
filters,
|
|
1094
|
-
search,
|
|
1095
|
-
limit
|
|
1096
|
-
]);
|
|
1097
|
-
const hasPrevPage = cursorHistory.length > 0;
|
|
1098
|
-
const nextPage = useCallback(() => {
|
|
1099
|
-
if (nextCursor && hasNextPage) {
|
|
1100
|
-
setCursorHistory((prev) => [...prev, cursor ?? ""]);
|
|
1101
|
-
setCursor(nextCursor);
|
|
1102
|
-
setRowSelection({});
|
|
1103
|
-
}
|
|
1104
|
-
}, [
|
|
1105
|
-
nextCursor,
|
|
1106
|
-
hasNextPage,
|
|
1107
|
-
cursor
|
|
1108
|
-
]);
|
|
1109
|
-
const prevPage = useCallback(() => {
|
|
1110
|
-
setCursorHistory((prev) => {
|
|
1111
|
-
const newHistory = [...prev];
|
|
1112
|
-
const previousCursor = newHistory.pop();
|
|
1113
|
-
setCursor(previousCursor === "" ? void 0 : previousCursor);
|
|
1114
|
-
return newHistory;
|
|
1115
|
-
});
|
|
1116
|
-
setRowSelection({});
|
|
1117
|
-
}, []);
|
|
1118
|
-
const pagination = useMemo(() => ({
|
|
1119
|
-
cursor,
|
|
1120
|
-
limit,
|
|
1121
|
-
hasNextPage,
|
|
1122
|
-
hasPrevPage,
|
|
1123
|
-
nextPage,
|
|
1124
|
-
prevPage,
|
|
1125
|
-
setLimit
|
|
1126
|
-
}), [
|
|
1127
|
-
cursor,
|
|
1128
|
-
limit,
|
|
1129
|
-
hasNextPage,
|
|
1130
|
-
hasPrevPage,
|
|
1131
|
-
nextPage,
|
|
1132
|
-
prevPage
|
|
1133
|
-
]);
|
|
1134
|
-
const selectionEnabled = !!enableRowSelection;
|
|
1135
|
-
const selectionOptions = typeof enableRowSelection === "object" ? enableRowSelection : void 0;
|
|
1136
|
-
return {
|
|
1137
|
-
data,
|
|
1138
|
-
columns: useMemo(() => selectionEnabled ? withSelectionColumn(columns, selectionOptions) : columns, [
|
|
1139
|
-
selectionEnabled,
|
|
1140
|
-
columns,
|
|
1141
|
-
selectionOptions
|
|
1142
|
-
]),
|
|
1143
|
-
isLoading,
|
|
1144
|
-
sorting,
|
|
1145
|
-
setSorting,
|
|
1146
|
-
filters,
|
|
1147
|
-
setFilter,
|
|
1148
|
-
clearFilter,
|
|
1149
|
-
clearAllFilters,
|
|
1150
|
-
search,
|
|
1151
|
-
setSearch,
|
|
1152
|
-
clearSearch,
|
|
1153
|
-
rowSelection,
|
|
1154
|
-
setRowSelection,
|
|
1155
|
-
pagination,
|
|
1156
|
-
getRowId,
|
|
1157
|
-
enableRowSelection: selectionEnabled
|
|
1158
|
-
};
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
//#endregion
|
|
1162
|
-
export { DEFAULT_DEBOUNCE_MS, DEFAULT_LOADING_ROWS, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES, DataTable, createSelectionColumn, useDataTableClient, useDataTableContext, useDataTableServer, useNuqsAdapter };
|
|
1651
|
+
export { DEFAULT_DEBOUNCE_MS, DEFAULT_LOADING_ROWS, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES, DataTable, createDataTableStore, createSelectionColumn, useDataTableFilters, useDataTableInlineContents, useDataTableLoading, useDataTablePagination, useDataTableRows, useDataTableSearch, useDataTableSelection, useDataTableSorting, useNuqsAdapter };
|