@facetui/react 1.0.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/LICENSE.md +60 -0
- package/README.md +509 -0
- package/THEMING.md +168 -0
- package/dist/index.cjs +1220 -0
- package/dist/index.d.cts +367 -0
- package/dist/index.d.ts +367 -0
- package/dist/index.js +1209 -0
- package/package.json +122 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1209 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/data-table/core/DataTableContext.tsx
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
|
+
var DataTableContext = createContext(null);
|
|
6
|
+
function useDataTableContext() {
|
|
7
|
+
const ctx = useContext(DataTableContext);
|
|
8
|
+
if (!ctx) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"useDataTableContext must be called inside a <DataTable> component tree."
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return ctx;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/components/data-table/core/useDataTable.ts
|
|
17
|
+
import { useCallback, useMemo, useState } from "react";
|
|
18
|
+
import {
|
|
19
|
+
useReactTable,
|
|
20
|
+
getCoreRowModel,
|
|
21
|
+
getFilteredRowModel,
|
|
22
|
+
getSortedRowModel,
|
|
23
|
+
getPaginationRowModel,
|
|
24
|
+
functionalUpdate
|
|
25
|
+
} from "@tanstack/react-table";
|
|
26
|
+
var SELECT_COLUMN_ID = "__select__";
|
|
27
|
+
var facetTextFilter = (row, columnId, filterValue) => {
|
|
28
|
+
const query = String(filterValue ?? "").trim().toLowerCase();
|
|
29
|
+
if (query === "") return true;
|
|
30
|
+
return String(row.getValue(columnId) ?? "").toLowerCase().includes(query);
|
|
31
|
+
};
|
|
32
|
+
function useDataTable(props) {
|
|
33
|
+
const {
|
|
34
|
+
data,
|
|
35
|
+
columns,
|
|
36
|
+
getRowId,
|
|
37
|
+
// pagination
|
|
38
|
+
manualPagination = false,
|
|
39
|
+
rowCount,
|
|
40
|
+
pageSizeOptions = [10, 25, 50, 100],
|
|
41
|
+
onPaginationChange,
|
|
42
|
+
// sorting
|
|
43
|
+
manualSorting = false,
|
|
44
|
+
enableMultiSort = false,
|
|
45
|
+
onSortingChange,
|
|
46
|
+
// filtering
|
|
47
|
+
manualFiltering = false,
|
|
48
|
+
onGlobalFilterChange,
|
|
49
|
+
onColumnFiltersChange,
|
|
50
|
+
// row selection
|
|
51
|
+
enableRowSelection,
|
|
52
|
+
enableMultiRowSelection = true,
|
|
53
|
+
onRowSelectionChange
|
|
54
|
+
} = props;
|
|
55
|
+
const resolvedColumns = useMemo(() => {
|
|
56
|
+
const wantsSelectionColumn = enableRowSelection !== void 0 && enableRowSelection !== false;
|
|
57
|
+
if (!wantsSelectionColumn || columns.some((c) => c.id === SELECT_COLUMN_ID)) {
|
|
58
|
+
return columns;
|
|
59
|
+
}
|
|
60
|
+
const selectColumn = {
|
|
61
|
+
id: SELECT_COLUMN_ID,
|
|
62
|
+
header: "",
|
|
63
|
+
enableSorting: false,
|
|
64
|
+
enableHiding: false,
|
|
65
|
+
size: 40
|
|
66
|
+
};
|
|
67
|
+
return [selectColumn, ...columns];
|
|
68
|
+
}, [columns, enableRowSelection]);
|
|
69
|
+
const engineColumns = useMemo(
|
|
70
|
+
() => resolvedColumns.map((c) => {
|
|
71
|
+
const accessorFn = c.accessorFn ? (row) => c.accessorFn(row) : c.accessorKey != null ? (row) => row[c.accessorKey] : void 0;
|
|
72
|
+
return {
|
|
73
|
+
id: c.id,
|
|
74
|
+
...accessorFn ? { accessorFn } : {},
|
|
75
|
+
enableSorting: c.enableSorting ?? true,
|
|
76
|
+
enableHiding: c.enableHiding ?? true,
|
|
77
|
+
sortingFn: "basic",
|
|
78
|
+
filterFn: facetTextFilter,
|
|
79
|
+
size: c.size ?? 150
|
|
80
|
+
};
|
|
81
|
+
}),
|
|
82
|
+
[resolvedColumns]
|
|
83
|
+
);
|
|
84
|
+
const [pagination, setPaginationState] = useState({
|
|
85
|
+
pageIndex: 0,
|
|
86
|
+
pageSize: pageSizeOptions[0]
|
|
87
|
+
});
|
|
88
|
+
const [sorting, setSortingState] = useState([]);
|
|
89
|
+
const [globalFilterState, setGlobalFilterState] = useState(
|
|
90
|
+
props.globalFilter ?? ""
|
|
91
|
+
);
|
|
92
|
+
const [columnFiltersState, setColumnFiltersState] = useState(
|
|
93
|
+
props.columnFilters ?? []
|
|
94
|
+
);
|
|
95
|
+
const [rowSelection, setRowSelectionState] = useState({});
|
|
96
|
+
const [columnVisibility, setColumnVisibilityState] = useState({});
|
|
97
|
+
const globalFilter = props.globalFilter !== void 0 ? props.globalFilter : globalFilterState;
|
|
98
|
+
const columnFilters = props.columnFilters !== void 0 ? props.columnFilters : columnFiltersState;
|
|
99
|
+
const enableRowSelectionOption = useMemo(() => {
|
|
100
|
+
if (typeof enableRowSelection === "function") {
|
|
101
|
+
return (tsRow) => enableRowSelection({
|
|
102
|
+
id: tsRow.id,
|
|
103
|
+
original: tsRow.original,
|
|
104
|
+
index: tsRow.index,
|
|
105
|
+
getIsSelected: () => tsRow.getIsSelected(),
|
|
106
|
+
getCanSelect: () => true,
|
|
107
|
+
toggleSelected: (value) => tsRow.toggleSelected(value)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return enableRowSelection ?? true;
|
|
111
|
+
}, [enableRowSelection]);
|
|
112
|
+
const table = useReactTable({
|
|
113
|
+
data,
|
|
114
|
+
columns: engineColumns,
|
|
115
|
+
state: {
|
|
116
|
+
pagination,
|
|
117
|
+
sorting,
|
|
118
|
+
globalFilter,
|
|
119
|
+
columnFilters,
|
|
120
|
+
rowSelection,
|
|
121
|
+
columnVisibility
|
|
122
|
+
},
|
|
123
|
+
getRowId,
|
|
124
|
+
enableRowSelection: enableRowSelectionOption,
|
|
125
|
+
enableMultiRowSelection,
|
|
126
|
+
enableMultiSort,
|
|
127
|
+
enableSortingRemoval: true,
|
|
128
|
+
sortDescFirst: false,
|
|
129
|
+
manualPagination,
|
|
130
|
+
manualSorting,
|
|
131
|
+
manualFiltering,
|
|
132
|
+
...manualPagination && rowCount != null ? { rowCount } : {},
|
|
133
|
+
// Page-reset on filter change is done explicitly in the setters below so
|
|
134
|
+
// it stays synchronous and matches FacetUI's historical behaviour.
|
|
135
|
+
autoResetPageIndex: false,
|
|
136
|
+
globalFilterFn: facetTextFilter,
|
|
137
|
+
getCoreRowModel: getCoreRowModel(),
|
|
138
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
139
|
+
getSortedRowModel: getSortedRowModel(),
|
|
140
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
141
|
+
onPaginationChange: (updater) => setPaginationState((prev) => {
|
|
142
|
+
const next = functionalUpdate(updater, prev);
|
|
143
|
+
onPaginationChange?.(next);
|
|
144
|
+
return next;
|
|
145
|
+
}),
|
|
146
|
+
onSortingChange: (updater) => setSortingState((prev) => {
|
|
147
|
+
const next = functionalUpdate(updater, prev);
|
|
148
|
+
onSortingChange?.(next);
|
|
149
|
+
return next;
|
|
150
|
+
}),
|
|
151
|
+
onGlobalFilterChange: (updater) => setGlobalFilterState((prev) => {
|
|
152
|
+
const next = functionalUpdate(updater, prev);
|
|
153
|
+
onGlobalFilterChange?.(next);
|
|
154
|
+
return next;
|
|
155
|
+
}),
|
|
156
|
+
onColumnFiltersChange: (updater) => setColumnFiltersState((prev) => {
|
|
157
|
+
const next = functionalUpdate(updater, prev);
|
|
158
|
+
onColumnFiltersChange?.(next);
|
|
159
|
+
return next;
|
|
160
|
+
}),
|
|
161
|
+
onRowSelectionChange: (updater) => setRowSelectionState((prev) => {
|
|
162
|
+
const next = functionalUpdate(updater, prev);
|
|
163
|
+
onRowSelectionChange?.(next);
|
|
164
|
+
return next;
|
|
165
|
+
}),
|
|
166
|
+
onColumnVisibilityChange: setColumnVisibilityState
|
|
167
|
+
});
|
|
168
|
+
const setPagination = useCallback(
|
|
169
|
+
(updater) => table.setPagination(updater),
|
|
170
|
+
[table]
|
|
171
|
+
);
|
|
172
|
+
const setSorting = useCallback(
|
|
173
|
+
(updater) => table.setSorting(updater),
|
|
174
|
+
[table]
|
|
175
|
+
);
|
|
176
|
+
const setGlobalFilter = useCallback(
|
|
177
|
+
(value) => {
|
|
178
|
+
table.setGlobalFilter(value);
|
|
179
|
+
table.setPageIndex(0);
|
|
180
|
+
},
|
|
181
|
+
[table]
|
|
182
|
+
);
|
|
183
|
+
const setColumnFilters = useCallback(
|
|
184
|
+
(updater) => {
|
|
185
|
+
table.setColumnFilters(updater);
|
|
186
|
+
table.setPageIndex(0);
|
|
187
|
+
},
|
|
188
|
+
[table]
|
|
189
|
+
);
|
|
190
|
+
const runtimeColumns = useMemo(() => {
|
|
191
|
+
const defById = new Map(resolvedColumns.map((c) => [c.id, c]));
|
|
192
|
+
return table.getAllLeafColumns().map((tsCol) => {
|
|
193
|
+
const def = defById.get(tsCol.id);
|
|
194
|
+
return {
|
|
195
|
+
...def,
|
|
196
|
+
enableSorting: def.enableSorting ?? true,
|
|
197
|
+
enableHiding: def.enableHiding ?? true,
|
|
198
|
+
getIsSorted: () => tsCol.getIsSorted(),
|
|
199
|
+
toggleSort: (multiSort = false) => tsCol.toggleSorting(void 0, multiSort && enableMultiSort),
|
|
200
|
+
getIsVisible: () => tsCol.getIsVisible(),
|
|
201
|
+
toggleVisibility: (value) => tsCol.toggleVisibility(value),
|
|
202
|
+
getSize: () => def.size ?? 150
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
}, [table, resolvedColumns, enableMultiSort]);
|
|
206
|
+
const rows = table.getRowModel().rows.map((r, index) => ({
|
|
207
|
+
id: r.id,
|
|
208
|
+
original: r.original,
|
|
209
|
+
index,
|
|
210
|
+
getIsSelected: () => r.getIsSelected(),
|
|
211
|
+
getCanSelect: () => r.getCanSelect(),
|
|
212
|
+
toggleSelected: (value) => r.toggleSelected(value)
|
|
213
|
+
}));
|
|
214
|
+
const getIsAllRowsSelected = useCallback(
|
|
215
|
+
() => table.getIsAllPageRowsSelected(),
|
|
216
|
+
[table]
|
|
217
|
+
);
|
|
218
|
+
const getIsSomeRowsSelected = useCallback(
|
|
219
|
+
() => table.getIsSomePageRowsSelected(),
|
|
220
|
+
[table]
|
|
221
|
+
);
|
|
222
|
+
const toggleAllRowsSelected = useCallback(
|
|
223
|
+
(value) => table.toggleAllPageRowsSelected(value),
|
|
224
|
+
[table]
|
|
225
|
+
);
|
|
226
|
+
const enginePageCount = table.getPageCount();
|
|
227
|
+
return {
|
|
228
|
+
rows,
|
|
229
|
+
columns: runtimeColumns,
|
|
230
|
+
pagination: table.getState().pagination,
|
|
231
|
+
sorting: table.getState().sorting,
|
|
232
|
+
rowSelection: table.getState().rowSelection,
|
|
233
|
+
globalFilter,
|
|
234
|
+
columnFilters,
|
|
235
|
+
// TanStack returns -1 when a manual page count is unknown; FacetUI has
|
|
236
|
+
// always reported 0 in that case.
|
|
237
|
+
pageCount: enginePageCount < 0 ? 0 : enginePageCount,
|
|
238
|
+
filteredRowCount: manualFiltering ? data.length : table.getFilteredRowModel().rows.length,
|
|
239
|
+
getIsAllRowsSelected,
|
|
240
|
+
getIsSomeRowsSelected,
|
|
241
|
+
toggleAllRowsSelected,
|
|
242
|
+
setSorting,
|
|
243
|
+
setPagination,
|
|
244
|
+
setGlobalFilter,
|
|
245
|
+
setColumnFilters
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/components/data-table/primitives/DataTableToolbar.tsx
|
|
250
|
+
import { SearchIcon, XIcon } from "lucide-react";
|
|
251
|
+
|
|
252
|
+
// src/components/data-table/primitives/DataTableColumnToggle.tsx
|
|
253
|
+
import {
|
|
254
|
+
Button,
|
|
255
|
+
Menu,
|
|
256
|
+
MenuItem,
|
|
257
|
+
MenuTrigger,
|
|
258
|
+
Popover
|
|
259
|
+
} from "react-aria-components";
|
|
260
|
+
import { Columns3Icon, CheckIcon } from "lucide-react";
|
|
261
|
+
|
|
262
|
+
// src/components/data-table/utils.ts
|
|
263
|
+
import { clsx } from "clsx";
|
|
264
|
+
import { twMerge } from "tailwind-merge";
|
|
265
|
+
function cn(...inputs) {
|
|
266
|
+
return twMerge(clsx(inputs));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/components/data-table/primitives/DataTableColumnToggle.tsx
|
|
270
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
271
|
+
function DataTableColumnToggle({
|
|
272
|
+
table
|
|
273
|
+
}) {
|
|
274
|
+
const toggleableColumns = table.columns.filter(
|
|
275
|
+
(col) => col.enableHiding !== false && col.id !== "__select__"
|
|
276
|
+
);
|
|
277
|
+
const selectedKeys = new Set(
|
|
278
|
+
toggleableColumns.filter((col) => col.getIsVisible()).map((col) => col.id)
|
|
279
|
+
);
|
|
280
|
+
return /* @__PURE__ */ jsxs(MenuTrigger, { children: [
|
|
281
|
+
/* @__PURE__ */ jsxs(
|
|
282
|
+
Button,
|
|
283
|
+
{
|
|
284
|
+
"aria-label": "Toggle column visibility",
|
|
285
|
+
className: cn(
|
|
286
|
+
"inline-flex h-9 items-center gap-1.5 rounded-md border border-input",
|
|
287
|
+
"bg-background px-3 text-sm shadow-xs",
|
|
288
|
+
"transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
289
|
+
"data-[focus-visible]:outline-hidden data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring",
|
|
290
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
|
|
291
|
+
),
|
|
292
|
+
children: [
|
|
293
|
+
/* @__PURE__ */ jsx(Columns3Icon, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
294
|
+
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Columns" })
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
),
|
|
298
|
+
/* @__PURE__ */ jsx(
|
|
299
|
+
Popover,
|
|
300
|
+
{
|
|
301
|
+
placement: "bottom end",
|
|
302
|
+
offset: 4,
|
|
303
|
+
className: cn(
|
|
304
|
+
"z-50 min-w-[160px] overflow-hidden rounded-md border border-border",
|
|
305
|
+
"bg-popover p-1 text-popover-foreground shadow-md",
|
|
306
|
+
"data-[entering]:animate-in data-[entering]:fade-in-0 data-[entering]:zoom-in-95",
|
|
307
|
+
"data-[exiting]:animate-out data-[exiting]:fade-out-0 data-[exiting]:zoom-out-95"
|
|
308
|
+
),
|
|
309
|
+
children: /* @__PURE__ */ jsx(
|
|
310
|
+
Menu,
|
|
311
|
+
{
|
|
312
|
+
"aria-label": "Toggle columns",
|
|
313
|
+
selectionMode: "multiple",
|
|
314
|
+
selectedKeys,
|
|
315
|
+
onSelectionChange: (keys) => {
|
|
316
|
+
if (keys === "all") return;
|
|
317
|
+
for (const col of toggleableColumns) {
|
|
318
|
+
col.toggleVisibility(keys.has(col.id));
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
className: "outline-hidden",
|
|
322
|
+
children: toggleableColumns.map((col) => {
|
|
323
|
+
const label = typeof col.header === "string" ? col.header : String(col.id).replace(/_/g, " ");
|
|
324
|
+
return /* @__PURE__ */ jsx(
|
|
325
|
+
MenuItem,
|
|
326
|
+
{
|
|
327
|
+
id: col.id,
|
|
328
|
+
textValue: label,
|
|
329
|
+
className: cn(
|
|
330
|
+
"relative flex cursor-pointer select-none items-center",
|
|
331
|
+
"rounded-xs py-1.5 pl-8 pr-2 text-sm capitalize",
|
|
332
|
+
"outline-hidden transition-colors",
|
|
333
|
+
"data-[focused]:bg-accent data-[focused]:text-accent-foreground",
|
|
334
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
|
335
|
+
),
|
|
336
|
+
children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
337
|
+
isSelected && /* @__PURE__ */ jsx("span", { className: "absolute left-2 flex items-center justify-center", children: /* @__PURE__ */ jsx(CheckIcon, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
338
|
+
label
|
|
339
|
+
] })
|
|
340
|
+
},
|
|
341
|
+
col.id
|
|
342
|
+
);
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
] });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/components/data-table/primitives/DataTableToolbar.tsx
|
|
352
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
353
|
+
function DataTableToolbar({
|
|
354
|
+
table,
|
|
355
|
+
classNames
|
|
356
|
+
}) {
|
|
357
|
+
const hasFilter = table.globalFilter.length > 0;
|
|
358
|
+
return /* @__PURE__ */ jsxs2(
|
|
359
|
+
"div",
|
|
360
|
+
{
|
|
361
|
+
role: "toolbar",
|
|
362
|
+
"aria-label": "Table controls",
|
|
363
|
+
className: cn(
|
|
364
|
+
"flex flex-wrap items-center justify-between gap-2",
|
|
365
|
+
classNames?.toolbar
|
|
366
|
+
),
|
|
367
|
+
children: [
|
|
368
|
+
/* @__PURE__ */ jsxs2("div", { className: "relative flex-1 min-w-[200px] max-w-sm", children: [
|
|
369
|
+
/* @__PURE__ */ jsx2(
|
|
370
|
+
SearchIcon,
|
|
371
|
+
{
|
|
372
|
+
className: "absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground",
|
|
373
|
+
"aria-hidden": "true"
|
|
374
|
+
}
|
|
375
|
+
),
|
|
376
|
+
/* @__PURE__ */ jsx2(
|
|
377
|
+
"input",
|
|
378
|
+
{
|
|
379
|
+
type: "search",
|
|
380
|
+
role: "searchbox",
|
|
381
|
+
"aria-label": "Search all columns",
|
|
382
|
+
placeholder: "Search\u2026",
|
|
383
|
+
value: table.globalFilter,
|
|
384
|
+
onChange: (e) => table.setGlobalFilter(e.target.value),
|
|
385
|
+
className: cn(
|
|
386
|
+
"h-9 w-full rounded-md border border-input bg-background pl-8",
|
|
387
|
+
hasFilter ? "pr-8" : "pr-3",
|
|
388
|
+
"text-sm shadow-xs placeholder:text-muted-foreground",
|
|
389
|
+
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
|
390
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
),
|
|
394
|
+
hasFilter && /* @__PURE__ */ jsx2(
|
|
395
|
+
"button",
|
|
396
|
+
{
|
|
397
|
+
type: "button",
|
|
398
|
+
onClick: () => table.setGlobalFilter(""),
|
|
399
|
+
"aria-label": "Clear search",
|
|
400
|
+
className: cn(
|
|
401
|
+
"absolute right-2 top-1/2 -translate-y-1/2 rounded-sm",
|
|
402
|
+
"text-muted-foreground hover:text-foreground",
|
|
403
|
+
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
|
|
404
|
+
),
|
|
405
|
+
children: /* @__PURE__ */ jsx2(XIcon, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
|
|
406
|
+
}
|
|
407
|
+
)
|
|
408
|
+
] }),
|
|
409
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
410
|
+
hasFilter && /* @__PURE__ */ jsxs2(
|
|
411
|
+
"p",
|
|
412
|
+
{
|
|
413
|
+
role: "status",
|
|
414
|
+
"aria-live": "polite",
|
|
415
|
+
"aria-atomic": "true",
|
|
416
|
+
className: "text-xs text-muted-foreground tabular-nums",
|
|
417
|
+
children: [
|
|
418
|
+
table.filteredRowCount.toLocaleString(),
|
|
419
|
+
" result",
|
|
420
|
+
table.filteredRowCount !== 1 ? "s" : ""
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
),
|
|
424
|
+
/* @__PURE__ */ jsx2(DataTableColumnToggle, { table })
|
|
425
|
+
] })
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/components/data-table/primitives/DataTableHeader.tsx
|
|
432
|
+
import { Checkbox } from "react-aria-components";
|
|
433
|
+
import { CheckIcon as CheckIcon2, MinusIcon, ChevronUpIcon, ChevronDownIcon, ChevronsUpDownIcon } from "lucide-react";
|
|
434
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
435
|
+
var densityPaddingTh = {
|
|
436
|
+
compact: "h-8 px-2",
|
|
437
|
+
default: "h-10 px-3",
|
|
438
|
+
comfortable: "h-12 px-4"
|
|
439
|
+
};
|
|
440
|
+
function DataTableHeader({
|
|
441
|
+
table,
|
|
442
|
+
density,
|
|
443
|
+
classNames
|
|
444
|
+
}) {
|
|
445
|
+
const visibleColumns = table.columns.filter((col) => col.getIsVisible());
|
|
446
|
+
const hasSelection = table.columns.some((col) => col.id === "__select__");
|
|
447
|
+
return /* @__PURE__ */ jsx3(
|
|
448
|
+
"thead",
|
|
449
|
+
{
|
|
450
|
+
className: cn("border-b border-border bg-muted/50", classNames?.thead),
|
|
451
|
+
children: /* @__PURE__ */ jsxs3(
|
|
452
|
+
"tr",
|
|
453
|
+
{
|
|
454
|
+
role: "row",
|
|
455
|
+
className: cn(classNames?.theadRow),
|
|
456
|
+
children: [
|
|
457
|
+
hasSelection && /* @__PURE__ */ jsx3(
|
|
458
|
+
"th",
|
|
459
|
+
{
|
|
460
|
+
role: "columnheader",
|
|
461
|
+
scope: "col",
|
|
462
|
+
"aria-label": "Select all rows",
|
|
463
|
+
className: cn(
|
|
464
|
+
"w-10 text-center align-middle",
|
|
465
|
+
densityPaddingTh[density],
|
|
466
|
+
classNames?.th
|
|
467
|
+
),
|
|
468
|
+
children: /* @__PURE__ */ jsx3(SelectAllCheckbox, { table })
|
|
469
|
+
}
|
|
470
|
+
),
|
|
471
|
+
visibleColumns.filter((col) => col.id !== "__select__").map((col) => {
|
|
472
|
+
const sortDir = col.getIsSorted();
|
|
473
|
+
const canSort = col.enableSorting ?? true;
|
|
474
|
+
return /* @__PURE__ */ jsx3(
|
|
475
|
+
"th",
|
|
476
|
+
{
|
|
477
|
+
role: "columnheader",
|
|
478
|
+
scope: "col",
|
|
479
|
+
"aria-sort": sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : canSort ? "none" : void 0,
|
|
480
|
+
style: { width: col.getSize() },
|
|
481
|
+
className: cn(
|
|
482
|
+
"text-left align-middle font-medium text-muted-foreground",
|
|
483
|
+
densityPaddingTh[density],
|
|
484
|
+
classNames?.th
|
|
485
|
+
),
|
|
486
|
+
children: canSort ? /* @__PURE__ */ jsxs3(
|
|
487
|
+
"button",
|
|
488
|
+
{
|
|
489
|
+
type: "button",
|
|
490
|
+
onClick: () => col.toggleSort(),
|
|
491
|
+
className: cn(
|
|
492
|
+
"flex items-center gap-1.5 rounded-xs",
|
|
493
|
+
"transition-colors hover:text-foreground focus-visible:outline-hidden",
|
|
494
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
495
|
+
sortDir && "text-foreground"
|
|
496
|
+
),
|
|
497
|
+
"aria-label": `Sort by ${String(col.id)}${sortDir === "asc" ? ", currently ascending" : sortDir === "desc" ? ", currently descending" : ""}`,
|
|
498
|
+
children: [
|
|
499
|
+
/* @__PURE__ */ jsx3(HeaderContent, { col, table }),
|
|
500
|
+
/* @__PURE__ */ jsx3(SortIcon, { direction: sortDir })
|
|
501
|
+
]
|
|
502
|
+
}
|
|
503
|
+
) : /* @__PURE__ */ jsx3(HeaderContent, { col, table })
|
|
504
|
+
},
|
|
505
|
+
col.id
|
|
506
|
+
);
|
|
507
|
+
})
|
|
508
|
+
]
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
function HeaderContent({
|
|
515
|
+
col,
|
|
516
|
+
table
|
|
517
|
+
}) {
|
|
518
|
+
return /* @__PURE__ */ jsx3(Fragment2, { children: typeof col.header === "function" ? col.header({ column: col, table }) : col.header });
|
|
519
|
+
}
|
|
520
|
+
function SortIcon({ direction }) {
|
|
521
|
+
const cls = "h-3.5 w-3.5 shrink-0";
|
|
522
|
+
if (direction === "asc") return /* @__PURE__ */ jsx3(ChevronUpIcon, { className: cls, "aria-hidden": "true" });
|
|
523
|
+
if (direction === "desc") return /* @__PURE__ */ jsx3(ChevronDownIcon, { className: cls, "aria-hidden": "true" });
|
|
524
|
+
return /* @__PURE__ */ jsx3(ChevronsUpDownIcon, { className: cn(cls, "opacity-40"), "aria-hidden": "true" });
|
|
525
|
+
}
|
|
526
|
+
function SelectAllCheckbox({ table }) {
|
|
527
|
+
const isAll = table.getIsAllRowsSelected();
|
|
528
|
+
const isSome = table.getIsSomeRowsSelected();
|
|
529
|
+
return /* @__PURE__ */ jsx3(
|
|
530
|
+
Checkbox,
|
|
531
|
+
{
|
|
532
|
+
"aria-label": "Select all rows",
|
|
533
|
+
isSelected: isAll,
|
|
534
|
+
isIndeterminate: isSome && !isAll,
|
|
535
|
+
onChange: (checked) => table.toggleAllRowsSelected(checked),
|
|
536
|
+
className: "group mx-auto flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center",
|
|
537
|
+
children: /* @__PURE__ */ jsx3("span", { className: checkboxBox, children: isSome && !isAll ? /* @__PURE__ */ jsx3(MinusIcon, { className: "h-3 w-3", "aria-hidden": "true" }) : isAll ? /* @__PURE__ */ jsx3(CheckIcon2, { className: "h-3 w-3", "aria-hidden": "true" }) : null })
|
|
538
|
+
}
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
var checkboxBox = cn(
|
|
542
|
+
"flex h-4 w-4 items-center justify-center rounded-sm border border-primary shadow-sm",
|
|
543
|
+
"group-data-[focus-visible]:outline-hidden group-data-[focus-visible]:ring-2 group-data-[focus-visible]:ring-ring",
|
|
544
|
+
"group-data-[selected]:bg-primary group-data-[selected]:text-primary-foreground",
|
|
545
|
+
"group-data-[indeterminate]:bg-primary group-data-[indeterminate]:text-primary-foreground"
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
// src/components/data-table/primitives/DataTableBody.tsx
|
|
549
|
+
import { Checkbox as Checkbox2 } from "react-aria-components";
|
|
550
|
+
import { CheckIcon as CheckIcon3 } from "lucide-react";
|
|
551
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
552
|
+
var densityPaddingTd = {
|
|
553
|
+
compact: "py-1 px-2",
|
|
554
|
+
default: "py-2 px-3",
|
|
555
|
+
comfortable: "py-3 px-4"
|
|
556
|
+
};
|
|
557
|
+
function DataTableBody({
|
|
558
|
+
table,
|
|
559
|
+
density,
|
|
560
|
+
isLoading,
|
|
561
|
+
renderEmpty,
|
|
562
|
+
renderRowWrapper,
|
|
563
|
+
classNames
|
|
564
|
+
}) {
|
|
565
|
+
const visibleColumns = table.columns.filter(
|
|
566
|
+
(col) => col.getIsVisible() && col.id !== "__select__"
|
|
567
|
+
);
|
|
568
|
+
const hasSelection = table.columns.some((col) => col.id === "__select__");
|
|
569
|
+
const colSpan = visibleColumns.length + (hasSelection ? 1 : 0);
|
|
570
|
+
if (!isLoading && table.rows.length === 0) {
|
|
571
|
+
return /* @__PURE__ */ jsx4("tbody", { children: /* @__PURE__ */ jsx4("tr", { role: "row", children: /* @__PURE__ */ jsx4(
|
|
572
|
+
"td",
|
|
573
|
+
{
|
|
574
|
+
colSpan,
|
|
575
|
+
className: "h-40 text-center align-middle text-muted-foreground",
|
|
576
|
+
children: renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx4(DefaultEmpty, {})
|
|
577
|
+
}
|
|
578
|
+
) }) });
|
|
579
|
+
}
|
|
580
|
+
return /* @__PURE__ */ jsx4("tbody", { className: cn("[&_tr:last-child]:border-0", classNames?.tbody), children: table.rows.map((row, rowIndex) => {
|
|
581
|
+
const isSelected = row.getIsSelected();
|
|
582
|
+
const ariaRowIndex = table.pagination.pageIndex * table.pagination.pageSize + rowIndex + 2;
|
|
583
|
+
const rowContent = /* @__PURE__ */ jsxs4(
|
|
584
|
+
"tr",
|
|
585
|
+
{
|
|
586
|
+
role: "row",
|
|
587
|
+
"aria-rowindex": ariaRowIndex,
|
|
588
|
+
"aria-selected": hasSelection ? isSelected : void 0,
|
|
589
|
+
"data-state": isSelected ? "selected" : void 0,
|
|
590
|
+
className: cn(
|
|
591
|
+
"border-b border-border transition-colors",
|
|
592
|
+
"hover:bg-muted/50",
|
|
593
|
+
isSelected && "bg-muted",
|
|
594
|
+
classNames?.tr
|
|
595
|
+
),
|
|
596
|
+
children: [
|
|
597
|
+
hasSelection && /* @__PURE__ */ jsx4(
|
|
598
|
+
"td",
|
|
599
|
+
{
|
|
600
|
+
role: "gridcell",
|
|
601
|
+
className: cn(
|
|
602
|
+
"w-10 text-center align-middle",
|
|
603
|
+
densityPaddingTd[density],
|
|
604
|
+
classNames?.td
|
|
605
|
+
),
|
|
606
|
+
children: /* @__PURE__ */ jsx4(RowCheckbox, { row })
|
|
607
|
+
}
|
|
608
|
+
),
|
|
609
|
+
visibleColumns.map((col) => {
|
|
610
|
+
const value = getCellValue(col, row.original);
|
|
611
|
+
return /* @__PURE__ */ jsx4(
|
|
612
|
+
"td",
|
|
613
|
+
{
|
|
614
|
+
role: "gridcell",
|
|
615
|
+
className: cn(
|
|
616
|
+
"align-middle",
|
|
617
|
+
densityPaddingTd[density],
|
|
618
|
+
classNames?.td
|
|
619
|
+
),
|
|
620
|
+
children: col.cell ? col.cell({ row, column: col, value, table }) : renderValue(value)
|
|
621
|
+
},
|
|
622
|
+
col.id
|
|
623
|
+
);
|
|
624
|
+
})
|
|
625
|
+
]
|
|
626
|
+
},
|
|
627
|
+
row.id
|
|
628
|
+
);
|
|
629
|
+
return renderRowWrapper ? renderRowWrapper(row, rowContent) : rowContent;
|
|
630
|
+
}) });
|
|
631
|
+
}
|
|
632
|
+
function RowCheckbox({ row }) {
|
|
633
|
+
const canSelect = row.getCanSelect();
|
|
634
|
+
const selected = row.getIsSelected();
|
|
635
|
+
return /* @__PURE__ */ jsx4(
|
|
636
|
+
Checkbox2,
|
|
637
|
+
{
|
|
638
|
+
"aria-label": `Select row ${row.index + 1}`,
|
|
639
|
+
isSelected: selected,
|
|
640
|
+
isDisabled: !canSelect,
|
|
641
|
+
onChange: (checked) => row.toggleSelected(checked),
|
|
642
|
+
className: cn(
|
|
643
|
+
"group mx-auto flex h-4 w-4 shrink-0 items-center justify-center",
|
|
644
|
+
canSelect ? "cursor-pointer" : "cursor-not-allowed opacity-50"
|
|
645
|
+
),
|
|
646
|
+
children: /* @__PURE__ */ jsx4("span", { className: checkboxBox, children: selected ? /* @__PURE__ */ jsx4(CheckIcon3, { className: "h-3 w-3", "aria-hidden": "true" }) : null })
|
|
647
|
+
}
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
function DefaultEmpty() {
|
|
651
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center gap-2 py-10", children: [
|
|
652
|
+
/* @__PURE__ */ jsx4(
|
|
653
|
+
"svg",
|
|
654
|
+
{
|
|
655
|
+
className: "h-12 w-12 text-muted-foreground/40",
|
|
656
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
657
|
+
fill: "none",
|
|
658
|
+
viewBox: "0 0 24 24",
|
|
659
|
+
stroke: "currentColor",
|
|
660
|
+
"aria-hidden": "true",
|
|
661
|
+
children: /* @__PURE__ */ jsx4(
|
|
662
|
+
"path",
|
|
663
|
+
{
|
|
664
|
+
strokeLinecap: "round",
|
|
665
|
+
strokeLinejoin: "round",
|
|
666
|
+
strokeWidth: 1.5,
|
|
667
|
+
d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
668
|
+
}
|
|
669
|
+
)
|
|
670
|
+
}
|
|
671
|
+
),
|
|
672
|
+
/* @__PURE__ */ jsx4("p", { className: "text-sm font-medium", children: "No results found" }),
|
|
673
|
+
/* @__PURE__ */ jsx4("p", { className: "text-xs text-muted-foreground", children: "Try adjusting your search or filters." })
|
|
674
|
+
] });
|
|
675
|
+
}
|
|
676
|
+
function renderValue(value) {
|
|
677
|
+
if (value === null || value === void 0) return null;
|
|
678
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
679
|
+
return String(value);
|
|
680
|
+
}
|
|
681
|
+
function getCellValue(col, row) {
|
|
682
|
+
if (col.accessorFn) return col.accessorFn(row);
|
|
683
|
+
if (col.accessorKey !== void 0) return row[col.accessorKey];
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/components/data-table/primitives/DataTablePagination.tsx
|
|
688
|
+
import {
|
|
689
|
+
Button as Button2,
|
|
690
|
+
ListBox,
|
|
691
|
+
ListBoxItem,
|
|
692
|
+
Popover as Popover2,
|
|
693
|
+
Select,
|
|
694
|
+
SelectValue
|
|
695
|
+
} from "react-aria-components";
|
|
696
|
+
import {
|
|
697
|
+
ChevronLeftIcon,
|
|
698
|
+
ChevronRightIcon,
|
|
699
|
+
ChevronsLeftIcon,
|
|
700
|
+
ChevronsRightIcon,
|
|
701
|
+
CheckIcon as CheckIcon4,
|
|
702
|
+
ChevronDownIcon as ChevronDownIcon2
|
|
703
|
+
} from "lucide-react";
|
|
704
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
705
|
+
function DataTablePagination({
|
|
706
|
+
table,
|
|
707
|
+
pageSizeOptions,
|
|
708
|
+
classNames
|
|
709
|
+
}) {
|
|
710
|
+
const { pageIndex, pageSize } = table.pagination;
|
|
711
|
+
const canPreviousPage = pageIndex > 0;
|
|
712
|
+
const canNextPage = pageIndex < table.pageCount - 1;
|
|
713
|
+
const selectedCount = Object.keys(table.rowSelection).length;
|
|
714
|
+
return /* @__PURE__ */ jsxs5(
|
|
715
|
+
"div",
|
|
716
|
+
{
|
|
717
|
+
role: "navigation",
|
|
718
|
+
"aria-label": "Table pagination",
|
|
719
|
+
className: cn(
|
|
720
|
+
"flex flex-wrap items-center justify-between gap-4 text-sm",
|
|
721
|
+
classNames?.pagination
|
|
722
|
+
),
|
|
723
|
+
children: [
|
|
724
|
+
/* @__PURE__ */ jsx5(
|
|
725
|
+
"p",
|
|
726
|
+
{
|
|
727
|
+
"aria-live": "polite",
|
|
728
|
+
"aria-atomic": "true",
|
|
729
|
+
className: "text-muted-foreground tabular-nums",
|
|
730
|
+
children: selectedCount > 0 ? `${selectedCount.toLocaleString()} of ${table.filteredRowCount.toLocaleString()} row(s) selected` : `${table.filteredRowCount.toLocaleString()} row(s) total`
|
|
731
|
+
}
|
|
732
|
+
),
|
|
733
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-4", children: [
|
|
734
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
735
|
+
/* @__PURE__ */ jsx5(
|
|
736
|
+
"label",
|
|
737
|
+
{
|
|
738
|
+
id: "rows-per-page-label",
|
|
739
|
+
className: "text-muted-foreground whitespace-nowrap",
|
|
740
|
+
children: "Rows per page"
|
|
741
|
+
}
|
|
742
|
+
),
|
|
743
|
+
/* @__PURE__ */ jsx5(
|
|
744
|
+
PageSizeSelect,
|
|
745
|
+
{
|
|
746
|
+
value: pageSize,
|
|
747
|
+
options: pageSizeOptions,
|
|
748
|
+
onChange: (size) => table.setPagination((prev) => ({
|
|
749
|
+
...prev,
|
|
750
|
+
pageSize: size,
|
|
751
|
+
pageIndex: 0
|
|
752
|
+
}))
|
|
753
|
+
}
|
|
754
|
+
)
|
|
755
|
+
] }),
|
|
756
|
+
/* @__PURE__ */ jsxs5(
|
|
757
|
+
"p",
|
|
758
|
+
{
|
|
759
|
+
"aria-live": "polite",
|
|
760
|
+
"aria-atomic": "true",
|
|
761
|
+
className: "text-muted-foreground whitespace-nowrap tabular-nums",
|
|
762
|
+
children: [
|
|
763
|
+
"Page ",
|
|
764
|
+
(pageIndex + 1).toLocaleString(),
|
|
765
|
+
" of",
|
|
766
|
+
" ",
|
|
767
|
+
Math.max(1, table.pageCount).toLocaleString()
|
|
768
|
+
]
|
|
769
|
+
}
|
|
770
|
+
),
|
|
771
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Page navigation", children: [
|
|
772
|
+
/* @__PURE__ */ jsx5(
|
|
773
|
+
NavButton,
|
|
774
|
+
{
|
|
775
|
+
onClick: () => table.setPagination((prev) => ({ ...prev, pageIndex: 0 })),
|
|
776
|
+
disabled: !canPreviousPage,
|
|
777
|
+
"aria-label": "Go to first page",
|
|
778
|
+
children: /* @__PURE__ */ jsx5(ChevronsLeftIcon, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
779
|
+
}
|
|
780
|
+
),
|
|
781
|
+
/* @__PURE__ */ jsx5(
|
|
782
|
+
NavButton,
|
|
783
|
+
{
|
|
784
|
+
onClick: () => table.setPagination((prev) => ({
|
|
785
|
+
...prev,
|
|
786
|
+
pageIndex: prev.pageIndex - 1
|
|
787
|
+
})),
|
|
788
|
+
disabled: !canPreviousPage,
|
|
789
|
+
"aria-label": "Go to previous page",
|
|
790
|
+
children: /* @__PURE__ */ jsx5(ChevronLeftIcon, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
791
|
+
}
|
|
792
|
+
),
|
|
793
|
+
/* @__PURE__ */ jsx5(
|
|
794
|
+
NavButton,
|
|
795
|
+
{
|
|
796
|
+
onClick: () => table.setPagination((prev) => ({
|
|
797
|
+
...prev,
|
|
798
|
+
pageIndex: prev.pageIndex + 1
|
|
799
|
+
})),
|
|
800
|
+
disabled: !canNextPage,
|
|
801
|
+
"aria-label": "Go to next page",
|
|
802
|
+
children: /* @__PURE__ */ jsx5(ChevronRightIcon, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
803
|
+
}
|
|
804
|
+
),
|
|
805
|
+
/* @__PURE__ */ jsx5(
|
|
806
|
+
NavButton,
|
|
807
|
+
{
|
|
808
|
+
onClick: () => table.setPagination((prev) => ({
|
|
809
|
+
...prev,
|
|
810
|
+
pageIndex: table.pageCount - 1
|
|
811
|
+
})),
|
|
812
|
+
disabled: !canNextPage,
|
|
813
|
+
"aria-label": "Go to last page",
|
|
814
|
+
children: /* @__PURE__ */ jsx5(ChevronsRightIcon, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
] })
|
|
818
|
+
] })
|
|
819
|
+
]
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
function NavButton({ children, disabled, ...props }) {
|
|
824
|
+
return /* @__PURE__ */ jsx5(
|
|
825
|
+
"button",
|
|
826
|
+
{
|
|
827
|
+
type: "button",
|
|
828
|
+
disabled,
|
|
829
|
+
...props,
|
|
830
|
+
className: cn(
|
|
831
|
+
"inline-flex h-8 w-8 items-center justify-center rounded-md border border-input",
|
|
832
|
+
"bg-background text-sm shadow-xs transition-colors",
|
|
833
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
834
|
+
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
|
835
|
+
"disabled:pointer-events-none disabled:opacity-50"
|
|
836
|
+
),
|
|
837
|
+
children
|
|
838
|
+
}
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
function PageSizeSelect({ value, options, onChange }) {
|
|
842
|
+
return /* @__PURE__ */ jsxs5(
|
|
843
|
+
Select,
|
|
844
|
+
{
|
|
845
|
+
"aria-label": "Rows per page",
|
|
846
|
+
selectedKey: String(value),
|
|
847
|
+
onSelectionChange: (key) => onChange(Number(key)),
|
|
848
|
+
children: [
|
|
849
|
+
/* @__PURE__ */ jsxs5(
|
|
850
|
+
Button2,
|
|
851
|
+
{
|
|
852
|
+
"aria-labelledby": "rows-per-page-label",
|
|
853
|
+
className: cn(
|
|
854
|
+
"inline-flex h-8 items-center justify-between gap-1 rounded-md border border-input",
|
|
855
|
+
"bg-background px-2.5 text-sm shadow-xs",
|
|
856
|
+
"transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
857
|
+
"data-[focus-visible]:outline-hidden data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring",
|
|
858
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
|
|
859
|
+
),
|
|
860
|
+
children: [
|
|
861
|
+
/* @__PURE__ */ jsx5(SelectValue, {}),
|
|
862
|
+
/* @__PURE__ */ jsx5(ChevronDownIcon2, { className: "h-3.5 w-3.5 opacity-50", "aria-hidden": "true" })
|
|
863
|
+
]
|
|
864
|
+
}
|
|
865
|
+
),
|
|
866
|
+
/* @__PURE__ */ jsx5(
|
|
867
|
+
Popover2,
|
|
868
|
+
{
|
|
869
|
+
offset: 4,
|
|
870
|
+
className: cn(
|
|
871
|
+
"z-50 overflow-hidden rounded-md border border-border",
|
|
872
|
+
"bg-popover text-popover-foreground shadow-md",
|
|
873
|
+
"data-[entering]:animate-in data-[entering]:fade-in-0 data-[entering]:zoom-in-95",
|
|
874
|
+
"data-[exiting]:animate-out data-[exiting]:fade-out-0 data-[exiting]:zoom-out-95"
|
|
875
|
+
),
|
|
876
|
+
children: /* @__PURE__ */ jsx5(ListBox, { className: "p-1 outline-hidden", children: options.map((opt) => /* @__PURE__ */ jsx5(
|
|
877
|
+
ListBoxItem,
|
|
878
|
+
{
|
|
879
|
+
id: String(opt),
|
|
880
|
+
textValue: String(opt),
|
|
881
|
+
className: cn(
|
|
882
|
+
"relative flex cursor-pointer select-none items-center",
|
|
883
|
+
"rounded-xs py-1.5 pl-8 pr-2 text-sm",
|
|
884
|
+
"outline-hidden transition-colors",
|
|
885
|
+
"data-[focused]:bg-accent data-[focused]:text-accent-foreground",
|
|
886
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
|
887
|
+
),
|
|
888
|
+
children: ({ isSelected }) => /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
889
|
+
isSelected && /* @__PURE__ */ jsx5("span", { className: "absolute left-2 flex items-center justify-center", children: /* @__PURE__ */ jsx5(CheckIcon4, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
890
|
+
opt
|
|
891
|
+
] })
|
|
892
|
+
},
|
|
893
|
+
opt
|
|
894
|
+
)) })
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
]
|
|
898
|
+
}
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/components/data-table/core/DataTable.tsx
|
|
903
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
904
|
+
function DataTable({
|
|
905
|
+
renderToolbar,
|
|
906
|
+
renderEmpty,
|
|
907
|
+
renderLoading,
|
|
908
|
+
renderRowWrapper,
|
|
909
|
+
isLoading = false,
|
|
910
|
+
density = "default",
|
|
911
|
+
classNames,
|
|
912
|
+
style,
|
|
913
|
+
"aria-label": ariaLabel,
|
|
914
|
+
"aria-describedby": ariaDescribedBy,
|
|
915
|
+
...rest
|
|
916
|
+
}) {
|
|
917
|
+
const table = useDataTable({ ...rest, renderToolbar, renderEmpty, renderLoading, renderRowWrapper });
|
|
918
|
+
return (
|
|
919
|
+
// Cast is safe: context consumers are co-located and always receive
|
|
920
|
+
// the same TData shape via the typed useDataTableContext<TData> hook.
|
|
921
|
+
/* @__PURE__ */ jsx6(DataTableContext.Provider, { value: table, children: /* @__PURE__ */ jsxs6(
|
|
922
|
+
"div",
|
|
923
|
+
{
|
|
924
|
+
className: cn("flex w-full flex-col gap-3", classNames?.root),
|
|
925
|
+
style,
|
|
926
|
+
children: [
|
|
927
|
+
renderToolbar ? renderToolbar(table) : /* @__PURE__ */ jsx6(DataTableToolbar, { table, classNames }),
|
|
928
|
+
/* @__PURE__ */ jsxs6("div", { className: "relative overflow-hidden rounded-md border border-border", children: [
|
|
929
|
+
isLoading && (renderLoading ? renderLoading() : /* @__PURE__ */ jsx6(
|
|
930
|
+
"div",
|
|
931
|
+
{
|
|
932
|
+
role: "status",
|
|
933
|
+
"aria-live": "polite",
|
|
934
|
+
"aria-label": "Loading table data",
|
|
935
|
+
className: "absolute inset-0 z-10 flex items-center justify-center\n bg-background/70 backdrop-blur-[2px]",
|
|
936
|
+
children: /* @__PURE__ */ jsx6(LoadingSpinner, {})
|
|
937
|
+
}
|
|
938
|
+
)),
|
|
939
|
+
/* @__PURE__ */ jsxs6(
|
|
940
|
+
"table",
|
|
941
|
+
{
|
|
942
|
+
role: "grid",
|
|
943
|
+
"aria-label": ariaLabel,
|
|
944
|
+
"aria-describedby": ariaDescribedBy,
|
|
945
|
+
"aria-busy": isLoading,
|
|
946
|
+
"aria-rowcount": rest.rowCount ?? rest.data.length,
|
|
947
|
+
className: cn("w-full caption-bottom text-sm", classNames?.table),
|
|
948
|
+
children: [
|
|
949
|
+
/* @__PURE__ */ jsx6(
|
|
950
|
+
DataTableHeader,
|
|
951
|
+
{
|
|
952
|
+
table,
|
|
953
|
+
density,
|
|
954
|
+
classNames
|
|
955
|
+
}
|
|
956
|
+
),
|
|
957
|
+
/* @__PURE__ */ jsx6(
|
|
958
|
+
DataTableBody,
|
|
959
|
+
{
|
|
960
|
+
table,
|
|
961
|
+
density,
|
|
962
|
+
isLoading,
|
|
963
|
+
renderEmpty,
|
|
964
|
+
renderRowWrapper,
|
|
965
|
+
classNames
|
|
966
|
+
}
|
|
967
|
+
)
|
|
968
|
+
]
|
|
969
|
+
}
|
|
970
|
+
)
|
|
971
|
+
] }),
|
|
972
|
+
/* @__PURE__ */ jsx6(
|
|
973
|
+
DataTablePagination,
|
|
974
|
+
{
|
|
975
|
+
table,
|
|
976
|
+
pageSizeOptions: rest.pageSizeOptions ?? [10, 25, 50, 100],
|
|
977
|
+
classNames
|
|
978
|
+
}
|
|
979
|
+
)
|
|
980
|
+
]
|
|
981
|
+
}
|
|
982
|
+
) })
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
function LoadingSpinner() {
|
|
986
|
+
return /* @__PURE__ */ jsxs6(
|
|
987
|
+
"svg",
|
|
988
|
+
{
|
|
989
|
+
className: "h-8 w-8 animate-spin text-primary",
|
|
990
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
991
|
+
fill: "none",
|
|
992
|
+
viewBox: "0 0 24 24",
|
|
993
|
+
"aria-hidden": "true",
|
|
994
|
+
children: [
|
|
995
|
+
/* @__PURE__ */ jsx6(
|
|
996
|
+
"circle",
|
|
997
|
+
{
|
|
998
|
+
className: "opacity-25",
|
|
999
|
+
cx: "12",
|
|
1000
|
+
cy: "12",
|
|
1001
|
+
r: "10",
|
|
1002
|
+
stroke: "currentColor",
|
|
1003
|
+
strokeWidth: "4"
|
|
1004
|
+
}
|
|
1005
|
+
),
|
|
1006
|
+
/* @__PURE__ */ jsx6(
|
|
1007
|
+
"path",
|
|
1008
|
+
{
|
|
1009
|
+
className: "opacity-75",
|
|
1010
|
+
fill: "currentColor",
|
|
1011
|
+
d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
|
|
1012
|
+
}
|
|
1013
|
+
)
|
|
1014
|
+
]
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/components/data-table/headless/useColumnSort.ts
|
|
1020
|
+
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
1021
|
+
function useColumnSort({
|
|
1022
|
+
enableMultiSort = false,
|
|
1023
|
+
onSortingChange
|
|
1024
|
+
} = {}) {
|
|
1025
|
+
const [sorting, setSortingInternal] = useState2([]);
|
|
1026
|
+
const setSorting = useCallback2(
|
|
1027
|
+
(updater) => {
|
|
1028
|
+
setSortingInternal((prev) => {
|
|
1029
|
+
const next = typeof updater === "function" ? updater(prev) : updater;
|
|
1030
|
+
onSortingChange?.(next);
|
|
1031
|
+
return next;
|
|
1032
|
+
});
|
|
1033
|
+
},
|
|
1034
|
+
[onSortingChange]
|
|
1035
|
+
);
|
|
1036
|
+
const toggleSort = useCallback2(
|
|
1037
|
+
(columnId, multiSort = false) => {
|
|
1038
|
+
setSorting((prev) => {
|
|
1039
|
+
const existing = prev.find((s) => s.id === columnId);
|
|
1040
|
+
const useMulti = multiSort && enableMultiSort;
|
|
1041
|
+
if (!existing) {
|
|
1042
|
+
return useMulti ? [...prev, { id: columnId, desc: false }] : [{ id: columnId, desc: false }];
|
|
1043
|
+
}
|
|
1044
|
+
if (!existing.desc) {
|
|
1045
|
+
return prev.map(
|
|
1046
|
+
(s) => s.id === columnId ? { ...s, desc: true } : s
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
return prev.filter((s) => s.id !== columnId);
|
|
1050
|
+
});
|
|
1051
|
+
},
|
|
1052
|
+
[enableMultiSort, setSorting]
|
|
1053
|
+
);
|
|
1054
|
+
const clearSort = useCallback2(() => setSorting([]), [setSorting]);
|
|
1055
|
+
return { sorting, setSorting, toggleSort, clearSort };
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// src/components/data-table/headless/usePagination.ts
|
|
1059
|
+
import { useCallback as useCallback3, useState as useState3 } from "react";
|
|
1060
|
+
function usePagination({
|
|
1061
|
+
initialPageSize = 10,
|
|
1062
|
+
onPaginationChange
|
|
1063
|
+
} = {}) {
|
|
1064
|
+
const [pagination, setPaginationInternal] = useState3({
|
|
1065
|
+
pageIndex: 0,
|
|
1066
|
+
pageSize: initialPageSize
|
|
1067
|
+
});
|
|
1068
|
+
const setPagination = useCallback3(
|
|
1069
|
+
(updater) => {
|
|
1070
|
+
setPaginationInternal((prev) => {
|
|
1071
|
+
const next = typeof updater === "function" ? updater(prev) : updater;
|
|
1072
|
+
onPaginationChange?.(next);
|
|
1073
|
+
return next;
|
|
1074
|
+
});
|
|
1075
|
+
},
|
|
1076
|
+
[onPaginationChange]
|
|
1077
|
+
);
|
|
1078
|
+
return {
|
|
1079
|
+
pagination,
|
|
1080
|
+
setPagination,
|
|
1081
|
+
goToPage: (pageIndex) => setPagination((p) => ({ ...p, pageIndex })),
|
|
1082
|
+
goToFirstPage: () => setPagination((p) => ({ ...p, pageIndex: 0 })),
|
|
1083
|
+
goToLastPage: (pageCount) => setPagination((p) => ({ ...p, pageIndex: pageCount - 1 })),
|
|
1084
|
+
nextPage: () => setPagination((p) => ({ ...p, pageIndex: p.pageIndex + 1 })),
|
|
1085
|
+
previousPage: () => setPagination((p) => ({ ...p, pageIndex: Math.max(0, p.pageIndex - 1) })),
|
|
1086
|
+
setPageSize: (size) => setPagination((p) => ({ ...p, pageSize: size, pageIndex: 0 }))
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/components/data-table/headless/useRowSelection.ts
|
|
1091
|
+
import { useCallback as useCallback4, useState as useState4 } from "react";
|
|
1092
|
+
function useRowSelection({
|
|
1093
|
+
enableMultiRowSelection = true,
|
|
1094
|
+
onRowSelectionChange
|
|
1095
|
+
} = {}) {
|
|
1096
|
+
const [rowSelection, setRowSelectionInternal] = useState4({});
|
|
1097
|
+
const setRowSelection = useCallback4(
|
|
1098
|
+
(next) => {
|
|
1099
|
+
setRowSelectionInternal(next);
|
|
1100
|
+
onRowSelectionChange?.(next);
|
|
1101
|
+
},
|
|
1102
|
+
[onRowSelectionChange]
|
|
1103
|
+
);
|
|
1104
|
+
const toggleRow = useCallback4(
|
|
1105
|
+
(rowId, value) => {
|
|
1106
|
+
setRowSelectionInternal((prev) => {
|
|
1107
|
+
const shouldSelect = value ?? !prev[rowId];
|
|
1108
|
+
let next;
|
|
1109
|
+
if (shouldSelect) {
|
|
1110
|
+
next = enableMultiRowSelection ? { ...prev, [rowId]: true } : { [rowId]: true };
|
|
1111
|
+
} else {
|
|
1112
|
+
const { [rowId]: _removed, ...rest } = prev;
|
|
1113
|
+
next = rest;
|
|
1114
|
+
}
|
|
1115
|
+
onRowSelectionChange?.(next);
|
|
1116
|
+
return next;
|
|
1117
|
+
});
|
|
1118
|
+
},
|
|
1119
|
+
[enableMultiRowSelection, onRowSelectionChange]
|
|
1120
|
+
);
|
|
1121
|
+
const toggleAllRows = useCallback4(
|
|
1122
|
+
(rows, value) => {
|
|
1123
|
+
const allSelected = rows.every((r) => r.getIsSelected());
|
|
1124
|
+
const next = value ?? !allSelected ? Object.fromEntries(rows.map((r) => [r.id, true])) : {};
|
|
1125
|
+
setRowSelection(next);
|
|
1126
|
+
},
|
|
1127
|
+
[setRowSelection]
|
|
1128
|
+
);
|
|
1129
|
+
return {
|
|
1130
|
+
rowSelection,
|
|
1131
|
+
setRowSelection,
|
|
1132
|
+
toggleRow,
|
|
1133
|
+
toggleAllRows,
|
|
1134
|
+
clearSelection: () => setRowSelection({}),
|
|
1135
|
+
isSelected: (rowId) => !!rowSelection[rowId],
|
|
1136
|
+
selectedCount: Object.keys(rowSelection).length
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// src/components/data-table/headless/useColumnVisibility.ts
|
|
1141
|
+
import { useCallback as useCallback5, useState as useState5 } from "react";
|
|
1142
|
+
function useColumnVisibility(columns) {
|
|
1143
|
+
const [columnVisibility, setColumnVisibility] = useState5(
|
|
1144
|
+
() => Object.fromEntries(columns.map((c) => [c.id, true]))
|
|
1145
|
+
);
|
|
1146
|
+
const isVisible = useCallback5(
|
|
1147
|
+
(columnId) => columnVisibility[columnId] ?? true,
|
|
1148
|
+
[columnVisibility]
|
|
1149
|
+
);
|
|
1150
|
+
const toggleColumn = useCallback5((columnId, value) => {
|
|
1151
|
+
setColumnVisibility((prev) => ({
|
|
1152
|
+
...prev,
|
|
1153
|
+
[columnId]: value ?? !(prev[columnId] ?? true)
|
|
1154
|
+
}));
|
|
1155
|
+
}, []);
|
|
1156
|
+
const showAll = useCallback5(() => {
|
|
1157
|
+
setColumnVisibility(
|
|
1158
|
+
(prev) => Object.fromEntries(Object.keys(prev).map((id) => [id, true]))
|
|
1159
|
+
);
|
|
1160
|
+
}, []);
|
|
1161
|
+
const hideAll = useCallback5((hideableIds) => {
|
|
1162
|
+
setColumnVisibility(
|
|
1163
|
+
(prev) => Object.fromEntries(Object.keys(prev).map((id) => [id, !hideableIds.includes(id)]))
|
|
1164
|
+
);
|
|
1165
|
+
}, []);
|
|
1166
|
+
return { columnVisibility, isVisible, toggleColumn, showAll, hideAll };
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/components/data-table/headless/useGlobalFilter.ts
|
|
1170
|
+
import { useCallback as useCallback6, useState as useState6 } from "react";
|
|
1171
|
+
function useGlobalFilter(columns, { onGlobalFilterChange } = {}) {
|
|
1172
|
+
const [globalFilter, setGlobalFilterInternal] = useState6("");
|
|
1173
|
+
const setGlobalFilter = useCallback6(
|
|
1174
|
+
(value) => {
|
|
1175
|
+
setGlobalFilterInternal(value);
|
|
1176
|
+
onGlobalFilterChange?.(value);
|
|
1177
|
+
},
|
|
1178
|
+
[onGlobalFilterChange]
|
|
1179
|
+
);
|
|
1180
|
+
const filterData = useCallback6(
|
|
1181
|
+
(data) => {
|
|
1182
|
+
if (!globalFilter.trim()) return data;
|
|
1183
|
+
const lower = globalFilter.toLowerCase();
|
|
1184
|
+
return data.filter(
|
|
1185
|
+
(row) => columns.some((col) => {
|
|
1186
|
+
const val = col.accessorFn ? col.accessorFn(row) : col.accessorKey !== void 0 ? row[col.accessorKey] : null;
|
|
1187
|
+
return String(val ?? "").toLowerCase().includes(lower);
|
|
1188
|
+
})
|
|
1189
|
+
);
|
|
1190
|
+
},
|
|
1191
|
+
[columns, globalFilter]
|
|
1192
|
+
);
|
|
1193
|
+
return { globalFilter, setGlobalFilter, filterData };
|
|
1194
|
+
}
|
|
1195
|
+
export {
|
|
1196
|
+
DataTable,
|
|
1197
|
+
DataTableBody,
|
|
1198
|
+
DataTableColumnToggle,
|
|
1199
|
+
DataTableHeader,
|
|
1200
|
+
DataTablePagination,
|
|
1201
|
+
DataTableToolbar,
|
|
1202
|
+
useColumnSort,
|
|
1203
|
+
useColumnVisibility,
|
|
1204
|
+
useDataTable,
|
|
1205
|
+
useDataTableContext,
|
|
1206
|
+
useGlobalFilter,
|
|
1207
|
+
usePagination,
|
|
1208
|
+
useRowSelection
|
|
1209
|
+
};
|