@moontra/moonui 2.3.5 → 2.3.6
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.d.mts +14 -76
- package/dist/index.d.ts +14 -76
- package/dist/index.global.js +29 -69
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +827 -1081
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +826 -1078
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/data-table-basic.tsx +261 -0
- package/src/components/ui/index.ts +4 -3
- package/src/components/ui/data-table.tsx +0 -623
|
@@ -1,623 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import {
|
|
5
|
-
ColumnDef,
|
|
6
|
-
ColumnFiltersState,
|
|
7
|
-
FilterFn,
|
|
8
|
-
SortingState,
|
|
9
|
-
VisibilityState,
|
|
10
|
-
flexRender,
|
|
11
|
-
getCoreRowModel,
|
|
12
|
-
getFacetedRowModel,
|
|
13
|
-
getFacetedUniqueValues,
|
|
14
|
-
getFilteredRowModel,
|
|
15
|
-
getPaginationRowModel,
|
|
16
|
-
getSortedRowModel,
|
|
17
|
-
useReactTable,
|
|
18
|
-
} from "@tanstack/react-table";
|
|
19
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
20
|
-
import { cn } from "../../lib/utils";
|
|
21
|
-
import { Button } from "./button";
|
|
22
|
-
import { Input } from "./input";
|
|
23
|
-
import {
|
|
24
|
-
Table,
|
|
25
|
-
TableBody,
|
|
26
|
-
TableCell,
|
|
27
|
-
TableHead,
|
|
28
|
-
TableHeader,
|
|
29
|
-
TableRow
|
|
30
|
-
} from "./table";
|
|
31
|
-
import {
|
|
32
|
-
Select,
|
|
33
|
-
SelectContent,
|
|
34
|
-
SelectItem,
|
|
35
|
-
SelectTrigger,
|
|
36
|
-
SelectValue,
|
|
37
|
-
} from "./select";
|
|
38
|
-
import {
|
|
39
|
-
DropdownMenu,
|
|
40
|
-
DropdownMenuCheckboxItem,
|
|
41
|
-
DropdownMenuContent,
|
|
42
|
-
DropdownMenuLabel,
|
|
43
|
-
DropdownMenuSeparator,
|
|
44
|
-
DropdownMenuTrigger,
|
|
45
|
-
} from "./dropdown-menu";
|
|
46
|
-
import {
|
|
47
|
-
ChevronLeft,
|
|
48
|
-
ChevronRight,
|
|
49
|
-
ChevronsLeft,
|
|
50
|
-
ChevronsRight,
|
|
51
|
-
ArrowUpDown,
|
|
52
|
-
ArrowUp,
|
|
53
|
-
ArrowDown,
|
|
54
|
-
Search,
|
|
55
|
-
Filter,
|
|
56
|
-
Columns,
|
|
57
|
-
Download,
|
|
58
|
-
RefreshCw,
|
|
59
|
-
Settings,
|
|
60
|
-
X
|
|
61
|
-
} from "lucide-react";
|
|
62
|
-
import { Badge } from "./badge";
|
|
63
|
-
import { Skeleton } from "./skeleton";
|
|
64
|
-
import { motion, AnimatePresence } from "framer-motion";
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Premium DataTable Component
|
|
68
|
-
*
|
|
69
|
-
* A powerful data table with sorting, filtering, pagination, and more.
|
|
70
|
-
* Built on top of @tanstack/react-table v8.
|
|
71
|
-
*/
|
|
72
|
-
|
|
73
|
-
const dataTableVariants = cva(
|
|
74
|
-
"w-full",
|
|
75
|
-
{
|
|
76
|
-
variants: {
|
|
77
|
-
variant: {
|
|
78
|
-
default: "",
|
|
79
|
-
bordered: "border rounded-lg",
|
|
80
|
-
striped: "[&_tbody_tr:nth-child(even)]:bg-muted/50",
|
|
81
|
-
cards: "gap-4 [&_tbody]:flex [&_tbody]:flex-col [&_tbody_tr]:rounded-lg [&_tbody_tr]:border [&_tbody_tr]:p-4",
|
|
82
|
-
},
|
|
83
|
-
size: {
|
|
84
|
-
default: "",
|
|
85
|
-
sm: "text-sm [&_th]:py-2 [&_td]:py-2",
|
|
86
|
-
lg: "text-base [&_th]:py-4 [&_td]:py-4",
|
|
87
|
-
},
|
|
88
|
-
density: {
|
|
89
|
-
default: "",
|
|
90
|
-
compact: "[&_th]:py-1 [&_td]:py-1",
|
|
91
|
-
comfortable: "[&_th]:py-6 [&_td]:py-6",
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
defaultVariants: {
|
|
95
|
-
variant: "default",
|
|
96
|
-
size: "default",
|
|
97
|
-
density: "default",
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
interface DataTableProps<TData, TValue>
|
|
103
|
-
extends VariantProps<typeof dataTableVariants> {
|
|
104
|
-
/**
|
|
105
|
-
* The table columns definition
|
|
106
|
-
*/
|
|
107
|
-
columns: ColumnDef<TData, TValue>[];
|
|
108
|
-
/**
|
|
109
|
-
* The data to display
|
|
110
|
-
*/
|
|
111
|
-
data: TData[];
|
|
112
|
-
/**
|
|
113
|
-
* Enable/disable filtering
|
|
114
|
-
*/
|
|
115
|
-
enableFiltering?: boolean;
|
|
116
|
-
/**
|
|
117
|
-
* Enable/disable sorting
|
|
118
|
-
*/
|
|
119
|
-
enableSorting?: boolean;
|
|
120
|
-
/**
|
|
121
|
-
* Enable/disable column visibility toggle
|
|
122
|
-
*/
|
|
123
|
-
enableColumnVisibility?: boolean;
|
|
124
|
-
/**
|
|
125
|
-
* Enable/disable row selection
|
|
126
|
-
*/
|
|
127
|
-
enableRowSelection?: boolean;
|
|
128
|
-
/**
|
|
129
|
-
* Enable/disable pagination
|
|
130
|
-
*/
|
|
131
|
-
enablePagination?: boolean;
|
|
132
|
-
/**
|
|
133
|
-
* Rows per page options
|
|
134
|
-
*/
|
|
135
|
-
pageSizeOptions?: number[];
|
|
136
|
-
/**
|
|
137
|
-
* Default page size
|
|
138
|
-
*/
|
|
139
|
-
defaultPageSize?: number;
|
|
140
|
-
/**
|
|
141
|
-
* Filter placeholder text
|
|
142
|
-
*/
|
|
143
|
-
filterPlaceholder?: string;
|
|
144
|
-
/**
|
|
145
|
-
* Filter column key
|
|
146
|
-
*/
|
|
147
|
-
filterColumn?: string;
|
|
148
|
-
/**
|
|
149
|
-
* Show loading state
|
|
150
|
-
*/
|
|
151
|
-
isLoading?: boolean;
|
|
152
|
-
/**
|
|
153
|
-
* Empty state message
|
|
154
|
-
*/
|
|
155
|
-
emptyMessage?: string;
|
|
156
|
-
/**
|
|
157
|
-
* Table title
|
|
158
|
-
*/
|
|
159
|
-
title?: string;
|
|
160
|
-
/**
|
|
161
|
-
* Table description
|
|
162
|
-
*/
|
|
163
|
-
description?: string;
|
|
164
|
-
/**
|
|
165
|
-
* Enable export functionality
|
|
166
|
-
*/
|
|
167
|
-
enableExport?: boolean;
|
|
168
|
-
/**
|
|
169
|
-
* Export handler
|
|
170
|
-
*/
|
|
171
|
-
onExport?: (data: TData[]) => void;
|
|
172
|
-
/**
|
|
173
|
-
* Enable refresh functionality
|
|
174
|
-
*/
|
|
175
|
-
enableRefresh?: boolean;
|
|
176
|
-
/**
|
|
177
|
-
* Refresh handler
|
|
178
|
-
*/
|
|
179
|
-
onRefresh?: () => void;
|
|
180
|
-
/**
|
|
181
|
-
* Additional toolbar actions
|
|
182
|
-
*/
|
|
183
|
-
toolbarActions?: React.ReactNode;
|
|
184
|
-
/**
|
|
185
|
-
* Custom filter component
|
|
186
|
-
*/
|
|
187
|
-
customFilter?: React.ReactNode;
|
|
188
|
-
/**
|
|
189
|
-
* Row click handler
|
|
190
|
-
*/
|
|
191
|
-
onRowClick?: (row: TData) => void;
|
|
192
|
-
/**
|
|
193
|
-
* Class name
|
|
194
|
-
*/
|
|
195
|
-
className?: string;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function DataTableLoading() {
|
|
199
|
-
return (
|
|
200
|
-
<div className="space-y-4">
|
|
201
|
-
<div className="flex items-center justify-between">
|
|
202
|
-
<Skeleton className="h-8 w-[250px]" />
|
|
203
|
-
<div className="flex gap-2">
|
|
204
|
-
<Skeleton className="h-8 w-8" />
|
|
205
|
-
<Skeleton className="h-8 w-8" />
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
<div className="rounded-lg border">
|
|
209
|
-
<div className="p-4">
|
|
210
|
-
<div className="flex gap-4">
|
|
211
|
-
{Array.from({ length: 4 }).map((_, i) => (
|
|
212
|
-
<Skeleton key={i} className="h-8 flex-1" />
|
|
213
|
-
))}
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
<div className="p-4 space-y-2">
|
|
217
|
-
{Array.from({ length: 5 }).map((_, i) => (
|
|
218
|
-
<Skeleton key={i} className="h-12 w-full" />
|
|
219
|
-
))}
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export function DataTable<TData, TValue>({
|
|
227
|
-
columns,
|
|
228
|
-
data,
|
|
229
|
-
enableFiltering = true,
|
|
230
|
-
enableSorting = true,
|
|
231
|
-
enableColumnVisibility = true,
|
|
232
|
-
enableRowSelection = false,
|
|
233
|
-
enablePagination = true,
|
|
234
|
-
pageSizeOptions = [10, 20, 30, 50, 100],
|
|
235
|
-
defaultPageSize = 10,
|
|
236
|
-
filterPlaceholder = "Filter...",
|
|
237
|
-
filterColumn,
|
|
238
|
-
isLoading = false,
|
|
239
|
-
emptyMessage = "No results found.",
|
|
240
|
-
title,
|
|
241
|
-
description,
|
|
242
|
-
enableExport = false,
|
|
243
|
-
onExport,
|
|
244
|
-
enableRefresh = false,
|
|
245
|
-
onRefresh,
|
|
246
|
-
toolbarActions,
|
|
247
|
-
customFilter,
|
|
248
|
-
onRowClick,
|
|
249
|
-
variant,
|
|
250
|
-
size,
|
|
251
|
-
density,
|
|
252
|
-
className,
|
|
253
|
-
}: DataTableProps<TData, TValue>) {
|
|
254
|
-
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
255
|
-
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
|
|
256
|
-
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
|
257
|
-
const [rowSelection, setRowSelection] = React.useState({});
|
|
258
|
-
const [globalFilter, setGlobalFilter] = React.useState("");
|
|
259
|
-
|
|
260
|
-
const table = useReactTable({
|
|
261
|
-
data,
|
|
262
|
-
columns,
|
|
263
|
-
onSortingChange: enableSorting ? setSorting : undefined,
|
|
264
|
-
onColumnFiltersChange: enableFiltering ? setColumnFilters : undefined,
|
|
265
|
-
onColumnVisibilityChange: enableColumnVisibility ? setColumnVisibility : undefined,
|
|
266
|
-
onRowSelectionChange: enableRowSelection ? setRowSelection : undefined,
|
|
267
|
-
onGlobalFilterChange: enableFiltering && !filterColumn ? setGlobalFilter : undefined,
|
|
268
|
-
getCoreRowModel: getCoreRowModel(),
|
|
269
|
-
getPaginationRowModel: enablePagination ? getPaginationRowModel() : undefined,
|
|
270
|
-
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
|
|
271
|
-
getFilteredRowModel: enableFiltering ? getFilteredRowModel() : undefined,
|
|
272
|
-
getFacetedRowModel: enableFiltering ? getFacetedRowModel() : undefined,
|
|
273
|
-
getFacetedUniqueValues: enableFiltering ? getFacetedUniqueValues() : undefined,
|
|
274
|
-
state: {
|
|
275
|
-
sorting,
|
|
276
|
-
columnFilters,
|
|
277
|
-
columnVisibility,
|
|
278
|
-
rowSelection,
|
|
279
|
-
globalFilter,
|
|
280
|
-
},
|
|
281
|
-
initialState: {
|
|
282
|
-
pagination: {
|
|
283
|
-
pageSize: defaultPageSize,
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
if (isLoading) {
|
|
289
|
-
return <DataTableLoading />;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const selectedRows = table.getFilteredSelectedRowModel().rows;
|
|
293
|
-
|
|
294
|
-
return (
|
|
295
|
-
<div className={cn("moonui-theme", "space-y-4", className)}>
|
|
296
|
-
{/* Header */}
|
|
297
|
-
{(title || description) && (
|
|
298
|
-
<div className="space-y-1">
|
|
299
|
-
{title && <h3 className="text-lg font-semibold">{title}</h3>}
|
|
300
|
-
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
|
301
|
-
</div>
|
|
302
|
-
)}
|
|
303
|
-
|
|
304
|
-
{/* Toolbar */}
|
|
305
|
-
<div className="flex items-center justify-between gap-4">
|
|
306
|
-
<div className="flex-1 max-w-sm">
|
|
307
|
-
{/* Search/Filter */}
|
|
308
|
-
{enableFiltering && (
|
|
309
|
-
<>
|
|
310
|
-
{customFilter || (
|
|
311
|
-
<div className="relative">
|
|
312
|
-
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
313
|
-
<Input
|
|
314
|
-
placeholder={filterPlaceholder}
|
|
315
|
-
value={
|
|
316
|
-
filterColumn
|
|
317
|
-
? (table.getColumn(filterColumn)?.getFilterValue() as string) ?? ""
|
|
318
|
-
: globalFilter
|
|
319
|
-
}
|
|
320
|
-
onChange={(event) => {
|
|
321
|
-
const value = event.target.value;
|
|
322
|
-
if (filterColumn) {
|
|
323
|
-
table.getColumn(filterColumn)?.setFilterValue(value);
|
|
324
|
-
} else {
|
|
325
|
-
setGlobalFilter(value);
|
|
326
|
-
}
|
|
327
|
-
}}
|
|
328
|
-
className="pl-8"
|
|
329
|
-
/>
|
|
330
|
-
{((filterColumn && table.getColumn(filterColumn)?.getFilterValue()) ||
|
|
331
|
-
(!filterColumn && globalFilter)) && (
|
|
332
|
-
<Button
|
|
333
|
-
variant="ghost"
|
|
334
|
-
size="sm"
|
|
335
|
-
className="absolute right-0 top-0 h-full px-2"
|
|
336
|
-
onClick={() => {
|
|
337
|
-
if (filterColumn) {
|
|
338
|
-
table.getColumn(filterColumn)?.setFilterValue("");
|
|
339
|
-
} else {
|
|
340
|
-
setGlobalFilter("");
|
|
341
|
-
}
|
|
342
|
-
}}
|
|
343
|
-
>
|
|
344
|
-
<X className="h-4 w-4" />
|
|
345
|
-
</Button>
|
|
346
|
-
)}
|
|
347
|
-
</div>
|
|
348
|
-
)}
|
|
349
|
-
</>
|
|
350
|
-
)}
|
|
351
|
-
</div>
|
|
352
|
-
|
|
353
|
-
{/* Actions */}
|
|
354
|
-
<div className="flex items-center gap-2">
|
|
355
|
-
{/* Selection info */}
|
|
356
|
-
{enableRowSelection && selectedRows.length > 0 && (
|
|
357
|
-
<Badge variant="secondary" className="whitespace-nowrap">
|
|
358
|
-
{selectedRows.length} selected
|
|
359
|
-
</Badge>
|
|
360
|
-
)}
|
|
361
|
-
|
|
362
|
-
{toolbarActions}
|
|
363
|
-
|
|
364
|
-
{/* Export */}
|
|
365
|
-
{enableExport && onExport && (
|
|
366
|
-
<Button
|
|
367
|
-
variant="outline"
|
|
368
|
-
size="sm"
|
|
369
|
-
onClick={() => onExport(table.getFilteredRowModel().rows.map(row => row.original))}
|
|
370
|
-
>
|
|
371
|
-
<Download className="mr-2 h-4 w-4" />
|
|
372
|
-
Export
|
|
373
|
-
</Button>
|
|
374
|
-
)}
|
|
375
|
-
|
|
376
|
-
{/* Refresh */}
|
|
377
|
-
{enableRefresh && onRefresh && (
|
|
378
|
-
<Button
|
|
379
|
-
variant="outline"
|
|
380
|
-
size="icon"
|
|
381
|
-
onClick={onRefresh}
|
|
382
|
-
>
|
|
383
|
-
<RefreshCw className="h-4 w-4" />
|
|
384
|
-
</Button>
|
|
385
|
-
)}
|
|
386
|
-
|
|
387
|
-
{/* Column visibility */}
|
|
388
|
-
{enableColumnVisibility && (
|
|
389
|
-
<DropdownMenu>
|
|
390
|
-
<DropdownMenuTrigger asChild>
|
|
391
|
-
<Button variant="outline" size="icon">
|
|
392
|
-
<Columns className="h-4 w-4" />
|
|
393
|
-
</Button>
|
|
394
|
-
</DropdownMenuTrigger>
|
|
395
|
-
<DropdownMenuContent align="end" className="w-[180px]">
|
|
396
|
-
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
|
|
397
|
-
<DropdownMenuSeparator />
|
|
398
|
-
{table
|
|
399
|
-
.getAllColumns()
|
|
400
|
-
.filter((column) => column.getCanHide())
|
|
401
|
-
.map((column) => {
|
|
402
|
-
return (
|
|
403
|
-
<DropdownMenuCheckboxItem
|
|
404
|
-
key={column.id}
|
|
405
|
-
className="capitalize"
|
|
406
|
-
checked={column.getIsVisible()}
|
|
407
|
-
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
|
408
|
-
>
|
|
409
|
-
{column.id}
|
|
410
|
-
</DropdownMenuCheckboxItem>
|
|
411
|
-
);
|
|
412
|
-
})}
|
|
413
|
-
</DropdownMenuContent>
|
|
414
|
-
</DropdownMenu>
|
|
415
|
-
)}
|
|
416
|
-
</div>
|
|
417
|
-
</div>
|
|
418
|
-
|
|
419
|
-
{/* Table */}
|
|
420
|
-
<div className={cn("rounded-lg", variant === "bordered" && "border")}>
|
|
421
|
-
<Table>
|
|
422
|
-
<TableHeader>
|
|
423
|
-
{table.getHeaderGroups().map((headerGroup) => (
|
|
424
|
-
<TableRow key={headerGroup.id}>
|
|
425
|
-
{headerGroup.headers.map((header) => {
|
|
426
|
-
return (
|
|
427
|
-
<TableHead key={header.id}>
|
|
428
|
-
{header.isPlaceholder ? null : (
|
|
429
|
-
<div
|
|
430
|
-
className={cn(
|
|
431
|
-
"flex items-center gap-2",
|
|
432
|
-
header.column.getCanSort() && "cursor-pointer select-none"
|
|
433
|
-
)}
|
|
434
|
-
onClick={header.column.getToggleSortingHandler()}
|
|
435
|
-
>
|
|
436
|
-
{flexRender(
|
|
437
|
-
header.column.columnDef.header,
|
|
438
|
-
header.getContext()
|
|
439
|
-
)}
|
|
440
|
-
{enableSorting && header.column.getCanSort() && (
|
|
441
|
-
<div className="ml-auto">
|
|
442
|
-
{header.column.getIsSorted() === "desc" ? (
|
|
443
|
-
<ArrowDown className="h-4 w-4" />
|
|
444
|
-
) : header.column.getIsSorted() === "asc" ? (
|
|
445
|
-
<ArrowUp className="h-4 w-4" />
|
|
446
|
-
) : (
|
|
447
|
-
<ArrowUpDown className="h-4 w-4 opacity-50" />
|
|
448
|
-
)}
|
|
449
|
-
</div>
|
|
450
|
-
)}
|
|
451
|
-
</div>
|
|
452
|
-
)}
|
|
453
|
-
</TableHead>
|
|
454
|
-
);
|
|
455
|
-
})}
|
|
456
|
-
</TableRow>
|
|
457
|
-
))}
|
|
458
|
-
</TableHeader>
|
|
459
|
-
<TableBody>
|
|
460
|
-
{table.getRowModel().rows?.length ? (
|
|
461
|
-
<AnimatePresence>
|
|
462
|
-
{table.getRowModel().rows.map((row) => (
|
|
463
|
-
<motion.tr
|
|
464
|
-
key={row.id}
|
|
465
|
-
initial={{ opacity: 0, y: -10 }}
|
|
466
|
-
animate={{ opacity: 1, y: 0 }}
|
|
467
|
-
exit={{ opacity: 0, y: 10 }}
|
|
468
|
-
transition={{ duration: 0.2 }}
|
|
469
|
-
data-state={row.getIsSelected() && "selected"}
|
|
470
|
-
className={cn(
|
|
471
|
-
"border-b transition-colors",
|
|
472
|
-
onRowClick && "cursor-pointer hover:bg-muted/50",
|
|
473
|
-
row.getIsSelected() && "bg-muted"
|
|
474
|
-
)}
|
|
475
|
-
onClick={() => onRowClick?.(row.original)}
|
|
476
|
-
>
|
|
477
|
-
{row.getVisibleCells().map((cell) => (
|
|
478
|
-
<TableCell key={cell.id}>
|
|
479
|
-
{flexRender(
|
|
480
|
-
cell.column.columnDef.cell,
|
|
481
|
-
cell.getContext()
|
|
482
|
-
)}
|
|
483
|
-
</TableCell>
|
|
484
|
-
))}
|
|
485
|
-
</motion.tr>
|
|
486
|
-
))}
|
|
487
|
-
</AnimatePresence>
|
|
488
|
-
) : (
|
|
489
|
-
<TableRow>
|
|
490
|
-
<TableCell
|
|
491
|
-
colSpan={columns.length}
|
|
492
|
-
className="h-24 text-center"
|
|
493
|
-
>
|
|
494
|
-
{emptyMessage}
|
|
495
|
-
</TableCell>
|
|
496
|
-
</TableRow>
|
|
497
|
-
)}
|
|
498
|
-
</TableBody>
|
|
499
|
-
</Table>
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
{/* Pagination */}
|
|
503
|
-
{enablePagination && (
|
|
504
|
-
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
505
|
-
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
506
|
-
<span>
|
|
507
|
-
{table.getFilteredRowModel().rows.length} of{" "}
|
|
508
|
-
{table.getCoreRowModel().rows.length} row(s)
|
|
509
|
-
</span>
|
|
510
|
-
{enableRowSelection && selectedRows.length > 0 && (
|
|
511
|
-
<span>• {selectedRows.length} selected</span>
|
|
512
|
-
)}
|
|
513
|
-
</div>
|
|
514
|
-
|
|
515
|
-
<div className="flex items-center gap-2">
|
|
516
|
-
<div className="flex items-center gap-2">
|
|
517
|
-
<p className="text-sm">Rows per page</p>
|
|
518
|
-
<Select
|
|
519
|
-
value={`${table.getState().pagination.pageSize}`}
|
|
520
|
-
onValueChange={(value) => {
|
|
521
|
-
table.setPageSize(Number(value));
|
|
522
|
-
}}
|
|
523
|
-
>
|
|
524
|
-
<SelectTrigger className="h-8 w-[70px]">
|
|
525
|
-
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
|
526
|
-
</SelectTrigger>
|
|
527
|
-
<SelectContent side="top">
|
|
528
|
-
{pageSizeOptions.map((pageSize) => (
|
|
529
|
-
<SelectItem key={pageSize} value={`${pageSize}`}>
|
|
530
|
-
{pageSize}
|
|
531
|
-
</SelectItem>
|
|
532
|
-
))}
|
|
533
|
-
</SelectContent>
|
|
534
|
-
</Select>
|
|
535
|
-
</div>
|
|
536
|
-
|
|
537
|
-
<div className="flex items-center gap-1">
|
|
538
|
-
<Button
|
|
539
|
-
variant="outline"
|
|
540
|
-
size="icon"
|
|
541
|
-
onClick={() => table.setPageIndex(0)}
|
|
542
|
-
disabled={!table.getCanPreviousPage()}
|
|
543
|
-
>
|
|
544
|
-
<ChevronsLeft className="h-4 w-4" />
|
|
545
|
-
</Button>
|
|
546
|
-
<Button
|
|
547
|
-
variant="outline"
|
|
548
|
-
size="icon"
|
|
549
|
-
onClick={() => table.previousPage()}
|
|
550
|
-
disabled={!table.getCanPreviousPage()}
|
|
551
|
-
>
|
|
552
|
-
<ChevronLeft className="h-4 w-4" />
|
|
553
|
-
</Button>
|
|
554
|
-
<div className="flex items-center gap-1 text-sm">
|
|
555
|
-
<span>Page</span>
|
|
556
|
-
<span className="font-medium">
|
|
557
|
-
{table.getState().pagination.pageIndex + 1} of{" "}
|
|
558
|
-
{table.getPageCount()}
|
|
559
|
-
</span>
|
|
560
|
-
</div>
|
|
561
|
-
<Button
|
|
562
|
-
variant="outline"
|
|
563
|
-
size="icon"
|
|
564
|
-
onClick={() => table.nextPage()}
|
|
565
|
-
disabled={!table.getCanNextPage()}
|
|
566
|
-
>
|
|
567
|
-
<ChevronRight className="h-4 w-4" />
|
|
568
|
-
</Button>
|
|
569
|
-
<Button
|
|
570
|
-
variant="outline"
|
|
571
|
-
size="icon"
|
|
572
|
-
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
573
|
-
disabled={!table.getCanNextPage()}
|
|
574
|
-
>
|
|
575
|
-
<ChevronsRight className="h-4 w-4" />
|
|
576
|
-
</Button>
|
|
577
|
-
</div>
|
|
578
|
-
</div>
|
|
579
|
-
</div>
|
|
580
|
-
)}
|
|
581
|
-
</div>
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Column Helper Functions
|
|
587
|
-
*/
|
|
588
|
-
|
|
589
|
-
// Create a sortable header
|
|
590
|
-
export function createSortableHeader(label: string) {
|
|
591
|
-
return ({ column }: any) => (
|
|
592
|
-
<Button
|
|
593
|
-
variant="ghost"
|
|
594
|
-
className="-ml-3 h-8 data-[state=open]:bg-accent"
|
|
595
|
-
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
596
|
-
>
|
|
597
|
-
{label}
|
|
598
|
-
{column.getIsSorted() === "desc" ? (
|
|
599
|
-
<ArrowDown className="ml-2 h-4 w-4" />
|
|
600
|
-
) : column.getIsSorted() === "asc" ? (
|
|
601
|
-
<ArrowUp className="ml-2 h-4 w-4" />
|
|
602
|
-
) : (
|
|
603
|
-
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
604
|
-
)}
|
|
605
|
-
</Button>
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Create a filterable column
|
|
610
|
-
export function createFilterableColumn<TData, TValue>(
|
|
611
|
-
accessorKey: string,
|
|
612
|
-
header: string,
|
|
613
|
-
filterFn?: FilterFn<TData>
|
|
614
|
-
): ColumnDef<TData, TValue> {
|
|
615
|
-
return {
|
|
616
|
-
accessorKey,
|
|
617
|
-
header: createSortableHeader(header),
|
|
618
|
-
filterFn,
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Export utilities
|
|
623
|
-
export { type ColumnDef } from "@tanstack/react-table";
|