@hexclave/ui 1.0.2 → 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.
Files changed (86) hide show
  1. package/dist/components/copy-button.d.ts +1 -1
  2. package/dist/components/simple-tooltip.js +2 -2
  3. package/dist/components/simple-tooltip.js.map +1 -1
  4. package/dist/components/ui/badge.d.ts +2 -2
  5. package/dist/components/ui/button.d.ts +2 -2
  6. package/dist/components/ui/form.d.ts +1 -1
  7. package/dist/components/ui/form.d.ts.map +1 -1
  8. package/dist/components/ui/resizable.d.ts +1 -1
  9. package/dist/components/ui/resizable.d.ts.map +1 -1
  10. package/dist/components/ui/typography.d.ts +1 -1
  11. package/dist/esm/components/copy-button.d.ts +1 -1
  12. package/dist/esm/components/simple-tooltip.js +3 -3
  13. package/dist/esm/components/simple-tooltip.js.map +1 -1
  14. package/dist/esm/components/ui/badge.d.ts +2 -2
  15. package/dist/esm/components/ui/button.d.ts +2 -2
  16. package/dist/esm/components/ui/form.d.ts +1 -1
  17. package/dist/esm/components/ui/form.d.ts.map +1 -1
  18. package/dist/esm/components/ui/resizable.d.ts +1 -1
  19. package/dist/esm/components/ui/resizable.d.ts.map +1 -1
  20. package/dist/esm/components/ui/typography.d.ts +1 -1
  21. package/package.json +3 -2
  22. package/src/components/action-dialog.tsx +135 -0
  23. package/src/components/brand-icons.tsx +276 -0
  24. package/src/components/browser-frame/LICENSE +3 -0
  25. package/src/components/browser-frame/index.tsx +51 -0
  26. package/src/components/copy-button.tsx +36 -0
  27. package/src/components/copy-field.tsx +52 -0
  28. package/src/components/data-table/cells.tsx +133 -0
  29. package/src/components/data-table/column-header.tsx +52 -0
  30. package/src/components/data-table/data-table.tsx +318 -0
  31. package/src/components/data-table/faceted-filter.tsx +138 -0
  32. package/src/components/data-table/index.tsx +9 -0
  33. package/src/components/data-table/pagination.tsx +76 -0
  34. package/src/components/data-table/toolbar-items.tsx +13 -0
  35. package/src/components/data-table/toolbar.tsx +100 -0
  36. package/src/components/data-table/utils.tsx +7 -0
  37. package/src/components/data-table/view-options.tsx +64 -0
  38. package/src/components/simple-tooltip.tsx +48 -0
  39. package/src/components/ui/accordion.tsx +58 -0
  40. package/src/components/ui/alert.tsx +60 -0
  41. package/src/components/ui/aspect-ratio.tsx +7 -0
  42. package/src/components/ui/avatar.tsx +51 -0
  43. package/src/components/ui/badge.tsx +36 -0
  44. package/src/components/ui/breadcrumb.tsx +116 -0
  45. package/src/components/ui/button.tsx +95 -0
  46. package/src/components/ui/calendar.tsx +77 -0
  47. package/src/components/ui/card.tsx +88 -0
  48. package/src/components/ui/checkbox.tsx +31 -0
  49. package/src/components/ui/collapsible.tsx +11 -0
  50. package/src/components/ui/command.tsx +159 -0
  51. package/src/components/ui/context-menu.tsx +205 -0
  52. package/src/components/ui/dialog.tsx +135 -0
  53. package/src/components/ui/dropdown-menu.tsx +245 -0
  54. package/src/components/ui/form.tsx +173 -0
  55. package/src/components/ui/hover-card.tsx +30 -0
  56. package/src/components/ui/inline-code.tsx +40 -0
  57. package/src/components/ui/input-otp.tsx +73 -0
  58. package/src/components/ui/input.tsx +68 -0
  59. package/src/components/ui/label.tsx +40 -0
  60. package/src/components/ui/menubar.tsx +241 -0
  61. package/src/components/ui/navigation-menu.tsx +131 -0
  62. package/src/components/ui/password-input.tsx +50 -0
  63. package/src/components/ui/popover.tsx +34 -0
  64. package/src/components/ui/progress.tsx +29 -0
  65. package/src/components/ui/radio-group.tsx +45 -0
  66. package/src/components/ui/resizable.tsx +45 -0
  67. package/src/components/ui/scroll-area.tsx +49 -0
  68. package/src/components/ui/select.tsx +162 -0
  69. package/src/components/ui/separator.tsx +32 -0
  70. package/src/components/ui/sheet.tsx +139 -0
  71. package/src/components/ui/skeleton.tsx +23 -0
  72. package/src/components/ui/slider.tsx +29 -0
  73. package/src/components/ui/spinner.tsx +18 -0
  74. package/src/components/ui/switch.tsx +75 -0
  75. package/src/components/ui/table.tsx +117 -0
  76. package/src/components/ui/tabs.tsx +56 -0
  77. package/src/components/ui/textarea.tsx +24 -0
  78. package/src/components/ui/toast.tsx +135 -0
  79. package/src/components/ui/toaster.tsx +35 -0
  80. package/src/components/ui/toggle-group.tsx +62 -0
  81. package/src/components/ui/toggle.tsx +46 -0
  82. package/src/components/ui/tooltip.tsx +40 -0
  83. package/src/components/ui/typography.tsx +47 -0
  84. package/src/components/ui/use-toast.tsx +195 -0
  85. package/src/index.ts +54 -0
  86. package/src/lib/utils.tsx +6 -0
@@ -0,0 +1,318 @@
1
+ "use client";
2
+
3
+ import { runAsynchronouslyWithAlert } from "@hexclave/shared/dist/utils/promises";
4
+ import {
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from "../ui/table";
12
+ import {
13
+ ColumnDef,
14
+ ColumnFiltersState,
15
+ GlobalFiltering,
16
+ OnChangeFn,
17
+ PaginationState,
18
+ SortingState,
19
+ Table as TableType,
20
+ VisibilityState,
21
+ flexRender,
22
+ getCoreRowModel,
23
+ getFacetedRowModel,
24
+ getFacetedUniqueValues,
25
+ getFilteredRowModel,
26
+ getPaginationRowModel,
27
+ getSortedRowModel,
28
+ useReactTable,
29
+ } from "@tanstack/react-table";
30
+ import React from "react";
31
+ import { DataTablePagination } from "./pagination";
32
+ import { DataTableToolbar } from "./toolbar";
33
+
34
+ export function TableView<TData, TValue>(props: {
35
+ table: TableType<TData>,
36
+ columns: ColumnDef<TData, TValue>[],
37
+ toolbarRender?: (table: TableType<TData>) => React.ReactNode,
38
+ showDefaultToolbar?: boolean,
39
+ defaultColumnFilters: ColumnFiltersState,
40
+ defaultSorting: SortingState,
41
+ onRowClick?: (row: TData) => void,
42
+ }) {
43
+ return (
44
+ <div className="space-y-4">
45
+ <DataTableToolbar
46
+ table={props.table}
47
+ toolbarRender={props.toolbarRender}
48
+ showDefaultToolbar={props.showDefaultToolbar}
49
+ defaultColumnFilters={props.defaultColumnFilters}
50
+ defaultSorting={props.defaultSorting}
51
+ />
52
+ <div className="rounded-md border">
53
+ <Table>
54
+ <TableHeader>
55
+ {props.table.getHeaderGroups().map((headerGroup) => (
56
+ <TableRow key={headerGroup.id} >
57
+ {headerGroup.headers.map((header) => {
58
+ return (
59
+ <TableHead key={header.id} colSpan={header.colSpan}>
60
+ {header.isPlaceholder
61
+ ? null
62
+ : flexRender(
63
+ header.column.columnDef.header,
64
+ header.getContext()
65
+ )}
66
+ </TableHead>
67
+ );
68
+ })}
69
+ </TableRow>
70
+ ))}
71
+ </TableHeader>
72
+ <TableBody>
73
+ {props.table.getRowModel().rows.length ? (
74
+ props.table.getRowModel().rows.map((row) => (
75
+ <TableRow
76
+ key={row.id}
77
+ data-state={row.getIsSelected() && "selected"}
78
+ onClick={(ev) => {
79
+ // only trigger onRowClick if the element is a direct descendant; don't trigger for portals
80
+ if (ev.target instanceof Node && ev.currentTarget.contains(ev.target)) {
81
+ props.onRowClick?.(row.original);
82
+ }
83
+ }}
84
+ >
85
+ {row.getVisibleCells().map((cell) => (
86
+ <TableCell key={cell.id}>
87
+ {flexRender(
88
+ cell.column.columnDef.cell,
89
+ cell.getContext()
90
+ )}
91
+ </TableCell>
92
+ ))}
93
+ </TableRow>
94
+ ))
95
+ ) : (
96
+ <TableRow>
97
+ <TableCell
98
+ colSpan={props.columns.length}
99
+ className="h-24 text-center"
100
+ >
101
+ No results.
102
+ </TableCell>
103
+ </TableRow>
104
+ )}
105
+ </TableBody>
106
+ </Table>
107
+ </div>
108
+ <DataTablePagination table={props.table} />
109
+ </div>
110
+ );
111
+ }
112
+
113
+ type DataTableProps<TData, TValue> = {
114
+ columns: ColumnDef<TData, TValue>[],
115
+ data: TData[],
116
+ toolbarRender?: (table: TableType<TData>) => React.ReactNode,
117
+ defaultVisibility?: VisibilityState,
118
+ defaultColumnFilters: ColumnFiltersState,
119
+ defaultSorting: SortingState,
120
+ showDefaultToolbar?: boolean,
121
+ onRowClick?: (row: TData) => void,
122
+ }
123
+
124
+ export function DataTable<TData, TValue>({
125
+ columns,
126
+ data,
127
+ toolbarRender,
128
+ defaultVisibility,
129
+ defaultColumnFilters,
130
+ defaultSorting,
131
+ showDefaultToolbar = true,
132
+ onRowClick,
133
+ }: DataTableProps<TData, TValue>) {
134
+ const [sorting, setSorting] = React.useState<SortingState>(defaultSorting);
135
+ const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(defaultColumnFilters);
136
+ const [pagination, setPagination] = React.useState<PaginationState>({
137
+ pageIndex: 0,
138
+ pageSize: 10,
139
+ });
140
+ const [globalFilter, setGlobalFilter] = React.useState<any>();
141
+
142
+ return <DataTableBase
143
+ columns={columns}
144
+ data={data}
145
+ toolbarRender={toolbarRender}
146
+ defaultVisibility={defaultVisibility}
147
+ sorting={sorting}
148
+ setSorting={setSorting}
149
+ defaultSorting={defaultSorting}
150
+ columnFilters={columnFilters}
151
+ setColumnFilters={setColumnFilters}
152
+ defaultColumnFilters={defaultColumnFilters}
153
+ manualPagination={false}
154
+ manualFiltering={false}
155
+ pagination={pagination}
156
+ setPagination={setPagination}
157
+ globalFilter={globalFilter}
158
+ setGlobalFilter={setGlobalFilter}
159
+ showDefaultToolbar={showDefaultToolbar}
160
+ onRowClick={onRowClick}
161
+ />;
162
+ }
163
+
164
+ type DataTableManualPaginationProps<TData, TValue> = DataTableProps<TData, TValue> & {
165
+ onUpdate: (options: {
166
+ cursor: string,
167
+ limit: number,
168
+ sorting: SortingState,
169
+ columnFilters: ColumnFiltersState,
170
+ globalFilters: any,
171
+ }) => Promise<{ nextCursor: string | null }>,
172
+ }
173
+
174
+ export function DataTableManualPagination<TData, TValue>({
175
+ columns,
176
+ data,
177
+ toolbarRender,
178
+ defaultVisibility,
179
+ defaultColumnFilters,
180
+ defaultSorting,
181
+ onRowClick,
182
+ onUpdate,
183
+ showDefaultToolbar = true,
184
+ }: DataTableManualPaginationProps<TData, TValue>) {
185
+ const [sorting, setSorting] = React.useState<SortingState>(defaultSorting);
186
+ const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 10 });
187
+ const [cursors, setCursors] = React.useState<Record<number, string>>({});
188
+ const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(defaultColumnFilters);
189
+ const [globalFilter, setGlobalFilter] = React.useState<any>();
190
+ const [refreshCounter, setRefreshCounter] = React.useState(0);
191
+
192
+ React.useEffect(() => {
193
+ runAsynchronouslyWithAlert(async () => {
194
+ const { nextCursor } = await onUpdate({
195
+ cursor: cursors[pagination.pageIndex],
196
+ limit: pagination.pageSize,
197
+ sorting,
198
+ columnFilters,
199
+ globalFilters: globalFilter,
200
+ });
201
+ setCursors(c => nextCursor ? { ...c, [pagination.pageIndex + 1]: nextCursor } : c);
202
+ });
203
+ }, [pagination, sorting, columnFilters, refreshCounter]);
204
+
205
+ // Reset to first page when filters change
206
+ React.useEffect(() => {
207
+ setPagination(pagination => ({ ...pagination, pageIndex: 0 }));
208
+ setCursors({});
209
+ }, [columnFilters, sorting, pagination.pageSize]);
210
+
211
+ // Refresh the users when the global filter changes. Delay to prevent unnecessary re-renders.
212
+ React.useEffect(() => {
213
+ const timer = setTimeout(() => {
214
+ setRefreshCounter(x => x + 1);
215
+ }, 3_000);
216
+ return () => clearTimeout(timer);
217
+ }, [globalFilter]);
218
+
219
+ return <DataTableBase
220
+ columns={columns}
221
+ data={data}
222
+ toolbarRender={toolbarRender}
223
+ sorting={sorting}
224
+ setSorting={setSorting}
225
+ pagination={pagination}
226
+ setPagination={setPagination}
227
+ columnFilters={columnFilters}
228
+ setColumnFilters={setColumnFilters}
229
+ rowCount={pagination.pageSize * Object.keys(cursors).length + (cursors[pagination.pageIndex + 1] ? 1 : 0)}
230
+ globalFilter={globalFilter}
231
+ setGlobalFilter={setGlobalFilter}
232
+ defaultColumnFilters={defaultColumnFilters}
233
+ defaultSorting={defaultSorting}
234
+ defaultVisibility={defaultVisibility}
235
+ showDefaultToolbar={showDefaultToolbar}
236
+ onRowClick={onRowClick}
237
+ />;
238
+ }
239
+
240
+ type DataTableBaseProps<TData, TValue> = DataTableProps<TData, TValue> & {
241
+ sorting?: SortingState,
242
+ setSorting?: OnChangeFn<SortingState>,
243
+ pagination?: PaginationState,
244
+ setPagination?: OnChangeFn<PaginationState>,
245
+ rowCount?: number,
246
+ columnFilters?: ColumnFiltersState,
247
+ setColumnFilters?: OnChangeFn<ColumnFiltersState>,
248
+ manualPagination?: boolean,
249
+ manualFiltering?: boolean,
250
+ globalFilter?: any,
251
+ setGlobalFilter?: OnChangeFn<any>,
252
+ }
253
+
254
+ function DataTableBase<TData, TValue>({
255
+ columns,
256
+ data,
257
+ toolbarRender,
258
+ defaultVisibility,
259
+ sorting,
260
+ setSorting,
261
+ defaultColumnFilters,
262
+ defaultSorting,
263
+ pagination,
264
+ setPagination,
265
+ rowCount,
266
+ columnFilters,
267
+ setColumnFilters,
268
+ globalFilter,
269
+ setGlobalFilter,
270
+ manualPagination = true,
271
+ manualFiltering = true,
272
+ showDefaultToolbar = true,
273
+ onRowClick,
274
+ }: DataTableBaseProps<TData, TValue>) {
275
+ const [rowSelection, setRowSelection] = React.useState({});
276
+ const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(defaultVisibility || {});
277
+
278
+ const table: TableType<TData> = useReactTable({
279
+ data,
280
+ columns,
281
+ state: {
282
+ sorting,
283
+ columnVisibility,
284
+ rowSelection,
285
+ columnFilters,
286
+ pagination,
287
+ globalFilter: globalFilter,
288
+ },
289
+ enableRowSelection: true,
290
+ onGlobalFilterChange: setGlobalFilter,
291
+ onRowSelectionChange: setRowSelection,
292
+ onSortingChange: setSorting,
293
+ onColumnFiltersChange: setColumnFilters,
294
+ onColumnVisibilityChange: setColumnVisibility,
295
+ onPaginationChange: setPagination,
296
+ getColumnCanGlobalFilter: (c) => c.columnDef.enableGlobalFilter ?? GlobalFiltering.getDefaultOptions!(table).getColumnCanGlobalFilter!(c),
297
+ getCoreRowModel: getCoreRowModel(),
298
+ getFilteredRowModel: getFilteredRowModel(),
299
+ getPaginationRowModel: getPaginationRowModel(),
300
+ getSortedRowModel: getSortedRowModel(),
301
+ getFacetedRowModel: getFacetedRowModel(),
302
+ getFacetedUniqueValues: getFacetedUniqueValues(),
303
+ autoResetAll: false,
304
+ manualPagination,
305
+ manualFiltering,
306
+ rowCount,
307
+ });
308
+
309
+ return <TableView
310
+ table={table}
311
+ columns={columns}
312
+ toolbarRender={toolbarRender}
313
+ showDefaultToolbar={showDefaultToolbar}
314
+ defaultColumnFilters={defaultColumnFilters}
315
+ defaultSorting={defaultSorting}
316
+ onRowClick={onRowClick}
317
+ />;
318
+ }
@@ -0,0 +1,138 @@
1
+ import { CheckIcon } from "@radix-ui/react-icons";
2
+ import { Badge } from "../ui/badge";
3
+ import { Button } from "../ui/button";
4
+ import {
5
+ Command,
6
+ CommandEmpty,
7
+ CommandGroup,
8
+ CommandInput,
9
+ CommandItem,
10
+ CommandList,
11
+ CommandSeparator,
12
+ } from "../ui/command";
13
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
14
+ import { Separator } from "../ui/separator";
15
+ import { Column } from "@tanstack/react-table";
16
+ import { ListFilter } from "lucide-react";
17
+ import React from "react";
18
+ import { cn } from "../../lib/utils";
19
+
20
+ type DataTableFacetedFilterProps<TData, TValue> = {
21
+ column?: Column<TData, TValue>,
22
+ title?: string,
23
+ options: {
24
+ label: string,
25
+ value: string,
26
+ icon?: React.ComponentType<{ className?: string }>,
27
+ }[],
28
+ }
29
+
30
+ export function DataTableFacetedFilter<TData, TValue>({
31
+ column,
32
+ title,
33
+ options,
34
+ }: DataTableFacetedFilterProps<TData, TValue>) {
35
+ const facets = column?.getFacetedUniqueValues();
36
+ const selectedValues = new Set(column?.getFilterValue() as string[]);
37
+
38
+ return (
39
+ <Popover>
40
+ <PopoverTrigger asChild>
41
+ <Button variant="outline" size="sm" className="h-8 border">
42
+ <ListFilter className="mr-2 h-4 w-4 text-gray-500" />
43
+ {title}
44
+ {selectedValues.size > 0 && (
45
+ <>
46
+ <Separator orientation="vertical" className="mx-2 h-4" />
47
+ <Badge
48
+ variant="secondary"
49
+ className="rounded-sm px-1 font-normal lg:hidden"
50
+ >
51
+ {selectedValues.size}
52
+ </Badge>
53
+ <div className="hidden space-x-1 lg:flex">
54
+ {selectedValues.size > 2 ? (
55
+ <Badge
56
+ variant="secondary"
57
+ className="rounded-sm px-1 font-normal"
58
+ >
59
+ {selectedValues.size} selected
60
+ </Badge>
61
+ ) : (
62
+ options
63
+ .filter((option) => selectedValues.has(option.value))
64
+ .map((option) => (
65
+ <Badge
66
+ variant="secondary"
67
+ key={option.value}
68
+ className="rounded-sm px-1 font-normal"
69
+ >
70
+ {option.label}
71
+ </Badge>
72
+ ))
73
+ )}
74
+ </div>
75
+ </>
76
+ )}
77
+ </Button>
78
+ </PopoverTrigger>
79
+ <PopoverContent className="w-[200px] p-0" align="start">
80
+ <Command>
81
+ <CommandInput placeholder={title} />
82
+ <CommandList>
83
+ <CommandEmpty>No results found.</CommandEmpty>
84
+ <CommandGroup>
85
+ {options.map((option) => {
86
+ const isSelected = selectedValues.has(option.value);
87
+ return (
88
+ <CommandItem
89
+ key={option.value}
90
+ onSelect={() => {
91
+ if (isSelected) {
92
+ selectedValues.delete(option.value);
93
+ } else {
94
+ selectedValues.add(option.value);
95
+ }
96
+ const filterValues = Array.from(selectedValues);
97
+ column?.setFilterValue(
98
+ filterValues.length ? filterValues : undefined
99
+ );
100
+ }}
101
+ >
102
+ <div
103
+ className={cn(
104
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
105
+ isSelected
106
+ ? "bg-primary text-primary-foreground"
107
+ : "opacity-50 [&_svg]:invisible"
108
+ )}
109
+ >
110
+ <CheckIcon className={cn("h-4 w-4")} />
111
+ </div>
112
+ {option.icon && (
113
+ <option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
114
+ )}
115
+ <span>{option.label}</span>
116
+ </CommandItem>
117
+ );
118
+ })}
119
+ </CommandGroup>
120
+ {selectedValues.size > 0 && (
121
+ <>
122
+ <CommandSeparator />
123
+ <CommandGroup>
124
+ <CommandItem
125
+ onSelect={() => column?.setFilterValue(undefined)}
126
+ className="justify-center text-center"
127
+ >
128
+ Clear filters
129
+ </CommandItem>
130
+ </CommandGroup>
131
+ </>
132
+ )}
133
+ </CommandList>
134
+ </Command>
135
+ </PopoverContent>
136
+ </Popover>
137
+ );
138
+ }
@@ -0,0 +1,9 @@
1
+ export * from "./cells";
2
+ export * from "./column-header";
3
+ export * from "./data-table";
4
+ export * from "./faceted-filter";
5
+ export * from "./pagination";
6
+ export * from "./toolbar-items";
7
+ export * from "./toolbar";
8
+ export * from "./utils";
9
+ export * from "./view-options";
@@ -0,0 +1,76 @@
1
+ import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons";
2
+ import { Button } from "../ui/button";
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectItem,
7
+ SelectTrigger,
8
+ SelectValue,
9
+ } from "../ui/select";
10
+ import { Table } from "@tanstack/react-table";
11
+
12
+ type DataTablePaginationProps<TData> = {
13
+ table: Table<TData>,
14
+ }
15
+
16
+ export function DataTablePagination<TData>({
17
+ table,
18
+ }: DataTablePaginationProps<TData>) {
19
+ return (
20
+ <div className="flex items-center justify-between px-2 flex-col sm:flex-row gap-y-4 sm:gap-y-0">
21
+ <div className="flex-1 text-sm text-muted-foreground">
22
+ {table.getFilteredSelectedRowModel().rows.length !== 0 ?
23
+ `${table.getFilteredSelectedRowModel().rows.length} of ${table.getFilteredRowModel().rows.length} row(s) selected` :
24
+ undefined}
25
+ </div>
26
+ <div className="flex items-center gap-x-6 lg:gap-x-8 flex-col sm:flex-row gap-y-4 sm:gap-y-0">
27
+ <div className="flex items-center space-x-2">
28
+ <p className="text-sm font-medium">Rows per page</p>
29
+ <Select
30
+ value={`${table.getState().pagination.pageSize}`}
31
+ onValueChange={(value) => {
32
+ table.setPageSize(Number(value));
33
+ }}
34
+ >
35
+ <SelectTrigger className="h-8 w-[70px]">
36
+ <SelectValue placeholder={table.getState().pagination.pageSize} />
37
+ </SelectTrigger>
38
+ <SelectContent side="top">
39
+ {[10, 20, 30, 40, 50].map((pageSize) => (
40
+ <SelectItem key={pageSize} value={`${pageSize}`}>
41
+ {pageSize}
42
+ </SelectItem>
43
+ ))}
44
+ </SelectContent>
45
+ </Select>
46
+ </div>
47
+ <div className="flex items-center gap-4">
48
+ <div className="flex items-center justify-center text-sm font-medium">
49
+ {table.getState().pagination.pageIndex + 1}
50
+ {/* {" / "}{table.getPageCount()} */}
51
+ </div>
52
+ <div className="flex items-center space-x-2">
53
+ <Button
54
+ variant="outline"
55
+ className="h-8 w-8 p-0"
56
+ onClick={() => table.previousPage()}
57
+ disabled={!table.getCanPreviousPage()}
58
+ >
59
+ <span className="sr-only">Go to previous page</span>
60
+ <ChevronLeftIcon className="h-4 w-4" />
61
+ </Button>
62
+ <Button
63
+ variant="outline"
64
+ className="h-8 w-8 p-0"
65
+ onClick={() => table.nextPage()}
66
+ disabled={!table.getCanNextPage()}
67
+ >
68
+ <span className="sr-only">Go to next page</span>
69
+ <ChevronRightIcon className="h-4 w-4" />
70
+ </Button>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1,13 @@
1
+ import { Input, cn } from "../..";
2
+ import { Table } from "@tanstack/react-table";
3
+
4
+ export function SearchToolbarItem<TData>(props: { table: Table<TData>, keyName?: string | null, placeholder: string, className?: string }) {
5
+ return (
6
+ <Input
7
+ placeholder={props.placeholder}
8
+ value={props.keyName ? `${props.table.getColumn(props.keyName)?.getFilterValue() ?? ""}` : props.table.getState().globalFilter ?? ""}
9
+ onChange={(event) => props.keyName ? props.table.getColumn(props.keyName)?.setFilterValue(event.target.value) : props.table.setGlobalFilter(event.target.value)}
10
+ className={cn("h-8 w-[250px]", props.className)}
11
+ />
12
+ );
13
+ }
@@ -0,0 +1,100 @@
1
+ "use client";
2
+
3
+ import { Cross2Icon } from "@radix-ui/react-icons";
4
+ import { deepPlainEquals } from "@hexclave/shared/dist/utils/objects";
5
+ import { Button } from "../ui/button";
6
+ import { Cell, ColumnFiltersState, SortingState, Table } from "@tanstack/react-table";
7
+ import { download, generateCsv, mkConfig } from 'export-to-csv';
8
+ import { DownloadIcon } from "lucide-react";
9
+ import { DataTableViewOptions } from "./view-options";
10
+
11
+ type DataTableToolbarProps<TData> = {
12
+ table: Table<TData>,
13
+ toolbarRender?: (table: Table<TData>) => React.ReactNode,
14
+ showDefaultToolbar?: boolean,
15
+ defaultColumnFilters: ColumnFiltersState,
16
+ defaultSorting: SortingState,
17
+ showResetFilters?: boolean,
18
+ }
19
+
20
+ export function DataTableToolbar<TData>({
21
+ table,
22
+ toolbarRender,
23
+ showDefaultToolbar,
24
+ defaultColumnFilters,
25
+ defaultSorting,
26
+ showResetFilters = true,
27
+ }: DataTableToolbarProps<TData>) {
28
+ const isFiltered = !deepPlainEquals(table.getState().columnFilters, defaultColumnFilters);
29
+ const isSorted = !deepPlainEquals(table.getState().sorting, defaultSorting);
30
+
31
+ return (
32
+ <div className="flex items-center justify-between">
33
+ <div className="flex items-center gap-2 flex-wrap flex-1 stack-scope">
34
+ {toolbarRender?.(table)}
35
+ {showResetFilters && (isFiltered || isSorted) && (
36
+ <Button
37
+ variant="ghost"
38
+ onClick={() => {
39
+ table.setColumnFilters(defaultColumnFilters);
40
+ table.setSorting(defaultSorting);
41
+ }}
42
+ className="h-8 px-2 lg:px-3"
43
+ >
44
+ Reset filters
45
+ <Cross2Icon className="ml-2 h-4 w-4" />
46
+ </Button>
47
+ )}
48
+ </div>
49
+ {showDefaultToolbar && (
50
+ <>
51
+ <div className="flex items-center gap-2 flex-wrap">
52
+ <DataTableViewOptions table={table} />
53
+ <Button
54
+ variant="outline"
55
+ size="sm"
56
+ className="ml-auto hidden h-8 lg:flex"
57
+ onClick={() => {
58
+ const csvConfig = mkConfig({
59
+ fieldSeparator: ',',
60
+ filename: 'data',
61
+ decimalSeparator: '.',
62
+ useKeysAsHeaders: true,
63
+ });
64
+
65
+ const renderCellValue = (cell: Cell<TData, unknown>) => {
66
+ const rendered = cell.renderValue();
67
+ if (rendered === null) {
68
+ return undefined;
69
+ }
70
+ if (['string', 'number', 'boolean', 'undefined'].includes(typeof rendered)) {
71
+ return rendered;
72
+ }
73
+ if (rendered instanceof Date) {
74
+ return rendered.toISOString();
75
+ }
76
+ if (typeof rendered === 'object') {
77
+ return JSON.stringify(rendered);
78
+ }
79
+ };
80
+
81
+
82
+ const rowModel = table.getCoreRowModel();
83
+ const rows = rowModel.rows.map(row => Object.fromEntries(row.getAllCells().map(c => [c.column.id, renderCellValue(c)]).filter(([_, v]) => v !== undefined)));
84
+ if (rows.length === 0) {
85
+ alert("No data to export");
86
+ return;
87
+ }
88
+ const csv = generateCsv(csvConfig)(rows as any);
89
+ download(csvConfig)(csv);
90
+ }}
91
+ >
92
+ <DownloadIcon className="mr-2 h-4 w-4" />
93
+ Export CSV
94
+ </Button>
95
+ </div>
96
+ </>
97
+ )}
98
+ </div>
99
+ );
100
+ }
@@ -0,0 +1,7 @@
1
+ export function standardFilterFn (row: any, id: string, value: any) {
2
+ return value.includes(row.getValue(id));
3
+ }
4
+
5
+ export function arrayFilterFn (row: any, id: string, value: any) {
6
+ return value.some((v: any) => row.getValue(id).includes(v));
7
+ }