@dynostack/react-grid 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2671 @@
1
+ import { createContext, useState, useRef, useEffect, useMemo, useContext } from 'react';
2
+ import { useReactTable, getPaginationRowModel, getExpandedRowModel, getSortedRowModel, getFilteredRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
3
+ import { Trash2Icon, XIcon, SearchIcon, RefreshCwIcon, ColumnsIcon, DownloadIcon, FileTextIcon, FileSpreadsheetIcon, PlusIcon, ChevronsLeftIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsRightIcon, ArrowDownIcon, ArrowUpIcon, ChevronsUpDownIcon, PinOffIcon, PinIcon, EyeOffIcon, FilterIcon, CheckIcon, MoreHorizontalIcon, ChevronDownIcon, GripVerticalIcon, Loader2Icon, MinusIcon, CopyIcon, PencilIcon, EyeIcon, ChevronUpIcon } from 'lucide-react';
4
+ import { clsx } from 'clsx';
5
+ import { twMerge } from 'tailwind-merge';
6
+ import { Slot, Separator as Separator$1, Popover as Popover$1, Checkbox as Checkbox$1, DropdownMenu as DropdownMenu$1, Select as Select$1 } from 'radix-ui';
7
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
8
+ import { cva } from 'class-variance-authority';
9
+
10
+ function cn(...inputs) {
11
+ return twMerge(clsx(inputs));
12
+ }
13
+ function Checkbox({
14
+ className,
15
+ ...props
16
+ }) {
17
+ return /* @__PURE__ */ jsx(
18
+ Checkbox$1.Root,
19
+ {
20
+ "data-slot": "checkbox",
21
+ className: cn(
22
+ "peer inline-flex size-4 shrink-0 items-center justify-center rounded-[5px] border border-input bg-background shadow-xs outline-none transition-colors focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground",
23
+ className
24
+ ),
25
+ ...props,
26
+ children: /* @__PURE__ */ jsx(
27
+ Checkbox$1.Indicator,
28
+ {
29
+ "data-slot": "checkbox-indicator",
30
+ className: "flex items-center justify-center text-current",
31
+ children: props.checked === "indeterminate" ? /* @__PURE__ */ jsx(MinusIcon, { className: "size-3" }) : /* @__PURE__ */ jsx(CheckIcon, { className: "size-3" })
32
+ }
33
+ )
34
+ }
35
+ );
36
+ }
37
+ function Skeleton({ className, ...props }) {
38
+ return /* @__PURE__ */ jsx(
39
+ "div",
40
+ {
41
+ "data-slot": "skeleton",
42
+ className: cn("animate-pulse rounded-md bg-muted", className),
43
+ ...props
44
+ }
45
+ );
46
+ }
47
+ var buttonVariants = cva(
48
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
49
+ {
50
+ variants: {
51
+ variant: {
52
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
53
+ outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
54
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
55
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 data-[size^=icon]:hover:bg-primary/10 data-[size^=icon]:hover:text-primary data-[size^=icon]:hover:[&_svg]:text-primary data-[size^=icon]:aria-expanded:bg-primary/10 data-[size^=icon]:aria-expanded:text-primary data-[size^=icon]:aria-expanded:border data-[size^=icon]:aria-expanded:border-primary/30",
56
+ destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
57
+ link: "text-primary underline-offset-4 hover:underline"
58
+ },
59
+ size: {
60
+ default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
61
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
62
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
63
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
64
+ icon: "size-8",
65
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
66
+ "icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
67
+ "icon-lg": "size-9"
68
+ }
69
+ },
70
+ defaultVariants: {
71
+ variant: "default",
72
+ size: "default"
73
+ }
74
+ }
75
+ );
76
+ function Button({
77
+ className,
78
+ variant = "default",
79
+ size = "default",
80
+ asChild = false,
81
+ ...props
82
+ }) {
83
+ const Comp = asChild ? Slot.Root : "button";
84
+ return /* @__PURE__ */ jsx(
85
+ Comp,
86
+ {
87
+ "data-slot": "button",
88
+ "data-variant": variant,
89
+ "data-size": size,
90
+ className: cn(buttonVariants({ variant, size, className })),
91
+ ...props
92
+ }
93
+ );
94
+ }
95
+ function Input({ className, type, ...props }) {
96
+ return /* @__PURE__ */ jsx(
97
+ "input",
98
+ {
99
+ type,
100
+ "data-slot": "input",
101
+ className: cn(
102
+ "h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
103
+ className
104
+ ),
105
+ ...props
106
+ }
107
+ );
108
+ }
109
+ var badgeVariants = cva(
110
+ "inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-xs font-medium whitespace-nowrap transition-colors [&_svg]:pointer-events-none [&_svg]:size-3",
111
+ {
112
+ variants: {
113
+ variant: {
114
+ default: "border-transparent bg-primary/10 text-primary [a&]:hover:bg-primary/20",
115
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/80",
116
+ destructive: "border-transparent bg-destructive/10 text-destructive [a&]:hover:bg-destructive/20",
117
+ success: "border-transparent bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 [a&]:hover:bg-emerald-500/20",
118
+ warning: "border-transparent bg-amber-500/15 text-amber-700 dark:text-amber-400 [a&]:hover:bg-amber-500/25",
119
+ outline: "border-border text-foreground"
120
+ }
121
+ },
122
+ defaultVariants: {
123
+ variant: "default"
124
+ }
125
+ }
126
+ );
127
+ function Badge({
128
+ className,
129
+ variant,
130
+ ...props
131
+ }) {
132
+ return /* @__PURE__ */ jsx(
133
+ "span",
134
+ {
135
+ "data-slot": "badge",
136
+ className: cn(badgeVariants({ variant }), className),
137
+ ...props
138
+ }
139
+ );
140
+ }
141
+ function Separator({
142
+ className,
143
+ orientation = "horizontal",
144
+ decorative = true,
145
+ ...props
146
+ }) {
147
+ return /* @__PURE__ */ jsx(
148
+ Separator$1.Root,
149
+ {
150
+ "data-slot": "separator",
151
+ decorative,
152
+ orientation,
153
+ className: cn(
154
+ "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
155
+ className
156
+ ),
157
+ ...props
158
+ }
159
+ );
160
+ }
161
+ function DropdownMenu({
162
+ ...props
163
+ }) {
164
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
165
+ }
166
+ function DropdownMenuTrigger({
167
+ ...props
168
+ }) {
169
+ return /* @__PURE__ */ jsx(
170
+ DropdownMenu$1.Trigger,
171
+ {
172
+ "data-slot": "dropdown-menu-trigger",
173
+ ...props
174
+ }
175
+ );
176
+ }
177
+ function DropdownMenuContent({
178
+ className,
179
+ align = "start",
180
+ sideOffset = 4,
181
+ ...props
182
+ }) {
183
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { children: /* @__PURE__ */ jsx(
184
+ DropdownMenu$1.Content,
185
+ {
186
+ "data-slot": "dropdown-menu-content",
187
+ sideOffset,
188
+ align,
189
+ className: cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!", className),
190
+ ...props
191
+ }
192
+ ) });
193
+ }
194
+ function DropdownMenuItem({
195
+ className,
196
+ inset,
197
+ variant = "default",
198
+ ...props
199
+ }) {
200
+ return /* @__PURE__ */ jsx(
201
+ DropdownMenu$1.Item,
202
+ {
203
+ "data-slot": "dropdown-menu-item",
204
+ "data-inset": inset,
205
+ "data-variant": variant,
206
+ className: cn(
207
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
208
+ className
209
+ ),
210
+ ...props
211
+ }
212
+ );
213
+ }
214
+ function DropdownMenuLabel({
215
+ className,
216
+ inset,
217
+ ...props
218
+ }) {
219
+ return /* @__PURE__ */ jsx(
220
+ DropdownMenu$1.Label,
221
+ {
222
+ "data-slot": "dropdown-menu-label",
223
+ "data-inset": inset,
224
+ className: cn(
225
+ "px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7",
226
+ className
227
+ ),
228
+ ...props
229
+ }
230
+ );
231
+ }
232
+ function DropdownMenuSeparator({
233
+ className,
234
+ ...props
235
+ }) {
236
+ return /* @__PURE__ */ jsx(
237
+ DropdownMenu$1.Separator,
238
+ {
239
+ "data-slot": "dropdown-menu-separator",
240
+ className: cn("-mx-1 my-1 h-px bg-border", className),
241
+ ...props
242
+ }
243
+ );
244
+ }
245
+ function Popover({
246
+ ...props
247
+ }) {
248
+ return /* @__PURE__ */ jsx(Popover$1.Root, { "data-slot": "popover", ...props });
249
+ }
250
+ function PopoverTrigger({
251
+ ...props
252
+ }) {
253
+ return /* @__PURE__ */ jsx(Popover$1.Trigger, { "data-slot": "popover-trigger", ...props });
254
+ }
255
+ function PopoverContent({
256
+ className,
257
+ align = "center",
258
+ sideOffset = 6,
259
+ ...props
260
+ }) {
261
+ return /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(
262
+ Popover$1.Content,
263
+ {
264
+ "data-slot": "popover-content",
265
+ align,
266
+ sideOffset,
267
+ className: cn(
268
+ "z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-lg border bg-popover p-3 text-popover-foreground shadow-md outline-none data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
269
+ className
270
+ ),
271
+ ...props
272
+ }
273
+ ) });
274
+ }
275
+
276
+ // src/export.ts
277
+ function escapeCsv(value) {
278
+ if (value === null || value === void 0) return "";
279
+ const s = String(value);
280
+ if (/[",\n\r]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
281
+ return s;
282
+ }
283
+ function escapeHtml(value) {
284
+ if (value === null || value === void 0) return "";
285
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
286
+ }
287
+ function getExportRows(table) {
288
+ const visible = table.getVisibleLeafColumns().filter((c) => {
289
+ if (c.id === "__select" || c.id === "__expand" || c.id === "__actions")
290
+ return false;
291
+ return c.columnDef.meta?.exportable !== false;
292
+ });
293
+ const headers = visible.map(
294
+ (c) => c.columnDef.meta?.label ?? (typeof c.columnDef.header === "string" ? c.columnDef.header : c.id)
295
+ );
296
+ const selected = table.getSelectedRowModel().rows;
297
+ const rows = selected.length > 0 ? selected : table.getFilteredRowModel().rows;
298
+ const data = rows.map(
299
+ (row) => visible.map((col) => {
300
+ const value = row.getValue(col.id);
301
+ if (value instanceof Date) return value.toISOString().slice(0, 10);
302
+ if (typeof value === "object" && value !== null) return JSON.stringify(value);
303
+ return value;
304
+ })
305
+ );
306
+ return { headers, data };
307
+ }
308
+ function triggerDownload(content, mime, filename) {
309
+ const bom = "\uFEFF";
310
+ const blob = new Blob([bom + content], { type: mime });
311
+ const url = URL.createObjectURL(blob);
312
+ const a = document.createElement("a");
313
+ a.href = url;
314
+ a.download = filename;
315
+ document.body.appendChild(a);
316
+ a.click();
317
+ document.body.removeChild(a);
318
+ setTimeout(() => URL.revokeObjectURL(url), 100);
319
+ }
320
+ function exportToCsv(table, fileName = "export") {
321
+ const { headers, data } = getExportRows(table);
322
+ const lines = [headers.map(escapeCsv).join(","), ...data.map((r) => r.map(escapeCsv).join(","))];
323
+ triggerDownload(lines.join("\r\n"), "text/csv;charset=utf-8;", `${fileName}.csv`);
324
+ }
325
+ function exportToExcel(table, fileName = "export") {
326
+ const { headers, data } = getExportRows(table);
327
+ const headerHtml = headers.map((h) => `<th style="background:#f4f4f5;text-align:left;padding:6px 10px;border:1px solid #d4d4d8">${escapeHtml(h)}</th>`).join("");
328
+ const bodyHtml = data.map(
329
+ (row) => `<tr>${row.map((c) => `<td style="padding:6px 10px;border:1px solid #d4d4d8">${escapeHtml(c)}</td>`).join("")}</tr>`
330
+ ).join("");
331
+ const html = `<!DOCTYPE html><html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta charset="utf-8"/><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>Sheet1</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>${`<thead><tr>${headerHtml}</tr></thead><tbody>${bodyHtml}</tbody>`}</table></body></html>`;
332
+ triggerDownload(html, "application/vnd.ms-excel;charset=utf-8;", `${fileName}.xls`);
333
+ }
334
+ var TOOLBAR_DEFAULT_LABELS = {
335
+ search: "Search...",
336
+ addRow: "Add row",
337
+ delete: "Delete",
338
+ clear: "Clear",
339
+ selected: "selected",
340
+ refresh: "Refresh",
341
+ columns: "Columns",
342
+ export: "Export",
343
+ csv: "CSV",
344
+ excel: "Excel",
345
+ total: "Total"
346
+ };
347
+ function DataTableToolbar({
348
+ table,
349
+ totalRecords,
350
+ isFetching,
351
+ onRefresh,
352
+ onAddRow,
353
+ onBulkDelete,
354
+ exportFileName = "export",
355
+ globalFilter,
356
+ onGlobalFilterChange,
357
+ toolbarSlot,
358
+ features,
359
+ labels: labelsProp
360
+ }) {
361
+ const labels = {
362
+ ...TOOLBAR_DEFAULT_LABELS,
363
+ ...labelsProp
364
+ };
365
+ const showSearch = features?.search ?? true;
366
+ const showColumnVisibility = features?.columnVisibility ?? true;
367
+ const showExport = features?.export ?? true;
368
+ const [searchOpen, setSearchOpen] = useState(Boolean(globalFilter));
369
+ const searchInputRef = useRef(null);
370
+ const selectedRows = table.getSelectedRowModel().rows;
371
+ const selectedCount = selectedRows.length;
372
+ const hideableColumns = table.getAllLeafColumns().filter((c) => c.getCanHide());
373
+ const visibleCount = hideableColumns.filter((c) => c.getIsVisible()).length;
374
+ useEffect(() => {
375
+ if (globalFilter) setSearchOpen(true);
376
+ }, [globalFilter]);
377
+ useEffect(() => {
378
+ if (searchOpen) searchInputRef.current?.focus();
379
+ }, [searchOpen]);
380
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b p-2", children: [
381
+ toolbarSlot,
382
+ selectedCount > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
383
+ /* @__PURE__ */ jsxs(
384
+ Badge,
385
+ {
386
+ variant: "secondary",
387
+ className: "flex h-8 items-center gap-1 rounded-md px-2 text-xs",
388
+ children: [
389
+ selectedCount,
390
+ " ",
391
+ labels.selected
392
+ ]
393
+ }
394
+ ),
395
+ onBulkDelete && /* @__PURE__ */ jsxs(
396
+ Button,
397
+ {
398
+ variant: "destructive",
399
+ size: "sm",
400
+ className: "h-8",
401
+ onClick: () => onBulkDelete(selectedRows.map((r) => r.original)),
402
+ children: [
403
+ /* @__PURE__ */ jsx(Trash2Icon, {}),
404
+ " ",
405
+ labels.delete
406
+ ]
407
+ }
408
+ ),
409
+ /* @__PURE__ */ jsxs(
410
+ Button,
411
+ {
412
+ variant: "ghost",
413
+ size: "sm",
414
+ className: "h-8",
415
+ onClick: () => table.resetRowSelection(),
416
+ children: [
417
+ /* @__PURE__ */ jsx(XIcon, {}),
418
+ " ",
419
+ labels.clear
420
+ ]
421
+ }
422
+ ),
423
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-2 !h-6 !self-center" })
424
+ ] }),
425
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex h-8 items-center gap-1", children: [
426
+ showSearch && /* @__PURE__ */ jsx(
427
+ "div",
428
+ {
429
+ className: cn(
430
+ "relative h-8 overflow-hidden transition-[width] duration-200 ease-out",
431
+ searchOpen ? "w-56" : "w-8"
432
+ ),
433
+ children: searchOpen ? /* @__PURE__ */ jsxs(Fragment, { children: [
434
+ /* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute top-1/2 left-2 size-3.5 -translate-y-1/2 opacity-60" }),
435
+ /* @__PURE__ */ jsx(
436
+ Input,
437
+ {
438
+ ref: searchInputRef,
439
+ placeholder: labels.search,
440
+ value: globalFilter,
441
+ onChange: (e) => onGlobalFilterChange(e.target.value),
442
+ onKeyDown: (e) => {
443
+ if (e.key === "Escape" && !globalFilter) setSearchOpen(false);
444
+ },
445
+ onBlur: () => {
446
+ if (!globalFilter) setSearchOpen(false);
447
+ },
448
+ className: "h-8 w-56 border-input bg-transparent pl-7 pr-7 shadow-none focus-visible:bg-transparent focus-visible:ring-0"
449
+ }
450
+ ),
451
+ /* @__PURE__ */ jsx(
452
+ "button",
453
+ {
454
+ type: "button",
455
+ onMouseDown: (e) => e.preventDefault(),
456
+ onClick: () => {
457
+ if (globalFilter) {
458
+ onGlobalFilterChange("");
459
+ searchInputRef.current?.focus();
460
+ return;
461
+ }
462
+ setSearchOpen(false);
463
+ },
464
+ className: "absolute top-1/2 right-1.5 -translate-y-1/2 rounded p-0.5 hover:bg-muted",
465
+ "aria-label": globalFilter ? "Clear search" : "Close search",
466
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-3" })
467
+ }
468
+ )
469
+ ] }) : /* @__PURE__ */ jsx(
470
+ Button,
471
+ {
472
+ type: "button",
473
+ variant: "outline",
474
+ size: "icon-sm",
475
+ onClick: () => setSearchOpen(true),
476
+ "aria-label": "Open search",
477
+ children: /* @__PURE__ */ jsx(SearchIcon, {})
478
+ }
479
+ )
480
+ }
481
+ ),
482
+ onRefresh && /* @__PURE__ */ jsx(
483
+ Button,
484
+ {
485
+ variant: "ghost",
486
+ size: "icon-sm",
487
+ onClick: onRefresh,
488
+ disabled: isFetching,
489
+ "aria-label": labels.refresh,
490
+ children: /* @__PURE__ */ jsx(RefreshCwIcon, { className: isFetching ? "animate-spin" : void 0 })
491
+ }
492
+ ),
493
+ showColumnVisibility && /* @__PURE__ */ jsxs(Popover, { children: [
494
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
495
+ /* @__PURE__ */ jsx(ColumnsIcon, {}),
496
+ " ",
497
+ labels.columns,
498
+ /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "ml-1", children: [
499
+ visibleCount,
500
+ "/",
501
+ hideableColumns.length
502
+ ] })
503
+ ] }) }),
504
+ /* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "w-56 p-2", children: [
505
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between text-xs text-muted-foreground", children: [
506
+ /* @__PURE__ */ jsx("span", { children: "Toggle columns" }),
507
+ /* @__PURE__ */ jsx(
508
+ "button",
509
+ {
510
+ className: "hover:text-foreground",
511
+ onClick: () => table.resetColumnVisibility(),
512
+ children: "Reset"
513
+ }
514
+ )
515
+ ] }),
516
+ /* @__PURE__ */ jsx("div", { className: "flex max-h-72 flex-col gap-0.5 overflow-auto", children: hideableColumns.map((col) => /* @__PURE__ */ jsxs(
517
+ "label",
518
+ {
519
+ className: "flex cursor-pointer items-center gap-2 rounded px-2 py-1 text-sm hover:bg-accent",
520
+ children: [
521
+ /* @__PURE__ */ jsx(
522
+ Checkbox,
523
+ {
524
+ checked: col.getIsVisible(),
525
+ onCheckedChange: (c) => col.toggleVisibility(!!c)
526
+ }
527
+ ),
528
+ /* @__PURE__ */ jsx("span", { className: "capitalize", children: col.columnDef.meta?.label ?? (typeof col.columnDef.header === "string" ? col.columnDef.header : col.id) })
529
+ ]
530
+ },
531
+ col.id
532
+ )) })
533
+ ] })
534
+ ] }),
535
+ showExport && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
536
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
537
+ /* @__PURE__ */ jsx(DownloadIcon, {}),
538
+ " ",
539
+ labels.export
540
+ ] }) }),
541
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-44", children: [
542
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: selectedCount > 0 ? `${labels.export} ${selectedCount} ${labels.selected}` : `${labels.export} all rows` }),
543
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
544
+ /* @__PURE__ */ jsxs(
545
+ DropdownMenuItem,
546
+ {
547
+ onSelect: () => exportToCsv(table, exportFileName),
548
+ children: [
549
+ /* @__PURE__ */ jsx(FileTextIcon, {}),
550
+ " ",
551
+ labels.csv
552
+ ]
553
+ }
554
+ ),
555
+ /* @__PURE__ */ jsxs(
556
+ DropdownMenuItem,
557
+ {
558
+ onSelect: () => exportToExcel(table, exportFileName),
559
+ children: [
560
+ /* @__PURE__ */ jsx(FileSpreadsheetIcon, {}),
561
+ " ",
562
+ labels.excel
563
+ ]
564
+ }
565
+ )
566
+ ] })
567
+ ] }),
568
+ onAddRow && /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: onAddRow, children: [
569
+ /* @__PURE__ */ jsx(PlusIcon, {}),
570
+ " ",
571
+ labels.addRow
572
+ ] }),
573
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-2 !h-6 !self-center" }),
574
+ /* @__PURE__ */ jsxs("div", { className: "flex h-8 items-center gap-1.5 rounded-md border bg-muted/40 px-2 text-xs", children: [
575
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: labels.total }),
576
+ /* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", children: new Intl.NumberFormat().format(totalRecords) })
577
+ ] })
578
+ ] })
579
+ ] });
580
+ }
581
+ function Select(props) {
582
+ return /* @__PURE__ */ jsx(Select$1.Root, { "data-slot": "select", ...props });
583
+ }
584
+ function SelectValue(props) {
585
+ return /* @__PURE__ */ jsx(Select$1.Value, { "data-slot": "select-value", ...props });
586
+ }
587
+ function SelectTrigger({
588
+ className,
589
+ size = "default",
590
+ children,
591
+ ...props
592
+ }) {
593
+ return /* @__PURE__ */ jsxs(
594
+ Select$1.Trigger,
595
+ {
596
+ "data-slot": "select-trigger",
597
+ "data-size": size,
598
+ className: cn(
599
+ "flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-background px-2.5 text-sm whitespace-nowrap shadow-xs outline-none transition-colors focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-8 data-[size=sm]:h-7 data-[placeholder]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:size-3.5 [&_svg]:shrink-0",
600
+ className
601
+ ),
602
+ ...props,
603
+ children: [
604
+ children,
605
+ /* @__PURE__ */ jsx(Select$1.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "opacity-60" }) })
606
+ ]
607
+ }
608
+ );
609
+ }
610
+ function SelectContent({
611
+ className,
612
+ children,
613
+ position = "popper",
614
+ ...props
615
+ }) {
616
+ return /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsxs(
617
+ Select$1.Content,
618
+ {
619
+ "data-slot": "select-content",
620
+ position,
621
+ className: cn(
622
+ "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg border bg-popover text-popover-foreground shadow-md data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
623
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1",
624
+ className
625
+ ),
626
+ ...props,
627
+ children: [
628
+ /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
629
+ /* @__PURE__ */ jsx(
630
+ Select$1.Viewport,
631
+ {
632
+ className: cn(
633
+ "p-1",
634
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
635
+ ),
636
+ children
637
+ }
638
+ ),
639
+ /* @__PURE__ */ jsx(SelectScrollDownButton, {})
640
+ ]
641
+ }
642
+ ) });
643
+ }
644
+ function SelectItem({
645
+ className,
646
+ children,
647
+ ...props
648
+ }) {
649
+ return /* @__PURE__ */ jsxs(
650
+ Select$1.Item,
651
+ {
652
+ "data-slot": "select-item",
653
+ className: cn(
654
+ "relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
655
+ className
656
+ ),
657
+ ...props,
658
+ children: [
659
+ /* @__PURE__ */ jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(Select$1.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" }) }) }),
660
+ /* @__PURE__ */ jsx(Select$1.ItemText, { children })
661
+ ]
662
+ }
663
+ );
664
+ }
665
+ function SelectScrollUpButton({
666
+ className,
667
+ ...props
668
+ }) {
669
+ return /* @__PURE__ */ jsx(
670
+ Select$1.ScrollUpButton,
671
+ {
672
+ "data-slot": "select-scroll-up-button",
673
+ className: cn(
674
+ "flex cursor-default items-center justify-center py-1",
675
+ className
676
+ ),
677
+ ...props,
678
+ children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-3.5" })
679
+ }
680
+ );
681
+ }
682
+ function SelectScrollDownButton({
683
+ className,
684
+ ...props
685
+ }) {
686
+ return /* @__PURE__ */ jsx(
687
+ Select$1.ScrollDownButton,
688
+ {
689
+ "data-slot": "select-scroll-down-button",
690
+ className: cn(
691
+ "flex cursor-default items-center justify-center py-1",
692
+ className
693
+ ),
694
+ ...props,
695
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3.5" })
696
+ }
697
+ );
698
+ }
699
+ function getPageRange(current, total) {
700
+ const pages = [];
701
+ if (total <= 7) {
702
+ for (let i = 0; i < total; i++) pages.push(i);
703
+ return pages;
704
+ }
705
+ pages.push(0);
706
+ if (current > 3) pages.push("\u2026");
707
+ const start = Math.max(1, current - 1);
708
+ const end = Math.min(total - 2, current + 1);
709
+ for (let i = start; i <= end; i++) pages.push(i);
710
+ if (current < total - 4) pages.push("\u2026");
711
+ pages.push(total - 1);
712
+ return pages;
713
+ }
714
+ function DataTablePagination({
715
+ table,
716
+ pageSizeOptions = [10, 25, 50, 100]
717
+ }) {
718
+ const pageIndex = table.getState().pagination.pageIndex;
719
+ const pageSize = table.getState().pagination.pageSize;
720
+ const pageCount = table.getPageCount();
721
+ const totalRows = table.getFilteredRowModel().rows.length;
722
+ const fromRow = totalRows === 0 ? 0 : pageIndex * pageSize + 1;
723
+ const toRow = Math.min(totalRows, (pageIndex + 1) * pageSize);
724
+ const selected = table.getSelectedRowModel().rows.length;
725
+ const range = getPageRange(pageIndex, pageCount);
726
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 items-center gap-2 border-t p-2 text-xs md:grid-cols-3", children: [
727
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground md:justify-self-start", children: [
728
+ selected > 0 ? /* @__PURE__ */ jsxs("span", { children: [
729
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: selected }),
730
+ " ",
731
+ "selected \xB7",
732
+ " "
733
+ ] }) : null,
734
+ /* @__PURE__ */ jsxs("span", { children: [
735
+ "Showing",
736
+ " ",
737
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: fromRow }),
738
+ "\u2013",
739
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: toRow }),
740
+ " of",
741
+ " ",
742
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: new Intl.NumberFormat().format(totalRows) })
743
+ ] })
744
+ ] }),
745
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 md:justify-self-center", children: [
746
+ /* @__PURE__ */ jsx(
747
+ Button,
748
+ {
749
+ variant: "ghost",
750
+ size: "icon-sm",
751
+ onClick: () => table.setPageIndex(0),
752
+ disabled: !table.getCanPreviousPage(),
753
+ "aria-label": "First page",
754
+ children: /* @__PURE__ */ jsx(ChevronsLeftIcon, {})
755
+ }
756
+ ),
757
+ /* @__PURE__ */ jsx(
758
+ Button,
759
+ {
760
+ variant: "ghost",
761
+ size: "icon-sm",
762
+ onClick: () => table.previousPage(),
763
+ disabled: !table.getCanPreviousPage(),
764
+ "aria-label": "Previous page",
765
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
766
+ }
767
+ ),
768
+ range.map(
769
+ (p, idx) => p === "\u2026" ? /* @__PURE__ */ jsx("span", { className: "px-1 text-muted-foreground", children: "\u2026" }, `e-${idx}`) : /* @__PURE__ */ jsx(
770
+ "button",
771
+ {
772
+ type: "button",
773
+ onClick: () => table.setPageIndex(p),
774
+ className: cn(
775
+ "h-7 min-w-7 rounded-md border border-transparent px-2 text-xs font-medium transition-colors hover:bg-accent",
776
+ p === pageIndex ? "border-border bg-primary/10 text-primary" : "text-muted-foreground"
777
+ ),
778
+ children: p + 1
779
+ },
780
+ p
781
+ )
782
+ ),
783
+ /* @__PURE__ */ jsx(
784
+ Button,
785
+ {
786
+ variant: "ghost",
787
+ size: "icon-sm",
788
+ onClick: () => table.nextPage(),
789
+ disabled: !table.getCanNextPage(),
790
+ "aria-label": "Next page",
791
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
792
+ }
793
+ ),
794
+ /* @__PURE__ */ jsx(
795
+ Button,
796
+ {
797
+ variant: "ghost",
798
+ size: "icon-sm",
799
+ onClick: () => table.setPageIndex(pageCount - 1),
800
+ disabled: !table.getCanNextPage(),
801
+ "aria-label": "Last page",
802
+ children: /* @__PURE__ */ jsx(ChevronsRightIcon, {})
803
+ }
804
+ )
805
+ ] }),
806
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-muted-foreground md:justify-self-end", children: [
807
+ /* @__PURE__ */ jsx("span", { children: "Rows per page" }),
808
+ /* @__PURE__ */ jsxs(
809
+ Select,
810
+ {
811
+ value: String(pageSize),
812
+ onValueChange: (v) => table.setPageSize(Number(v)),
813
+ children: [
814
+ /* @__PURE__ */ jsx(SelectTrigger, { size: "sm", className: "h-7 w-16", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
815
+ /* @__PURE__ */ jsx(SelectContent, { children: pageSizeOptions.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: String(opt), children: opt }, opt)) })
816
+ ]
817
+ }
818
+ )
819
+ ] })
820
+ ] });
821
+ }
822
+ var TEXT_OPS = [
823
+ { v: "contains", l: "Contains" },
824
+ { v: "notContains", l: "Does not contain" },
825
+ { v: "equals", l: "Equals" },
826
+ { v: "notEqual", l: "Does not equal" },
827
+ { v: "startsWith", l: "Starts with" },
828
+ { v: "endsWith", l: "Ends with" },
829
+ { v: "blank", l: "Blank" },
830
+ { v: "notBlank", l: "Not blank" }
831
+ ];
832
+ var NUMBER_OPS = [
833
+ { v: "equals", l: "Equals" },
834
+ { v: "notEqual", l: "Does not equal" },
835
+ { v: "lessThan", l: "Less than" },
836
+ { v: "lessThanOrEqual", l: "Less than or equal" },
837
+ { v: "greaterThan", l: "Greater than" },
838
+ { v: "greaterThanOrEqual", l: "Greater than or equal" },
839
+ { v: "inRange", l: "In range" },
840
+ { v: "blank", l: "Blank" },
841
+ { v: "notBlank", l: "Not blank" }
842
+ ];
843
+ var DATE_OPS = [
844
+ { v: "equals", l: "Equals" },
845
+ { v: "notEqual", l: "Does not equal" },
846
+ { v: "before", l: "Before" },
847
+ { v: "after", l: "After" },
848
+ { v: "inRange", l: "In range" },
849
+ { v: "blank", l: "Blank" },
850
+ { v: "notBlank", l: "Not blank" }
851
+ ];
852
+ function isUnary(op) {
853
+ return op === "blank" || op === "notBlank";
854
+ }
855
+ function isRange(op) {
856
+ return op === "inRange";
857
+ }
858
+ function evalText(op, cell, q) {
859
+ const c = (cell ?? "").toString().toLowerCase();
860
+ const v = (q ?? "").toString().toLowerCase();
861
+ switch (op) {
862
+ case "contains":
863
+ return v === "" ? true : c.includes(v);
864
+ case "notContains":
865
+ return v === "" ? true : !c.includes(v);
866
+ case "equals":
867
+ return c === v;
868
+ case "notEqual":
869
+ return c !== v;
870
+ case "startsWith":
871
+ return c.startsWith(v);
872
+ case "endsWith":
873
+ return c.endsWith(v);
874
+ case "blank":
875
+ return c === "";
876
+ case "notBlank":
877
+ return c !== "";
878
+ }
879
+ }
880
+ function evalNumber(op, cell, a, b) {
881
+ const n = cell == null || cell === "" ? null : Number(cell);
882
+ switch (op) {
883
+ case "blank":
884
+ return n === null;
885
+ case "notBlank":
886
+ return n !== null;
887
+ }
888
+ if (n === null || Number.isNaN(n)) return false;
889
+ if (a == null) return true;
890
+ switch (op) {
891
+ case "equals":
892
+ return n === a;
893
+ case "notEqual":
894
+ return n !== a;
895
+ case "lessThan":
896
+ return n < a;
897
+ case "lessThanOrEqual":
898
+ return n <= a;
899
+ case "greaterThan":
900
+ return n > a;
901
+ case "greaterThanOrEqual":
902
+ return n >= a;
903
+ case "inRange":
904
+ if (b == null) return n >= a;
905
+ return n >= a && n <= b;
906
+ }
907
+ return true;
908
+ }
909
+ function evalDate(op, cell, a, b) {
910
+ const c = cell ? String(cell) : "";
911
+ switch (op) {
912
+ case "blank":
913
+ return c === "";
914
+ case "notBlank":
915
+ return c !== "";
916
+ }
917
+ if (!c) return false;
918
+ if (!a) return true;
919
+ switch (op) {
920
+ case "equals":
921
+ return c === a;
922
+ case "notEqual":
923
+ return c !== a;
924
+ case "before":
925
+ return c < a;
926
+ case "after":
927
+ return c > a;
928
+ case "inRange":
929
+ if (!b) return c >= a;
930
+ return c >= a && c <= b;
931
+ }
932
+ return true;
933
+ }
934
+ function combineResults(a, b, mode) {
935
+ if (mode === "OR") return a || b;
936
+ return a && b;
937
+ }
938
+ function isAdvActive(f) {
939
+ if (!f?.c1) return false;
940
+ const c1 = f.c1;
941
+ if (isUnary(c1.op)) return true;
942
+ if (c1.v == null || c1.v === "") {
943
+ if (!f.c2) return false;
944
+ const c2 = f.c2;
945
+ if (isUnary(c2.op)) return true;
946
+ return c2.v != null && c2.v !== "";
947
+ }
948
+ return true;
949
+ }
950
+ var textFilterFn = (row, columnId, value) => {
951
+ const f = value;
952
+ if (!isAdvActive(f)) return true;
953
+ const cell = row.getValue(columnId);
954
+ const c1 = f.c1;
955
+ const aActive = isUnary(c1.op) || c1.v != null && c1.v !== "";
956
+ const aRes = aActive ? evalText(c1.op, cell, c1.v) : true;
957
+ if (!f.c2) return aRes;
958
+ const c2 = f.c2;
959
+ const bActive = isUnary(c2.op) || c2.v != null && c2.v !== "";
960
+ if (!bActive) return aRes;
961
+ const bRes = evalText(c2.op, cell, c2.v);
962
+ if (!aActive) return bRes;
963
+ return combineResults(aRes, bRes, f.combine);
964
+ };
965
+ var numberFilterFn = (row, columnId, value) => {
966
+ const f = value;
967
+ if (!isAdvActive(f)) return true;
968
+ const cell = row.getValue(columnId);
969
+ const c1 = f.c1;
970
+ const aActive = isUnary(c1.op) || c1.v != null;
971
+ const aRes = aActive ? evalNumber(c1.op, cell, c1.v, c1.v2) : true;
972
+ if (!f.c2) return aRes;
973
+ const c2 = f.c2;
974
+ const bActive = isUnary(c2.op) || c2.v != null;
975
+ if (!bActive) return aRes;
976
+ const bRes = evalNumber(c2.op, cell, c2.v, c2.v2);
977
+ if (!aActive) return bRes;
978
+ return combineResults(aRes, bRes, f.combine);
979
+ };
980
+ var dateFilterFn = (row, columnId, value) => {
981
+ const f = value;
982
+ if (!isAdvActive(f)) return true;
983
+ const cell = row.getValue(columnId);
984
+ const c1 = f.c1;
985
+ const aActive = isUnary(c1.op) || c1.v != null && c1.v !== "";
986
+ const aRes = aActive ? evalDate(c1.op, cell, c1.v, c1.v2) : true;
987
+ if (!f.c2) return aRes;
988
+ const c2 = f.c2;
989
+ const bActive = isUnary(c2.op) || c2.v != null && c2.v !== "";
990
+ if (!bActive) return aRes;
991
+ const bRes = evalDate(c2.op, cell, c2.v, c2.v2);
992
+ if (!aActive) return bRes;
993
+ return combineResults(aRes, bRes, f.combine);
994
+ };
995
+ var setFilterFn = (row, columnId, value) => {
996
+ const f = value;
997
+ if (!f || !Array.isArray(f.selected)) return true;
998
+ if (f.selected.length === 0) return false;
999
+ const cell = row.getValue(columnId);
1000
+ if (Array.isArray(cell)) {
1001
+ return cell.some((c) => f.selected.includes(String(c)));
1002
+ }
1003
+ return f.selected.includes(String(cell ?? ""));
1004
+ };
1005
+ var booleanFilterFn = (row, columnId, value) => {
1006
+ if (value === void 0 || value === null || value === "") return true;
1007
+ const cell = row.getValue(columnId);
1008
+ return Boolean(cell) === Boolean(value);
1009
+ };
1010
+ function isFilterActive(filterType, value) {
1011
+ if (value === void 0 || value === null || value === "") return false;
1012
+ if (filterType === "multi-select" || filterType === "select") {
1013
+ const f = value;
1014
+ return Array.isArray(f.selected);
1015
+ }
1016
+ if (filterType === "text" || filterType === "number" || filterType === "date") {
1017
+ return isAdvActive(value);
1018
+ }
1019
+ if (filterType === "boolean") return value !== void 0;
1020
+ return true;
1021
+ }
1022
+ function ColumnFilterPanel(props) {
1023
+ const filterType = props.column.columnDef.meta?.filterType;
1024
+ switch (filterType) {
1025
+ case "text":
1026
+ return /* @__PURE__ */ jsx(TextFilterPanel, { ...props });
1027
+ case "number":
1028
+ return /* @__PURE__ */ jsx(NumberFilterPanel, { ...props });
1029
+ case "date":
1030
+ return /* @__PURE__ */ jsx(DateFilterPanel, { ...props });
1031
+ case "select":
1032
+ case "multi-select":
1033
+ return /* @__PURE__ */ jsx(SetFilterPanel, { ...props });
1034
+ case "boolean":
1035
+ return /* @__PURE__ */ jsx(BooleanFilterPanel, { ...props });
1036
+ default:
1037
+ return null;
1038
+ }
1039
+ }
1040
+ function OpSelect({
1041
+ ops,
1042
+ value,
1043
+ onChange
1044
+ }) {
1045
+ return /* @__PURE__ */ jsx(
1046
+ "select",
1047
+ {
1048
+ value,
1049
+ onChange: (e) => onChange(e.target.value),
1050
+ className: "h-8 w-full rounded-md border border-input bg-background px-2 text-xs outline-none focus-visible:border-ring",
1051
+ children: ops.map((o) => /* @__PURE__ */ jsx("option", { value: o.v, children: o.l }, o.v))
1052
+ }
1053
+ );
1054
+ }
1055
+ function CombineToggle({
1056
+ value,
1057
+ onChange
1058
+ }) {
1059
+ return /* @__PURE__ */ jsx("div", { className: "inline-flex h-7 overflow-hidden rounded-md border bg-muted/30 text-[11px]", children: ["AND", "OR"].map((m) => /* @__PURE__ */ jsx(
1060
+ "button",
1061
+ {
1062
+ type: "button",
1063
+ className: cn(
1064
+ "px-2 transition-colors hover:bg-muted",
1065
+ value === m && "bg-primary/10 text-primary"
1066
+ ),
1067
+ onClick: () => onChange(m),
1068
+ children: m
1069
+ },
1070
+ m
1071
+ )) });
1072
+ }
1073
+ function FooterBar({
1074
+ onApply,
1075
+ onClear,
1076
+ onClose,
1077
+ applyDisabled,
1078
+ clearDisabled
1079
+ }) {
1080
+ return /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between gap-1.5", children: [
1081
+ /* @__PURE__ */ jsx(
1082
+ Button,
1083
+ {
1084
+ size: "xs",
1085
+ variant: "ghost",
1086
+ onClick: onClear,
1087
+ disabled: clearDisabled,
1088
+ children: "Clear"
1089
+ }
1090
+ ),
1091
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
1092
+ /* @__PURE__ */ jsx(Button, { size: "xs", variant: "ghost", onClick: onClose, children: "Close" }),
1093
+ onApply && /* @__PURE__ */ jsx(Button, { size: "xs", onClick: onApply, disabled: applyDisabled, children: "Apply" })
1094
+ ] })
1095
+ ] });
1096
+ }
1097
+ function TextFilterPanel({
1098
+ column,
1099
+ onClose
1100
+ }) {
1101
+ const value = column.getFilterValue();
1102
+ const [draft, setDraft] = useState(
1103
+ value ?? { c1: { op: "contains", v: "" } }
1104
+ );
1105
+ useEffect(() => {
1106
+ setDraft(value ?? { c1: { op: "contains", v: "" } });
1107
+ }, [value]);
1108
+ const apply = () => {
1109
+ column.setFilterValue(
1110
+ isAdvActive(draft) ? draft : void 0
1111
+ );
1112
+ onClose();
1113
+ };
1114
+ const clear = () => {
1115
+ column.setFilterValue(void 0);
1116
+ setDraft({ c1: { op: "contains", v: "" } });
1117
+ };
1118
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1119
+ /* @__PURE__ */ jsx(
1120
+ ConditionRow,
1121
+ {
1122
+ ops: TEXT_OPS,
1123
+ c: draft.c1,
1124
+ onChange: (c1) => setDraft((d) => ({ ...d, c1 })),
1125
+ renderInput: (c, set) => /* @__PURE__ */ jsx(
1126
+ Input,
1127
+ {
1128
+ autoFocus: true,
1129
+ className: "h-8 text-sm",
1130
+ placeholder: "Value",
1131
+ value: c.v ?? "",
1132
+ onChange: (e) => set({ ...c, v: e.target.value }),
1133
+ onKeyDown: (e) => e.key === "Enter" && apply()
1134
+ }
1135
+ )
1136
+ }
1137
+ ),
1138
+ /* @__PURE__ */ jsx(Separator, {}),
1139
+ /* @__PURE__ */ jsx(
1140
+ SecondCondition,
1141
+ {
1142
+ draft,
1143
+ setDraft,
1144
+ ops: TEXT_OPS,
1145
+ renderInput: (c, set) => /* @__PURE__ */ jsx(
1146
+ Input,
1147
+ {
1148
+ className: "h-8 text-sm",
1149
+ placeholder: "Value",
1150
+ value: c.v ?? "",
1151
+ onChange: (e) => set({ ...c, v: e.target.value }),
1152
+ onKeyDown: (e) => e.key === "Enter" && apply()
1153
+ }
1154
+ )
1155
+ }
1156
+ ),
1157
+ /* @__PURE__ */ jsx(FooterBar, { onApply: apply, onClear: clear, onClose, clearDisabled: !value })
1158
+ ] });
1159
+ }
1160
+ function NumberFilterPanel({
1161
+ column,
1162
+ onClose
1163
+ }) {
1164
+ const value = column.getFilterValue();
1165
+ const [draft, setDraft] = useState(
1166
+ value ?? { c1: { op: "equals" } }
1167
+ );
1168
+ useEffect(() => {
1169
+ setDraft(value ?? { c1: { op: "equals" } });
1170
+ }, [value]);
1171
+ const apply = () => {
1172
+ column.setFilterValue(
1173
+ isAdvActive(draft) ? draft : void 0
1174
+ );
1175
+ onClose();
1176
+ };
1177
+ const clear = () => {
1178
+ column.setFilterValue(void 0);
1179
+ setDraft({ c1: { op: "equals" } });
1180
+ };
1181
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1182
+ /* @__PURE__ */ jsx(
1183
+ ConditionRow,
1184
+ {
1185
+ ops: NUMBER_OPS,
1186
+ c: draft.c1,
1187
+ onChange: (c1) => setDraft((d) => ({ ...d, c1 })),
1188
+ renderInput: (c, set) => isRange(c.op) ? /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
1189
+ /* @__PURE__ */ jsx(
1190
+ Input,
1191
+ {
1192
+ autoFocus: true,
1193
+ type: "number",
1194
+ className: "h-8 text-sm",
1195
+ placeholder: "From",
1196
+ value: c.v ?? "",
1197
+ onChange: (e) => set({
1198
+ ...c,
1199
+ v: e.target.value === "" ? void 0 : Number(e.target.value)
1200
+ })
1201
+ }
1202
+ ),
1203
+ /* @__PURE__ */ jsx(
1204
+ Input,
1205
+ {
1206
+ type: "number",
1207
+ className: "h-8 text-sm",
1208
+ placeholder: "To",
1209
+ value: c.v2 ?? "",
1210
+ onChange: (e) => set({
1211
+ ...c,
1212
+ v2: e.target.value === "" ? void 0 : Number(e.target.value)
1213
+ })
1214
+ }
1215
+ )
1216
+ ] }) : /* @__PURE__ */ jsx(
1217
+ Input,
1218
+ {
1219
+ autoFocus: true,
1220
+ type: "number",
1221
+ className: "h-8 text-sm",
1222
+ placeholder: "Value",
1223
+ value: c.v ?? "",
1224
+ onChange: (e) => set({
1225
+ ...c,
1226
+ v: e.target.value === "" ? void 0 : Number(e.target.value)
1227
+ }),
1228
+ onKeyDown: (e) => e.key === "Enter" && apply()
1229
+ }
1230
+ )
1231
+ }
1232
+ ),
1233
+ /* @__PURE__ */ jsx(Separator, {}),
1234
+ /* @__PURE__ */ jsx(
1235
+ SecondCondition,
1236
+ {
1237
+ draft,
1238
+ setDraft,
1239
+ ops: NUMBER_OPS,
1240
+ renderInput: (c, set) => isRange(c.op) ? /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
1241
+ /* @__PURE__ */ jsx(
1242
+ Input,
1243
+ {
1244
+ type: "number",
1245
+ className: "h-8 text-sm",
1246
+ placeholder: "From",
1247
+ value: c.v ?? "",
1248
+ onChange: (e) => set({
1249
+ ...c,
1250
+ v: e.target.value === "" ? void 0 : Number(e.target.value)
1251
+ })
1252
+ }
1253
+ ),
1254
+ /* @__PURE__ */ jsx(
1255
+ Input,
1256
+ {
1257
+ type: "number",
1258
+ className: "h-8 text-sm",
1259
+ placeholder: "To",
1260
+ value: c.v2 ?? "",
1261
+ onChange: (e) => set({
1262
+ ...c,
1263
+ v2: e.target.value === "" ? void 0 : Number(e.target.value)
1264
+ })
1265
+ }
1266
+ )
1267
+ ] }) : /* @__PURE__ */ jsx(
1268
+ Input,
1269
+ {
1270
+ type: "number",
1271
+ className: "h-8 text-sm",
1272
+ placeholder: "Value",
1273
+ value: c.v ?? "",
1274
+ onChange: (e) => set({
1275
+ ...c,
1276
+ v: e.target.value === "" ? void 0 : Number(e.target.value)
1277
+ })
1278
+ }
1279
+ )
1280
+ }
1281
+ ),
1282
+ /* @__PURE__ */ jsx(FooterBar, { onApply: apply, onClear: clear, onClose, clearDisabled: !value })
1283
+ ] });
1284
+ }
1285
+ function DateFilterPanel({
1286
+ column,
1287
+ onClose
1288
+ }) {
1289
+ const value = column.getFilterValue();
1290
+ const [draft, setDraft] = useState(
1291
+ value ?? { c1: { op: "equals" } }
1292
+ );
1293
+ useEffect(() => {
1294
+ setDraft(value ?? { c1: { op: "equals" } });
1295
+ }, [value]);
1296
+ const apply = () => {
1297
+ column.setFilterValue(
1298
+ isAdvActive(draft) ? draft : void 0
1299
+ );
1300
+ onClose();
1301
+ };
1302
+ const clear = () => {
1303
+ column.setFilterValue(void 0);
1304
+ setDraft({ c1: { op: "equals" } });
1305
+ };
1306
+ const renderInput = (c, set) => isRange(c.op) ? /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
1307
+ /* @__PURE__ */ jsx(
1308
+ Input,
1309
+ {
1310
+ type: "date",
1311
+ className: "h-8 text-sm",
1312
+ value: c.v ?? "",
1313
+ onChange: (e) => set({ ...c, v: e.target.value || void 0 })
1314
+ }
1315
+ ),
1316
+ /* @__PURE__ */ jsx(
1317
+ Input,
1318
+ {
1319
+ type: "date",
1320
+ className: "h-8 text-sm",
1321
+ value: c.v2 ?? "",
1322
+ onChange: (e) => set({ ...c, v2: e.target.value || void 0 })
1323
+ }
1324
+ )
1325
+ ] }) : /* @__PURE__ */ jsx(
1326
+ Input,
1327
+ {
1328
+ type: "date",
1329
+ className: "h-8 text-sm",
1330
+ value: c.v ?? "",
1331
+ onChange: (e) => set({ ...c, v: e.target.value || void 0 })
1332
+ }
1333
+ );
1334
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1335
+ /* @__PURE__ */ jsx(
1336
+ ConditionRow,
1337
+ {
1338
+ ops: DATE_OPS,
1339
+ c: draft.c1,
1340
+ onChange: (c1) => setDraft((d) => ({ ...d, c1 })),
1341
+ renderInput
1342
+ }
1343
+ ),
1344
+ /* @__PURE__ */ jsx(Separator, {}),
1345
+ /* @__PURE__ */ jsx(
1346
+ SecondCondition,
1347
+ {
1348
+ draft,
1349
+ setDraft,
1350
+ ops: DATE_OPS,
1351
+ renderInput
1352
+ }
1353
+ ),
1354
+ /* @__PURE__ */ jsx(FooterBar, { onApply: apply, onClear: clear, onClose, clearDisabled: !value })
1355
+ ] });
1356
+ }
1357
+ function SetFilterPanel({
1358
+ column,
1359
+ onClose
1360
+ }) {
1361
+ const value = column.getFilterValue();
1362
+ const meta = column.columnDef.meta;
1363
+ const declared = meta?.selectOptions ?? [];
1364
+ const options = useMemo(() => {
1365
+ if (declared.length) return declared;
1366
+ const rows = column.getFacetedRowModel?.().rows ?? [];
1367
+ const seen = /* @__PURE__ */ new Set();
1368
+ const out = [];
1369
+ for (const r of rows) {
1370
+ const v = r.getValue(column.id);
1371
+ const arr = Array.isArray(v) ? v : [v];
1372
+ for (const item of arr) {
1373
+ const k = item == null ? "" : String(item);
1374
+ if (!seen.has(k)) {
1375
+ seen.add(k);
1376
+ out.push({ value: k, label: k === "" ? "(Blank)" : k });
1377
+ }
1378
+ }
1379
+ }
1380
+ return out.sort((a, b) => a.label.localeCompare(b.label));
1381
+ }, [declared, column]);
1382
+ const [search, setSearch] = useState("");
1383
+ const [draft, setDraft] = useState(() => {
1384
+ if (value && Array.isArray(value.selected)) return value.selected;
1385
+ return options.map((o) => o.value);
1386
+ });
1387
+ useEffect(() => {
1388
+ if (!value) setDraft(options.map((o) => o.value));
1389
+ else if (Array.isArray(value.selected)) setDraft(value.selected);
1390
+ }, [value, options]);
1391
+ const visible = useMemo(() => {
1392
+ const q = search.trim().toLowerCase();
1393
+ if (!q) return options;
1394
+ return options.filter((o) => o.label.toLowerCase().includes(q));
1395
+ }, [options, search]);
1396
+ const visibleAllSelected = visible.length > 0 && visible.every((o) => draft.includes(o.value));
1397
+ const visibleSomeSelected = visible.some((o) => draft.includes(o.value));
1398
+ const toggleAll = (checked) => {
1399
+ const visibleVals = visible.map((o) => o.value);
1400
+ if (checked) {
1401
+ const set = /* @__PURE__ */ new Set([...draft, ...visibleVals]);
1402
+ setDraft(Array.from(set));
1403
+ } else {
1404
+ setDraft(draft.filter((v) => !visibleVals.includes(v)));
1405
+ }
1406
+ };
1407
+ const toggle = (v) => {
1408
+ setDraft((d) => d.includes(v) ? d.filter((x) => x !== v) : [...d, v]);
1409
+ };
1410
+ const apply = () => {
1411
+ if (draft.length === options.length) {
1412
+ column.setFilterValue(void 0);
1413
+ } else {
1414
+ column.setFilterValue({ selected: draft });
1415
+ }
1416
+ onClose();
1417
+ };
1418
+ const clear = () => {
1419
+ column.setFilterValue(void 0);
1420
+ setDraft(options.map((o) => o.value));
1421
+ setSearch("");
1422
+ };
1423
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1424
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1425
+ /* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute top-1/2 left-2 size-3.5 -translate-y-1/2 opacity-60" }),
1426
+ /* @__PURE__ */ jsx(
1427
+ Input,
1428
+ {
1429
+ autoFocus: true,
1430
+ placeholder: "Search...",
1431
+ value: search,
1432
+ onChange: (e) => setSearch(e.target.value),
1433
+ className: "h-8 pl-7 pr-7 text-sm"
1434
+ }
1435
+ ),
1436
+ search && /* @__PURE__ */ jsx(
1437
+ "button",
1438
+ {
1439
+ type: "button",
1440
+ onClick: () => setSearch(""),
1441
+ className: "absolute top-1/2 right-1.5 -translate-y-1/2 rounded p-0.5 hover:bg-muted",
1442
+ "aria-label": "Clear search",
1443
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-3" })
1444
+ }
1445
+ )
1446
+ ] }),
1447
+ /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 rounded px-1 py-1 text-sm hover:bg-accent", children: [
1448
+ /* @__PURE__ */ jsx(
1449
+ Checkbox,
1450
+ {
1451
+ checked: visibleAllSelected ? true : visibleSomeSelected ? "indeterminate" : false,
1452
+ onCheckedChange: (c) => toggleAll(!!c)
1453
+ }
1454
+ ),
1455
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
1456
+ "Select all ",
1457
+ search ? "(filtered)" : ""
1458
+ ] }),
1459
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto text-[10px] text-muted-foreground", children: [
1460
+ draft.length,
1461
+ "/",
1462
+ options.length
1463
+ ] })
1464
+ ] }),
1465
+ /* @__PURE__ */ jsx("div", { className: "flex max-h-60 flex-col gap-0.5 overflow-auto pr-0.5", children: visible.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-xs text-muted-foreground", children: "No matches" }) : visible.map((o) => {
1466
+ const checked = draft.includes(o.value);
1467
+ return /* @__PURE__ */ jsxs(
1468
+ "label",
1469
+ {
1470
+ className: "flex cursor-pointer items-center gap-2 rounded px-1 py-1 text-sm hover:bg-accent",
1471
+ children: [
1472
+ /* @__PURE__ */ jsx(
1473
+ Checkbox,
1474
+ {
1475
+ checked,
1476
+ onCheckedChange: () => toggle(o.value)
1477
+ }
1478
+ ),
1479
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: o.label })
1480
+ ]
1481
+ },
1482
+ o.value
1483
+ );
1484
+ }) }),
1485
+ /* @__PURE__ */ jsx(
1486
+ FooterBar,
1487
+ {
1488
+ onApply: apply,
1489
+ onClear: clear,
1490
+ onClose,
1491
+ clearDisabled: !value
1492
+ }
1493
+ )
1494
+ ] });
1495
+ }
1496
+ function BooleanFilterPanel({
1497
+ column,
1498
+ onClose
1499
+ }) {
1500
+ const value = column.getFilterValue();
1501
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: [
1502
+ { v: void 0, l: "All" },
1503
+ { v: true, l: "True" },
1504
+ { v: false, l: "False" }
1505
+ ].map((o) => /* @__PURE__ */ jsx(
1506
+ "button",
1507
+ {
1508
+ type: "button",
1509
+ onClick: () => {
1510
+ column.setFilterValue(o.v);
1511
+ onClose();
1512
+ },
1513
+ className: cn(
1514
+ "rounded px-2 py-1 text-left text-sm hover:bg-accent",
1515
+ value === o.v && "bg-accent text-accent-foreground"
1516
+ ),
1517
+ children: o.l
1518
+ },
1519
+ String(o.l)
1520
+ )) });
1521
+ }
1522
+ function ConditionRow({
1523
+ ops,
1524
+ c,
1525
+ onChange,
1526
+ renderInput
1527
+ }) {
1528
+ const showInput = !isUnary(c.op);
1529
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1530
+ /* @__PURE__ */ jsx(OpSelect, { ops, value: c.op, onChange: (op) => onChange({ ...c, op }) }),
1531
+ showInput && renderInput(c, onChange)
1532
+ ] });
1533
+ }
1534
+ function SecondCondition({
1535
+ draft,
1536
+ setDraft,
1537
+ ops,
1538
+ renderInput
1539
+ }) {
1540
+ const enabled = !!draft.c2;
1541
+ const c2 = draft.c2;
1542
+ const combine = draft.combine ?? "AND";
1543
+ if (!enabled) {
1544
+ return /* @__PURE__ */ jsx(
1545
+ "button",
1546
+ {
1547
+ type: "button",
1548
+ onClick: () => setDraft((d) => ({
1549
+ ...d,
1550
+ combine: d.combine ?? "AND",
1551
+ c2: { op: ops[0].v }
1552
+ })),
1553
+ className: "self-start text-[11px] font-medium text-primary hover:underline",
1554
+ children: "+ Add condition"
1555
+ }
1556
+ );
1557
+ }
1558
+ const setC2 = (n) => setDraft((d) => ({ ...d, c2: n }));
1559
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1560
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1561
+ /* @__PURE__ */ jsx(
1562
+ CombineToggle,
1563
+ {
1564
+ value: combine,
1565
+ onChange: (combine2) => setDraft((d) => ({ ...d, combine: combine2 }))
1566
+ }
1567
+ ),
1568
+ /* @__PURE__ */ jsx(
1569
+ "button",
1570
+ {
1571
+ type: "button",
1572
+ onClick: () => setDraft((d) => ({ ...d, combine: void 0, c2: void 0 })),
1573
+ className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
1574
+ "aria-label": "Remove second condition",
1575
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
1576
+ }
1577
+ )
1578
+ ] }),
1579
+ /* @__PURE__ */ jsx(
1580
+ ConditionRow,
1581
+ {
1582
+ ops,
1583
+ c: c2,
1584
+ onChange: setC2,
1585
+ renderInput
1586
+ }
1587
+ )
1588
+ ] });
1589
+ }
1590
+ function DataTableColumnHeader({
1591
+ column,
1592
+ title
1593
+ }) {
1594
+ const filterType = column.columnDef.meta?.filterType;
1595
+ const canSort = column.getCanSort();
1596
+ const canFilter = column.getCanFilter() && !!filterType;
1597
+ const canHide = column.getCanHide();
1598
+ const canPin = column.getCanPin();
1599
+ const sorted = column.getIsSorted();
1600
+ const isPinned = column.getIsPinned();
1601
+ const filterValue = column.getFilterValue();
1602
+ const isFiltered = isFilterActive(filterType, filterValue);
1603
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-1", children: [
1604
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1605
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1606
+ Button,
1607
+ {
1608
+ variant: "ghost",
1609
+ size: "xs",
1610
+ className: "-ml-2 h-7 px-1.5 text-xs font-medium data-[state=open]:bg-accent",
1611
+ children: [
1612
+ /* @__PURE__ */ jsx("span", { children: title }),
1613
+ canSort ? sorted === "desc" ? /* @__PURE__ */ jsx(ArrowDownIcon, { className: "opacity-70" }) : sorted === "asc" ? /* @__PURE__ */ jsx(ArrowUpIcon, { className: "opacity-70" }) : /* @__PURE__ */ jsx(ChevronsUpDownIcon, { className: "opacity-50" }) : null
1614
+ ]
1615
+ }
1616
+ ) }),
1617
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", className: "w-44", children: [
1618
+ canSort && /* @__PURE__ */ jsxs(Fragment, { children: [
1619
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => column.toggleSorting(false), children: [
1620
+ /* @__PURE__ */ jsx(ArrowUpIcon, {}),
1621
+ " Ascending"
1622
+ ] }),
1623
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => column.toggleSorting(true), children: [
1624
+ /* @__PURE__ */ jsx(ArrowDownIcon, {}),
1625
+ " Descending"
1626
+ ] }),
1627
+ sorted && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => column.clearSorting(), children: [
1628
+ /* @__PURE__ */ jsx(ChevronsUpDownIcon, {}),
1629
+ " Clear sort"
1630
+ ] }),
1631
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {})
1632
+ ] }),
1633
+ canPin && /* @__PURE__ */ jsxs(Fragment, { children: [
1634
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Pin column" }),
1635
+ /* @__PURE__ */ jsxs(
1636
+ DropdownMenuItem,
1637
+ {
1638
+ onSelect: () => column.pin(isPinned === "left" ? false : "left"),
1639
+ children: [
1640
+ isPinned === "left" ? /* @__PURE__ */ jsx(PinOffIcon, {}) : /* @__PURE__ */ jsx(PinIcon, {}),
1641
+ " ",
1642
+ isPinned === "left" ? "Unpin from left" : "Pin to left"
1643
+ ]
1644
+ }
1645
+ ),
1646
+ /* @__PURE__ */ jsxs(
1647
+ DropdownMenuItem,
1648
+ {
1649
+ onSelect: () => column.pin(isPinned === "right" ? false : "right"),
1650
+ children: [
1651
+ isPinned === "right" ? /* @__PURE__ */ jsx(PinOffIcon, {}) : /* @__PURE__ */ jsx(PinIcon, {}),
1652
+ " ",
1653
+ isPinned === "right" ? "Unpin from right" : "Pin to right"
1654
+ ]
1655
+ }
1656
+ ),
1657
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {})
1658
+ ] }),
1659
+ canHide && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => column.toggleVisibility(false), children: [
1660
+ /* @__PURE__ */ jsx(EyeOffIcon, {}),
1661
+ " Hide column"
1662
+ ] })
1663
+ ] })
1664
+ ] }),
1665
+ canFilter && /* @__PURE__ */ jsx(ColumnFilter, { column, isFiltered })
1666
+ ] });
1667
+ }
1668
+ function ColumnFilter({
1669
+ column,
1670
+ isFiltered
1671
+ }) {
1672
+ const [open, setOpen] = useState(false);
1673
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
1674
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1675
+ Button,
1676
+ {
1677
+ variant: "ghost",
1678
+ size: "icon-xs",
1679
+ className: cn(
1680
+ "size-6",
1681
+ isFiltered && "bg-primary/10 text-primary"
1682
+ ),
1683
+ "aria-label": "Filter column",
1684
+ children: /* @__PURE__ */ jsx(FilterIcon, {})
1685
+ }
1686
+ ) }),
1687
+ /* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "w-72 p-2", children: [
1688
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
1689
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Filter" }),
1690
+ isFiltered && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary", children: "Active" })
1691
+ ] }),
1692
+ /* @__PURE__ */ jsx(ColumnFilterPanel, { column, onClose: () => setOpen(false) })
1693
+ ] })
1694
+ ] });
1695
+ }
1696
+ var builtinIcons = {
1697
+ view: /* @__PURE__ */ jsx(EyeIcon, {}),
1698
+ edit: /* @__PURE__ */ jsx(PencilIcon, {}),
1699
+ duplicate: /* @__PURE__ */ jsx(CopyIcon, {}),
1700
+ delete: /* @__PURE__ */ jsx(Trash2Icon, {})
1701
+ };
1702
+ var builtinLabels = {
1703
+ view: "View",
1704
+ edit: "Edit",
1705
+ duplicate: "Duplicate",
1706
+ delete: "Delete"
1707
+ };
1708
+ function DataTableRowActions({
1709
+ row,
1710
+ actions = ["view", "edit", "duplicate", "delete"],
1711
+ customActions = [],
1712
+ onAction,
1713
+ isEditing,
1714
+ onSaveEdit,
1715
+ onCancelEdit
1716
+ }) {
1717
+ const [open, setOpen] = useState(false);
1718
+ const menuRef = useRef(null);
1719
+ useEffect(() => {
1720
+ if (!open) return;
1721
+ const onPointerDown = (event) => {
1722
+ if (!menuRef.current?.contains(event.target)) setOpen(false);
1723
+ };
1724
+ document.addEventListener("pointerdown", onPointerDown);
1725
+ return () => document.removeEventListener("pointerdown", onPointerDown);
1726
+ }, [open]);
1727
+ if (isEditing) {
1728
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1", children: [
1729
+ /* @__PURE__ */ jsx(
1730
+ Button,
1731
+ {
1732
+ size: "icon-xs",
1733
+ variant: "ghost",
1734
+ onClick: onSaveEdit,
1735
+ "aria-label": "Save row",
1736
+ className: "text-emerald-600 hover:text-emerald-600",
1737
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
1738
+ }
1739
+ ),
1740
+ /* @__PURE__ */ jsx(
1741
+ Button,
1742
+ {
1743
+ size: "icon-xs",
1744
+ variant: "ghost",
1745
+ onClick: onCancelEdit,
1746
+ "aria-label": "Cancel edit",
1747
+ children: /* @__PURE__ */ jsx(XIcon, {})
1748
+ }
1749
+ )
1750
+ ] });
1751
+ }
1752
+ return /* @__PURE__ */ jsxs("div", { ref: menuRef, className: "relative flex justify-end", children: [
1753
+ /* @__PURE__ */ jsx(
1754
+ Button,
1755
+ {
1756
+ variant: "ghost",
1757
+ size: "icon-xs",
1758
+ "aria-label": "Open row actions",
1759
+ "aria-expanded": open,
1760
+ onClick: () => setOpen((value) => !value),
1761
+ children: /* @__PURE__ */ jsx(MoreHorizontalIcon, {})
1762
+ }
1763
+ ),
1764
+ open && /* @__PURE__ */ jsxs("div", { className: "absolute top-7 right-0 z-50 w-40 rounded-md border bg-popover p-1 text-popover-foreground shadow-md", children: [
1765
+ actions.map((a) => /* @__PURE__ */ jsxs(
1766
+ RowActionButton,
1767
+ {
1768
+ danger: a === "delete",
1769
+ onClick: () => {
1770
+ setOpen(false);
1771
+ onAction?.(a, row);
1772
+ },
1773
+ children: [
1774
+ builtinIcons[a],
1775
+ " ",
1776
+ builtinLabels[a]
1777
+ ]
1778
+ },
1779
+ a
1780
+ )),
1781
+ customActions.length > 0 && actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "-mx-1 my-1 h-px bg-border" }),
1782
+ customActions.filter((c) => c.show ? c.show(row) : true).map((c) => /* @__PURE__ */ jsxs(
1783
+ RowActionButton,
1784
+ {
1785
+ danger: c.danger,
1786
+ onClick: () => {
1787
+ setOpen(false);
1788
+ onAction?.(c.id, row);
1789
+ },
1790
+ children: [
1791
+ c.icon,
1792
+ " ",
1793
+ c.label
1794
+ ]
1795
+ },
1796
+ c.id
1797
+ ))
1798
+ ] })
1799
+ ] });
1800
+ }
1801
+ function RowActionButton({
1802
+ children,
1803
+ danger,
1804
+ onClick
1805
+ }) {
1806
+ return /* @__PURE__ */ jsx(
1807
+ "button",
1808
+ {
1809
+ type: "button",
1810
+ onClick,
1811
+ className: cn(
1812
+ "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent",
1813
+ danger && "text-destructive hover:bg-destructive/10 hover:text-destructive"
1814
+ ),
1815
+ children
1816
+ }
1817
+ );
1818
+ }
1819
+ function EditableCell({
1820
+ getValue,
1821
+ column,
1822
+ row,
1823
+ isEditing,
1824
+ autoOpen,
1825
+ onCommit,
1826
+ onCancel,
1827
+ onStartEdit
1828
+ }) {
1829
+ const meta = column.columnDef.meta;
1830
+ const editor = meta?.editor ?? "text";
1831
+ const align = meta?.align ?? "left";
1832
+ const initial = getValue();
1833
+ const [local, setLocal] = useState(initial);
1834
+ const inputRef = useRef(null);
1835
+ useEffect(() => {
1836
+ setLocal(initial);
1837
+ }, [initial, isEditing]);
1838
+ useEffect(() => {
1839
+ if (isEditing && inputRef.current) inputRef.current.focus();
1840
+ }, [isEditing]);
1841
+ const isEditable = (() => {
1842
+ const flag = meta?.isEditable;
1843
+ if (flag === void 0) return true;
1844
+ if (typeof flag === "function") return flag(row.original);
1845
+ return flag;
1846
+ })();
1847
+ if (!isEditing) {
1848
+ const display = renderDisplay(initial, editor, meta);
1849
+ return /* @__PURE__ */ jsx(
1850
+ "div",
1851
+ {
1852
+ className: cn(
1853
+ "flex h-7 items-center gap-1.5 rounded px-1.5 text-sm",
1854
+ isEditable && "cursor-text outline-1 outline-transparent transition-colors hover:bg-muted/60 hover:outline-border",
1855
+ align === "right" && "justify-end",
1856
+ align === "center" && "justify-center"
1857
+ ),
1858
+ onDoubleClick: () => isEditable && onStartEdit(),
1859
+ title: isEditable ? "Double-click to edit" : void 0,
1860
+ children: display
1861
+ }
1862
+ );
1863
+ }
1864
+ const commit = (value) => onCommit(value);
1865
+ const handleKey = (e) => {
1866
+ if (e.key === "Enter") commit(local);
1867
+ else if (e.key === "Escape") onCancel();
1868
+ };
1869
+ if (editor === "select") {
1870
+ const opts = meta?.selectOptions ?? [];
1871
+ if (!autoOpen) {
1872
+ return /* @__PURE__ */ jsx(
1873
+ "select",
1874
+ {
1875
+ value: String(local ?? ""),
1876
+ onChange: (e) => {
1877
+ setLocal(e.target.value);
1878
+ commit(e.target.value);
1879
+ },
1880
+ className: cn(
1881
+ "h-7 w-full rounded-md border border-input bg-background px-1.5 text-xs outline-none focus-visible:border-ring",
1882
+ align === "right" && "text-right",
1883
+ align === "center" && "text-center"
1884
+ ),
1885
+ children: opts.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
1886
+ }
1887
+ );
1888
+ }
1889
+ return /* @__PURE__ */ jsxs(
1890
+ Select,
1891
+ {
1892
+ defaultOpen: autoOpen,
1893
+ value: String(local ?? ""),
1894
+ onValueChange: (v) => commit(v),
1895
+ children: [
1896
+ /* @__PURE__ */ jsx(SelectTrigger, { size: "sm", className: "h-7 w-full text-xs", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1897
+ /* @__PURE__ */ jsx(SelectContent, { children: opts.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value)) })
1898
+ ]
1899
+ }
1900
+ );
1901
+ }
1902
+ if (editor === "checkbox" || editor === "switch") {
1903
+ return /* @__PURE__ */ jsx("div", { className: "flex h-7 items-center justify-center", children: /* @__PURE__ */ jsx(
1904
+ Checkbox,
1905
+ {
1906
+ checked: !!local,
1907
+ onCheckedChange: (c) => commit(!!c),
1908
+ autoFocus: true
1909
+ }
1910
+ ) });
1911
+ }
1912
+ return /* @__PURE__ */ jsx(
1913
+ Input,
1914
+ {
1915
+ ref: inputRef,
1916
+ type: editor === "number" || editor === "currency" ? "number" : editor === "date" ? "date" : "text",
1917
+ value: local == null ? "" : String(local),
1918
+ onChange: (e) => {
1919
+ if (editor === "number" || editor === "currency") {
1920
+ setLocal(e.target.value === "" ? null : Number(e.target.value));
1921
+ } else {
1922
+ setLocal(e.target.value);
1923
+ }
1924
+ },
1925
+ onBlur: () => commit(local),
1926
+ onKeyDown: handleKey,
1927
+ className: cn(
1928
+ "h-7 px-1.5 text-sm",
1929
+ align === "right" && "text-right",
1930
+ align === "center" && "text-center"
1931
+ )
1932
+ }
1933
+ );
1934
+ }
1935
+ function renderDisplay(value, editor, meta) {
1936
+ if (value === null || value === void 0 || value === "") {
1937
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60", children: "\u2014" });
1938
+ }
1939
+ if (editor === "currency") {
1940
+ const n = typeof value === "number" ? value : Number(value);
1941
+ return Number.isFinite(n) ? new Intl.NumberFormat(void 0, {
1942
+ style: "currency",
1943
+ currency: "USD",
1944
+ maximumFractionDigits: 2
1945
+ }).format(n) : String(value);
1946
+ }
1947
+ if (editor === "number") {
1948
+ const n = typeof value === "number" ? value : Number(value);
1949
+ return Number.isFinite(n) ? new Intl.NumberFormat().format(n) : String(value);
1950
+ }
1951
+ if (editor === "checkbox" || editor === "switch") {
1952
+ return /* @__PURE__ */ jsx(Checkbox, { checked: !!value, disabled: true, "aria-label": "value" });
1953
+ }
1954
+ if (editor === "select") {
1955
+ const opt = meta?.selectOptions?.find((o) => o.value === value);
1956
+ const label = opt?.label ?? String(value);
1957
+ if (meta?.badgeMap) {
1958
+ const variant = meta.badgeMap[String(value)] ?? "default";
1959
+ return /* @__PURE__ */ jsx(Badge, { variant, children: label });
1960
+ }
1961
+ return label;
1962
+ }
1963
+ if (editor === "date") {
1964
+ if (value instanceof Date) return value.toLocaleDateString();
1965
+ return String(value);
1966
+ }
1967
+ return String(value);
1968
+ }
1969
+
1970
+ // src/theme.ts
1971
+ var TOKEN_TO_VAR = {
1972
+ background: "--background",
1973
+ foreground: "--foreground",
1974
+ card: "--card",
1975
+ cardForeground: "--card-foreground",
1976
+ popover: "--popover",
1977
+ popoverForeground: "--popover-foreground",
1978
+ primary: "--primary",
1979
+ primaryForeground: "--primary-foreground",
1980
+ secondary: "--secondary",
1981
+ secondaryForeground: "--secondary-foreground",
1982
+ muted: "--muted",
1983
+ mutedForeground: "--muted-foreground",
1984
+ accent: "--accent",
1985
+ accentForeground: "--accent-foreground",
1986
+ destructive: "--destructive",
1987
+ destructiveForeground: "--destructive-foreground",
1988
+ border: "--border",
1989
+ input: "--input",
1990
+ ring: "--ring",
1991
+ radius: "--radius",
1992
+ fontFamily: "--font-family"
1993
+ };
1994
+ function themeToStyle(theme) {
1995
+ if (!theme) return {};
1996
+ const out = {};
1997
+ for (const [k, v] of Object.entries(theme)) {
1998
+ if (v == null) continue;
1999
+ const cssVar = TOKEN_TO_VAR[k];
2000
+ if (cssVar) out[cssVar] = v;
2001
+ }
2002
+ return out;
2003
+ }
2004
+ var themePresets = {
2005
+ light: {
2006
+ background: "oklch(1 0 0)",
2007
+ foreground: "oklch(0.145 0 0)",
2008
+ card: "oklch(1 0 0)",
2009
+ cardForeground: "oklch(0.145 0 0)",
2010
+ primary: "oklch(0.205 0 0)",
2011
+ primaryForeground: "oklch(0.985 0 0)",
2012
+ border: "oklch(0.922 0 0)"
2013
+ },
2014
+ dark: {
2015
+ background: "oklch(0.145 0 0)",
2016
+ foreground: "oklch(0.985 0 0)",
2017
+ card: "oklch(0.205 0 0)",
2018
+ cardForeground: "oklch(0.985 0 0)",
2019
+ primary: "oklch(0.985 0 0)",
2020
+ primaryForeground: "oklch(0.205 0 0)",
2021
+ border: "oklch(0.269 0 0)"
2022
+ },
2023
+ emerald: {
2024
+ primary: "oklch(0.66 0.15 162)",
2025
+ primaryForeground: "oklch(1 0 0)",
2026
+ accent: "oklch(0.94 0.04 162)",
2027
+ accentForeground: "oklch(0.3 0.1 162)",
2028
+ ring: "oklch(0.66 0.15 162 / 0.5)"
2029
+ },
2030
+ violet: {
2031
+ primary: "oklch(0.55 0.22 285)",
2032
+ primaryForeground: "oklch(1 0 0)",
2033
+ accent: "oklch(0.94 0.04 285)",
2034
+ accentForeground: "oklch(0.3 0.12 285)",
2035
+ ring: "oklch(0.55 0.22 285 / 0.5)"
2036
+ },
2037
+ amber: {
2038
+ primary: "oklch(0.7 0.18 65)",
2039
+ primaryForeground: "oklch(0.2 0 0)",
2040
+ accent: "oklch(0.94 0.05 65)",
2041
+ accentForeground: "oklch(0.3 0.12 65)",
2042
+ ring: "oklch(0.7 0.18 65 / 0.5)"
2043
+ }
2044
+ };
2045
+ function getPinningStyles(column) {
2046
+ const isPinned = column.getIsPinned();
2047
+ if (!isPinned) return {};
2048
+ const isLast = isPinned === "left" && column.getIsLastColumn("left");
2049
+ const isFirst = isPinned === "right" && column.getIsFirstColumn("right");
2050
+ return {
2051
+ position: "sticky",
2052
+ left: isPinned === "left" ? `${column.getStart("left")}px` : void 0,
2053
+ right: isPinned === "right" ? `${column.getAfter("right")}px` : void 0,
2054
+ zIndex: 1,
2055
+ boxShadow: isLast ? "1px 0 0 0 var(--border)" : isFirst ? "-1px 0 0 0 var(--border)" : void 0
2056
+ };
2057
+ }
2058
+ var DataTableEditingContext = createContext(
2059
+ null
2060
+ );
2061
+ function useEditingContext() {
2062
+ const ctx = useContext(DataTableEditingContext);
2063
+ if (!ctx) throw new Error("DataTable editing context missing");
2064
+ return ctx;
2065
+ }
2066
+ function EditableCellHost(ctx) {
2067
+ const {
2068
+ editingCell,
2069
+ editingRowId,
2070
+ setEditingCell,
2071
+ setRowDraft,
2072
+ onCellEdit
2073
+ } = useEditingContext();
2074
+ const colId = ctx.column.id;
2075
+ const rowId = ctx.row.id;
2076
+ const isCellEdit = editingCell?.rowId === rowId && editingCell.colId === colId;
2077
+ const isRowEdit = editingRowId === rowId;
2078
+ const isEditing = isCellEdit || isRowEdit;
2079
+ return /* @__PURE__ */ jsx(
2080
+ EditableCell,
2081
+ {
2082
+ ...ctx,
2083
+ isEditing,
2084
+ autoOpen: isCellEdit,
2085
+ onStartEdit: () => setEditingCell({ rowId, colId }),
2086
+ onCancel: () => {
2087
+ if (isCellEdit) setEditingCell(null);
2088
+ },
2089
+ onCommit: (value) => {
2090
+ if (isRowEdit) {
2091
+ setRowDraft((d) => ({
2092
+ ...d,
2093
+ [rowId]: {
2094
+ ...d[rowId],
2095
+ [colId]: value
2096
+ }
2097
+ }));
2098
+ return;
2099
+ }
2100
+ setEditingCell(null);
2101
+ if (value !== ctx.getValue()) {
2102
+ onCellEdit?.(ctx.row.original, colId, value);
2103
+ }
2104
+ }
2105
+ }
2106
+ );
2107
+ }
2108
+ function ActionsCellHost({ row }) {
2109
+ const {
2110
+ editingRowId,
2111
+ setEditingRowId,
2112
+ rowDraft,
2113
+ setRowDraft,
2114
+ rowActions,
2115
+ customRowActions,
2116
+ onRowAction,
2117
+ onRowSave
2118
+ } = useEditingContext();
2119
+ const rowId = row.id;
2120
+ const isRowEdit = editingRowId === rowId;
2121
+ return /* @__PURE__ */ jsx(
2122
+ DataTableRowActions,
2123
+ {
2124
+ row: row.original,
2125
+ actions: rowActions,
2126
+ customActions: customRowActions,
2127
+ isEditing: isRowEdit,
2128
+ onSaveEdit: () => {
2129
+ onRowSave?.(row.original, rowDraft[rowId] ?? {});
2130
+ setRowDraft((d) => {
2131
+ const { [rowId]: _, ...rest } = d;
2132
+ return rest;
2133
+ });
2134
+ setEditingRowId(null);
2135
+ },
2136
+ onCancelEdit: () => {
2137
+ setRowDraft((d) => {
2138
+ const { [rowId]: _, ...rest } = d;
2139
+ return rest;
2140
+ });
2141
+ setEditingRowId(null);
2142
+ },
2143
+ onAction: (a) => {
2144
+ if (a === "edit") {
2145
+ setEditingRowId(rowId);
2146
+ return;
2147
+ }
2148
+ onRowAction?.(a, row.original);
2149
+ }
2150
+ }
2151
+ );
2152
+ }
2153
+ var DEFAULT_LABELS = {
2154
+ search: "Search...",
2155
+ addRow: "Add row",
2156
+ delete: "Delete",
2157
+ clear: "Clear",
2158
+ selected: "selected",
2159
+ refresh: "Refresh",
2160
+ columns: "Columns",
2161
+ export: "Export",
2162
+ csv: "CSV",
2163
+ excel: "Excel",
2164
+ total: "Total",
2165
+ noData: "No data.",
2166
+ noResults: "No rows match the current filters.",
2167
+ refreshing: "Refreshing",
2168
+ rowsPerPage: "Rows per page",
2169
+ page: "Page",
2170
+ of: "of"
2171
+ };
2172
+ var DENSITY_CLASS = {
2173
+ compact: "[&_tbody_td]:py-0.5 [&_tbody_td]:text-xs",
2174
+ default: "",
2175
+ comfortable: "[&_tbody_td]:py-2"
2176
+ };
2177
+ function DataTable({
2178
+ data,
2179
+ columns,
2180
+ isLoading,
2181
+ isFetching,
2182
+ onRefresh,
2183
+ totalRecords,
2184
+ exportFileName,
2185
+ enableSelection = true,
2186
+ renderSubRow,
2187
+ getSubRows,
2188
+ rowActions = ["view", "edit", "duplicate", "delete"],
2189
+ customRowActions,
2190
+ onRowAction,
2191
+ onCellEdit,
2192
+ onRowSave,
2193
+ onAddRow,
2194
+ onBulkDelete,
2195
+ initialPageSize = 10,
2196
+ pageSizeOptions,
2197
+ initialColumnPinning,
2198
+ initialColumnVisibility,
2199
+ globalFilter: globalFilterProp,
2200
+ onGlobalFilterChange,
2201
+ className,
2202
+ toolbarSlot,
2203
+ features,
2204
+ labels: labelsProp,
2205
+ density = "default",
2206
+ theme
2207
+ }) {
2208
+ const labels = { ...DEFAULT_LABELS, ...labelsProp };
2209
+ const f = {
2210
+ search: features?.search ?? true,
2211
+ refresh: features?.refresh ?? !!onRefresh,
2212
+ columnVisibility: features?.columnVisibility ?? true,
2213
+ export: features?.export ?? true,
2214
+ addRow: features?.addRow ?? !!onAddRow,
2215
+ pagination: features?.pagination ?? true,
2216
+ sorting: features?.sorting ?? true,
2217
+ filtering: features?.filtering ?? true,
2218
+ resizing: features?.resizing ?? true,
2219
+ reordering: features?.reordering ?? true,
2220
+ pinning: features?.pinning ?? true
2221
+ };
2222
+ const themeStyle = themeToStyle(theme);
2223
+ const [sorting, setSorting] = useState([]);
2224
+ const [columnFilters, setColumnFilters] = useState([]);
2225
+ const [internalGlobalFilter, setInternalGlobalFilter] = useState("");
2226
+ const isControlledFilter = globalFilterProp !== void 0;
2227
+ const globalFilter = isControlledFilter ? globalFilterProp : internalGlobalFilter;
2228
+ const setGlobalFilter = (v) => {
2229
+ if (isControlledFilter) onGlobalFilterChange?.(v);
2230
+ else setInternalGlobalFilter(v);
2231
+ };
2232
+ const [columnVisibility, setColumnVisibility] = useState(
2233
+ initialColumnVisibility ?? {}
2234
+ );
2235
+ const [columnPinning, setColumnPinning] = useState(
2236
+ () => {
2237
+ const base = initialColumnPinning ?? { left: [], right: [] };
2238
+ const left = [...base.left ?? []];
2239
+ const right = [...base.right ?? []];
2240
+ const hasExpandColumn = !!(renderSubRow || getSubRows);
2241
+ if (hasExpandColumn && left.includes("__select") && !left.includes("__expand")) {
2242
+ const idx = left.indexOf("__select");
2243
+ left.splice(idx + 1, 0, "__expand");
2244
+ }
2245
+ return { left, right };
2246
+ }
2247
+ );
2248
+ const [columnSizing, setColumnSizing] = useState({});
2249
+ const [rowSelection, setRowSelection] = useState({});
2250
+ const [expanded, setExpanded] = useState({});
2251
+ const [pagination, setPagination] = useState({
2252
+ pageIndex: 0,
2253
+ pageSize: initialPageSize
2254
+ });
2255
+ const [editingCell, setEditingCell] = useState(null);
2256
+ const [editingRowId, setEditingRowId] = useState(null);
2257
+ const [rowDraft, setRowDraft] = useState({});
2258
+ const [extraRows, setExtraRows] = useState([]);
2259
+ const mergedData = useMemo(
2260
+ () => extraRows.length ? [...extraRows, ...data] : data,
2261
+ [extraRows, data]
2262
+ );
2263
+ const augmentedColumns = useMemo(() => {
2264
+ const wrap = (col) => {
2265
+ const meta = col.meta;
2266
+ const hasEditor = !!meta?.editor;
2267
+ const id = col.id ?? col.accessorKey;
2268
+ const original = col.cell;
2269
+ const headerLabel = meta?.label ?? (typeof col.header === "string" ? col.header : id ?? "");
2270
+ const defaultFilterFn = (() => {
2271
+ switch (meta?.filterType) {
2272
+ case "text":
2273
+ return textFilterFn;
2274
+ case "number":
2275
+ return numberFilterFn;
2276
+ case "date":
2277
+ return dateFilterFn;
2278
+ case "select":
2279
+ case "multi-select":
2280
+ return setFilterFn;
2281
+ case "boolean":
2282
+ return booleanFilterFn;
2283
+ default:
2284
+ return void 0;
2285
+ }
2286
+ })();
2287
+ return {
2288
+ ...col,
2289
+ filterFn: col.filterFn ?? defaultFilterFn,
2290
+ header: typeof col.header === "function" ? col.header : (ctx) => /* @__PURE__ */ jsx(
2291
+ DataTableColumnHeader,
2292
+ {
2293
+ column: ctx.column,
2294
+ title: headerLabel
2295
+ }
2296
+ ),
2297
+ cell: hasEditor ? EditableCellHost : original
2298
+ };
2299
+ };
2300
+ const built = columns.map(wrap);
2301
+ if (renderSubRow || getSubRows) {
2302
+ built.unshift({
2303
+ id: "__expand",
2304
+ size: 32,
2305
+ enableSorting: false,
2306
+ enableHiding: false,
2307
+ enableResizing: false,
2308
+ header: () => null,
2309
+ cell: ({ row }) => row.getCanExpand() ? /* @__PURE__ */ jsx(
2310
+ "button",
2311
+ {
2312
+ type: "button",
2313
+ onClick: row.getToggleExpandedHandler(),
2314
+ className: "flex size-6 items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground",
2315
+ "aria-label": row.getIsExpanded() ? "Collapse row" : "Expand row",
2316
+ children: row.getIsExpanded() ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
2317
+ }
2318
+ ) : null
2319
+ });
2320
+ }
2321
+ if (enableSelection) {
2322
+ built.unshift({
2323
+ id: "__select",
2324
+ size: 36,
2325
+ enableSorting: false,
2326
+ enableHiding: false,
2327
+ enableResizing: false,
2328
+ header: ({ table: table2 }) => /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center px-1", children: /* @__PURE__ */ jsx(
2329
+ Checkbox,
2330
+ {
2331
+ checked: table2.getIsAllPageRowsSelected() ? true : table2.getIsSomePageRowsSelected() ? "indeterminate" : false,
2332
+ onCheckedChange: (c) => table2.toggleAllPageRowsSelected(!!c),
2333
+ "aria-label": "Select all rows on this page"
2334
+ }
2335
+ ) }),
2336
+ cell: ({ row }) => /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center px-1", children: /* @__PURE__ */ jsx(
2337
+ Checkbox,
2338
+ {
2339
+ checked: row.getIsSelected(),
2340
+ onCheckedChange: (c) => row.toggleSelected(!!c),
2341
+ "aria-label": "Select row"
2342
+ }
2343
+ ) })
2344
+ });
2345
+ }
2346
+ if (rowActions.length || customRowActions?.length) {
2347
+ built.push({
2348
+ id: "__actions",
2349
+ size: 56,
2350
+ enableSorting: false,
2351
+ enableHiding: false,
2352
+ enableResizing: false,
2353
+ header: () => /* @__PURE__ */ jsx("div", { className: "text-right text-xs", children: "Actions" }),
2354
+ cell: ActionsCellHost
2355
+ });
2356
+ }
2357
+ return built;
2358
+ }, [
2359
+ columns,
2360
+ enableSelection,
2361
+ renderSubRow,
2362
+ getSubRows,
2363
+ rowActions.length,
2364
+ customRowActions?.length
2365
+ ]);
2366
+ const [columnOrder, setColumnOrder] = useState(
2367
+ () => augmentedColumns.map((c) => c.id ?? c.accessorKey ?? "")
2368
+ );
2369
+ useEffect(() => {
2370
+ const next = augmentedColumns.map(
2371
+ (c) => c.id ?? c.accessorKey ?? ""
2372
+ );
2373
+ setColumnOrder((prev) => {
2374
+ const existing = prev.filter((id) => next.includes(id));
2375
+ const added = next.filter((id) => !prev.includes(id));
2376
+ const final = [...existing, ...added];
2377
+ return final.length === prev.length && final.every((v, i) => prev[i] === v) ? prev : final;
2378
+ });
2379
+ }, [augmentedColumns]);
2380
+ const table = useReactTable({
2381
+ data: mergedData,
2382
+ columns: augmentedColumns,
2383
+ state: {
2384
+ sorting,
2385
+ columnFilters,
2386
+ globalFilter,
2387
+ columnVisibility,
2388
+ columnPinning,
2389
+ columnSizing,
2390
+ columnOrder,
2391
+ rowSelection,
2392
+ expanded,
2393
+ pagination
2394
+ },
2395
+ onSortingChange: setSorting,
2396
+ onColumnFiltersChange: setColumnFilters,
2397
+ onGlobalFilterChange: setGlobalFilter,
2398
+ onColumnVisibilityChange: setColumnVisibility,
2399
+ onColumnPinningChange: setColumnPinning,
2400
+ onColumnSizingChange: setColumnSizing,
2401
+ onColumnOrderChange: setColumnOrder,
2402
+ onRowSelectionChange: setRowSelection,
2403
+ onExpandedChange: setExpanded,
2404
+ onPaginationChange: setPagination,
2405
+ enableSorting: f.sorting,
2406
+ enableColumnFilters: f.filtering,
2407
+ enableColumnResizing: f.resizing,
2408
+ columnResizeMode: "onChange",
2409
+ enableRowSelection: true,
2410
+ enableExpanding: !!(renderSubRow || getSubRows),
2411
+ enablePinning: f.pinning,
2412
+ getRowId: (row) => String(row.id),
2413
+ getSubRows,
2414
+ getRowCanExpand: (row) => {
2415
+ if (renderSubRow) return true;
2416
+ const subs = getSubRows?.(row.original);
2417
+ return !!subs && subs.length > 0;
2418
+ },
2419
+ globalFilterFn: (row, _columnId, value) => {
2420
+ const q = String(value ?? "").toLowerCase().trim();
2421
+ if (!q) return true;
2422
+ return row.getAllCells().some((cell) => {
2423
+ const v = cell.getValue();
2424
+ return v != null && String(v).toLowerCase().includes(q);
2425
+ });
2426
+ },
2427
+ filterFns: {
2428
+ // attached via columnDef.filterFn below where needed
2429
+ },
2430
+ getCoreRowModel: getCoreRowModel(),
2431
+ getFilteredRowModel: getFilteredRowModel(),
2432
+ getSortedRowModel: getSortedRowModel(),
2433
+ getExpandedRowModel: getExpandedRowModel(),
2434
+ getPaginationRowModel: getPaginationRowModel()
2435
+ });
2436
+ const dragState = useRef({ src: null });
2437
+ const onHeaderDragStart = (id, e) => {
2438
+ dragState.current.src = id;
2439
+ e.dataTransfer.effectAllowed = "move";
2440
+ e.dataTransfer.setData("text/plain", id);
2441
+ };
2442
+ const onHeaderDragOver = (e) => {
2443
+ e.preventDefault();
2444
+ e.dataTransfer.dropEffect = "move";
2445
+ };
2446
+ const onHeaderDrop = (targetId) => {
2447
+ const src = dragState.current.src;
2448
+ dragState.current.src = null;
2449
+ if (!src || src === targetId) return;
2450
+ setColumnOrder((order) => {
2451
+ const next = [...order];
2452
+ const from = next.indexOf(src);
2453
+ const to = next.indexOf(targetId);
2454
+ if (from === -1 || to === -1) return order;
2455
+ next.splice(from, 1);
2456
+ next.splice(to, 0, src);
2457
+ return next;
2458
+ });
2459
+ };
2460
+ const handleAddRow = onAddRow ? () => {
2461
+ const draft = onAddRow();
2462
+ const tempId = `__new_${Date.now()}`;
2463
+ const newRow = { id: tempId, ...draft };
2464
+ setExtraRows((r) => [newRow, ...r]);
2465
+ setPagination((p) => ({ ...p, pageIndex: 0 }));
2466
+ setEditingRowId(tempId);
2467
+ } : void 0;
2468
+ const rows = table.getRowModel().rows;
2469
+ const visibleLeafCount = table.getVisibleLeafColumns().length;
2470
+ const editingContextValue = useMemo(
2471
+ () => ({
2472
+ editingCell,
2473
+ editingRowId,
2474
+ rowDraft,
2475
+ setEditingCell,
2476
+ setEditingRowId,
2477
+ setRowDraft,
2478
+ rowActions,
2479
+ customRowActions,
2480
+ onRowAction,
2481
+ onCellEdit,
2482
+ onRowSave
2483
+ }),
2484
+ [
2485
+ editingCell,
2486
+ editingRowId,
2487
+ rowDraft,
2488
+ rowActions,
2489
+ customRowActions,
2490
+ onRowAction,
2491
+ onCellEdit,
2492
+ onRowSave
2493
+ ]
2494
+ );
2495
+ return /* @__PURE__ */ jsx(
2496
+ DataTableEditingContext.Provider,
2497
+ {
2498
+ value: editingContextValue,
2499
+ children: /* @__PURE__ */ jsxs(
2500
+ "div",
2501
+ {
2502
+ className: cn(
2503
+ "flex flex-col rounded-lg border bg-card text-card-foreground shadow-sm",
2504
+ DENSITY_CLASS[density],
2505
+ className
2506
+ ),
2507
+ style: themeStyle,
2508
+ children: [
2509
+ /* @__PURE__ */ jsx(
2510
+ DataTableToolbar,
2511
+ {
2512
+ table,
2513
+ totalRecords: totalRecords ?? mergedData.length,
2514
+ isFetching,
2515
+ onRefresh: f.refresh ? onRefresh : void 0,
2516
+ onAddRow: f.addRow ? handleAddRow : void 0,
2517
+ onBulkDelete,
2518
+ exportFileName,
2519
+ globalFilter,
2520
+ onGlobalFilterChange: (v) => {
2521
+ setGlobalFilter(v);
2522
+ table.setPageIndex(0);
2523
+ },
2524
+ toolbarSlot,
2525
+ features: f,
2526
+ labels
2527
+ }
2528
+ ),
2529
+ /* @__PURE__ */ jsxs("div", { className: "relative w-full overflow-auto", children: [
2530
+ /* @__PURE__ */ jsxs(
2531
+ "table",
2532
+ {
2533
+ className: "w-full caption-bottom text-sm",
2534
+ style: {
2535
+ width: table.getCenterTotalSize() ? void 0 : "100%",
2536
+ minWidth: "100%"
2537
+ },
2538
+ children: [
2539
+ /* @__PURE__ */ jsx("thead", { className: "sticky top-0 z-10 bg-card/95 backdrop-blur supports-[backdrop-filter]:bg-card/70", children: table.getHeaderGroups().map((hg) => /* @__PURE__ */ jsx("tr", { className: "border-b", children: hg.headers.map((header) => {
2540
+ const meta = header.column.columnDef.meta;
2541
+ const isUtility = header.column.id === "__select" || header.column.id === "__expand" || header.column.id === "__actions";
2542
+ const isPinned = !!header.column.getIsPinned();
2543
+ return /* @__PURE__ */ jsxs(
2544
+ "th",
2545
+ {
2546
+ colSpan: header.colSpan,
2547
+ style: {
2548
+ width: header.getSize(),
2549
+ ...getPinningStyles(header.column),
2550
+ ...isPinned ? { zIndex: 2 } : {}
2551
+ },
2552
+ className: cn(
2553
+ "group/th relative h-9 px-2 text-left align-middle text-xs font-medium text-muted-foreground",
2554
+ isPinned && "bg-card",
2555
+ meta?.headerClassName
2556
+ ),
2557
+ children: [
2558
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2559
+ !isUtility && f.reordering && /* @__PURE__ */ jsx(
2560
+ "span",
2561
+ {
2562
+ draggable: true,
2563
+ onDragStart: (e) => onHeaderDragStart(header.column.id, e),
2564
+ onDragOver: onHeaderDragOver,
2565
+ onDrop: () => onHeaderDrop(header.column.id),
2566
+ className: "-ml-1 cursor-grab rounded p-0.5 text-muted-foreground/40 opacity-0 transition group-hover/th:opacity-100 hover:bg-muted hover:text-foreground active:cursor-grabbing",
2567
+ title: "Drag to reorder",
2568
+ children: /* @__PURE__ */ jsx(GripVerticalIcon, { className: "size-3" })
2569
+ }
2570
+ ),
2571
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) })
2572
+ ] }),
2573
+ header.column.getCanResize() && /* @__PURE__ */ jsx(
2574
+ "span",
2575
+ {
2576
+ onMouseDown: header.getResizeHandler(),
2577
+ onTouchStart: header.getResizeHandler(),
2578
+ onDoubleClick: () => header.column.resetSize(),
2579
+ className: cn(
2580
+ "absolute top-0 right-0 h-full w-1 cursor-col-resize touch-none select-none bg-transparent transition-colors hover:bg-primary/40",
2581
+ header.column.getIsResizing() && "bg-primary"
2582
+ )
2583
+ }
2584
+ )
2585
+ ]
2586
+ },
2587
+ header.id
2588
+ );
2589
+ }) }, hg.id)) }),
2590
+ /* @__PURE__ */ jsx("tbody", { children: isLoading ? renderSkeleton(visibleLeafCount, pagination.pageSize) : rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
2591
+ "td",
2592
+ {
2593
+ colSpan: visibleLeafCount,
2594
+ className: "h-32 text-center text-sm text-muted-foreground",
2595
+ children: globalFilter || columnFilters.length ? labels.noResults : labels.noData
2596
+ }
2597
+ ) }) : rows.map((row) => /* @__PURE__ */ jsx(
2598
+ DataTableRow,
2599
+ {
2600
+ row,
2601
+ renderSubRow,
2602
+ isRowEditing: editingRowId === row.id
2603
+ },
2604
+ row.id
2605
+ )) })
2606
+ ]
2607
+ }
2608
+ ),
2609
+ isFetching && !isLoading && /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute top-1.5 right-2 flex items-center gap-1.5 rounded-md bg-background/80 px-2 py-1 text-xs text-muted-foreground shadow-sm backdrop-blur", children: [
2610
+ /* @__PURE__ */ jsx(Loader2Icon, { className: "size-3 animate-spin" }),
2611
+ " ",
2612
+ labels.refreshing
2613
+ ] })
2614
+ ] }),
2615
+ f.pagination && /* @__PURE__ */ jsx(DataTablePagination, { table, pageSizeOptions })
2616
+ ]
2617
+ }
2618
+ )
2619
+ }
2620
+ );
2621
+ }
2622
+ function DataTableRow({
2623
+ row,
2624
+ renderSubRow,
2625
+ isRowEditing
2626
+ }) {
2627
+ const expanded = row.getIsExpanded();
2628
+ const isSelected = row.getIsSelected();
2629
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2630
+ /* @__PURE__ */ jsx(
2631
+ "tr",
2632
+ {
2633
+ "data-state": isSelected ? "selected" : void 0,
2634
+ className: cn(
2635
+ "group/row border-b transition-colors hover:bg-muted/40 data-[state=selected]:bg-primary/5",
2636
+ isRowEditing && "bg-amber-500/5",
2637
+ row.depth > 0 && "bg-muted/20"
2638
+ ),
2639
+ children: row.getVisibleCells().map((cell) => {
2640
+ const meta = cell.column.columnDef.meta;
2641
+ const isPinned = !!cell.column.getIsPinned();
2642
+ return /* @__PURE__ */ jsx(
2643
+ "td",
2644
+ {
2645
+ style: {
2646
+ width: cell.column.getSize(),
2647
+ ...getPinningStyles(cell.column)
2648
+ },
2649
+ className: cn(
2650
+ "px-2 py-1 align-middle text-sm",
2651
+ isPinned && "bg-card group-hover/row:bg-[color-mix(in_oklab,var(--card),var(--muted)_40%)] group-data-[state=selected]/row:bg-[color-mix(in_oklab,var(--card),var(--primary)_5%)]",
2652
+ isPinned && isRowEditing && "bg-[color-mix(in_oklab,var(--card),#f59e0b_5%)]",
2653
+ meta?.cellClassName
2654
+ ),
2655
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
2656
+ },
2657
+ cell.id
2658
+ );
2659
+ })
2660
+ }
2661
+ ),
2662
+ expanded && renderSubRow && /* @__PURE__ */ jsx("tr", { className: "border-b bg-muted/30", children: /* @__PURE__ */ jsx("td", { colSpan: row.getVisibleCells().length, className: "p-3", children: /* @__PURE__ */ jsx("div", { className: "rounded-md border bg-background p-3 shadow-inner", children: renderSubRow(row.original) }) }) })
2663
+ ] });
2664
+ }
2665
+ function renderSkeleton(cols, rows) {
2666
+ return Array.from({ length: rows }).map((_, r) => /* @__PURE__ */ jsx("tr", { className: "border-b", children: Array.from({ length: cols }).map((__, c) => /* @__PURE__ */ jsx("td", { className: "px-2 py-2", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }) }, c)) }, r));
2667
+ }
2668
+
2669
+ export { ColumnFilterPanel, DataTable, DataTableColumnHeader, DataTablePagination, DataTableRowActions, DataTableToolbar, EditableCell, booleanFilterFn, dateFilterFn, exportToCsv, exportToExcel, isFilterActive, numberFilterFn, setFilterFn, textFilterFn, themePresets, themeToStyle };
2670
+ //# sourceMappingURL=index.js.map
2671
+ //# sourceMappingURL=index.js.map