@catalystsoftware/ui 1.0.4 → 1.0.5
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/data/tailwind.config.js +261 -3821
- package/dist/components/catalyst-ui/buttons/burger.tsx +207 -0
- package/dist/components/catalyst-ui/core/data-display/timeline.tsx +210 -0
- package/dist/components/catalyst-ui/core/feedback/alert.tsx +491 -0
- package/dist/components/catalyst-ui/core/feedback/spinner-1.tsx +65 -0
- package/dist/components/catalyst-ui/core/feedback/toast.tsx +1857 -0
- package/dist/components/catalyst-ui/core/navigation/menu.tsx +164 -0
- package/dist/components/catalyst-ui/forms/toggle-class.tsx +176 -0
- package/dist/components/catalyst-ui/hooks/use-copy-to-clipboard.tsx +419 -0
- package/dist/components/catalyst-ui/hooks/use-counter.tsx +13 -0
- package/dist/components/catalyst-ui/hooks/use-event-listener.tsx +23 -0
- package/dist/components/catalyst-ui/hooks/use-export-markdown.tsx +47 -0
- package/dist/components/catalyst-ui/hooks/use-focus.tsx +17 -0
- package/dist/components/catalyst-ui/hooks/use-interval.tsx +23 -0
- package/dist/components/catalyst-ui/hooks/use-is-client.tsx +16 -0
- package/dist/components/catalyst-ui/hooks/use-media-query.tsx +19 -0
- package/dist/components/catalyst-ui/hooks/use-mobile.tsx +19 -0
- package/dist/components/catalyst-ui/hooks/use-resize-observer.tsx +81 -0
- package/dist/components/catalyst-ui/hooks/use-timeout.tsx +21 -0
- package/dist/components/catalyst-ui/hooks/use-timer.tsx +209 -0
- package/dist/components/catalyst-ui/hooks/use-toggle.tsx +12 -0
- package/dist/components/catalyst-ui/media/image.tsx +13 -0
- package/dist/components/catalyst-ui/overlays/dual-sidebar.tsx +4142 -0
- package/dist/components/catalyst-ui/overlays/sidebar-original.tsx +726 -0
- package/dist/components/catalyst-ui/primitives/accordion.tsx +250 -0
- package/dist/components/catalyst-ui/primitives/alert-dialog.tsx +126 -0
- package/dist/components/catalyst-ui/primitives/aspect-ratio.tsx +9 -0
- package/dist/components/catalyst-ui/primitives/avatar.tsx +296 -0
- package/dist/components/catalyst-ui/primitives/badge.tsx +57 -0
- package/dist/components/catalyst-ui/primitives/breadcrumb.tsx +101 -0
- package/dist/components/catalyst-ui/primitives/button.tsx +265 -0
- package/dist/components/catalyst-ui/primitives/calendar-v4.tsx +208 -0
- package/dist/components/catalyst-ui/primitives/calendar.tsx +295 -0
- package/dist/components/catalyst-ui/primitives/card.tsx +618 -0
- package/dist/components/catalyst-ui/primitives/carousel.tsx +238 -0
- package/dist/components/catalyst-ui/primitives/chart.tsx +347 -0
- package/dist/components/catalyst-ui/primitives/checkbox.tsx +225 -0
- package/dist/components/catalyst-ui/primitives/collapsible.tsx +212 -0
- package/dist/components/catalyst-ui/primitives/command.tsx +393 -0
- package/dist/components/catalyst-ui/primitives/context-menu.tsx +236 -0
- package/dist/components/catalyst-ui/primitives/dialog.tsx +471 -0
- package/dist/components/catalyst-ui/primitives/drawer.tsx +761 -0
- package/dist/components/catalyst-ui/primitives/dropdown-menu.tsx +290 -0
- package/dist/components/catalyst-ui/primitives/empty.tsx +104 -0
- package/dist/components/catalyst-ui/primitives/field.tsx +244 -0
- package/dist/components/catalyst-ui/primitives/hover-card.tsx +124 -0
- package/dist/components/catalyst-ui/primitives/input-otp.tsx +76 -0
- package/dist/components/catalyst-ui/primitives/input.tsx +64 -0
- package/dist/components/catalyst-ui/primitives/item.tsx +196 -0
- package/dist/components/catalyst-ui/primitives/kbd.tsx +75 -0
- package/dist/components/catalyst-ui/primitives/label.tsx +24 -0
- package/dist/components/catalyst-ui/primitives/navigation-menu.tsx +150 -0
- package/dist/components/catalyst-ui/primitives/pagination.tsx +198 -0
- package/dist/components/catalyst-ui/primitives/popover.tsx +232 -0
- package/dist/components/catalyst-ui/primitives/progress.tsx +34 -0
- package/dist/components/catalyst-ui/primitives/radio-group.tsx +43 -0
- package/dist/components/catalyst-ui/primitives/resizable.tsx +56 -0
- package/dist/components/catalyst-ui/primitives/select.tsx +155 -0
- package/dist/components/catalyst-ui/primitives/separator.tsx +74 -0
- package/dist/components/catalyst-ui/primitives/sheet.tsx +126 -0
- package/dist/components/catalyst-ui/primitives/skeleton.tsx +15 -0
- package/dist/components/catalyst-ui/primitives/slider.tsx +27 -0
- package/dist/components/catalyst-ui/primitives/switch.tsx +187 -0
- package/dist/components/catalyst-ui/primitives/tabs.tsx +335 -0
- package/dist/components/catalyst-ui/primitives/textarea.tsx +24 -0
- package/dist/components/catalyst-ui/primitives/toggle-group.tsx +55 -0
- package/dist/components/catalyst-ui/primitives/toggle.tsx +42 -0
- package/dist/components/catalyst-ui/primitives/tooltip.tsx +116 -0
- package/dist/components/catalyst-ui/utils/basic-auth.tsx +40 -0
- package/dist/components/catalyst-ui/utils/context-storage.tsx +19 -0
- package/dist/components/catalyst-ui/utils/cors-middleware.tsx +71 -0
- package/dist/components/catalyst-ui/utils/deferred-content.tsx +595 -0
- package/dist/components/catalyst-ui/utils/honeypot-middleware.tsx +38 -0
- package/dist/components/catalyst-ui/utils/incId.tsx +75 -0
- package/dist/components/catalyst-ui/utils/jwk-auth.tsx +36 -0
- package/dist/components/catalyst-ui/utils/request-id.tsx +14 -0
- package/dist/components/catalyst-ui/utils/secure-headers.tsx +37 -0
- package/dist/components/catalyst-ui/utils/server-timing.tsx +23 -0
- package/dist/components/catalyst-ui/utils/utils.ts +43 -0
- package/dist/components/catalyst-ui/utils/with-cookie.tsx +43 -0
- package/dist/components/catalyst-ui/x/accordian-x.tsx +428 -0
- package/dist/components/catalyst-ui/x/alert-x.tsx +413 -0
- package/dist/components/catalyst-ui/x/animated-text-x.tsx +2242 -0
- package/dist/components/catalyst-ui/x/avatar-x.tsx +515 -0
- package/dist/components/catalyst-ui/x/badge-x.tsx +670 -0
- package/dist/components/catalyst-ui/x/button-X.tsx +2857 -0
- package/dist/components/catalyst-ui/x/button-group-x.tsx +847 -0
- package/dist/components/catalyst-ui/x/calendar-x.tsx +1910 -0
- package/dist/components/catalyst-ui/x/card-x.tsx +2597 -0
- package/dist/components/catalyst-ui/x/checkbox-x.tsx +656 -0
- package/dist/components/catalyst-ui/x/collapsible-x.tsx +1360 -0
- package/dist/components/catalyst-ui/x/combobox-x.tsx +911 -0
- package/dist/components/catalyst-ui/x/data-table-x.tsx +1753 -0
- package/dist/components/catalyst-ui/x/date-picker-x.tsx +648 -0
- package/dist/components/catalyst-ui/x/dialog-x.tsx +659 -0
- package/dist/components/catalyst-ui/x/dropdown-menu-x.tsx +612 -0
- package/dist/components/catalyst-ui/x/hover-card-x.tsx +375 -0
- package/dist/components/catalyst-ui/x/icon-x.tsx +840 -0
- package/dist/components/catalyst-ui/x/input-mask-x.tsx +981 -0
- package/dist/components/catalyst-ui/x/input-otp-x.tsx +659 -0
- package/dist/components/catalyst-ui/x/loader-x.tsx +1757 -0
- package/dist/components/catalyst-ui/x/pagination-x.tsx +622 -0
- package/dist/components/catalyst-ui/x/popover-x.tsx +744 -0
- package/dist/components/catalyst-ui/x/radio-group-x.tsx +499 -0
- package/dist/components/catalyst-ui/x/select-x.tsx +1127 -0
- package/dist/components/catalyst-ui/x/sheet-x.tsx +668 -0
- package/dist/components/catalyst-ui/x/switch-x.tsx +681 -0
- package/dist/components/catalyst-ui/x/table-x.tsx +574 -0
- package/dist/components/catalyst-ui/x/tabs-x.tsx +839 -0
- package/dist/components/catalyst-ui/x/textarea-x.tsx +1263 -0
- package/dist/components/catalyst-ui/x/tooltip-x.tsx +396 -0
- package/dist/components/catalyst-ui/x/tracker-x.tsx +560 -0
- package/dist/data/bg-data.tsx +901 -0
- package/dist/data/buttons-data.tsx +2327 -0
- package/dist/data/charts-data.tsx +102 -0
- package/dist/data/chat-data.tsx +83 -0
- package/dist/data/code-data.tsx +1040 -0
- package/dist/data/comboboxes-data.tsx +1843 -0
- package/dist/data/command-data.tsx +1381 -0
- package/dist/data/core-data.tsx +15953 -0
- package/dist/data/crm-data.tsx +47 -0
- package/dist/data/data.tsx +159 -0
- package/dist/data/date-and-time-data.tsx +554 -0
- package/dist/data/dependencies.tsx +7 -0
- package/dist/data/ecommerce-data.tsx +1387 -0
- package/dist/data/forms-data.tsx +7890 -0
- package/dist/data/hooks-data.tsx +5487 -0
- package/dist/data/index.ts +34 -0
- package/dist/data/inputs-data.tsx +557 -0
- package/dist/data/interactive-data.tsx +5394 -0
- package/dist/data/lofi-data.tsx +18295 -0
- package/dist/data/marketing-data.tsx +2546 -0
- package/dist/data/media-data.tsx +1510 -0
- package/dist/data/motion-data.tsx +5801 -0
- package/dist/data/overlay-data.tsx +4136 -0
- package/dist/data/pdf-data.tsx +124 -0
- package/dist/data/pos-data.tsx +213 -0
- package/dist/data/postcss.config.js +6 -0
- package/dist/data/primitive-data.tsx +5170 -0
- package/dist/data/prompt-data.tsx +1226 -0
- package/dist/data/requiredLibs.ts +4 -0
- package/dist/data/sandbox-data.tsx +1 -0
- package/dist/data/sidebars-data.tsx +5421 -0
- package/dist/data/stacks-data.tsx +32 -0
- package/dist/data/table-data.tsx +706 -0
- package/dist/data/tailwind.config.js +270 -0
- package/dist/data/tailwind.config.ngin.js +3830 -0
- package/dist/data/tailwind.css +431 -0
- package/dist/data/tools-data.tsx +6910 -0
- package/dist/data/typography-data.tsx +2050 -0
- package/dist/data/utils-data.tsx +6500 -0
- package/dist/data/x-data.tsx +1171 -0
- package/dist/data.tsx +159 -0
- package/package.json +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -362
|
@@ -0,0 +1,1753 @@
|
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function DataTableX({ dataTable = 'default', children, ...props }: any) {
|
|
9
|
+
switch (dataTable) {
|
|
10
|
+
case 'Closable':
|
|
11
|
+
break
|
|
12
|
+
default:
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DataTableDemo = () => {
|
|
18
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
19
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
20
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
21
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
22
|
+
|
|
23
|
+
const table = useReactTable({
|
|
24
|
+
data,
|
|
25
|
+
columns,
|
|
26
|
+
onSortingChange: setSorting,
|
|
27
|
+
onColumnFiltersChange: setColumnFilters,
|
|
28
|
+
getCoreRowModel: getCoreRowModel(),
|
|
29
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
30
|
+
getSortedRowModel: getSortedRowModel(),
|
|
31
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
32
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
33
|
+
onRowSelectionChange: setRowSelection,
|
|
34
|
+
state: {
|
|
35
|
+
sorting,
|
|
36
|
+
columnFilters,
|
|
37
|
+
columnVisibility,
|
|
38
|
+
rowSelection
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className='w-full'>
|
|
44
|
+
<div className='rounded-md border'>
|
|
45
|
+
<Table>
|
|
46
|
+
<TableHeader>
|
|
47
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
48
|
+
<TableRow key={headerGroup.id}>
|
|
49
|
+
{headerGroup.headers.map(header => {
|
|
50
|
+
return (
|
|
51
|
+
<TableHead key={header.id}>
|
|
52
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
53
|
+
</TableHead>
|
|
54
|
+
)
|
|
55
|
+
})}
|
|
56
|
+
</TableRow>
|
|
57
|
+
))}
|
|
58
|
+
</TableHeader>
|
|
59
|
+
<TableBody>
|
|
60
|
+
{table.getRowModel().rows?.length ? (
|
|
61
|
+
table.getRowModel().rows.map(row => (
|
|
62
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
63
|
+
{row.getVisibleCells().map(cell => (
|
|
64
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
65
|
+
))}
|
|
66
|
+
</TableRow>
|
|
67
|
+
))
|
|
68
|
+
) : (
|
|
69
|
+
<TableRow>
|
|
70
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
71
|
+
No results.
|
|
72
|
+
</TableCell>
|
|
73
|
+
</TableRow>
|
|
74
|
+
)}
|
|
75
|
+
</TableBody>
|
|
76
|
+
</Table>
|
|
77
|
+
</div>
|
|
78
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Default data table</p>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const DataTableDensityDemo = () => {
|
|
84
|
+
const [density, setDensity] = useState<string>()
|
|
85
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
86
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
87
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
88
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
89
|
+
|
|
90
|
+
const table = useReactTable({
|
|
91
|
+
data,
|
|
92
|
+
columns,
|
|
93
|
+
onSortingChange: setSorting,
|
|
94
|
+
onColumnFiltersChange: setColumnFilters,
|
|
95
|
+
getCoreRowModel: getCoreRowModel(),
|
|
96
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
97
|
+
getSortedRowModel: getSortedRowModel(),
|
|
98
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
99
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
100
|
+
onRowSelectionChange: setRowSelection,
|
|
101
|
+
state: {
|
|
102
|
+
sorting,
|
|
103
|
+
columnFilters,
|
|
104
|
+
columnVisibility,
|
|
105
|
+
rowSelection
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div className='w-full'>
|
|
111
|
+
<div className='py-4'>
|
|
112
|
+
<Select value={density} onValueChange={setDensity}>
|
|
113
|
+
<SelectTrigger className='w-full max-w-3xs' aria-label='Density select'>
|
|
114
|
+
<SelectValue placeholder='Density' />
|
|
115
|
+
</SelectTrigger>
|
|
116
|
+
<SelectContent>
|
|
117
|
+
<SelectGroup>
|
|
118
|
+
<SelectLabel>Density</SelectLabel>
|
|
119
|
+
<SelectItem value='compact'>
|
|
120
|
+
<div className='flex items-center gap-2'>
|
|
121
|
+
<Rows4Icon className='size-4' />
|
|
122
|
+
Compact
|
|
123
|
+
</div>
|
|
124
|
+
</SelectItem>
|
|
125
|
+
<SelectItem value='standard' className='flex items-center gap-2'>
|
|
126
|
+
<div className='flex items-center gap-2'>
|
|
127
|
+
<Rows3Icon className='size-4' /> Standard
|
|
128
|
+
</div>
|
|
129
|
+
</SelectItem>
|
|
130
|
+
<SelectItem value='flexible' className='flex items-center gap-2'>
|
|
131
|
+
<div className='flex items-center gap-2'>
|
|
132
|
+
<Rows2Icon className='size-4' />
|
|
133
|
+
Flexible
|
|
134
|
+
</div>
|
|
135
|
+
</SelectItem>
|
|
136
|
+
</SelectGroup>
|
|
137
|
+
</SelectContent>
|
|
138
|
+
</Select>
|
|
139
|
+
</div>
|
|
140
|
+
<div className='rounded-md border'>
|
|
141
|
+
<Table
|
|
142
|
+
className={cn({
|
|
143
|
+
'[&_td]:py-px [&_th]:py-px': density === 'compact',
|
|
144
|
+
'[&_td]:py-1 [&_th]:py-1': density === 'standard',
|
|
145
|
+
'[&_td]:py-2 [&_th]:py-1': density === 'flexible'
|
|
146
|
+
})}
|
|
147
|
+
>
|
|
148
|
+
<TableHeader>
|
|
149
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
150
|
+
<TableRow key={headerGroup.id}>
|
|
151
|
+
{headerGroup.headers.map(header => {
|
|
152
|
+
return (
|
|
153
|
+
<TableHead key={header.id}>
|
|
154
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
155
|
+
</TableHead>
|
|
156
|
+
)
|
|
157
|
+
})}
|
|
158
|
+
</TableRow>
|
|
159
|
+
))}
|
|
160
|
+
</TableHeader>
|
|
161
|
+
<TableBody>
|
|
162
|
+
{table.getRowModel().rows?.length ? (
|
|
163
|
+
table.getRowModel().rows.map(row => (
|
|
164
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
165
|
+
{row.getVisibleCells().map(cell => (
|
|
166
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
167
|
+
))}
|
|
168
|
+
</TableRow>
|
|
169
|
+
))
|
|
170
|
+
) : (
|
|
171
|
+
<TableRow>
|
|
172
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
173
|
+
No results.
|
|
174
|
+
</TableCell>
|
|
175
|
+
</TableRow>
|
|
176
|
+
)}
|
|
177
|
+
</TableBody>
|
|
178
|
+
</Table>
|
|
179
|
+
</div>
|
|
180
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>
|
|
181
|
+
Density data table{' '}
|
|
182
|
+
<a
|
|
183
|
+
href='https://www.shadcnui-blocks.com/components/table'
|
|
184
|
+
className='hover:text-primary underline'
|
|
185
|
+
target='_blank'
|
|
186
|
+
>
|
|
187
|
+
Shadcn UI Blocks
|
|
188
|
+
</a>
|
|
189
|
+
</p>
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const DataTableColumnsVisibilityDemo = () => {
|
|
195
|
+
const [searchQuery, setSearchQuery] = useState<string>('')
|
|
196
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
197
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
198
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
199
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
200
|
+
|
|
201
|
+
const table = useReactTable({
|
|
202
|
+
data,
|
|
203
|
+
columns,
|
|
204
|
+
onSortingChange: setSorting,
|
|
205
|
+
onColumnFiltersChange: setColumnFilters,
|
|
206
|
+
getCoreRowModel: getCoreRowModel(),
|
|
207
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
208
|
+
getSortedRowModel: getSortedRowModel(),
|
|
209
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
210
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
211
|
+
onRowSelectionChange: setRowSelection,
|
|
212
|
+
state: {
|
|
213
|
+
sorting,
|
|
214
|
+
columnFilters,
|
|
215
|
+
columnVisibility,
|
|
216
|
+
rowSelection
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div className='w-full'>
|
|
222
|
+
<div className='py-4'>
|
|
223
|
+
<DropdownMenu>
|
|
224
|
+
<DropdownMenuTrigger asChild>
|
|
225
|
+
<Button variant='outline' className='w-full max-w-3xs justify-between'>
|
|
226
|
+
<span className='flex items-center gap-2'>
|
|
227
|
+
<Columns3Icon />
|
|
228
|
+
Columns
|
|
229
|
+
</span>{' '}
|
|
230
|
+
<ChevronDownIcon className='ml-3' />
|
|
231
|
+
</Button>
|
|
232
|
+
</DropdownMenuTrigger>
|
|
233
|
+
<DropdownMenuContent align='end'>
|
|
234
|
+
<div className='relative'>
|
|
235
|
+
<Input
|
|
236
|
+
value={searchQuery}
|
|
237
|
+
onChange={e => setSearchQuery(e.target.value)}
|
|
238
|
+
className='pl-8'
|
|
239
|
+
placeholder='Search'
|
|
240
|
+
onKeyDown={e => e.stopPropagation()}
|
|
241
|
+
/>
|
|
242
|
+
<SearchIcon className='absolute inset-y-0 left-2 my-auto size-4' />
|
|
243
|
+
</div>
|
|
244
|
+
<DropdownMenuSeparator />
|
|
245
|
+
{table
|
|
246
|
+
.getAllColumns()
|
|
247
|
+
.filter(column => column.getCanHide())
|
|
248
|
+
.map(column => {
|
|
249
|
+
if (searchQuery && !column.id.toLowerCase().includes(searchQuery.toLowerCase())) {
|
|
250
|
+
return null
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<DropdownMenuCheckboxItem
|
|
255
|
+
key={column.id}
|
|
256
|
+
className='capitalize'
|
|
257
|
+
checked={column.getIsVisible()}
|
|
258
|
+
onCheckedChange={value => column.toggleVisibility(!!value)}
|
|
259
|
+
onSelect={e => e.preventDefault()}
|
|
260
|
+
>
|
|
261
|
+
{column.id}
|
|
262
|
+
</DropdownMenuCheckboxItem>
|
|
263
|
+
)
|
|
264
|
+
})}
|
|
265
|
+
<DropdownMenuSeparator />
|
|
266
|
+
<DropdownMenuItem
|
|
267
|
+
onClick={() => {
|
|
268
|
+
table.resetColumnVisibility()
|
|
269
|
+
setSearchQuery('')
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
<RefreshCcwIcon /> Reset
|
|
273
|
+
</DropdownMenuItem>
|
|
274
|
+
</DropdownMenuContent>
|
|
275
|
+
</DropdownMenu>
|
|
276
|
+
</div>
|
|
277
|
+
<div className='rounded-md border'>
|
|
278
|
+
<Table>
|
|
279
|
+
<TableHeader>
|
|
280
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
281
|
+
<TableRow key={headerGroup.id}>
|
|
282
|
+
{headerGroup.headers.map(header => {
|
|
283
|
+
return (
|
|
284
|
+
<TableHead key={header.id}>
|
|
285
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
286
|
+
</TableHead>
|
|
287
|
+
)
|
|
288
|
+
})}
|
|
289
|
+
</TableRow>
|
|
290
|
+
))}
|
|
291
|
+
</TableHeader>
|
|
292
|
+
<TableBody>
|
|
293
|
+
{table.getRowModel().rows?.length ? (
|
|
294
|
+
table.getRowModel().rows.map(row => (
|
|
295
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
296
|
+
{row.getVisibleCells().map(cell => (
|
|
297
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
298
|
+
))}
|
|
299
|
+
</TableRow>
|
|
300
|
+
))
|
|
301
|
+
) : (
|
|
302
|
+
<TableRow>
|
|
303
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
304
|
+
No results.
|
|
305
|
+
</TableCell>
|
|
306
|
+
</TableRow>
|
|
307
|
+
)}
|
|
308
|
+
</TableBody>
|
|
309
|
+
</Table>
|
|
310
|
+
</div>
|
|
311
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Data table column visibility</p>
|
|
312
|
+
</div>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const DataTableWithColumnFilterDemo = () => {
|
|
317
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
318
|
+
|
|
319
|
+
const [sorting, setSorting] = useState<SortingState>([
|
|
320
|
+
{
|
|
321
|
+
id: 'price',
|
|
322
|
+
desc: false
|
|
323
|
+
}
|
|
324
|
+
])
|
|
325
|
+
|
|
326
|
+
const table = useReactTable({
|
|
327
|
+
data: items,
|
|
328
|
+
columns,
|
|
329
|
+
state: {
|
|
330
|
+
sorting,
|
|
331
|
+
columnFilters
|
|
332
|
+
},
|
|
333
|
+
onColumnFiltersChange: setColumnFilters,
|
|
334
|
+
getCoreRowModel: getCoreRowModel(),
|
|
335
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
336
|
+
getSortedRowModel: getSortedRowModel(),
|
|
337
|
+
getFacetedRowModel: getFacetedRowModel(),
|
|
338
|
+
getFacetedUniqueValues: getFacetedUniqueValues(),
|
|
339
|
+
getFacetedMinMaxValues: getFacetedMinMaxValues(),
|
|
340
|
+
onSortingChange: setSorting,
|
|
341
|
+
enableSortingRemoval: false
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div className='w-full'>
|
|
346
|
+
<div className='rounded-md border'>
|
|
347
|
+
<div className='flex flex-wrap gap-3 px-2 py-6'>
|
|
348
|
+
<div className='w-44'>
|
|
349
|
+
<Filter column={table.getColumn('product')!} />
|
|
350
|
+
</div>
|
|
351
|
+
<div className='w-36'>
|
|
352
|
+
<Filter column={table.getColumn('price')!} />
|
|
353
|
+
</div>
|
|
354
|
+
<div className='w-44'>
|
|
355
|
+
<Filter column={table.getColumn('availability')!} />
|
|
356
|
+
</div>
|
|
357
|
+
<div className='w-36'>
|
|
358
|
+
<Filter column={table.getColumn('rating')!} />
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
<Table>
|
|
362
|
+
<TableHeader>
|
|
363
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
364
|
+
<TableRow key={headerGroup.id} className='bg-muted/50'>
|
|
365
|
+
{headerGroup.headers.map(header => {
|
|
366
|
+
return (
|
|
367
|
+
<TableHead key={header.id} className='relative h-10 border-t select-none'>
|
|
368
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
369
|
+
</TableHead>
|
|
370
|
+
)
|
|
371
|
+
})}
|
|
372
|
+
</TableRow>
|
|
373
|
+
))}
|
|
374
|
+
</TableHeader>
|
|
375
|
+
<TableBody>
|
|
376
|
+
{table.getRowModel().rows?.length ? (
|
|
377
|
+
table.getRowModel().rows.map(row => (
|
|
378
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
379
|
+
{row.getVisibleCells().map(cell => (
|
|
380
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
381
|
+
))}
|
|
382
|
+
</TableRow>
|
|
383
|
+
))
|
|
384
|
+
) : (
|
|
385
|
+
<TableRow>
|
|
386
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
387
|
+
No results.
|
|
388
|
+
</TableCell>
|
|
389
|
+
</TableRow>
|
|
390
|
+
)}
|
|
391
|
+
</TableBody>
|
|
392
|
+
</Table>
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Data table with column filter</p>
|
|
396
|
+
</div>
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const DataTableWithSortableColumnDemo = () => {
|
|
401
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
402
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
403
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
404
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
405
|
+
|
|
406
|
+
const table = useReactTable({
|
|
407
|
+
data,
|
|
408
|
+
columns,
|
|
409
|
+
onSortingChange: setSorting,
|
|
410
|
+
onColumnFiltersChange: setColumnFilters,
|
|
411
|
+
getCoreRowModel: getCoreRowModel(),
|
|
412
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
413
|
+
getSortedRowModel: getSortedRowModel(),
|
|
414
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
415
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
416
|
+
onRowSelectionChange: setRowSelection,
|
|
417
|
+
state: {
|
|
418
|
+
sorting,
|
|
419
|
+
columnFilters,
|
|
420
|
+
columnVisibility,
|
|
421
|
+
rowSelection
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<div className='w-full'>
|
|
427
|
+
<div className='rounded-md border'>
|
|
428
|
+
<Table>
|
|
429
|
+
<TableHeader>
|
|
430
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
431
|
+
<TableRow key={headerGroup.id}>
|
|
432
|
+
{headerGroup.headers.map(header => {
|
|
433
|
+
return (
|
|
434
|
+
<TableHead
|
|
435
|
+
key={header.id}
|
|
436
|
+
aria-sort={
|
|
437
|
+
header.column.getIsSorted() === 'asc'
|
|
438
|
+
? 'ascending'
|
|
439
|
+
: header.column.getIsSorted() === 'desc'
|
|
440
|
+
? 'descending'
|
|
441
|
+
: 'none'
|
|
442
|
+
}
|
|
443
|
+
>
|
|
444
|
+
{header.isPlaceholder ? null : (
|
|
445
|
+
<div
|
|
446
|
+
className={cn(
|
|
447
|
+
header.column.getCanSort() &&
|
|
448
|
+
'flex h-full cursor-pointer items-center justify-between gap-2 select-none'
|
|
449
|
+
)}
|
|
450
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
451
|
+
onKeyDown={e => {
|
|
452
|
+
if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) {
|
|
453
|
+
e.preventDefault()
|
|
454
|
+
header.column.getToggleSortingHandler()?.(e)
|
|
455
|
+
}
|
|
456
|
+
}}
|
|
457
|
+
tabIndex={header.column.getCanSort() ? 0 : undefined}
|
|
458
|
+
>
|
|
459
|
+
<span className='truncate'>
|
|
460
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
461
|
+
</span>
|
|
462
|
+
{{
|
|
463
|
+
asc: <ChevronUpIcon className='shrink-0 opacity-60' size={16} aria-hidden='true' />,
|
|
464
|
+
desc: <ChevronDownIcon className='shrink-0 opacity-60' size={16} aria-hidden='true' />
|
|
465
|
+
}[header.column.getIsSorted() as string] ?? null}
|
|
466
|
+
</div>
|
|
467
|
+
)}
|
|
468
|
+
</TableHead>
|
|
469
|
+
)
|
|
470
|
+
})}
|
|
471
|
+
</TableRow>
|
|
472
|
+
))}
|
|
473
|
+
</TableHeader>
|
|
474
|
+
<TableBody>
|
|
475
|
+
{table.getRowModel().rows?.length ? (
|
|
476
|
+
table.getRowModel().rows.map(row => (
|
|
477
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
478
|
+
{row.getVisibleCells().map(cell => (
|
|
479
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
480
|
+
))}
|
|
481
|
+
</TableRow>
|
|
482
|
+
))
|
|
483
|
+
) : (
|
|
484
|
+
<TableRow>
|
|
485
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
486
|
+
No results.
|
|
487
|
+
</TableCell>
|
|
488
|
+
</TableRow>
|
|
489
|
+
)}
|
|
490
|
+
</TableBody>
|
|
491
|
+
</Table>
|
|
492
|
+
</div>
|
|
493
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Data table with sortable column</p>
|
|
494
|
+
</div>
|
|
495
|
+
)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const DataTableWithResizableColumnsDemo = () => {
|
|
499
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
500
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
501
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
502
|
+
|
|
503
|
+
const table = useReactTable({
|
|
504
|
+
data,
|
|
505
|
+
columns,
|
|
506
|
+
columnResizeMode: 'onChange',
|
|
507
|
+
onSortingChange: setSorting,
|
|
508
|
+
onColumnFiltersChange: setColumnFilters,
|
|
509
|
+
getCoreRowModel: getCoreRowModel(),
|
|
510
|
+
onRowSelectionChange: setRowSelection,
|
|
511
|
+
state: {
|
|
512
|
+
sorting,
|
|
513
|
+
columnFilters,
|
|
514
|
+
rowSelection
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
<div className='max-w-3xl max-md:max-w-full'>
|
|
520
|
+
<div className='rounded-md border'>
|
|
521
|
+
<Table
|
|
522
|
+
className='table-fixed'
|
|
523
|
+
style={{
|
|
524
|
+
width: table.getCenterTotalSize()
|
|
525
|
+
}}
|
|
526
|
+
>
|
|
527
|
+
<TableHeader>
|
|
528
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
529
|
+
<TableRow key={headerGroup.id}>
|
|
530
|
+
{headerGroup.headers.map(header => {
|
|
531
|
+
return (
|
|
532
|
+
<TableHead
|
|
533
|
+
key={header.id}
|
|
534
|
+
className='group/head relative h-10 select-none last:[&>.cursor-col-resize]:opacity-0'
|
|
535
|
+
{...{
|
|
536
|
+
colSpan: header.colSpan,
|
|
537
|
+
style: {
|
|
538
|
+
width: header.getSize()
|
|
539
|
+
}
|
|
540
|
+
}}
|
|
541
|
+
>
|
|
542
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
543
|
+
{header.column.getCanResize() && (
|
|
544
|
+
<div
|
|
545
|
+
{...{
|
|
546
|
+
onDoubleClick: () => header.column.resetSize(),
|
|
547
|
+
onMouseDown: header.getResizeHandler(),
|
|
548
|
+
onTouchStart: header.getResizeHandler(),
|
|
549
|
+
className:
|
|
550
|
+
'group-last/head:hidden absolute top-0 h-full w-4 cursor-col-resize user-select-none touch-none -right-2 z-10 flex justify-center before:absolute before:w-px before:inset-y-0 before:bg-border before:translate-x-px'
|
|
551
|
+
}}
|
|
552
|
+
/>
|
|
553
|
+
)}
|
|
554
|
+
</TableHead>
|
|
555
|
+
)
|
|
556
|
+
})}
|
|
557
|
+
</TableRow>
|
|
558
|
+
))}
|
|
559
|
+
</TableHeader>
|
|
560
|
+
<TableBody>
|
|
561
|
+
{table.getRowModel().rows?.length ? (
|
|
562
|
+
table.getRowModel().rows.map(row => (
|
|
563
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
564
|
+
{row.getVisibleCells().map(cell => (
|
|
565
|
+
<TableCell key={cell.id} className='truncate'>
|
|
566
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
567
|
+
</TableCell>
|
|
568
|
+
))}
|
|
569
|
+
</TableRow>
|
|
570
|
+
))
|
|
571
|
+
) : (
|
|
572
|
+
<TableRow>
|
|
573
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
574
|
+
No results.
|
|
575
|
+
</TableCell>
|
|
576
|
+
</TableRow>
|
|
577
|
+
)}
|
|
578
|
+
</TableBody>
|
|
579
|
+
</Table>
|
|
580
|
+
</div>
|
|
581
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Data table with resizable columns</p>
|
|
582
|
+
</div>
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const DataTablePinnableColumnDemo = () => {
|
|
587
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
588
|
+
|
|
589
|
+
const table = useReactTable({
|
|
590
|
+
data,
|
|
591
|
+
columns,
|
|
592
|
+
getCoreRowModel: getCoreRowModel(),
|
|
593
|
+
getSortedRowModel: getSortedRowModel(),
|
|
594
|
+
onSortingChange: setSorting,
|
|
595
|
+
state: {
|
|
596
|
+
sorting
|
|
597
|
+
},
|
|
598
|
+
enableSortingRemoval: false
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
return (
|
|
602
|
+
<div className='w-full'>
|
|
603
|
+
<div className='rounded-md border'>
|
|
604
|
+
<Table className='[&_td]:border-border [&_th]:border-border border-separate border-spacing-0 [&_tfoot_td]:border-t [&_th]:border-b [&_tr]:border-none [&_tr:not(:last-child)_td]:border-b'>
|
|
605
|
+
<TableHeader>
|
|
606
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
607
|
+
<TableRow key={headerGroup.id}>
|
|
608
|
+
{headerGroup.headers.map(header => {
|
|
609
|
+
const { column } = header
|
|
610
|
+
const isPinned = column.getIsPinned()
|
|
611
|
+
const isLastLeftPinned = isPinned === 'left' && column.getIsLastColumn('left')
|
|
612
|
+
const isFirstRightPinned = isPinned === 'right' && column.getIsFirstColumn('right')
|
|
613
|
+
|
|
614
|
+
return (
|
|
615
|
+
<TableHead
|
|
616
|
+
key={header.id}
|
|
617
|
+
className='data-pinned:bg-muted/90 relative h-10 truncate data-pinned:backdrop-blur-xs [&:not([data-pinned]):has(+[data-pinned])_div.cursor-col-resize:last-child]:opacity-0 [&[data-last-col=left]_div.cursor-col-resize:last-child]:opacity-0 [&[data-pinned=right]:last-child_div.cursor-col-resize:last-child]:opacity-0'
|
|
618
|
+
colSpan={header.colSpan}
|
|
619
|
+
style={{ ...getPinningStyles(column) }}
|
|
620
|
+
data-pinned={isPinned || undefined}
|
|
621
|
+
data-last-col={isLastLeftPinned ? 'left' : isFirstRightPinned ? 'right' : undefined}
|
|
622
|
+
>
|
|
623
|
+
<div className='flex items-center justify-between gap-2'>
|
|
624
|
+
<span className='truncate'>
|
|
625
|
+
{header.isPlaceholder
|
|
626
|
+
? null
|
|
627
|
+
: flexRender(header.column.columnDef.header, header.getContext())}
|
|
628
|
+
</span>
|
|
629
|
+
|
|
630
|
+
{!header.isPlaceholder &&
|
|
631
|
+
header.column.getCanPin() &&
|
|
632
|
+
(header.column.getIsPinned() ? (
|
|
633
|
+
<Button
|
|
634
|
+
size='icon'
|
|
635
|
+
variant='ghost'
|
|
636
|
+
className='-mr-1 size-7'
|
|
637
|
+
onClick={() => header.column.pin(false)}
|
|
638
|
+
aria-label={`Unpin ${header.column.columnDef.header as string} column`}
|
|
639
|
+
title={`Unpin ${header.column.columnDef.header as string} column`}
|
|
640
|
+
>
|
|
641
|
+
<PinOffIcon className='opacity-60' aria-hidden='true' />
|
|
642
|
+
</Button>
|
|
643
|
+
) : (
|
|
644
|
+
<DropdownMenu>
|
|
645
|
+
<DropdownMenuTrigger asChild>
|
|
646
|
+
<Button
|
|
647
|
+
size='icon'
|
|
648
|
+
variant='ghost'
|
|
649
|
+
className='-mr-1 size-7'
|
|
650
|
+
aria-label={`Pin options for ${header.column.columnDef.header as string} column`}
|
|
651
|
+
title={`Pin options for ${header.column.columnDef.header as string} column`}
|
|
652
|
+
>
|
|
653
|
+
<EllipsisIcon className='opacity-60' aria-hidden='true' />
|
|
654
|
+
</Button>
|
|
655
|
+
</DropdownMenuTrigger>
|
|
656
|
+
<DropdownMenuContent align='end'>
|
|
657
|
+
<DropdownMenuItem onClick={() => header.column.pin('left')}>
|
|
658
|
+
<ArrowLeftFromLineIcon size={16} className='opacity-60' aria-hidden='true' />
|
|
659
|
+
Stick to left
|
|
660
|
+
</DropdownMenuItem>
|
|
661
|
+
<DropdownMenuItem onClick={() => header.column.pin('right')}>
|
|
662
|
+
<ArrowRightFromLineIcon size={16} className='opacity-60' aria-hidden='true' />
|
|
663
|
+
Stick to right
|
|
664
|
+
</DropdownMenuItem>
|
|
665
|
+
</DropdownMenuContent>
|
|
666
|
+
</DropdownMenu>
|
|
667
|
+
))}
|
|
668
|
+
</div>
|
|
669
|
+
</TableHead>
|
|
670
|
+
)
|
|
671
|
+
})}
|
|
672
|
+
</TableRow>
|
|
673
|
+
))}
|
|
674
|
+
</TableHeader>
|
|
675
|
+
<TableBody>
|
|
676
|
+
{table.getRowModel().rows?.length ? (
|
|
677
|
+
table.getRowModel().rows.map(row => (
|
|
678
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
679
|
+
{row.getVisibleCells().map(cell => {
|
|
680
|
+
const { column } = cell
|
|
681
|
+
const isPinned = column.getIsPinned()
|
|
682
|
+
const isLastLeftPinned = isPinned === 'left' && column.getIsLastColumn('left')
|
|
683
|
+
const isFirstRightPinned = isPinned === 'right' && column.getIsFirstColumn('right')
|
|
684
|
+
|
|
685
|
+
return (
|
|
686
|
+
<TableCell
|
|
687
|
+
key={cell.id}
|
|
688
|
+
className='data-pinned:bg-background/90 truncate data-pinned:backdrop-blur-xs'
|
|
689
|
+
style={{ ...getPinningStyles(column) }}
|
|
690
|
+
data-pinned={isPinned || undefined}
|
|
691
|
+
data-last-col={isLastLeftPinned ? 'left' : isFirstRightPinned ? 'right' : undefined}
|
|
692
|
+
>
|
|
693
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
694
|
+
</TableCell>
|
|
695
|
+
)
|
|
696
|
+
})}
|
|
697
|
+
</TableRow>
|
|
698
|
+
))
|
|
699
|
+
) : (
|
|
700
|
+
<TableRow>
|
|
701
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
702
|
+
No results.
|
|
703
|
+
</TableCell>
|
|
704
|
+
</TableRow>
|
|
705
|
+
)}
|
|
706
|
+
</TableBody>
|
|
707
|
+
</Table>
|
|
708
|
+
</div>
|
|
709
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Data table with pinnable columns</p>
|
|
710
|
+
</div>
|
|
711
|
+
)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const DraggableColumnDataTableDemo = () => {
|
|
715
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
716
|
+
const [columnOrder, setColumnOrder] = useState<string[]>(columns.map(column => column.id as string))
|
|
717
|
+
|
|
718
|
+
const table = useReactTable({
|
|
719
|
+
data,
|
|
720
|
+
columns,
|
|
721
|
+
columnResizeMode: 'onChange',
|
|
722
|
+
getCoreRowModel: getCoreRowModel(),
|
|
723
|
+
getSortedRowModel: getSortedRowModel(),
|
|
724
|
+
onSortingChange: setSorting,
|
|
725
|
+
state: {
|
|
726
|
+
sorting,
|
|
727
|
+
columnOrder
|
|
728
|
+
},
|
|
729
|
+
onColumnOrderChange: setColumnOrder,
|
|
730
|
+
enableSortingRemoval: false
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
function handleDragEnd(event: DragEndEvent) {
|
|
734
|
+
const { active, over } = event
|
|
735
|
+
|
|
736
|
+
if (active && over && active.id !== over.id) {
|
|
737
|
+
setColumnOrder(columnOrder => {
|
|
738
|
+
const oldIndex = columnOrder.indexOf(active.id as string)
|
|
739
|
+
const newIndex = columnOrder.indexOf(over.id as string)
|
|
740
|
+
|
|
741
|
+
return arrayMove(columnOrder, oldIndex, newIndex)
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {}))
|
|
747
|
+
|
|
748
|
+
return (
|
|
749
|
+
<div className='w-full'>
|
|
750
|
+
<div className='rounded-md border'>
|
|
751
|
+
<DndContext
|
|
752
|
+
id={useId()}
|
|
753
|
+
collisionDetection={closestCenter}
|
|
754
|
+
modifiers={[restrictToHorizontalAxis]}
|
|
755
|
+
onDragEnd={handleDragEnd}
|
|
756
|
+
sensors={sensors}
|
|
757
|
+
>
|
|
758
|
+
<Table>
|
|
759
|
+
<TableHeader>
|
|
760
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
761
|
+
<TableRow key={headerGroup.id} className='bg-muted/50 [&>th]:border-t-0'>
|
|
762
|
+
<SortableContext items={columnOrder} strategy={horizontalListSortingStrategy}>
|
|
763
|
+
{headerGroup.headers.map(header => (
|
|
764
|
+
<DraggableTableHeader key={header.id} header={header} />
|
|
765
|
+
))}
|
|
766
|
+
</SortableContext>
|
|
767
|
+
</TableRow>
|
|
768
|
+
))}
|
|
769
|
+
</TableHeader>
|
|
770
|
+
<TableBody>
|
|
771
|
+
{table.getRowModel().rows?.length ? (
|
|
772
|
+
table.getRowModel().rows.map(row => (
|
|
773
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
774
|
+
{row.getVisibleCells().map(cell => (
|
|
775
|
+
<SortableContext key={cell.id} items={columnOrder} strategy={horizontalListSortingStrategy}>
|
|
776
|
+
<DragAlongCell key={cell.id} cell={cell} />
|
|
777
|
+
</SortableContext>
|
|
778
|
+
))}
|
|
779
|
+
</TableRow>
|
|
780
|
+
))
|
|
781
|
+
) : (
|
|
782
|
+
<TableRow>
|
|
783
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
784
|
+
No results.
|
|
785
|
+
</TableCell>
|
|
786
|
+
</TableRow>
|
|
787
|
+
)}
|
|
788
|
+
</TableBody>
|
|
789
|
+
</Table>
|
|
790
|
+
</DndContext>
|
|
791
|
+
</div>
|
|
792
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Data table with draggable columns</p>
|
|
793
|
+
</div>
|
|
794
|
+
)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const DraggableTableHeader = ({ header }: { header: Header<Employee, unknown> }) => {
|
|
798
|
+
const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({
|
|
799
|
+
id: header.column.id
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
const style: CSSProperties = {
|
|
803
|
+
opacity: isDragging ? 0.8 : 1,
|
|
804
|
+
position: 'relative',
|
|
805
|
+
transform: CSS.Translate.toString(transform),
|
|
806
|
+
transition,
|
|
807
|
+
whiteSpace: 'nowrap',
|
|
808
|
+
width: header.column.getSize(),
|
|
809
|
+
zIndex: isDragging ? 1 : 0
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return (
|
|
813
|
+
<TableHead
|
|
814
|
+
ref={setNodeRef}
|
|
815
|
+
className='before:bg-border relative h-10 border-t before:absolute before:inset-y-0 before:left-0 before:w-px first:before:bg-transparent'
|
|
816
|
+
style={style}
|
|
817
|
+
aria-sort={
|
|
818
|
+
header.column.getIsSorted() === 'asc'
|
|
819
|
+
? 'ascending'
|
|
820
|
+
: header.column.getIsSorted() === 'desc'
|
|
821
|
+
? 'descending'
|
|
822
|
+
: 'none'
|
|
823
|
+
}
|
|
824
|
+
>
|
|
825
|
+
<div className='flex items-center justify-start gap-0.5'>
|
|
826
|
+
<Button
|
|
827
|
+
size='icon'
|
|
828
|
+
variant='ghost'
|
|
829
|
+
className='-ml-2 size-7'
|
|
830
|
+
{...attributes}
|
|
831
|
+
{...listeners}
|
|
832
|
+
aria-label='Drag to reorder'
|
|
833
|
+
>
|
|
834
|
+
<GripVerticalIcon className='opacity-60' aria-hidden='true' />
|
|
835
|
+
</Button>
|
|
836
|
+
<span className='grow truncate'>
|
|
837
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
838
|
+
</span>
|
|
839
|
+
<Button
|
|
840
|
+
size='icon'
|
|
841
|
+
variant='ghost'
|
|
842
|
+
className='group -mr-1 size-7'
|
|
843
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
844
|
+
onKeyDown={e => {
|
|
845
|
+
if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) {
|
|
846
|
+
e.preventDefault()
|
|
847
|
+
header.column.getToggleSortingHandler()?.(e)
|
|
848
|
+
}
|
|
849
|
+
}}
|
|
850
|
+
aria-label='Toggle sorting'
|
|
851
|
+
>
|
|
852
|
+
{{
|
|
853
|
+
asc: <ChevronUpIcon className='shrink-0 opacity-60' size={16} aria-hidden='true' />,
|
|
854
|
+
desc: <ChevronDownIcon className='shrink-0 opacity-60' size={16} aria-hidden='true' />
|
|
855
|
+
}[header.column.getIsSorted() as string] ?? (
|
|
856
|
+
<ChevronUpIcon className='shrink-0 opacity-0 group-hover:opacity-60' size={16} aria-hidden='true' />
|
|
857
|
+
)}
|
|
858
|
+
</Button>
|
|
859
|
+
</div>
|
|
860
|
+
</TableHead>
|
|
861
|
+
)
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const DragAlongCell = ({ cell }: { cell: Cell<Employee, unknown> }) => {
|
|
865
|
+
const { isDragging, setNodeRef, transform, transition } = useSortable({
|
|
866
|
+
id: cell.column.id
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
const style: CSSProperties = {
|
|
870
|
+
opacity: isDragging ? 0.8 : 1,
|
|
871
|
+
position: 'relative',
|
|
872
|
+
transform: CSS.Translate.toString(transform),
|
|
873
|
+
transition,
|
|
874
|
+
width: cell.column.getSize(),
|
|
875
|
+
zIndex: isDragging ? 1 : 0
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return (
|
|
879
|
+
<TableCell ref={setNodeRef} className='truncate' style={style}>
|
|
880
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
881
|
+
</TableCell>
|
|
882
|
+
)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const DataTableWithExpandableRowsDemo = () => {
|
|
886
|
+
const table = useReactTable({
|
|
887
|
+
data,
|
|
888
|
+
columns,
|
|
889
|
+
getRowCanExpand: row => Boolean(row.original.members),
|
|
890
|
+
getCoreRowModel: getCoreRowModel(),
|
|
891
|
+
getExpandedRowModel: getExpandedRowModel()
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
return (
|
|
895
|
+
<div className='w-full'>
|
|
896
|
+
<div className='rounded-md border'>
|
|
897
|
+
<Table>
|
|
898
|
+
<TableHeader>
|
|
899
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
900
|
+
<TableRow key={headerGroup.id} className='hover:bg-transparent'>
|
|
901
|
+
{headerGroup.headers.map(header => {
|
|
902
|
+
return (
|
|
903
|
+
<TableHead key={header.id}>
|
|
904
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
905
|
+
</TableHead>
|
|
906
|
+
)
|
|
907
|
+
})}
|
|
908
|
+
</TableRow>
|
|
909
|
+
))}
|
|
910
|
+
</TableHeader>
|
|
911
|
+
<TableBody>
|
|
912
|
+
{table.getRowModel().rows?.length ? (
|
|
913
|
+
table.getRowModel().rows.map(row => (
|
|
914
|
+
<Fragment key={row.id}>
|
|
915
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
916
|
+
{row.getVisibleCells().map(cell => (
|
|
917
|
+
<TableCell
|
|
918
|
+
key={cell.id}
|
|
919
|
+
className='[&:has([aria-expanded])]: [&:has([aria-expanded])]:w-px [&:has([aria-expanded])]:py-0'
|
|
920
|
+
>
|
|
921
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
922
|
+
</TableCell>
|
|
923
|
+
))}
|
|
924
|
+
</TableRow>
|
|
925
|
+
{row.getIsExpanded() && (
|
|
926
|
+
<TableRow className='hover:bg-transparent'>
|
|
927
|
+
<TableCell colSpan={row.getVisibleCells().length} className='p-0'>
|
|
928
|
+
<Table>
|
|
929
|
+
<TableHeader className='border-b'>
|
|
930
|
+
<TableRow className='hover:bg-muted/30!'>
|
|
931
|
+
<TableHead className='w-23.5'></TableHead>
|
|
932
|
+
<TableHead>Member Name</TableHead>
|
|
933
|
+
<TableHead>Role</TableHead>
|
|
934
|
+
<TableHead>Email</TableHead>
|
|
935
|
+
<TableHead>Hire Date</TableHead>
|
|
936
|
+
<TableHead>Date of Birth</TableHead>
|
|
937
|
+
</TableRow>
|
|
938
|
+
</TableHeader>
|
|
939
|
+
<TableBody>
|
|
940
|
+
{row.original.members.map(member => (
|
|
941
|
+
<TableRow key={member.email}>
|
|
942
|
+
<TableCell></TableCell>
|
|
943
|
+
<TableCell>{member.name}</TableCell>
|
|
944
|
+
<TableCell>{member.role}</TableCell>
|
|
945
|
+
<TableCell>{member.email}</TableCell>
|
|
946
|
+
<TableCell>{member.hireDate}</TableCell>
|
|
947
|
+
<TableCell>{member.dob}</TableCell>
|
|
948
|
+
</TableRow>
|
|
949
|
+
))}
|
|
950
|
+
</TableBody>
|
|
951
|
+
</Table>
|
|
952
|
+
</TableCell>
|
|
953
|
+
</TableRow>
|
|
954
|
+
)}
|
|
955
|
+
</Fragment>
|
|
956
|
+
))
|
|
957
|
+
) : (
|
|
958
|
+
<TableRow>
|
|
959
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
960
|
+
No results.
|
|
961
|
+
</TableCell>
|
|
962
|
+
</TableRow>
|
|
963
|
+
)}
|
|
964
|
+
</TableBody>
|
|
965
|
+
</Table>
|
|
966
|
+
</div>
|
|
967
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Data table with expanding sub-rows made</p>
|
|
968
|
+
</div>
|
|
969
|
+
)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const PaginatedDataTableDemo = () => {
|
|
973
|
+
const id = useId()
|
|
974
|
+
|
|
975
|
+
const [pagination, setPagination] = useState<PaginationState>({
|
|
976
|
+
pageIndex: 0,
|
|
977
|
+
pageSize: 5
|
|
978
|
+
})
|
|
979
|
+
|
|
980
|
+
const [sorting, setSorting] = useState<SortingState>([
|
|
981
|
+
{
|
|
982
|
+
id: 'product_name',
|
|
983
|
+
desc: false
|
|
984
|
+
}
|
|
985
|
+
])
|
|
986
|
+
|
|
987
|
+
const [data, setData] = useState<Item[]>([])
|
|
988
|
+
|
|
989
|
+
useEffect(() => {
|
|
990
|
+
async function fetchPosts() {
|
|
991
|
+
const res = await fetch('https://cdn.jsdelivr.net/gh/themeselection/fy-assets/assets/json/mobile-stock.json')
|
|
992
|
+
|
|
993
|
+
if (!res.ok) {
|
|
994
|
+
throw new Error('Failed to fetch data')
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const items = await res.json()
|
|
998
|
+
|
|
999
|
+
const data = await items.data
|
|
1000
|
+
|
|
1001
|
+
setData([...data, ...data])
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
fetchPosts()
|
|
1005
|
+
}, [])
|
|
1006
|
+
|
|
1007
|
+
const table = useReactTable({
|
|
1008
|
+
data,
|
|
1009
|
+
columns,
|
|
1010
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1011
|
+
getSortedRowModel: getSortedRowModel(),
|
|
1012
|
+
onSortingChange: setSorting,
|
|
1013
|
+
enableSortingRemoval: false,
|
|
1014
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
1015
|
+
onPaginationChange: setPagination,
|
|
1016
|
+
state: {
|
|
1017
|
+
sorting,
|
|
1018
|
+
pagination
|
|
1019
|
+
}
|
|
1020
|
+
})
|
|
1021
|
+
|
|
1022
|
+
return (
|
|
1023
|
+
<div className='space-y-4 md:w-full'>
|
|
1024
|
+
<div className='rounded-md border'>
|
|
1025
|
+
<Table>
|
|
1026
|
+
<TableHeader>
|
|
1027
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
1028
|
+
<TableRow key={headerGroup.id} className='hover:bg-transparent'>
|
|
1029
|
+
{headerGroup.headers.map(header => {
|
|
1030
|
+
return (
|
|
1031
|
+
<TableHead key={header.id} style={{ width: `${header.getSize()}px` }} className='h-11'>
|
|
1032
|
+
{header.isPlaceholder ? null : header.column.getCanSort() ? (
|
|
1033
|
+
<div
|
|
1034
|
+
className={cn(
|
|
1035
|
+
header.column.getCanSort() &&
|
|
1036
|
+
'flex h-full cursor-pointer items-center justify-between gap-2 select-none'
|
|
1037
|
+
)}
|
|
1038
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
1039
|
+
onKeyDown={e => {
|
|
1040
|
+
if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) {
|
|
1041
|
+
e.preventDefault()
|
|
1042
|
+
header.column.getToggleSortingHandler()?.(e)
|
|
1043
|
+
}
|
|
1044
|
+
}}
|
|
1045
|
+
tabIndex={header.column.getCanSort() ? 0 : undefined}
|
|
1046
|
+
>
|
|
1047
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
1048
|
+
{{
|
|
1049
|
+
asc: <ChevronUpIcon className='shrink-0 opacity-60' size={16} aria-hidden='true' />,
|
|
1050
|
+
desc: <ChevronDownIcon className='shrink-0 opacity-60' size={16} aria-hidden='true' />
|
|
1051
|
+
}[header.column.getIsSorted() as string] ?? null}
|
|
1052
|
+
</div>
|
|
1053
|
+
) : (
|
|
1054
|
+
flexRender(header.column.columnDef.header, header.getContext())
|
|
1055
|
+
)}
|
|
1056
|
+
</TableHead>
|
|
1057
|
+
)
|
|
1058
|
+
})}
|
|
1059
|
+
</TableRow>
|
|
1060
|
+
))}
|
|
1061
|
+
</TableHeader>
|
|
1062
|
+
<TableBody>
|
|
1063
|
+
{table.getRowModel().rows?.length ? (
|
|
1064
|
+
table.getRowModel().rows.map(row => (
|
|
1065
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
1066
|
+
{row.getVisibleCells().map(cell => (
|
|
1067
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
1068
|
+
))}
|
|
1069
|
+
</TableRow>
|
|
1070
|
+
))
|
|
1071
|
+
) : (
|
|
1072
|
+
<TableRow>
|
|
1073
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
1074
|
+
No results.
|
|
1075
|
+
</TableCell>
|
|
1076
|
+
</TableRow>
|
|
1077
|
+
)}
|
|
1078
|
+
</TableBody>
|
|
1079
|
+
</Table>
|
|
1080
|
+
</div>
|
|
1081
|
+
|
|
1082
|
+
<div className='flex items-center justify-between gap-8'>
|
|
1083
|
+
<div className='flex items-center gap-3'>
|
|
1084
|
+
<Label htmlFor={id} className='max-sm:sr-only'>
|
|
1085
|
+
Rows per page
|
|
1086
|
+
</Label>
|
|
1087
|
+
<Select
|
|
1088
|
+
value={table.getState().pagination.pageSize.toString()}
|
|
1089
|
+
onValueChange={value => {
|
|
1090
|
+
table.setPageSize(Number(value))
|
|
1091
|
+
}}
|
|
1092
|
+
>
|
|
1093
|
+
<SelectTrigger id={id} className='w-fit whitespace-nowrap'>
|
|
1094
|
+
<SelectValue placeholder='Select number of results' />
|
|
1095
|
+
</SelectTrigger>
|
|
1096
|
+
<SelectContent className='[&_*[role=option]]:pr-8 [&_*[role=option]]:pl-2 [&_*[role=option]>span]:right-2 [&_*[role=option]>span]:left-auto'>
|
|
1097
|
+
{[5, 10, 25, 50].map(pageSize => (
|
|
1098
|
+
<SelectItem key={pageSize} value={pageSize.toString()}>
|
|
1099
|
+
{pageSize}
|
|
1100
|
+
</SelectItem>
|
|
1101
|
+
))}
|
|
1102
|
+
</SelectContent>
|
|
1103
|
+
</Select>
|
|
1104
|
+
</div>
|
|
1105
|
+
|
|
1106
|
+
<div className='text-muted-foreground flex grow justify-end text-sm whitespace-nowrap'>
|
|
1107
|
+
<p className='text-muted-foreground text-sm whitespace-nowrap' aria-live='polite'>
|
|
1108
|
+
<span className='text-foreground'>
|
|
1109
|
+
{table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}-
|
|
1110
|
+
{Math.min(
|
|
1111
|
+
Math.max(
|
|
1112
|
+
table.getState().pagination.pageIndex * table.getState().pagination.pageSize +
|
|
1113
|
+
table.getState().pagination.pageSize,
|
|
1114
|
+
0
|
|
1115
|
+
),
|
|
1116
|
+
table.getRowCount()
|
|
1117
|
+
)}
|
|
1118
|
+
</span>{' '}
|
|
1119
|
+
of <span className='text-foreground'>{table.getRowCount().toString()}</span>
|
|
1120
|
+
</p>
|
|
1121
|
+
</div>
|
|
1122
|
+
|
|
1123
|
+
<div>
|
|
1124
|
+
<Pagination>
|
|
1125
|
+
<PaginationContent>
|
|
1126
|
+
<PaginationItem>
|
|
1127
|
+
<Button
|
|
1128
|
+
size='icon'
|
|
1129
|
+
variant='outline'
|
|
1130
|
+
className='disabled:pointer-events-none disabled:opacity-50'
|
|
1131
|
+
onClick={() => table.firstPage()}
|
|
1132
|
+
disabled={!table.getCanPreviousPage()}
|
|
1133
|
+
aria-label='Go to first page'
|
|
1134
|
+
>
|
|
1135
|
+
<ChevronFirstIcon aria-hidden='true' />
|
|
1136
|
+
</Button>
|
|
1137
|
+
</PaginationItem>
|
|
1138
|
+
|
|
1139
|
+
<PaginationItem>
|
|
1140
|
+
<Button
|
|
1141
|
+
size='icon'
|
|
1142
|
+
variant='outline'
|
|
1143
|
+
className='disabled:pointer-events-none disabled:opacity-50'
|
|
1144
|
+
onClick={() => table.previousPage()}
|
|
1145
|
+
disabled={!table.getCanPreviousPage()}
|
|
1146
|
+
aria-label='Go to previous page'
|
|
1147
|
+
>
|
|
1148
|
+
<ChevronLeftIcon aria-hidden='true' />
|
|
1149
|
+
</Button>
|
|
1150
|
+
</PaginationItem>
|
|
1151
|
+
|
|
1152
|
+
<PaginationItem>
|
|
1153
|
+
<Button
|
|
1154
|
+
size='icon'
|
|
1155
|
+
variant='outline'
|
|
1156
|
+
className='disabled:pointer-events-none disabled:opacity-50'
|
|
1157
|
+
onClick={() => table.nextPage()}
|
|
1158
|
+
disabled={!table.getCanNextPage()}
|
|
1159
|
+
aria-label='Go to next page'
|
|
1160
|
+
>
|
|
1161
|
+
<ChevronRightIcon aria-hidden='true' />
|
|
1162
|
+
</Button>
|
|
1163
|
+
</PaginationItem>
|
|
1164
|
+
|
|
1165
|
+
<PaginationItem>
|
|
1166
|
+
<Button
|
|
1167
|
+
size='icon'
|
|
1168
|
+
variant='outline'
|
|
1169
|
+
className='disabled:pointer-events-none disabled:opacity-50'
|
|
1170
|
+
onClick={() => table.lastPage()}
|
|
1171
|
+
disabled={!table.getCanNextPage()}
|
|
1172
|
+
aria-label='Go to last page'
|
|
1173
|
+
>
|
|
1174
|
+
<ChevronLastIcon aria-hidden='true' />
|
|
1175
|
+
</Button>
|
|
1176
|
+
</PaginationItem>
|
|
1177
|
+
</PaginationContent>
|
|
1178
|
+
</Pagination>
|
|
1179
|
+
</div>
|
|
1180
|
+
</div>
|
|
1181
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>Paginated data table </p>
|
|
1182
|
+
</div>
|
|
1183
|
+
)
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const DataTableWithPaginationDemo = () => {
|
|
1187
|
+
const pageSize = 5
|
|
1188
|
+
|
|
1189
|
+
const [pagination, setPagination] = useState<PaginationState>({
|
|
1190
|
+
pageIndex: 0,
|
|
1191
|
+
pageSize: pageSize
|
|
1192
|
+
})
|
|
1193
|
+
|
|
1194
|
+
const [sorting, setSorting] = useState<SortingState>([
|
|
1195
|
+
{
|
|
1196
|
+
id: 'product_name',
|
|
1197
|
+
desc: false
|
|
1198
|
+
}
|
|
1199
|
+
])
|
|
1200
|
+
|
|
1201
|
+
const [data, setData] = useState<Item[]>([])
|
|
1202
|
+
|
|
1203
|
+
useEffect(() => {
|
|
1204
|
+
async function fetchPosts() {
|
|
1205
|
+
const res = await fetch('https://cdn.jsdelivr.net/gh/themeselection/fy-assets/assets/json/mobile-stock.json')
|
|
1206
|
+
|
|
1207
|
+
if (!res.ok) {
|
|
1208
|
+
throw new Error('Failed to fetch data')
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const items = await res.json()
|
|
1212
|
+
|
|
1213
|
+
const data = await items.data
|
|
1214
|
+
|
|
1215
|
+
setData([...data, ...data])
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
fetchPosts()
|
|
1219
|
+
}, [])
|
|
1220
|
+
|
|
1221
|
+
const table = useReactTable({
|
|
1222
|
+
data,
|
|
1223
|
+
columns,
|
|
1224
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1225
|
+
getSortedRowModel: getSortedRowModel(),
|
|
1226
|
+
onSortingChange: setSorting,
|
|
1227
|
+
enableSortingRemoval: false,
|
|
1228
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
1229
|
+
onPaginationChange: setPagination,
|
|
1230
|
+
state: {
|
|
1231
|
+
sorting,
|
|
1232
|
+
pagination
|
|
1233
|
+
}
|
|
1234
|
+
})
|
|
1235
|
+
|
|
1236
|
+
const { pages, showLeftEllipsis, showRightEllipsis } = usePagination({
|
|
1237
|
+
currentPage: table.getState().pagination.pageIndex + 1,
|
|
1238
|
+
totalPages: table.getPageCount(),
|
|
1239
|
+
paginationItemsToDisplay: 5
|
|
1240
|
+
})
|
|
1241
|
+
|
|
1242
|
+
return (
|
|
1243
|
+
<div className='w-full space-y-4'>
|
|
1244
|
+
<div className='rounded-md border'>
|
|
1245
|
+
<Table>
|
|
1246
|
+
<TableHeader>
|
|
1247
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
1248
|
+
<TableRow key={headerGroup.id} className='hover:bg-transparent'>
|
|
1249
|
+
{headerGroup.headers.map(header => {
|
|
1250
|
+
return (
|
|
1251
|
+
<TableHead key={header.id} style={{ width: `${header.getSize()}px` }} className='h-11'>
|
|
1252
|
+
{header.isPlaceholder ? null : header.column.getCanSort() ? (
|
|
1253
|
+
<div
|
|
1254
|
+
className={cn(
|
|
1255
|
+
header.column.getCanSort() &&
|
|
1256
|
+
'flex h-full cursor-pointer items-center justify-between gap-2 select-none'
|
|
1257
|
+
)}
|
|
1258
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
1259
|
+
onKeyDown={e => {
|
|
1260
|
+
if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) {
|
|
1261
|
+
e.preventDefault()
|
|
1262
|
+
header.column.getToggleSortingHandler()?.(e)
|
|
1263
|
+
}
|
|
1264
|
+
}}
|
|
1265
|
+
tabIndex={header.column.getCanSort() ? 0 : undefined}
|
|
1266
|
+
>
|
|
1267
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
1268
|
+
{{
|
|
1269
|
+
asc: <ChevronUpIcon className='shrink-0 opacity-60' size={16} aria-hidden='true' />,
|
|
1270
|
+
desc: <ChevronDownIcon className='shrink-0 opacity-60' size={16} aria-hidden='true' />
|
|
1271
|
+
}[header.column.getIsSorted() as string] ?? null}
|
|
1272
|
+
</div>
|
|
1273
|
+
) : (
|
|
1274
|
+
flexRender(header.column.columnDef.header, header.getContext())
|
|
1275
|
+
)}
|
|
1276
|
+
</TableHead>
|
|
1277
|
+
)
|
|
1278
|
+
})}
|
|
1279
|
+
</TableRow>
|
|
1280
|
+
))}
|
|
1281
|
+
</TableHeader>
|
|
1282
|
+
<TableBody>
|
|
1283
|
+
{table.getRowModel().rows?.length ? (
|
|
1284
|
+
table.getRowModel().rows.map(row => (
|
|
1285
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
1286
|
+
{row.getVisibleCells().map(cell => (
|
|
1287
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
1288
|
+
))}
|
|
1289
|
+
</TableRow>
|
|
1290
|
+
))
|
|
1291
|
+
) : (
|
|
1292
|
+
<TableRow>
|
|
1293
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
1294
|
+
No results.
|
|
1295
|
+
</TableCell>
|
|
1296
|
+
</TableRow>
|
|
1297
|
+
)}
|
|
1298
|
+
</TableBody>
|
|
1299
|
+
</Table>
|
|
1300
|
+
</div>
|
|
1301
|
+
|
|
1302
|
+
<div className='flex items-center justify-between gap-3 max-sm:flex-col'>
|
|
1303
|
+
<p className='text-muted-foreground flex-1 text-sm whitespace-nowrap' aria-live='polite'>
|
|
1304
|
+
Page <span className='text-foreground'>{table.getState().pagination.pageIndex + 1}</span> of{' '}
|
|
1305
|
+
<span className='text-foreground'>{table.getPageCount()}</span>
|
|
1306
|
+
</p>
|
|
1307
|
+
|
|
1308
|
+
<div className='grow'>
|
|
1309
|
+
<Pagination>
|
|
1310
|
+
<PaginationContent>
|
|
1311
|
+
<PaginationItem>
|
|
1312
|
+
<Button
|
|
1313
|
+
size='icon'
|
|
1314
|
+
variant='outline'
|
|
1315
|
+
className='disabled:pointer-events-none disabled:opacity-50'
|
|
1316
|
+
onClick={() => table.previousPage()}
|
|
1317
|
+
disabled={!table.getCanPreviousPage()}
|
|
1318
|
+
aria-label='Go to previous page'
|
|
1319
|
+
>
|
|
1320
|
+
<ChevronLeftIcon aria-hidden='true' />
|
|
1321
|
+
</Button>
|
|
1322
|
+
</PaginationItem>
|
|
1323
|
+
|
|
1324
|
+
{showLeftEllipsis && (
|
|
1325
|
+
<PaginationItem>
|
|
1326
|
+
<PaginationEllipsis />
|
|
1327
|
+
</PaginationItem>
|
|
1328
|
+
)}
|
|
1329
|
+
|
|
1330
|
+
{pages.map(page => {
|
|
1331
|
+
const isActive = page === table.getState().pagination.pageIndex + 1
|
|
1332
|
+
|
|
1333
|
+
return (
|
|
1334
|
+
<PaginationItem key={page}>
|
|
1335
|
+
<Button
|
|
1336
|
+
size='icon'
|
|
1337
|
+
variant={`${isActive ? 'outline' : 'ghost'}`}
|
|
1338
|
+
onClick={() => table.setPageIndex(page - 1)}
|
|
1339
|
+
aria-current={isActive ? 'page' : undefined}
|
|
1340
|
+
>
|
|
1341
|
+
{page}
|
|
1342
|
+
</Button>
|
|
1343
|
+
</PaginationItem>
|
|
1344
|
+
)
|
|
1345
|
+
})}
|
|
1346
|
+
|
|
1347
|
+
{showRightEllipsis && (
|
|
1348
|
+
<PaginationItem>
|
|
1349
|
+
<PaginationEllipsis />
|
|
1350
|
+
</PaginationItem>
|
|
1351
|
+
)}
|
|
1352
|
+
|
|
1353
|
+
<PaginationItem>
|
|
1354
|
+
<Button
|
|
1355
|
+
size='icon'
|
|
1356
|
+
variant='outline'
|
|
1357
|
+
className='disabled:pointer-events-none disabled:opacity-50'
|
|
1358
|
+
onClick={() => table.nextPage()}
|
|
1359
|
+
disabled={!table.getCanNextPage()}
|
|
1360
|
+
aria-label='Go to next page'
|
|
1361
|
+
>
|
|
1362
|
+
<ChevronRightIcon aria-hidden='true' />
|
|
1363
|
+
</Button>
|
|
1364
|
+
</PaginationItem>
|
|
1365
|
+
</PaginationContent>
|
|
1366
|
+
</Pagination>
|
|
1367
|
+
</div>
|
|
1368
|
+
|
|
1369
|
+
<div className='flex flex-1 justify-end'>
|
|
1370
|
+
<Select
|
|
1371
|
+
value={table.getState().pagination.pageSize.toString()}
|
|
1372
|
+
onValueChange={value => {
|
|
1373
|
+
table.setPageSize(Number(value))
|
|
1374
|
+
}}
|
|
1375
|
+
>
|
|
1376
|
+
<SelectTrigger id='results-per-page' className='w-fit whitespace-nowrap' aria-label='Results per page'>
|
|
1377
|
+
<SelectValue placeholder='Select number of results' />
|
|
1378
|
+
</SelectTrigger>
|
|
1379
|
+
<SelectContent>
|
|
1380
|
+
{[5, 10, 25, 50].map(pageSize => (
|
|
1381
|
+
<SelectItem key={pageSize} value={pageSize.toString()}>
|
|
1382
|
+
{pageSize} / page
|
|
1383
|
+
</SelectItem>
|
|
1384
|
+
))}
|
|
1385
|
+
</SelectContent>
|
|
1386
|
+
</Select>
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>
|
|
1390
|
+
Data table with pagination{' '}
|
|
1391
|
+
<a href='https://originui.com/table' className='hover:text-primary underline' target='_blank'>
|
|
1392
|
+
Origin UI
|
|
1393
|
+
</a>
|
|
1394
|
+
</p>
|
|
1395
|
+
</div>
|
|
1396
|
+
)
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const DataTableWithExportDemo = () => {
|
|
1400
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
1401
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
1402
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
1403
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
1404
|
+
const [globalFilter, setGlobalFilter] = useState('')
|
|
1405
|
+
|
|
1406
|
+
const table = useReactTable({
|
|
1407
|
+
data,
|
|
1408
|
+
columns,
|
|
1409
|
+
onSortingChange: setSorting,
|
|
1410
|
+
onColumnFiltersChange: setColumnFilters,
|
|
1411
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1412
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
1413
|
+
getSortedRowModel: getSortedRowModel(),
|
|
1414
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
1415
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
1416
|
+
onRowSelectionChange: setRowSelection,
|
|
1417
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
1418
|
+
globalFilterFn: 'includesString',
|
|
1419
|
+
state: {
|
|
1420
|
+
sorting,
|
|
1421
|
+
columnFilters,
|
|
1422
|
+
columnVisibility,
|
|
1423
|
+
rowSelection,
|
|
1424
|
+
globalFilter
|
|
1425
|
+
}
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
const exportToCSV = () => {
|
|
1429
|
+
const selectedRows = table.getSelectedRowModel().rows
|
|
1430
|
+
|
|
1431
|
+
const dataToExport =
|
|
1432
|
+
selectedRows.length > 0
|
|
1433
|
+
? selectedRows.map(row => row.original)
|
|
1434
|
+
: table.getFilteredRowModel().rows.map(row => row.original)
|
|
1435
|
+
|
|
1436
|
+
const csv = Papa.unparse(dataToExport, {
|
|
1437
|
+
header: true
|
|
1438
|
+
})
|
|
1439
|
+
|
|
1440
|
+
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
|
|
1441
|
+
const link = document.createElement('a')
|
|
1442
|
+
const url = URL.createObjectURL(blob)
|
|
1443
|
+
|
|
1444
|
+
link.setAttribute('href', url)
|
|
1445
|
+
link.setAttribute('download', `payments-export-${new Date().toISOString().split('T')[0]}.csv`)
|
|
1446
|
+
link.style.visibility = 'hidden'
|
|
1447
|
+
document.body.appendChild(link)
|
|
1448
|
+
link.click()
|
|
1449
|
+
document.body.removeChild(link)
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
const exportToExcel = () => {
|
|
1453
|
+
const selectedRows = table.getSelectedRowModel().rows
|
|
1454
|
+
|
|
1455
|
+
const dataToExport =
|
|
1456
|
+
selectedRows.length > 0
|
|
1457
|
+
? selectedRows.map(row => row.original)
|
|
1458
|
+
: table.getFilteredRowModel().rows.map(row => row.original)
|
|
1459
|
+
|
|
1460
|
+
const worksheet = XLSX.utils.json_to_sheet(dataToExport)
|
|
1461
|
+
const workbook = XLSX.utils.book_new()
|
|
1462
|
+
|
|
1463
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, 'Payments')
|
|
1464
|
+
|
|
1465
|
+
const cols = [{ wch: 10 }, { wch: 20 }, { wch: 15 }, { wch: 25 }, { wch: 15 }]
|
|
1466
|
+
|
|
1467
|
+
worksheet['!cols'] = cols
|
|
1468
|
+
|
|
1469
|
+
XLSX.writeFile(workbook, `payments-export-${new Date().toISOString().split('T')[0]}.xlsx`)
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
const exportToJSON = () => {
|
|
1473
|
+
const selectedRows = table.getSelectedRowModel().rows
|
|
1474
|
+
|
|
1475
|
+
const dataToExport =
|
|
1476
|
+
selectedRows.length > 0
|
|
1477
|
+
? selectedRows.map(row => row.original)
|
|
1478
|
+
: table.getFilteredRowModel().rows.map(row => row.original)
|
|
1479
|
+
|
|
1480
|
+
const json = JSON.stringify(dataToExport, null, 2)
|
|
1481
|
+
const blob = new Blob([json], { type: 'application/json' })
|
|
1482
|
+
const link = document.createElement('a')
|
|
1483
|
+
const url = URL.createObjectURL(blob)
|
|
1484
|
+
|
|
1485
|
+
link.setAttribute('href', url)
|
|
1486
|
+
link.setAttribute('download', `payments-export-${new Date().toISOString().split('T')[0]}.json`)
|
|
1487
|
+
link.style.visibility = 'hidden'
|
|
1488
|
+
document.body.appendChild(link)
|
|
1489
|
+
link.click()
|
|
1490
|
+
document.body.removeChild(link)
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
return (
|
|
1494
|
+
<div className='w-full'>
|
|
1495
|
+
<div className='flex justify-between gap-2 pb-4 max-sm:flex-col sm:items-center'>
|
|
1496
|
+
<div className='flex items-center space-x-2'>
|
|
1497
|
+
<Input
|
|
1498
|
+
placeholder='Search all columns...'
|
|
1499
|
+
value={globalFilter ?? ''}
|
|
1500
|
+
onChange={event => setGlobalFilter(String(event.target.value))}
|
|
1501
|
+
className='max-w-sm'
|
|
1502
|
+
/>
|
|
1503
|
+
</div>
|
|
1504
|
+
<div className='flex items-center space-x-2'>
|
|
1505
|
+
<div className='text-muted-foreground text-sm'>
|
|
1506
|
+
{table.getSelectedRowModel().rows.length > 0 && (
|
|
1507
|
+
<span className='mr-2'>
|
|
1508
|
+
{table.getSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s) selected
|
|
1509
|
+
</span>
|
|
1510
|
+
)}
|
|
1511
|
+
</div>
|
|
1512
|
+
<DropdownMenu>
|
|
1513
|
+
<DropdownMenuTrigger asChild>
|
|
1514
|
+
<Button variant='outline' size='sm'>
|
|
1515
|
+
<DownloadIcon className='mr-2' />
|
|
1516
|
+
Export
|
|
1517
|
+
</Button>
|
|
1518
|
+
</DropdownMenuTrigger>
|
|
1519
|
+
<DropdownMenuContent align='end'>
|
|
1520
|
+
<DropdownMenuItem onClick={exportToCSV}>
|
|
1521
|
+
<FileTextIcon className='mr-2 size-4' />
|
|
1522
|
+
Export as CSV
|
|
1523
|
+
</DropdownMenuItem>
|
|
1524
|
+
<DropdownMenuItem onClick={exportToExcel}>
|
|
1525
|
+
<FileSpreadsheetIcon className='mr-2 size-4' />
|
|
1526
|
+
Export as Excel
|
|
1527
|
+
</DropdownMenuItem>
|
|
1528
|
+
<DropdownMenuSeparator />
|
|
1529
|
+
<DropdownMenuItem onClick={exportToJSON}>
|
|
1530
|
+
<FileTextIcon className='mr-2 size-4' />
|
|
1531
|
+
Export as JSON
|
|
1532
|
+
</DropdownMenuItem>
|
|
1533
|
+
</DropdownMenuContent>
|
|
1534
|
+
</DropdownMenu>
|
|
1535
|
+
</div>
|
|
1536
|
+
</div>
|
|
1537
|
+
<div className='rounded-md border'>
|
|
1538
|
+
<Table>
|
|
1539
|
+
<TableHeader>
|
|
1540
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
1541
|
+
<TableRow key={headerGroup.id}>
|
|
1542
|
+
{headerGroup.headers.map(header => {
|
|
1543
|
+
return (
|
|
1544
|
+
<TableHead key={header.id}>
|
|
1545
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
1546
|
+
</TableHead>
|
|
1547
|
+
)
|
|
1548
|
+
})}
|
|
1549
|
+
</TableRow>
|
|
1550
|
+
))}
|
|
1551
|
+
</TableHeader>
|
|
1552
|
+
<TableBody>
|
|
1553
|
+
{table.getRowModel().rows?.length ? (
|
|
1554
|
+
table.getRowModel().rows.map(row => (
|
|
1555
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
1556
|
+
{row.getVisibleCells().map(cell => (
|
|
1557
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
1558
|
+
))}
|
|
1559
|
+
</TableRow>
|
|
1560
|
+
))
|
|
1561
|
+
) : (
|
|
1562
|
+
<TableRow>
|
|
1563
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
1564
|
+
No results.
|
|
1565
|
+
</TableCell>
|
|
1566
|
+
</TableRow>
|
|
1567
|
+
)}
|
|
1568
|
+
</TableBody>
|
|
1569
|
+
</Table>
|
|
1570
|
+
</div>
|
|
1571
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>
|
|
1572
|
+
Data table with export functionality (CSV, Excel, JSON)
|
|
1573
|
+
</p>
|
|
1574
|
+
</div>
|
|
1575
|
+
)
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
const EditableTextCell = ({ getValue, row: { index }, column: { id }, table }: CellContext<Person, unknown>) => {
|
|
1579
|
+
const initialValue = getValue() as string
|
|
1580
|
+
const [value, setValue] = useState(initialValue)
|
|
1581
|
+
|
|
1582
|
+
const onBlur = () => {
|
|
1583
|
+
table.options.meta?.updateData(index, id, value)
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
useEffect(() => {
|
|
1587
|
+
setValue(initialValue)
|
|
1588
|
+
}, [initialValue])
|
|
1589
|
+
|
|
1590
|
+
return (
|
|
1591
|
+
<Input
|
|
1592
|
+
value={value}
|
|
1593
|
+
onChange={e => setValue(e.target.value)}
|
|
1594
|
+
onBlur={onBlur}
|
|
1595
|
+
className='focus-visible:ring-ring h-8 w-full border-0 bg-transparent p-1 focus-visible:ring-1'
|
|
1596
|
+
aria-label='editable-text-input'
|
|
1597
|
+
/>
|
|
1598
|
+
)
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
const EditableSelectCell = ({ getValue, row: { index }, column: { id }, table }: CellContext<Person, unknown>) => {
|
|
1602
|
+
const initialValue = getValue() as string
|
|
1603
|
+
|
|
1604
|
+
const handleValueChange = (newValue: string) => {
|
|
1605
|
+
table.options.meta?.updateData(index, id, newValue)
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
return (
|
|
1609
|
+
<Select value={initialValue} onValueChange={handleValueChange}>
|
|
1610
|
+
<SelectTrigger
|
|
1611
|
+
className='focus:ring-ring h-8 w-full border-0 bg-transparent p-1 focus:ring-1'
|
|
1612
|
+
aria-label={`select-status-${id}`}
|
|
1613
|
+
>
|
|
1614
|
+
<SelectValue />
|
|
1615
|
+
</SelectTrigger>
|
|
1616
|
+
<SelectContent>
|
|
1617
|
+
<SelectItem value='active'>Active</SelectItem>
|
|
1618
|
+
<SelectItem value='inactive'>Inactive</SelectItem>
|
|
1619
|
+
<SelectItem value='pending'>Pending</SelectItem>
|
|
1620
|
+
</SelectContent>
|
|
1621
|
+
</Select>
|
|
1622
|
+
)
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
const EditableProgressCell = ({ getValue, row: { index }, column: { id }, table }: CellContext<Person, unknown>) => {
|
|
1626
|
+
const initialValue = getValue() as number
|
|
1627
|
+
const [value, setValue] = useState(initialValue.toString())
|
|
1628
|
+
|
|
1629
|
+
const onBlur = () => {
|
|
1630
|
+
const numValue = parseFloat(value)
|
|
1631
|
+
const clampedValue = Math.max(0, Math.min(100, isNaN(numValue) ? initialValue : numValue))
|
|
1632
|
+
|
|
1633
|
+
table.options.meta?.updateData(index, id, clampedValue)
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
useEffect(() => {
|
|
1637
|
+
setValue(initialValue.toString())
|
|
1638
|
+
}, [initialValue])
|
|
1639
|
+
|
|
1640
|
+
return (
|
|
1641
|
+
<div className='flex items-center space-x-2'>
|
|
1642
|
+
<Input
|
|
1643
|
+
type='number'
|
|
1644
|
+
min='0'
|
|
1645
|
+
max='100'
|
|
1646
|
+
value={value}
|
|
1647
|
+
onChange={e => setValue(e.target.value)}
|
|
1648
|
+
onBlur={onBlur}
|
|
1649
|
+
className='focus-visible:ring-ring h-8 w-20 border-0 bg-transparent p-1 focus-visible:ring-1'
|
|
1650
|
+
aria-label='editable-progress-input'
|
|
1651
|
+
/>
|
|
1652
|
+
<span className='text-muted-foreground text-sm'>%</span>
|
|
1653
|
+
</div>
|
|
1654
|
+
)
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
const EditableDataTableDemo = () => {
|
|
1658
|
+
const [data, setData] = useState(() => [...initialData])
|
|
1659
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
1660
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
1661
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
|
1662
|
+
const [rowSelection, setRowSelection] = useState({})
|
|
1663
|
+
|
|
1664
|
+
const refreshData = () => setData(() => [...initialData])
|
|
1665
|
+
|
|
1666
|
+
const table = useReactTable({
|
|
1667
|
+
data,
|
|
1668
|
+
columns,
|
|
1669
|
+
onSortingChange: setSorting,
|
|
1670
|
+
onColumnFiltersChange: setColumnFilters,
|
|
1671
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1672
|
+
getSortedRowModel: getSortedRowModel(),
|
|
1673
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
1674
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
1675
|
+
onRowSelectionChange: setRowSelection,
|
|
1676
|
+
meta: {
|
|
1677
|
+
updateData: (rowIndex, columnId, value) => {
|
|
1678
|
+
setData(old =>
|
|
1679
|
+
old.map((row, index) => {
|
|
1680
|
+
if (index === rowIndex) {
|
|
1681
|
+
return {
|
|
1682
|
+
...old[rowIndex]!,
|
|
1683
|
+
[columnId]: value
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
return row
|
|
1688
|
+
})
|
|
1689
|
+
)
|
|
1690
|
+
}
|
|
1691
|
+
},
|
|
1692
|
+
state: {
|
|
1693
|
+
sorting,
|
|
1694
|
+
columnFilters,
|
|
1695
|
+
columnVisibility,
|
|
1696
|
+
rowSelection
|
|
1697
|
+
}
|
|
1698
|
+
})
|
|
1699
|
+
|
|
1700
|
+
return (
|
|
1701
|
+
<div className='w-full space-y-4'>
|
|
1702
|
+
<div className='rounded-md border'>
|
|
1703
|
+
<Table>
|
|
1704
|
+
<TableHeader>
|
|
1705
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
1706
|
+
<TableRow key={headerGroup.id}>
|
|
1707
|
+
{headerGroup.headers.map(header => {
|
|
1708
|
+
return (
|
|
1709
|
+
<TableHead key={header.id} colSpan={header.colSpan}>
|
|
1710
|
+
{header.isPlaceholder ? null : (
|
|
1711
|
+
<div>{flexRender(header.column.columnDef.header, header.getContext())}</div>
|
|
1712
|
+
)}
|
|
1713
|
+
</TableHead>
|
|
1714
|
+
)
|
|
1715
|
+
})}
|
|
1716
|
+
</TableRow>
|
|
1717
|
+
))}
|
|
1718
|
+
</TableHeader>
|
|
1719
|
+
<TableBody>
|
|
1720
|
+
{table.getRowModel().rows?.length ? (
|
|
1721
|
+
table.getRowModel().rows.map(row => (
|
|
1722
|
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
|
1723
|
+
{row.getVisibleCells().map(cell => (
|
|
1724
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
1725
|
+
))}
|
|
1726
|
+
</TableRow>
|
|
1727
|
+
))
|
|
1728
|
+
) : (
|
|
1729
|
+
<TableRow>
|
|
1730
|
+
<TableCell colSpan={columns.length} className='h-24 text-center'>
|
|
1731
|
+
No results.
|
|
1732
|
+
</TableCell>
|
|
1733
|
+
</TableRow>
|
|
1734
|
+
)}
|
|
1735
|
+
</TableBody>
|
|
1736
|
+
</Table>
|
|
1737
|
+
</div>
|
|
1738
|
+
|
|
1739
|
+
<div className='text-muted-foreground flex items-center justify-between gap-2 text-sm max-md:flex-col'>
|
|
1740
|
+
<div>{table.getRowModel().rows.length} rows total</div>
|
|
1741
|
+
<div className='flex items-center space-x-2'>
|
|
1742
|
+
<Button variant='outline' size='sm' onClick={refreshData}>
|
|
1743
|
+
Refresh Data
|
|
1744
|
+
</Button>
|
|
1745
|
+
</div>
|
|
1746
|
+
</div>
|
|
1747
|
+
|
|
1748
|
+
<p className='text-muted-foreground mt-4 text-center text-sm'>
|
|
1749
|
+
Editable data table - Click on cells to edit values
|
|
1750
|
+
</p>
|
|
1751
|
+
</div>
|
|
1752
|
+
)
|
|
1753
|
+
} */
|