@multicomma/arkcn 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/README.md +78 -0
- package/bin/arkcn.mjs +78 -0
- package/cli/commands.mjs +251 -0
- package/cli/project.mjs +168 -0
- package/cli/registry.mjs +50 -0
- package/cli/ui.mjs +46 -0
- package/mcp/server.mjs +149 -0
- package/package.json +201 -0
- package/registry/base.css +680 -0
- package/registry/index.json +1833 -0
- package/registry/items/accordion.json +18 -0
- package/registry/items/activity-feed.json +18 -0
- package/registry/items/alert-dialog.json +18 -0
- package/registry/items/alert.json +17 -0
- package/registry/items/angle-slider.json +17 -0
- package/registry/items/app-shell.json +28 -0
- package/registry/items/aspect-ratio.json +13 -0
- package/registry/items/attachment.json +19 -0
- package/registry/items/avatar.json +17 -0
- package/registry/items/badge.json +18 -0
- package/registry/items/breadcrumb.json +18 -0
- package/registry/items/bubble.json +18 -0
- package/registry/items/button-group.json +19 -0
- package/registry/items/button.json +18 -0
- package/registry/items/calendar.json +19 -0
- package/registry/items/canvas.json +24 -0
- package/registry/items/card.json +15 -0
- package/registry/items/carousel.json +19 -0
- package/registry/items/cascader.json +23 -0
- package/registry/items/chart.json +17 -0
- package/registry/items/chat.json +23 -0
- package/registry/items/checkbox.json +18 -0
- package/registry/items/clipboard.json +19 -0
- package/registry/items/collapsible.json +15 -0
- package/registry/items/color-picker.json +19 -0
- package/registry/items/combobox.json +20 -0
- package/registry/items/command.json +21 -0
- package/registry/items/comment-thread.json +25 -0
- package/registry/items/context-menu.json +18 -0
- package/registry/items/controllable.json +14 -0
- package/registry/items/data-grid.json +24 -0
- package/registry/items/data-table-adapter.json +14 -0
- package/registry/items/data-table.json +32 -0
- package/registry/items/date-input.json +17 -0
- package/registry/items/dialog.json +19 -0
- package/registry/items/direction.json +13 -0
- package/registry/items/drag-preview.json +16 -0
- package/registry/items/drawer.json +18 -0
- package/registry/items/dropdown-menu.json +18 -0
- package/registry/items/editable.json +17 -0
- package/registry/items/empty.json +17 -0
- package/registry/items/field.json +19 -0
- package/registry/items/file-upload.json +19 -0
- package/registry/items/floating-panel.json +19 -0
- package/registry/items/floating-toolbar.json +16 -0
- package/registry/items/form.json +14 -0
- package/registry/items/format.json +15 -0
- package/registry/items/gantt.json +22 -0
- package/registry/items/history.json +14 -0
- package/registry/items/hotkeys.json +18 -0
- package/registry/items/hover-card.json +17 -0
- package/registry/items/image-cropper.json +17 -0
- package/registry/items/input-group.json +20 -0
- package/registry/items/input-otp.json +18 -0
- package/registry/items/input.json +15 -0
- package/registry/items/item.json +19 -0
- package/registry/items/json-tree-view.json +17 -0
- package/registry/items/kanban.json +26 -0
- package/registry/items/kbd.json +15 -0
- package/registry/items/label.json +15 -0
- package/registry/items/live-region.json +16 -0
- package/registry/items/marker.json +18 -0
- package/registry/items/marquee.json +17 -0
- package/registry/items/menubar.json +19 -0
- package/registry/items/message.json +15 -0
- package/registry/items/navigation-menu.json +19 -0
- package/registry/items/node-graph.json +22 -0
- package/registry/items/number-input.json +18 -0
- package/registry/items/pagination.json +18 -0
- package/registry/items/password-input.json +18 -0
- package/registry/items/popconfirm.json +20 -0
- package/registry/items/popover.json +18 -0
- package/registry/items/progress.json +17 -0
- package/registry/items/qr-code.json +17 -0
- package/registry/items/query-builder.json +23 -0
- package/registry/items/radio-group.json +17 -0
- package/registry/items/rating-group.json +18 -0
- package/registry/items/resizable.json +17 -0
- package/registry/items/rich-text-editor.json +37 -0
- package/registry/items/row-layout.json +15 -0
- package/registry/items/scheduler.json +23 -0
- package/registry/items/scroll-area.json +17 -0
- package/registry/items/segment-group.json +17 -0
- package/registry/items/select.json +19 -0
- package/registry/items/separator.json +15 -0
- package/registry/items/sheet.json +19 -0
- package/registry/items/sidebar.json +27 -0
- package/registry/items/signature-pad.json +19 -0
- package/registry/items/skeleton.json +15 -0
- package/registry/items/slider.json +17 -0
- package/registry/items/sonner.json +17 -0
- package/registry/items/spinner.json +17 -0
- package/registry/items/steps.json +19 -0
- package/registry/items/swap.json +17 -0
- package/registry/items/switch.json +17 -0
- package/registry/items/table.json +16 -0
- package/registry/items/tabs.json +18 -0
- package/registry/items/tags-input.json +18 -0
- package/registry/items/textarea.json +15 -0
- package/registry/items/time.json +14 -0
- package/registry/items/timer.json +18 -0
- package/registry/items/toc.json +17 -0
- package/registry/items/toggle-group.json +19 -0
- package/registry/items/toggle.json +18 -0
- package/registry/items/tooltip.json +17 -0
- package/registry/items/tour.json +19 -0
- package/registry/items/transfer-list.json +24 -0
- package/registry/items/tree-select.json +24 -0
- package/registry/items/tree-view.json +18 -0
- package/registry/items/use-mobile.json +13 -0
- package/registry/items/utils.json +17 -0
- package/registry/items/virtual-list.json +16 -0
- package/src/components/ui/accordion.tsx +111 -0
- package/src/components/ui/activity-feed.tsx +228 -0
- package/src/components/ui/alert-dialog.tsx +204 -0
- package/src/components/ui/alert.tsx +55 -0
- package/src/components/ui/angle-slider.tsx +117 -0
- package/src/components/ui/app-shell.tsx +985 -0
- package/src/components/ui/aspect-ratio.tsx +35 -0
- package/src/components/ui/attachment.tsx +193 -0
- package/src/components/ui/avatar.tsx +94 -0
- package/src/components/ui/badge.tsx +45 -0
- package/src/components/ui/breadcrumb.tsx +93 -0
- package/src/components/ui/bubble.tsx +119 -0
- package/src/components/ui/button-group.tsx +79 -0
- package/src/components/ui/button.tsx +65 -0
- package/src/components/ui/calendar.tsx +172 -0
- package/src/components/ui/canvas.tsx +723 -0
- package/src/components/ui/card.tsx +69 -0
- package/src/components/ui/carousel.tsx +228 -0
- package/src/components/ui/cascader.tsx +754 -0
- package/src/components/ui/chart.tsx +303 -0
- package/src/components/ui/chat.tsx +544 -0
- package/src/components/ui/checkbox.tsx +94 -0
- package/src/components/ui/clipboard.tsx +94 -0
- package/src/components/ui/collapsible.tsx +32 -0
- package/src/components/ui/color-picker.tsx +441 -0
- package/src/components/ui/combobox.tsx +400 -0
- package/src/components/ui/command.tsx +292 -0
- package/src/components/ui/comment-thread.tsx +923 -0
- package/src/components/ui/context-menu.tsx +331 -0
- package/src/components/ui/data-grid.tsx +1227 -0
- package/src/components/ui/data-table.tsx +993 -0
- package/src/components/ui/date-input.tsx +103 -0
- package/src/components/ui/dialog.tsx +159 -0
- package/src/components/ui/direction.tsx +32 -0
- package/src/components/ui/drawer.tsx +200 -0
- package/src/components/ui/dropdown-menu.tsx +329 -0
- package/src/components/ui/editable.tsx +90 -0
- package/src/components/ui/empty.tsx +86 -0
- package/src/components/ui/field.tsx +218 -0
- package/src/components/ui/file-upload.tsx +204 -0
- package/src/components/ui/floating-panel.tsx +199 -0
- package/src/components/ui/floating-toolbar.tsx +79 -0
- package/src/components/ui/format.tsx +22 -0
- package/src/components/ui/gantt.tsx +950 -0
- package/src/components/ui/hotkeys.tsx +340 -0
- package/src/components/ui/hover-card.tsx +83 -0
- package/src/components/ui/image-cropper.tsx +100 -0
- package/src/components/ui/input-group.tsx +133 -0
- package/src/components/ui/input-otp.tsx +82 -0
- package/src/components/ui/input.tsx +18 -0
- package/src/components/ui/item.tsx +174 -0
- package/src/components/ui/json-tree-view.tsx +37 -0
- package/src/components/ui/kanban.tsx +884 -0
- package/src/components/ui/kbd.tsx +20 -0
- package/src/components/ui/label.tsx +30 -0
- package/src/components/ui/live-region.tsx +54 -0
- package/src/components/ui/marker.tsx +66 -0
- package/src/components/ui/marquee.tsx +61 -0
- package/src/components/ui/menubar.tsx +456 -0
- package/src/components/ui/message.tsx +74 -0
- package/src/components/ui/navigation-menu.tsx +202 -0
- package/src/components/ui/node-graph.tsx +1723 -0
- package/src/components/ui/number-input.tsx +148 -0
- package/src/components/ui/pagination.tsx +88 -0
- package/src/components/ui/password-input.tsx +104 -0
- package/src/components/ui/popconfirm.tsx +149 -0
- package/src/components/ui/popover.tsx +126 -0
- package/src/components/ui/progress.tsx +114 -0
- package/src/components/ui/qr-code.tsx +51 -0
- package/src/components/ui/query-builder.tsx +938 -0
- package/src/components/ui/radio-group.tsx +108 -0
- package/src/components/ui/rating-group.tsx +91 -0
- package/src/components/ui/resizable.tsx +41 -0
- package/src/components/ui/rich-text-editor.tsx +845 -0
- package/src/components/ui/scheduler.tsx +1164 -0
- package/src/components/ui/scroll-area.tsx +82 -0
- package/src/components/ui/segment-group.tsx +95 -0
- package/src/components/ui/select.tsx +354 -0
- package/src/components/ui/separator.tsx +32 -0
- package/src/components/ui/sheet.tsx +130 -0
- package/src/components/ui/sidebar.tsx +665 -0
- package/src/components/ui/signature-pad.tsx +112 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/slider.tsx +183 -0
- package/src/components/ui/sonner.tsx +37 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/steps.tsx +152 -0
- package/src/components/ui/swap.tsx +33 -0
- package/src/components/ui/switch.tsx +83 -0
- package/src/components/ui/table.tsx +72 -0
- package/src/components/ui/tabs.tsx +100 -0
- package/src/components/ui/tags-input.tsx +153 -0
- package/src/components/ui/textarea.tsx +17 -0
- package/src/components/ui/timer.tsx +67 -0
- package/src/components/ui/toc.tsx +64 -0
- package/src/components/ui/toggle-group.tsx +89 -0
- package/src/components/ui/toggle.tsx +46 -0
- package/src/components/ui/tooltip.tsx +113 -0
- package/src/components/ui/tour.tsx +168 -0
- package/src/components/ui/transfer-list.tsx +700 -0
- package/src/components/ui/tree-select.tsx +638 -0
- package/src/components/ui/tree-view.tsx +217 -0
- package/src/components/ui/virtual-list.tsx +343 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/lib/controllable.ts +31 -0
- package/src/lib/data-table-adapter.ts +67 -0
- package/src/lib/drag-preview.ts +39 -0
- package/src/lib/form.ts +39 -0
- package/src/lib/history.ts +55 -0
- package/src/lib/row-layout.ts +81 -0
- package/src/lib/table.ts +483 -0
- package/src/lib/time.ts +37 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/arkcn.css +764 -0
- package/src/styles/index.css +3 -0
|
@@ -0,0 +1,993 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
type ColumnDef,
|
|
6
|
+
type ColumnFiltersState,
|
|
7
|
+
type PaginationState,
|
|
8
|
+
type Row,
|
|
9
|
+
type RowSelectionState,
|
|
10
|
+
type SortingState,
|
|
11
|
+
type TableOptions,
|
|
12
|
+
type VisibilityState,
|
|
13
|
+
useTable,
|
|
14
|
+
} from "@/lib/table"
|
|
15
|
+
import {
|
|
16
|
+
type DataTableColumn,
|
|
17
|
+
type DataTableInstance,
|
|
18
|
+
type DataTableRow as DataTableRowInstance,
|
|
19
|
+
} from "@/lib/data-table-adapter"
|
|
20
|
+
import {
|
|
21
|
+
ArrowDownIcon,
|
|
22
|
+
ArrowUpIcon,
|
|
23
|
+
CheckIcon,
|
|
24
|
+
ChevronLeftIcon,
|
|
25
|
+
ChevronRightIcon,
|
|
26
|
+
ChevronsLeftIcon,
|
|
27
|
+
ChevronsRightIcon,
|
|
28
|
+
ChevronsUpDownIcon,
|
|
29
|
+
EllipsisIcon,
|
|
30
|
+
EyeOffIcon,
|
|
31
|
+
PlusCircleIcon,
|
|
32
|
+
Settings2Icon,
|
|
33
|
+
XIcon,
|
|
34
|
+
} from "lucide-react"
|
|
35
|
+
import { cn } from "@/lib/utils"
|
|
36
|
+
import { LiveRegion, useLiveRegion } from "@/components/ui/live-region"
|
|
37
|
+
|
|
38
|
+
import { Badge } from "@/components/ui/badge"
|
|
39
|
+
import { Button } from "@/components/ui/button"
|
|
40
|
+
import { Checkbox } from "@/components/ui/checkbox"
|
|
41
|
+
import {
|
|
42
|
+
Command,
|
|
43
|
+
CommandEmpty,
|
|
44
|
+
CommandGroup,
|
|
45
|
+
CommandInput,
|
|
46
|
+
CommandItem,
|
|
47
|
+
CommandList,
|
|
48
|
+
CommandSeparator,
|
|
49
|
+
useFilter,
|
|
50
|
+
useListCollection,
|
|
51
|
+
} from "@/components/ui/command"
|
|
52
|
+
import {
|
|
53
|
+
DropdownMenu,
|
|
54
|
+
DropdownMenuCheckboxItem,
|
|
55
|
+
DropdownMenuContent,
|
|
56
|
+
DropdownMenuGroup,
|
|
57
|
+
DropdownMenuItem,
|
|
58
|
+
DropdownMenuLabel,
|
|
59
|
+
DropdownMenuSeparator,
|
|
60
|
+
DropdownMenuTrigger,
|
|
61
|
+
} from "@/components/ui/dropdown-menu"
|
|
62
|
+
import { Input } from "@/components/ui/input"
|
|
63
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
|
64
|
+
import {
|
|
65
|
+
Select,
|
|
66
|
+
SelectContent,
|
|
67
|
+
SelectControl,
|
|
68
|
+
SelectItem,
|
|
69
|
+
SelectTrigger,
|
|
70
|
+
SelectValue,
|
|
71
|
+
createListCollection,
|
|
72
|
+
} from "@/components/ui/select"
|
|
73
|
+
import { FloatingToolbar } from "@/components/ui/floating-toolbar"
|
|
74
|
+
import { Separator } from "@/components/ui/separator"
|
|
75
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
76
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
|
77
|
+
|
|
78
|
+
/* -------------------------------------------------------------------------- */
|
|
79
|
+
/* Hook */
|
|
80
|
+
/* -------------------------------------------------------------------------- */
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The built-in adapter: the dependency-free engine from `@/lib/table`. Its
|
|
84
|
+
* instance satisfies `DataTableInstance`, so it can be passed to `<DataTable>`
|
|
85
|
+
* directly. Any other object implementing `DataTableInstance` works the same way.
|
|
86
|
+
*/
|
|
87
|
+
function useDataTable<TData>(options: TableOptions<TData>) {
|
|
88
|
+
return useTable<TData>(options)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* -------------------------------------------------------------------------- */
|
|
92
|
+
/* Context */
|
|
93
|
+
/* -------------------------------------------------------------------------- */
|
|
94
|
+
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
+
const DataTableContext = React.createContext<DataTableInstance<any> | null>(null)
|
|
97
|
+
|
|
98
|
+
function useDataTableContext<TData>(explicit?: DataTableInstance<TData>): DataTableInstance<TData> {
|
|
99
|
+
const fromContext = React.useContext(DataTableContext)
|
|
100
|
+
const table = explicit ?? fromContext
|
|
101
|
+
if (!table) {
|
|
102
|
+
throw new Error("Data table parts must be rendered inside <DataTable> or receive a `table` prop.")
|
|
103
|
+
}
|
|
104
|
+
return table
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function DataTable<TData>({
|
|
108
|
+
table,
|
|
109
|
+
className,
|
|
110
|
+
...props
|
|
111
|
+
}: React.ComponentProps<"div"> & { table: DataTableInstance<TData> }) {
|
|
112
|
+
return (
|
|
113
|
+
<DataTableContext.Provider value={table}>
|
|
114
|
+
<div data-slot="data-table" className={cn("flex flex-1 flex-col gap-4", className)} {...props} />
|
|
115
|
+
</DataTableContext.Provider>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* -------------------------------------------------------------------------- */
|
|
120
|
+
/* Toolbar */
|
|
121
|
+
/* -------------------------------------------------------------------------- */
|
|
122
|
+
|
|
123
|
+
function DataTableToolbar({ className, ...props }: React.ComponentProps<"div">) {
|
|
124
|
+
return (
|
|
125
|
+
<div
|
|
126
|
+
data-slot="data-table-toolbar"
|
|
127
|
+
className={cn("flex items-center justify-between gap-2", className)}
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function DataTableToolbarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
data-slot="data-table-toolbar-group"
|
|
137
|
+
className={cn("flex flex-1 flex-col-reverse items-start gap-2 sm:flex-row sm:items-center", className)}
|
|
138
|
+
{...props}
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function DataTableSearch<TData>({
|
|
144
|
+
table: tableProp,
|
|
145
|
+
columnId,
|
|
146
|
+
className,
|
|
147
|
+
...props
|
|
148
|
+
}: Omit<React.ComponentProps<typeof Input>, "value" | "onChange"> & {
|
|
149
|
+
table?: DataTableInstance<TData>
|
|
150
|
+
/** Filter a single column instead of the global filter. */
|
|
151
|
+
columnId?: string
|
|
152
|
+
}) {
|
|
153
|
+
const table = useDataTableContext(tableProp)
|
|
154
|
+
const column = columnId ? table.getColumn(columnId) : undefined
|
|
155
|
+
const value = columnId
|
|
156
|
+
? ((column?.getFilterValue() as string) ?? "")
|
|
157
|
+
: ((table.getState().globalFilter as string) ?? "")
|
|
158
|
+
return (
|
|
159
|
+
<Input
|
|
160
|
+
data-slot="data-table-search"
|
|
161
|
+
value={value}
|
|
162
|
+
onChange={(event) =>
|
|
163
|
+
columnId ? column?.setFilterValue(event.target.value) : table.setGlobalFilter(event.target.value)
|
|
164
|
+
}
|
|
165
|
+
className={cn("h-8 w-37.5 lg:w-62.5", className)}
|
|
166
|
+
{...props}
|
|
167
|
+
/>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function DataTableResetFilters<TData>({
|
|
172
|
+
table: tableProp,
|
|
173
|
+
className,
|
|
174
|
+
children,
|
|
175
|
+
asChild,
|
|
176
|
+
...props
|
|
177
|
+
}: React.ComponentProps<typeof Button> & { table?: DataTableInstance<TData> }) {
|
|
178
|
+
const table = useDataTableContext(tableProp)
|
|
179
|
+
const isFiltered = table.getState().columnFilters.length > 0 || !!table.getState().globalFilter
|
|
180
|
+
if (!isFiltered) return null
|
|
181
|
+
return (
|
|
182
|
+
<Button
|
|
183
|
+
data-slot="data-table-reset-filters"
|
|
184
|
+
variant="ghost"
|
|
185
|
+
size="sm"
|
|
186
|
+
asChild={asChild}
|
|
187
|
+
onClick={() => {
|
|
188
|
+
table.resetColumnFilters()
|
|
189
|
+
table.setGlobalFilter("")
|
|
190
|
+
}}
|
|
191
|
+
className={cn("h-8 px-2 lg:px-3", className)}
|
|
192
|
+
{...props}
|
|
193
|
+
>
|
|
194
|
+
{asChild
|
|
195
|
+
? children
|
|
196
|
+
: (children ?? (
|
|
197
|
+
<>
|
|
198
|
+
Reset
|
|
199
|
+
<XIcon />
|
|
200
|
+
</>
|
|
201
|
+
))}
|
|
202
|
+
</Button>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* -------------------------------------------------------------------------- */
|
|
207
|
+
/* Faceted filter */
|
|
208
|
+
/* -------------------------------------------------------------------------- */
|
|
209
|
+
|
|
210
|
+
type FacetOption = {
|
|
211
|
+
label: string
|
|
212
|
+
value: string
|
|
213
|
+
icon?: React.ComponentType<{ className?: string }>
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function DataTableFacetedFilter<TData, TValue>({
|
|
217
|
+
table: tableProp,
|
|
218
|
+
columnId,
|
|
219
|
+
column: columnProp,
|
|
220
|
+
title,
|
|
221
|
+
options,
|
|
222
|
+
}: {
|
|
223
|
+
table?: DataTableInstance<TData>
|
|
224
|
+
columnId?: string
|
|
225
|
+
column?: DataTableColumn<TData, TValue>
|
|
226
|
+
title?: string
|
|
227
|
+
options: FacetOption[]
|
|
228
|
+
}) {
|
|
229
|
+
const table = useDataTableContext(tableProp)
|
|
230
|
+
const column = columnProp ?? (columnId ? table.getColumn(columnId) : undefined)
|
|
231
|
+
const facets = column?.getFacetedUniqueValues()
|
|
232
|
+
const selectedValues = new Set((column?.getFilterValue() as string[]) ?? [])
|
|
233
|
+
|
|
234
|
+
const { contains } = useFilter({ sensitivity: "base" })
|
|
235
|
+
const { collection, filter } = useListCollection<FacetOption>({
|
|
236
|
+
initialItems: options,
|
|
237
|
+
itemToString: (item) => item.label,
|
|
238
|
+
itemToValue: (item) => item.value,
|
|
239
|
+
filter: contains,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const toggle = (value: string) => {
|
|
243
|
+
const next = new Set(selectedValues)
|
|
244
|
+
if (next.has(value)) next.delete(value)
|
|
245
|
+
else next.add(value)
|
|
246
|
+
const values = Array.from(next)
|
|
247
|
+
column?.setFilterValue(values.length ? values : undefined)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<Popover positioning={{ placement: "bottom-start" }}>
|
|
252
|
+
<PopoverTrigger asChild>
|
|
253
|
+
<Button data-slot="data-table-faceted-filter-trigger" variant="outline" size="sm" className="h-8 border-dashed">
|
|
254
|
+
<PlusCircleIcon />
|
|
255
|
+
{title}
|
|
256
|
+
{selectedValues.size > 0 && (
|
|
257
|
+
<>
|
|
258
|
+
<Separator orientation="vertical" className="mx-1 h-4" />
|
|
259
|
+
<Badge variant="secondary" className="rounded-sm px-1 font-normal lg:hidden">
|
|
260
|
+
{selectedValues.size}
|
|
261
|
+
</Badge>
|
|
262
|
+
<div className="hidden gap-1 lg:flex">
|
|
263
|
+
{selectedValues.size > 2 ? (
|
|
264
|
+
<Badge variant="secondary" className="rounded-sm px-1 font-normal">
|
|
265
|
+
{selectedValues.size} selected
|
|
266
|
+
</Badge>
|
|
267
|
+
) : (
|
|
268
|
+
options
|
|
269
|
+
.filter((option) => selectedValues.has(option.value))
|
|
270
|
+
.map((option) => (
|
|
271
|
+
<Badge key={option.value} variant="secondary" className="rounded-sm px-1 font-normal">
|
|
272
|
+
{option.label}
|
|
273
|
+
</Badge>
|
|
274
|
+
))
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
</>
|
|
278
|
+
)}
|
|
279
|
+
</Button>
|
|
280
|
+
</PopoverTrigger>
|
|
281
|
+
<PopoverContent data-slot="data-table-faceted-filter-content" className="w-50 p-0">
|
|
282
|
+
<Command collection={collection} value={[]} onSelect={({ value }) => toggle(value)}>
|
|
283
|
+
<CommandInput placeholder={title} onValueChange={filter} />
|
|
284
|
+
<CommandList>
|
|
285
|
+
<CommandEmpty>No results found.</CommandEmpty>
|
|
286
|
+
<CommandGroup>
|
|
287
|
+
{collection.items.map((option) => {
|
|
288
|
+
const isSelected = selectedValues.has(option.value)
|
|
289
|
+
return (
|
|
290
|
+
<CommandItem
|
|
291
|
+
key={option.value}
|
|
292
|
+
item={option}
|
|
293
|
+
className="**:data-[slot=command-item-indicator]:hidden"
|
|
294
|
+
>
|
|
295
|
+
<div
|
|
296
|
+
className={cn(
|
|
297
|
+
"flex size-4 items-center justify-center rounded-sm border border-primary",
|
|
298
|
+
isSelected ? "bg-primary text-primary-foreground" : "opacity-50 [&_svg]:invisible"
|
|
299
|
+
)}
|
|
300
|
+
>
|
|
301
|
+
<CheckIcon className="size-3.5" />
|
|
302
|
+
</div>
|
|
303
|
+
{option.icon && <option.icon className="size-4 text-muted-foreground" />}
|
|
304
|
+
<span>{option.label}</span>
|
|
305
|
+
{facets?.get(option.value) ? (
|
|
306
|
+
<span className="ml-auto flex size-4 items-center justify-center font-mono text-xs">
|
|
307
|
+
{facets.get(option.value)}
|
|
308
|
+
</span>
|
|
309
|
+
) : null}
|
|
310
|
+
</CommandItem>
|
|
311
|
+
)
|
|
312
|
+
})}
|
|
313
|
+
</CommandGroup>
|
|
314
|
+
{selectedValues.size > 0 && (
|
|
315
|
+
<>
|
|
316
|
+
<CommandSeparator />
|
|
317
|
+
<CommandGroup>
|
|
318
|
+
<CommandItem
|
|
319
|
+
item={{ label: "Clear filters", value: "__clear__" }}
|
|
320
|
+
onSelect={() => column?.setFilterValue(undefined)}
|
|
321
|
+
className="justify-center text-center **:data-[slot=command-item-indicator]:hidden"
|
|
322
|
+
>
|
|
323
|
+
Clear filters
|
|
324
|
+
</CommandItem>
|
|
325
|
+
</CommandGroup>
|
|
326
|
+
</>
|
|
327
|
+
)}
|
|
328
|
+
</CommandList>
|
|
329
|
+
</Command>
|
|
330
|
+
</PopoverContent>
|
|
331
|
+
</Popover>
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* -------------------------------------------------------------------------- */
|
|
336
|
+
/* View options */
|
|
337
|
+
/* -------------------------------------------------------------------------- */
|
|
338
|
+
|
|
339
|
+
function DataTableViewOptions<TData>({
|
|
340
|
+
table: tableProp,
|
|
341
|
+
className,
|
|
342
|
+
children,
|
|
343
|
+
}: {
|
|
344
|
+
table?: DataTableInstance<TData>
|
|
345
|
+
className?: string
|
|
346
|
+
children?: React.ReactNode
|
|
347
|
+
}) {
|
|
348
|
+
const table = useDataTableContext(tableProp)
|
|
349
|
+
return (
|
|
350
|
+
<DropdownMenu positioning={{ placement: "bottom-end" }}>
|
|
351
|
+
<DropdownMenuTrigger asChild>
|
|
352
|
+
<Button
|
|
353
|
+
data-slot="data-table-view-options-trigger"
|
|
354
|
+
variant="outline"
|
|
355
|
+
size="sm"
|
|
356
|
+
className={cn("ml-auto hidden h-8 lg:flex", className)}
|
|
357
|
+
>
|
|
358
|
+
<Settings2Icon />
|
|
359
|
+
{children ?? "View"}
|
|
360
|
+
</Button>
|
|
361
|
+
</DropdownMenuTrigger>
|
|
362
|
+
<DropdownMenuContent data-slot="data-table-view-options-content" className="w-37.5">
|
|
363
|
+
<DropdownMenuGroup>
|
|
364
|
+
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
|
|
365
|
+
<DropdownMenuSeparator />
|
|
366
|
+
{table
|
|
367
|
+
.getAllColumns()
|
|
368
|
+
.filter((column) => typeof column.accessorFn !== "undefined" && column.getCanHide())
|
|
369
|
+
.map((column) => (
|
|
370
|
+
<DropdownMenuCheckboxItem
|
|
371
|
+
key={column.id}
|
|
372
|
+
value={column.id}
|
|
373
|
+
className="capitalize"
|
|
374
|
+
checked={column.getIsVisible()}
|
|
375
|
+
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
|
376
|
+
closeOnSelect={false}
|
|
377
|
+
>
|
|
378
|
+
{column.label ?? column.id}
|
|
379
|
+
</DropdownMenuCheckboxItem>
|
|
380
|
+
))}
|
|
381
|
+
</DropdownMenuGroup>
|
|
382
|
+
</DropdownMenuContent>
|
|
383
|
+
</DropdownMenu>
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* -------------------------------------------------------------------------- */
|
|
388
|
+
/* Column header */
|
|
389
|
+
/* -------------------------------------------------------------------------- */
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* A header cell bound to a column id. Sortable columns get the sort/hide menu
|
|
393
|
+
* with `children` as the title; hidden columns render nothing.
|
|
394
|
+
*/
|
|
395
|
+
function DataTableHead<TData>({
|
|
396
|
+
column: columnId,
|
|
397
|
+
table: tableProp,
|
|
398
|
+
className,
|
|
399
|
+
children,
|
|
400
|
+
...props
|
|
401
|
+
}: React.ComponentProps<typeof TableHead> & { column?: string; table?: DataTableInstance<TData> }) {
|
|
402
|
+
const table = useDataTableContext(tableProp)
|
|
403
|
+
const column = columnId ? table.getColumn(columnId) : undefined
|
|
404
|
+
if (column && !column.getIsVisible()) return null
|
|
405
|
+
if (!column?.getCanSort()) {
|
|
406
|
+
return (
|
|
407
|
+
<TableHead data-slot="data-table-head" data-column={columnId} className={className} {...props}>
|
|
408
|
+
{children}
|
|
409
|
+
</TableHead>
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
const sorted = column.getIsSorted()
|
|
413
|
+
return (
|
|
414
|
+
<TableHead data-slot="data-table-head" data-column={columnId} className={className} {...props}>
|
|
415
|
+
<DropdownMenu positioning={{ placement: "bottom-start" }}>
|
|
416
|
+
<DropdownMenuTrigger asChild>
|
|
417
|
+
<Button variant="ghost" size="sm" className="-ml-2.5 h-8 data-open:bg-accent">
|
|
418
|
+
<span>{children}</span>
|
|
419
|
+
{sorted === "desc" ? <ArrowDownIcon /> : sorted === "asc" ? <ArrowUpIcon /> : <ChevronsUpDownIcon />}
|
|
420
|
+
</Button>
|
|
421
|
+
</DropdownMenuTrigger>
|
|
422
|
+
<DropdownMenuContent>
|
|
423
|
+
<DropdownMenuItem value="asc" onSelect={() => column.toggleSorting(false)}>
|
|
424
|
+
<ArrowUpIcon className="text-muted-foreground/70" />
|
|
425
|
+
Asc
|
|
426
|
+
</DropdownMenuItem>
|
|
427
|
+
<DropdownMenuItem value="desc" onSelect={() => column.toggleSorting(true)}>
|
|
428
|
+
<ArrowDownIcon className="text-muted-foreground/70" />
|
|
429
|
+
Desc
|
|
430
|
+
</DropdownMenuItem>
|
|
431
|
+
{column.getCanHide() && (
|
|
432
|
+
<>
|
|
433
|
+
<DropdownMenuSeparator />
|
|
434
|
+
<DropdownMenuItem value="hide" onSelect={() => column.toggleVisibility(false)}>
|
|
435
|
+
<EyeOffIcon className="text-muted-foreground/70" />
|
|
436
|
+
Hide
|
|
437
|
+
</DropdownMenuItem>
|
|
438
|
+
</>
|
|
439
|
+
)}
|
|
440
|
+
</DropdownMenuContent>
|
|
441
|
+
</DropdownMenu>
|
|
442
|
+
</TableHead>
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* -------------------------------------------------------------------------- */
|
|
447
|
+
/* Selection cells */
|
|
448
|
+
/* -------------------------------------------------------------------------- */
|
|
449
|
+
|
|
450
|
+
function DataTableSelectAll<TData>({
|
|
451
|
+
table,
|
|
452
|
+
className,
|
|
453
|
+
...props
|
|
454
|
+
}: Omit<React.ComponentProps<typeof Checkbox>, "checked" | "onCheckedChange"> & {
|
|
455
|
+
table: DataTableInstance<TData>
|
|
456
|
+
}) {
|
|
457
|
+
return (
|
|
458
|
+
<Checkbox
|
|
459
|
+
data-slot="data-table-select-all"
|
|
460
|
+
checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")}
|
|
461
|
+
onCheckedChange={({ checked }) => table.toggleAllPageRowsSelected(checked === true)}
|
|
462
|
+
aria-label="Select all"
|
|
463
|
+
className={cn("translate-y-0.5", className)}
|
|
464
|
+
{...props}
|
|
465
|
+
/>
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function DataTableSelectRow<TData>({
|
|
470
|
+
row,
|
|
471
|
+
className,
|
|
472
|
+
...props
|
|
473
|
+
}: Omit<React.ComponentProps<typeof Checkbox>, "checked" | "onCheckedChange"> & {
|
|
474
|
+
row: DataTableRowInstance<TData>
|
|
475
|
+
}) {
|
|
476
|
+
return (
|
|
477
|
+
<Checkbox
|
|
478
|
+
data-slot="data-table-select-row"
|
|
479
|
+
checked={row.getIsSelected()}
|
|
480
|
+
onCheckedChange={({ checked }) => row.toggleSelected(checked === true)}
|
|
481
|
+
aria-label="Select row"
|
|
482
|
+
className={cn("translate-y-0.5", className)}
|
|
483
|
+
{...props}
|
|
484
|
+
/>
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* -------------------------------------------------------------------------- */
|
|
489
|
+
/* Row actions */
|
|
490
|
+
/* -------------------------------------------------------------------------- */
|
|
491
|
+
|
|
492
|
+
function DataTableRowActions({
|
|
493
|
+
children,
|
|
494
|
+
className,
|
|
495
|
+
trigger,
|
|
496
|
+
...props
|
|
497
|
+
}: React.ComponentProps<typeof DropdownMenu> & {
|
|
498
|
+
className?: string
|
|
499
|
+
/** Replace the default ellipsis button with your own element. */
|
|
500
|
+
trigger?: React.ReactElement
|
|
501
|
+
}) {
|
|
502
|
+
return (
|
|
503
|
+
<DropdownMenu positioning={{ placement: "bottom-end" }} {...props}>
|
|
504
|
+
<DropdownMenuTrigger asChild>
|
|
505
|
+
{trigger ?? (
|
|
506
|
+
<Button
|
|
507
|
+
data-slot="data-table-row-actions-trigger"
|
|
508
|
+
variant="ghost"
|
|
509
|
+
size="icon"
|
|
510
|
+
className={cn("size-8 data-open:bg-muted", className)}
|
|
511
|
+
>
|
|
512
|
+
<EllipsisIcon />
|
|
513
|
+
<span className="sr-only">Open menu</span>
|
|
514
|
+
</Button>
|
|
515
|
+
)}
|
|
516
|
+
</DropdownMenuTrigger>
|
|
517
|
+
<DropdownMenuContent data-slot="data-table-row-actions-content" className="w-40">
|
|
518
|
+
{children}
|
|
519
|
+
</DropdownMenuContent>
|
|
520
|
+
</DropdownMenu>
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* -------------------------------------------------------------------------- */
|
|
525
|
+
/* Table rendering */
|
|
526
|
+
/* -------------------------------------------------------------------------- */
|
|
527
|
+
|
|
528
|
+
function DataTableContainer({ className, ...props }: React.ComponentProps<"div">) {
|
|
529
|
+
return (
|
|
530
|
+
<div data-slot="data-table-container" className={cn("overflow-hidden rounded-md border", className)} {...props} />
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** The header row. Put `DataTableHead` cells inside, one per column, in display order. */
|
|
535
|
+
function DataTableHeader({ className, children, ...props }: React.ComponentProps<typeof TableHeader>) {
|
|
536
|
+
return (
|
|
537
|
+
<TableHeader data-slot="data-table-header" className={className} {...props}>
|
|
538
|
+
<TableRow>{children}</TableRow>
|
|
539
|
+
</TableHeader>
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** A body row bound to a row instance; carries the selected state. */
|
|
544
|
+
function DataTableRow<TData>({
|
|
545
|
+
row,
|
|
546
|
+
className,
|
|
547
|
+
...props
|
|
548
|
+
}: React.ComponentProps<typeof TableRow> & { row: DataTableRowInstance<TData> }) {
|
|
549
|
+
return (
|
|
550
|
+
<TableRow
|
|
551
|
+
data-slot="data-table-row"
|
|
552
|
+
data-state={row.getIsSelected() ? "selected" : undefined}
|
|
553
|
+
className={cn("group/row", className)}
|
|
554
|
+
{...props}
|
|
555
|
+
/>
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/** A body cell bound to a column id; hidden columns render nothing. */
|
|
560
|
+
function DataTableCell<TData>({
|
|
561
|
+
column: columnId,
|
|
562
|
+
table: tableProp,
|
|
563
|
+
className,
|
|
564
|
+
...props
|
|
565
|
+
}: React.ComponentProps<typeof TableCell> & { column?: string; table?: DataTableInstance<TData> }) {
|
|
566
|
+
const table = useDataTableContext(tableProp)
|
|
567
|
+
const column = columnId ? table.getColumn(columnId) : undefined
|
|
568
|
+
if (column && !column.getIsVisible()) return null
|
|
569
|
+
return <TableCell data-slot="data-table-cell" data-column={columnId} className={className} {...props} />
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Renders the current page of rows. `children` receives each row and returns
|
|
574
|
+
* its cells (wrap them in `DataTableRow`, or return a fragment of cells to get
|
|
575
|
+
* a default row). Shows `empty` when there are no rows.
|
|
576
|
+
*/
|
|
577
|
+
function DataTableBody<TData>({
|
|
578
|
+
table: tableProp,
|
|
579
|
+
empty = "No results.",
|
|
580
|
+
children,
|
|
581
|
+
...props
|
|
582
|
+
}: Omit<React.ComponentProps<typeof TableBody>, "children"> & {
|
|
583
|
+
table?: DataTableInstance<TData>
|
|
584
|
+
empty?: React.ReactNode
|
|
585
|
+
children: (row: DataTableRowInstance<TData>) => React.ReactNode
|
|
586
|
+
}) {
|
|
587
|
+
const table = useDataTableContext(tableProp)
|
|
588
|
+
const rows = table.getRowModel().rows
|
|
589
|
+
return (
|
|
590
|
+
<TableBody data-slot="data-table-body" {...props}>
|
|
591
|
+
{rows.length ? (
|
|
592
|
+
rows.map((row) => {
|
|
593
|
+
const content = children(row)
|
|
594
|
+
return React.isValidElement(content) && content.type === DataTableRow ? (
|
|
595
|
+
<React.Fragment key={row.id}>{content}</React.Fragment>
|
|
596
|
+
) : (
|
|
597
|
+
<DataTableRow key={row.id} row={row}>
|
|
598
|
+
{content}
|
|
599
|
+
</DataTableRow>
|
|
600
|
+
)
|
|
601
|
+
})
|
|
602
|
+
) : (
|
|
603
|
+
<DataTableEmpty table={table}>{empty}</DataTableEmpty>
|
|
604
|
+
)}
|
|
605
|
+
</TableBody>
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** A single full-width row for the empty state; spans the visible columns. */
|
|
610
|
+
function DataTableEmpty<TData>({
|
|
611
|
+
table: tableProp,
|
|
612
|
+
colSpan,
|
|
613
|
+
className,
|
|
614
|
+
...props
|
|
615
|
+
}: React.ComponentProps<typeof TableCell> & { table?: DataTableInstance<TData>; colSpan?: number }) {
|
|
616
|
+
const table = useDataTableContext(tableProp)
|
|
617
|
+
const span = colSpan ?? Math.max(1, table.getAllColumns().filter((c) => c.getIsVisible()).length)
|
|
618
|
+
return (
|
|
619
|
+
<TableRow data-slot="data-table-empty">
|
|
620
|
+
<TableCell colSpan={span} className={cn("h-24 text-center", className)} {...props} />
|
|
621
|
+
</TableRow>
|
|
622
|
+
)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** The scrolling, bordered table. Compose `DataTableHeader` and `DataTableBody` inside. */
|
|
626
|
+
function DataTableTable({ className, ...props }: React.ComponentProps<typeof Table>) {
|
|
627
|
+
return (
|
|
628
|
+
<DataTableContainer>
|
|
629
|
+
<Table data-slot="data-table-table" className={cn("min-w-xl", className)} {...props} />
|
|
630
|
+
</DataTableContainer>
|
|
631
|
+
)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* -------------------------------------------------------------------------- */
|
|
635
|
+
/* Pagination */
|
|
636
|
+
/* -------------------------------------------------------------------------- */
|
|
637
|
+
|
|
638
|
+
function getPageNumbers(currentPage: number, totalPages: number) {
|
|
639
|
+
const maxVisiblePages = 5
|
|
640
|
+
const range: Array<number | "..."> = []
|
|
641
|
+
if (totalPages <= maxVisiblePages) {
|
|
642
|
+
for (let i = 1; i <= totalPages; i++) range.push(i)
|
|
643
|
+
} else {
|
|
644
|
+
range.push(1)
|
|
645
|
+
if (currentPage <= 3) {
|
|
646
|
+
for (let i = 2; i <= 4; i++) range.push(i)
|
|
647
|
+
range.push("...", totalPages)
|
|
648
|
+
} else if (currentPage >= totalPages - 2) {
|
|
649
|
+
range.push("...")
|
|
650
|
+
for (let i = totalPages - 3; i <= totalPages; i++) range.push(i)
|
|
651
|
+
} else {
|
|
652
|
+
range.push("...")
|
|
653
|
+
for (let i = currentPage - 1; i <= currentPage + 1; i++) range.push(i)
|
|
654
|
+
range.push("...", totalPages)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return range
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function DataTablePagination({ className, ...props }: React.ComponentProps<"div">) {
|
|
661
|
+
return (
|
|
662
|
+
<div
|
|
663
|
+
data-slot="data-table-pagination"
|
|
664
|
+
className={cn("flex flex-col-reverse items-center justify-between gap-4 px-2 md:flex-row", className)}
|
|
665
|
+
{...props}
|
|
666
|
+
/>
|
|
667
|
+
)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const defaultPageSizes = [10, 20, 30, 40, 50]
|
|
671
|
+
|
|
672
|
+
function DataTablePageSize<TData>({
|
|
673
|
+
table: tableProp,
|
|
674
|
+
pageSizes = defaultPageSizes,
|
|
675
|
+
className,
|
|
676
|
+
children,
|
|
677
|
+
}: {
|
|
678
|
+
table?: DataTableInstance<TData>
|
|
679
|
+
pageSizes?: number[]
|
|
680
|
+
className?: string
|
|
681
|
+
children?: React.ReactNode
|
|
682
|
+
}) {
|
|
683
|
+
const table = useDataTableContext(tableProp)
|
|
684
|
+
const collection = React.useMemo(() => createListCollection({ items: pageSizes.map(String) }), [pageSizes])
|
|
685
|
+
const pageSize = table.getState().pagination.pageSize
|
|
686
|
+
return (
|
|
687
|
+
<div data-slot="data-table-page-size" className={cn("flex items-center gap-2", className)}>
|
|
688
|
+
<Select
|
|
689
|
+
collection={collection}
|
|
690
|
+
value={[String(pageSize)]}
|
|
691
|
+
onValueChange={({ value }) => table.setPageSize(Number(value[0]))}
|
|
692
|
+
positioning={{ placement: "top", sameWidth: true }}
|
|
693
|
+
>
|
|
694
|
+
<SelectControl>
|
|
695
|
+
<SelectTrigger className="h-8 w-17.5">
|
|
696
|
+
<SelectValue placeholder={String(pageSize)} />
|
|
697
|
+
</SelectTrigger>
|
|
698
|
+
</SelectControl>
|
|
699
|
+
<SelectContent className="min-w-0">
|
|
700
|
+
{collection.items.map((size) => (
|
|
701
|
+
<SelectItem key={size} item={size}>
|
|
702
|
+
{size}
|
|
703
|
+
</SelectItem>
|
|
704
|
+
))}
|
|
705
|
+
</SelectContent>
|
|
706
|
+
</Select>
|
|
707
|
+
<p className="hidden text-sm font-medium sm:block">{children ?? "Rows per page"}</p>
|
|
708
|
+
</div>
|
|
709
|
+
)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function DataTablePageInfo<TData>({
|
|
713
|
+
table: tableProp,
|
|
714
|
+
className,
|
|
715
|
+
...props
|
|
716
|
+
}: React.ComponentProps<"div"> & { table?: DataTableInstance<TData> }) {
|
|
717
|
+
const table = useDataTableContext(tableProp)
|
|
718
|
+
const currentPage = table.getState().pagination.pageIndex + 1
|
|
719
|
+
const totalPages = Math.max(table.getPageCount(), 1)
|
|
720
|
+
return (
|
|
721
|
+
<div
|
|
722
|
+
data-slot="data-table-page-info"
|
|
723
|
+
className={cn("flex w-25 items-center justify-center text-sm font-medium", className)}
|
|
724
|
+
{...props}
|
|
725
|
+
>
|
|
726
|
+
Page {currentPage} of {totalPages}
|
|
727
|
+
</div>
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function DataTablePageNav<TData>({
|
|
732
|
+
table: tableProp,
|
|
733
|
+
className,
|
|
734
|
+
showEdges = true,
|
|
735
|
+
...props
|
|
736
|
+
}: React.ComponentProps<"div"> & {
|
|
737
|
+
table?: DataTableInstance<TData>
|
|
738
|
+
/** Show the first and last page buttons. */
|
|
739
|
+
showEdges?: boolean
|
|
740
|
+
}) {
|
|
741
|
+
const table = useDataTableContext(tableProp)
|
|
742
|
+
const currentPage = table.getState().pagination.pageIndex + 1
|
|
743
|
+
const totalPages = table.getPageCount()
|
|
744
|
+
const pageNumbers = getPageNumbers(currentPage, totalPages)
|
|
745
|
+
return (
|
|
746
|
+
<div data-slot="data-table-page-nav" className={cn("flex items-center gap-2", className)} {...props}>
|
|
747
|
+
{showEdges && (
|
|
748
|
+
<Button
|
|
749
|
+
variant="outline"
|
|
750
|
+
size="icon"
|
|
751
|
+
className="hidden size-8 md:inline-flex"
|
|
752
|
+
onClick={() => table.setPageIndex(0)}
|
|
753
|
+
disabled={!table.getCanPreviousPage()}
|
|
754
|
+
>
|
|
755
|
+
<span className="sr-only">Go to first page</span>
|
|
756
|
+
<ChevronsLeftIcon />
|
|
757
|
+
</Button>
|
|
758
|
+
)}
|
|
759
|
+
<Button
|
|
760
|
+
variant="outline"
|
|
761
|
+
size="icon"
|
|
762
|
+
className="size-8"
|
|
763
|
+
onClick={() => table.previousPage()}
|
|
764
|
+
disabled={!table.getCanPreviousPage()}
|
|
765
|
+
>
|
|
766
|
+
<span className="sr-only">Go to previous page</span>
|
|
767
|
+
<ChevronLeftIcon />
|
|
768
|
+
</Button>
|
|
769
|
+
{pageNumbers.map((pageNumber, index) =>
|
|
770
|
+
pageNumber === "..." ? (
|
|
771
|
+
<span key={`ellipsis-${index}`} className="px-1 text-sm text-muted-foreground">
|
|
772
|
+
...
|
|
773
|
+
</span>
|
|
774
|
+
) : (
|
|
775
|
+
<Button
|
|
776
|
+
key={pageNumber}
|
|
777
|
+
variant={currentPage === pageNumber ? "default" : "outline"}
|
|
778
|
+
size="sm"
|
|
779
|
+
className="h-8 min-w-8 px-2"
|
|
780
|
+
onClick={() => table.setPageIndex(pageNumber - 1)}
|
|
781
|
+
aria-current={currentPage === pageNumber ? "page" : undefined}
|
|
782
|
+
>
|
|
783
|
+
<span className="sr-only">Go to page </span>
|
|
784
|
+
{pageNumber}
|
|
785
|
+
</Button>
|
|
786
|
+
)
|
|
787
|
+
)}
|
|
788
|
+
<Button
|
|
789
|
+
variant="outline"
|
|
790
|
+
size="icon"
|
|
791
|
+
className="size-8"
|
|
792
|
+
onClick={() => table.nextPage()}
|
|
793
|
+
disabled={!table.getCanNextPage()}
|
|
794
|
+
>
|
|
795
|
+
<span className="sr-only">Go to next page</span>
|
|
796
|
+
<ChevronRightIcon />
|
|
797
|
+
</Button>
|
|
798
|
+
{showEdges && (
|
|
799
|
+
<Button
|
|
800
|
+
variant="outline"
|
|
801
|
+
size="icon"
|
|
802
|
+
className="hidden size-8 md:inline-flex"
|
|
803
|
+
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
804
|
+
disabled={!table.getCanNextPage()}
|
|
805
|
+
>
|
|
806
|
+
<span className="sr-only">Go to last page</span>
|
|
807
|
+
<ChevronsRightIcon />
|
|
808
|
+
</Button>
|
|
809
|
+
)}
|
|
810
|
+
</div>
|
|
811
|
+
)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* -------------------------------------------------------------------------- */
|
|
815
|
+
/* Bulk actions */
|
|
816
|
+
/* -------------------------------------------------------------------------- */
|
|
817
|
+
|
|
818
|
+
function DataTableBulkActions<TData>({
|
|
819
|
+
table: tableProp,
|
|
820
|
+
entityName = "row",
|
|
821
|
+
className,
|
|
822
|
+
children,
|
|
823
|
+
...props
|
|
824
|
+
}: React.ComponentProps<"div"> & {
|
|
825
|
+
table?: DataTableInstance<TData>
|
|
826
|
+
entityName?: string
|
|
827
|
+
}) {
|
|
828
|
+
const table = useDataTableContext(tableProp)
|
|
829
|
+
const selectedCount = table.getFilteredSelectedRowModel().rows.length
|
|
830
|
+
const { message: announcement, announce } = useLiveRegion({ clearAfter: 3000 })
|
|
831
|
+
const plural = selectedCount > 1 ? "s" : ""
|
|
832
|
+
|
|
833
|
+
React.useEffect(() => {
|
|
834
|
+
if (selectedCount === 0) return
|
|
835
|
+
announce(`${selectedCount} ${entityName}${plural} selected. Bulk actions toolbar is available.`)
|
|
836
|
+
}, [selectedCount, entityName, plural, announce])
|
|
837
|
+
|
|
838
|
+
return (
|
|
839
|
+
<>
|
|
840
|
+
<LiveRegion data-slot="data-table-live-region" message={announcement} />
|
|
841
|
+
<FloatingToolbar
|
|
842
|
+
open={selectedCount > 0}
|
|
843
|
+
data-slot="data-table-bulk-actions"
|
|
844
|
+
aria-label={`Bulk actions for ${selectedCount} selected ${entityName}${plural}`}
|
|
845
|
+
onEscape={() => table.resetRowSelection()}
|
|
846
|
+
className={className}
|
|
847
|
+
{...props}
|
|
848
|
+
>
|
|
849
|
+
<DataTableBulkActionsClear table={table} />
|
|
850
|
+
<Separator orientation="vertical" className="h-5" aria-hidden />
|
|
851
|
+
<DataTableBulkActionsCount table={table} entityName={entityName} />
|
|
852
|
+
<Separator orientation="vertical" className="h-5" aria-hidden />
|
|
853
|
+
{children}
|
|
854
|
+
</FloatingToolbar>
|
|
855
|
+
</>
|
|
856
|
+
)
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function DataTableBulkActionsClear<TData>({
|
|
860
|
+
table: tableProp,
|
|
861
|
+
className,
|
|
862
|
+
asChild,
|
|
863
|
+
children,
|
|
864
|
+
...props
|
|
865
|
+
}: React.ComponentProps<typeof Button> & { table?: DataTableInstance<TData> }) {
|
|
866
|
+
const table = useDataTableContext(tableProp)
|
|
867
|
+
return (
|
|
868
|
+
<Tooltip>
|
|
869
|
+
<TooltipTrigger asChild>
|
|
870
|
+
<Button
|
|
871
|
+
data-slot="data-table-bulk-actions-clear"
|
|
872
|
+
variant="outline"
|
|
873
|
+
size="icon"
|
|
874
|
+
asChild={asChild}
|
|
875
|
+
onClick={() => table.resetRowSelection()}
|
|
876
|
+
className={cn("size-6 rounded-full", className)}
|
|
877
|
+
aria-label="Clear selection"
|
|
878
|
+
{...props}
|
|
879
|
+
>
|
|
880
|
+
{asChild ? children : (children ?? <XIcon />)}
|
|
881
|
+
</Button>
|
|
882
|
+
</TooltipTrigger>
|
|
883
|
+
<TooltipContent>Clear selection (Escape)</TooltipContent>
|
|
884
|
+
</Tooltip>
|
|
885
|
+
)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function DataTableBulkActionsCount<TData>({
|
|
889
|
+
table: tableProp,
|
|
890
|
+
entityName = "row",
|
|
891
|
+
className,
|
|
892
|
+
...props
|
|
893
|
+
}: React.ComponentProps<"div"> & {
|
|
894
|
+
table?: DataTableInstance<TData>
|
|
895
|
+
entityName?: string
|
|
896
|
+
}) {
|
|
897
|
+
const table = useDataTableContext(tableProp)
|
|
898
|
+
const count = table.getFilteredSelectedRowModel().rows.length
|
|
899
|
+
return (
|
|
900
|
+
<div
|
|
901
|
+
data-slot="data-table-bulk-actions-count"
|
|
902
|
+
className={cn("flex items-center gap-1 text-sm", className)}
|
|
903
|
+
{...props}
|
|
904
|
+
>
|
|
905
|
+
<Badge className="min-w-8 justify-center rounded-lg" aria-label={`${count} selected`}>
|
|
906
|
+
{count}
|
|
907
|
+
</Badge>{" "}
|
|
908
|
+
<span className="hidden sm:inline">
|
|
909
|
+
{entityName}
|
|
910
|
+
{count > 1 ? "s" : ""}
|
|
911
|
+
</span>{" "}
|
|
912
|
+
selected
|
|
913
|
+
</div>
|
|
914
|
+
)
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function DataTableBulkActionTrigger({
|
|
918
|
+
label,
|
|
919
|
+
className,
|
|
920
|
+
children,
|
|
921
|
+
asChild,
|
|
922
|
+
...props
|
|
923
|
+
}: React.ComponentProps<typeof Button> & { label: string }) {
|
|
924
|
+
return (
|
|
925
|
+
<Tooltip>
|
|
926
|
+
<TooltipTrigger asChild>
|
|
927
|
+
<Button
|
|
928
|
+
data-slot="data-table-bulk-action-trigger"
|
|
929
|
+
variant="outline"
|
|
930
|
+
size="icon"
|
|
931
|
+
asChild={asChild}
|
|
932
|
+
className={cn("size-8", className)}
|
|
933
|
+
aria-label={label}
|
|
934
|
+
{...props}
|
|
935
|
+
>
|
|
936
|
+
{asChild ? (
|
|
937
|
+
children
|
|
938
|
+
) : (
|
|
939
|
+
<>
|
|
940
|
+
{children}
|
|
941
|
+
<span className="sr-only">{label}</span>
|
|
942
|
+
</>
|
|
943
|
+
)}
|
|
944
|
+
</Button>
|
|
945
|
+
</TooltipTrigger>
|
|
946
|
+
<TooltipContent>{label}</TooltipContent>
|
|
947
|
+
</Tooltip>
|
|
948
|
+
)
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export {
|
|
952
|
+
DataTable,
|
|
953
|
+
DataTableBody,
|
|
954
|
+
DataTableBulkActionTrigger,
|
|
955
|
+
DataTableBulkActions,
|
|
956
|
+
DataTableBulkActionsClear,
|
|
957
|
+
DataTableBulkActionsCount,
|
|
958
|
+
DataTableCell,
|
|
959
|
+
DataTableHead,
|
|
960
|
+
DataTableContainer,
|
|
961
|
+
DataTableEmpty,
|
|
962
|
+
DataTableFacetedFilter,
|
|
963
|
+
DataTableHeader,
|
|
964
|
+
DataTablePageInfo,
|
|
965
|
+
DataTablePageNav,
|
|
966
|
+
DataTablePageSize,
|
|
967
|
+
DataTablePagination,
|
|
968
|
+
DataTableResetFilters,
|
|
969
|
+
DataTableRow,
|
|
970
|
+
DataTableRowActions,
|
|
971
|
+
DataTableSearch,
|
|
972
|
+
DataTableSelectAll,
|
|
973
|
+
DataTableSelectRow,
|
|
974
|
+
DataTableTable,
|
|
975
|
+
DataTableToolbar,
|
|
976
|
+
DataTableToolbarGroup,
|
|
977
|
+
DataTableViewOptions,
|
|
978
|
+
getPageNumbers,
|
|
979
|
+
useDataTable,
|
|
980
|
+
useDataTableContext,
|
|
981
|
+
type ColumnDef,
|
|
982
|
+
type ColumnFiltersState,
|
|
983
|
+
type FacetOption,
|
|
984
|
+
type PaginationState,
|
|
985
|
+
type Row,
|
|
986
|
+
type RowSelectionState,
|
|
987
|
+
type SortingState,
|
|
988
|
+
type DataTableColumn,
|
|
989
|
+
type DataTableInstance,
|
|
990
|
+
type DataTableRowInstance,
|
|
991
|
+
type TableOptions,
|
|
992
|
+
type VisibilityState,
|
|
993
|
+
}
|