@classytic/fluid 0.2.1 → 0.3.2
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/LICENSE +21 -0
- package/README.md +149 -62
- package/dist/api-pagination-CJ0vR_w6.d.mts +34 -0
- package/dist/api-pagination-DBTE0yk4.mjs +190 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client/calendar.d.mts +105 -0
- package/dist/client/calendar.mjs +202 -0
- package/dist/client/core.d.mts +1614 -0
- package/dist/client/core.mjs +2779 -0
- package/dist/client/error.d.mts +125 -0
- package/dist/client/error.mjs +166 -0
- package/dist/client/hooks.d.mts +162 -0
- package/dist/client/hooks.mjs +447 -0
- package/dist/client/table.d.mts +84 -0
- package/dist/client/table.mjs +373 -0
- package/dist/client/theme.d.mts +6 -0
- package/dist/client/theme.mjs +65 -0
- package/dist/command.d.mts +134 -0
- package/dist/command.mjs +132 -0
- package/dist/compact.d.mts +359 -0
- package/dist/compact.mjs +892 -0
- package/dist/dashboard.d.mts +778 -0
- package/dist/dashboard.mjs +1617 -0
- package/dist/filter-utils-DqMmy_v-.mjs +72 -0
- package/dist/filter-utils-IZ0GtuPo.d.mts +40 -0
- package/dist/forms.d.mts +1549 -0
- package/dist/forms.mjs +3740 -0
- package/dist/index.d.mts +296 -0
- package/dist/index.mjs +432 -0
- package/dist/layouts.d.mts +215 -0
- package/dist/layouts.mjs +460 -0
- package/dist/search-context-DR7DBs7S.mjs +19 -0
- package/dist/search.d.mts +254 -0
- package/dist/search.mjs +523 -0
- package/dist/sheet-wrapper-CWNCvYMD.mjs +211 -0
- package/dist/use-base-search-BGgWnWaF.d.mts +35 -0
- package/dist/use-debounce-xmZucz5e.mjs +53 -0
- package/dist/use-keyboard-shortcut-Bl6YM5Q7.mjs +82 -0
- package/dist/use-keyboard-shortcut-_mRCh3QO.d.mts +24 -0
- package/dist/use-media-query-BnVNIKT4.mjs +17 -0
- package/dist/use-mobile-BX3SQVo2.mjs +20 -0
- package/dist/use-scroll-detection-CsgsQYvy.mjs +43 -0
- package/dist/utils-CDue7cEt.d.mts +6 -0
- package/dist/utils-DQ5SCVoW.mjs +10 -0
- package/package.json +85 -45
- package/styles.css +2 -2
- package/dist/chunk-GUHK2DTW.js +0 -15
- package/dist/chunk-GUHK2DTW.js.map +0 -1
- package/dist/chunk-H3NFL3GJ.js +0 -57
- package/dist/chunk-H3NFL3GJ.js.map +0 -1
- package/dist/chunk-J2YRTQE4.js +0 -293
- package/dist/chunk-J2YRTQE4.js.map +0 -1
- package/dist/compact.d.ts +0 -217
- package/dist/compact.js +0 -986
- package/dist/compact.js.map +0 -1
- package/dist/dashboard.d.ts +0 -386
- package/dist/dashboard.js +0 -1032
- package/dist/dashboard.js.map +0 -1
- package/dist/index.d.ts +0 -2141
- package/dist/index.js +0 -6460
- package/dist/index.js.map +0 -1
- package/dist/layout.d.ts +0 -25
- package/dist/layout.js +0 -4
- package/dist/layout.js.map +0 -1
- package/dist/search.d.ts +0 -172
- package/dist/search.js +0 -341
- package/dist/search.js.map +0 -1
- package/dist/use-base-search-AS5Z3SAy.d.ts +0 -64
- package/dist/utils-Cbsgs0XP.d.ts +0 -5
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { t as cn } from "../utils-DQ5SCVoW.mjs";
|
|
4
|
+
import { t as useIsMobile } from "../use-mobile-BX3SQVo2.mjs";
|
|
5
|
+
import { t as useScrollDetection } from "../use-scroll-detection-CsgsQYvy.mjs";
|
|
6
|
+
import { t as ApiPagination } from "../api-pagination-DBTE0yk4.mjs";
|
|
7
|
+
import { n as useSearch } from "../search-context-DR7DBs7S.mjs";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
10
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, Search, SlidersHorizontal, X } from "lucide-react";
|
|
12
|
+
import { Button } from "@/components/ui/button";
|
|
13
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
14
|
+
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
15
|
+
import { Badge } from "@/components/ui/badge";
|
|
16
|
+
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
|
17
|
+
import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
|
18
|
+
import { Input } from "@/components/ui/input";
|
|
19
|
+
|
|
20
|
+
//#region src/components/data-table.tsx
|
|
21
|
+
const ScrollButton = React.memo(function ScrollButton({ direction, onClick, visible, className }) {
|
|
22
|
+
if (!visible) return null;
|
|
23
|
+
const handleClick = (e) => {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
e.stopPropagation();
|
|
26
|
+
onClick();
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ jsx("button", {
|
|
29
|
+
type: "button",
|
|
30
|
+
className: cn("absolute top-1/2 -translate-y-1/2 z-50", "flex items-center justify-center", "size-10 rounded-md shadow-lg border-2 border-border", "bg-background hover:bg-muted", "transition-transform duration-200", "hover:scale-105 active:scale-95", direction === "left" ? "left-2" : "right-2", className),
|
|
31
|
+
onClick: handleClick,
|
|
32
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
33
|
+
"aria-label": `Scroll ${direction}`,
|
|
34
|
+
children: direction === "left" ? /* @__PURE__ */ jsx(ChevronLeft, { className: "size-5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-5" })
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
function DataTable({ columns, data, isLoading = false, pagination, enableSorting = false, enableRowSelection = false, onRowSelectionChange, className, loadingState: customLoadingState, emptyState: customEmptyState }) {
|
|
38
|
+
const [sorting, setSorting] = useState([]);
|
|
39
|
+
const [rowSelection, setRowSelection] = useState({});
|
|
40
|
+
const scrollAreaRef = useRef(null);
|
|
41
|
+
const { canScrollLeft, canScrollRight, isScrollable, checkScroll } = useScrollDetection(scrollAreaRef);
|
|
42
|
+
const { total = 0, limit = 10, pages = 1, page = 1, hasNext = false, hasPrev = false, onPageChange = () => {} } = pagination ?? {};
|
|
43
|
+
const table = useReactTable({
|
|
44
|
+
data,
|
|
45
|
+
columns,
|
|
46
|
+
getCoreRowModel: getCoreRowModel(),
|
|
47
|
+
getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
|
|
48
|
+
onSortingChange: setSorting,
|
|
49
|
+
onRowSelectionChange: enableRowSelection ? (updater) => {
|
|
50
|
+
setRowSelection(updater);
|
|
51
|
+
if (onRowSelectionChange) {
|
|
52
|
+
const newSelection = typeof updater === "function" ? updater(rowSelection) : updater;
|
|
53
|
+
onRowSelectionChange(data.filter((_, index) => newSelection[index]));
|
|
54
|
+
}
|
|
55
|
+
} : void 0,
|
|
56
|
+
state: {
|
|
57
|
+
sorting,
|
|
58
|
+
rowSelection: enableRowSelection ? rowSelection : void 0
|
|
59
|
+
},
|
|
60
|
+
enableRowSelection
|
|
61
|
+
});
|
|
62
|
+
const scrollHorizontally = useCallback((direction) => {
|
|
63
|
+
const scrollContainer = scrollAreaRef.current?.querySelector("[data-slot=\"scroll-area-viewport\"]");
|
|
64
|
+
if (!scrollContainer) return;
|
|
65
|
+
const scrollAmount = Math.min(300, scrollContainer.clientWidth * .8);
|
|
66
|
+
scrollContainer.scrollBy({
|
|
67
|
+
left: direction === "left" ? -scrollAmount : scrollAmount,
|
|
68
|
+
behavior: "smooth"
|
|
69
|
+
});
|
|
70
|
+
}, []);
|
|
71
|
+
const handleWheel = useCallback((e) => {
|
|
72
|
+
if (!isScrollable) return;
|
|
73
|
+
const scrollContainer = scrollAreaRef.current?.querySelector("[data-slot=\"scroll-area-viewport\"]");
|
|
74
|
+
if (!scrollContainer?.contains(e.target)) return;
|
|
75
|
+
if (e.shiftKey || Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
const delta = e.deltaY || e.deltaX;
|
|
78
|
+
scrollContainer.scrollBy({
|
|
79
|
+
left: delta,
|
|
80
|
+
behavior: "auto"
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}, [isScrollable]);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const scrollContainer = scrollAreaRef.current?.querySelector("[data-slot=\"scroll-area-viewport\"]");
|
|
86
|
+
if (!scrollContainer) return;
|
|
87
|
+
checkScroll();
|
|
88
|
+
scrollContainer.addEventListener("scroll", checkScroll, { passive: true });
|
|
89
|
+
scrollContainer.addEventListener("wheel", handleWheel, { passive: false });
|
|
90
|
+
return () => {
|
|
91
|
+
scrollContainer.removeEventListener("scroll", checkScroll);
|
|
92
|
+
scrollContainer.removeEventListener("wheel", handleWheel);
|
|
93
|
+
};
|
|
94
|
+
}, [checkScroll, handleWheel]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const timer = setTimeout(checkScroll, 150);
|
|
97
|
+
return () => clearTimeout(timer);
|
|
98
|
+
}, [data, checkScroll]);
|
|
99
|
+
const defaultLoadingState = useMemo(() => /* @__PURE__ */ jsx("div", {
|
|
100
|
+
className: "w-full h-full min-h-[24rem] flex items-center justify-center bg-background/50 rounded-lg border border-border",
|
|
101
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
102
|
+
className: "flex flex-col items-center gap-3",
|
|
103
|
+
children: [/* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-2 border-primary border-t-transparent" }), /* @__PURE__ */ jsx("p", {
|
|
104
|
+
className: "text-sm text-muted-foreground",
|
|
105
|
+
children: "Loading data..."
|
|
106
|
+
})]
|
|
107
|
+
})
|
|
108
|
+
}), []);
|
|
109
|
+
const defaultEmptyState = useMemo(() => /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
110
|
+
colSpan: columns.length,
|
|
111
|
+
className: "h-32 text-center",
|
|
112
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
113
|
+
className: "flex flex-col items-center justify-center py-8",
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ jsx("div", {
|
|
116
|
+
className: "rounded-full bg-muted p-3 mb-4",
|
|
117
|
+
children: /* @__PURE__ */ jsx(Search, { className: "h-6 w-6 text-muted-foreground" })
|
|
118
|
+
}),
|
|
119
|
+
/* @__PURE__ */ jsx("p", {
|
|
120
|
+
className: "text-lg font-medium text-foreground mb-1",
|
|
121
|
+
children: "No results found"
|
|
122
|
+
}),
|
|
123
|
+
/* @__PURE__ */ jsx("p", {
|
|
124
|
+
className: "text-sm text-muted-foreground",
|
|
125
|
+
children: "Try adjusting your search or filters"
|
|
126
|
+
})
|
|
127
|
+
]
|
|
128
|
+
})
|
|
129
|
+
}) }), [columns.length]);
|
|
130
|
+
if (isLoading) return /* @__PURE__ */ jsx(Fragment, { children: customLoadingState || defaultLoadingState });
|
|
131
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
132
|
+
className: cn("flex flex-col h-full gap-4", className),
|
|
133
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
134
|
+
className: "flex-1 min-h-0 rounded-lg border overflow-hidden bg-background shadow-sm relative",
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ jsxs(ScrollArea, {
|
|
137
|
+
ref: scrollAreaRef,
|
|
138
|
+
className: "h-full w-full",
|
|
139
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
140
|
+
className: "min-w-full",
|
|
141
|
+
children: /* @__PURE__ */ jsxs(Table, { children: [/* @__PURE__ */ jsx(TableHeader, {
|
|
142
|
+
className: "sticky top-0 z-10 bg-muted/50 backdrop-blur-sm",
|
|
143
|
+
children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, {
|
|
144
|
+
className: "border-b border-border hover:bg-transparent",
|
|
145
|
+
children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(TableHead, {
|
|
146
|
+
className: cn("bg-muted/50 font-semibold text-foreground h-12 px-4 whitespace-nowrap", "first:rounded-tl-lg last:rounded-tr-lg"),
|
|
147
|
+
children: header.isPlaceholder ? null : enableSorting && header.column.getCanSort() ? /* @__PURE__ */ jsxs(Button, {
|
|
148
|
+
variant: "ghost",
|
|
149
|
+
onClick: () => header.column.toggleSorting(header.column.getIsSorted() === "asc"),
|
|
150
|
+
className: "h-auto p-0 font-semibold hover:bg-transparent whitespace-nowrap",
|
|
151
|
+
children: [flexRender(header.column.columnDef.header, header.getContext()), header.column.getIsSorted() === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "ml-2 h-4 w-4" }) : header.column.getIsSorted() === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "ml-2 h-4 w-4" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "ml-2 h-4 w-4 opacity-50" })]
|
|
152
|
+
}) : flexRender(header.column.columnDef.header, header.getContext())
|
|
153
|
+
}, header.id))
|
|
154
|
+
}, headerGroup.id))
|
|
155
|
+
}), /* @__PURE__ */ jsx(TableBody, { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row, index) => /* @__PURE__ */ jsx(TableRow, {
|
|
156
|
+
className: cn("hover:bg-muted/50 transition-colors border-b border-border/50", index % 2 === 0 ? "bg-background" : "bg-muted/20"),
|
|
157
|
+
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
|
|
158
|
+
className: "px-4 py-3 text-sm whitespace-nowrap",
|
|
159
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
160
|
+
}, cell.id))
|
|
161
|
+
}, row.id)) : customEmptyState ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
162
|
+
colSpan: columns.length,
|
|
163
|
+
className: "h-32 text-center",
|
|
164
|
+
children: customEmptyState
|
|
165
|
+
}) }) : defaultEmptyState })] })
|
|
166
|
+
}), /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal" })]
|
|
167
|
+
}),
|
|
168
|
+
canScrollLeft && /* @__PURE__ */ jsx(ScrollButton, {
|
|
169
|
+
direction: "left",
|
|
170
|
+
onClick: () => scrollHorizontally("left"),
|
|
171
|
+
visible: true
|
|
172
|
+
}),
|
|
173
|
+
canScrollRight && /* @__PURE__ */ jsx(ScrollButton, {
|
|
174
|
+
direction: "right",
|
|
175
|
+
onClick: () => scrollHorizontally("right"),
|
|
176
|
+
visible: true
|
|
177
|
+
})
|
|
178
|
+
]
|
|
179
|
+
}), pagination && /* @__PURE__ */ jsx(ApiPagination, {
|
|
180
|
+
total,
|
|
181
|
+
limit,
|
|
182
|
+
pages,
|
|
183
|
+
page,
|
|
184
|
+
hasNext,
|
|
185
|
+
hasPrev,
|
|
186
|
+
onPageChange
|
|
187
|
+
})]
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/components/data-table-toolbar.tsx
|
|
193
|
+
/**
|
|
194
|
+
* DataTableToolbar - Composable toolbar with search, filters, and bulk actions
|
|
195
|
+
*
|
|
196
|
+
* Must be used inside a `Search.Root` context.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```tsx
|
|
200
|
+
* <Search.Root hook={searchHook}>
|
|
201
|
+
* <DataTableToolbar
|
|
202
|
+
* searchPlaceholder="Search users..."
|
|
203
|
+
* filterContent={<SelectInput name="role" items={roles} />}
|
|
204
|
+
* totalResults={data?.total}
|
|
205
|
+
* selectedCount={selected.length}
|
|
206
|
+
* bulkActions={<Button variant="destructive">Delete</Button>}
|
|
207
|
+
* />
|
|
208
|
+
* <DataTable columns={columns} data={data?.items ?? []} />
|
|
209
|
+
* </Search.Root>
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
function DataTableToolbar({ children, className, showSearch = true, searchPlaceholder = "Search...", showFilters = true, filterContent, filterTitle = "Filters", selectedCount, bulkActions, totalResults, showResultCount }) {
|
|
213
|
+
const search = useSearch();
|
|
214
|
+
const isMobile = useIsMobile();
|
|
215
|
+
const [filterOpen, setFilterOpen] = useState(false);
|
|
216
|
+
const hasSelection = selectedCount != null && selectedCount > 0;
|
|
217
|
+
const showResults = showResultCount ?? (totalResults != null && totalResults >= 0);
|
|
218
|
+
const handleSearchInput = useCallback((e) => {
|
|
219
|
+
search.setSearchValue(e.target.value);
|
|
220
|
+
}, [search]);
|
|
221
|
+
if (hasSelection) return /* @__PURE__ */ jsxs("div", {
|
|
222
|
+
className: cn("flex items-center gap-3 py-2", className),
|
|
223
|
+
children: [
|
|
224
|
+
/* @__PURE__ */ jsxs(Badge, {
|
|
225
|
+
variant: "secondary",
|
|
226
|
+
className: "shrink-0",
|
|
227
|
+
children: [selectedCount, " selected"]
|
|
228
|
+
}),
|
|
229
|
+
/* @__PURE__ */ jsx("div", {
|
|
230
|
+
className: "flex items-center gap-2",
|
|
231
|
+
children: bulkActions
|
|
232
|
+
}),
|
|
233
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
234
|
+
children
|
|
235
|
+
]
|
|
236
|
+
});
|
|
237
|
+
const filterElement = showFilters && filterContent && /* @__PURE__ */ jsx(Fragment, { children: isMobile ? /* @__PURE__ */ jsxs(Sheet, {
|
|
238
|
+
open: filterOpen,
|
|
239
|
+
onOpenChange: setFilterOpen,
|
|
240
|
+
children: [/* @__PURE__ */ jsxs(Button, {
|
|
241
|
+
variant: "outline",
|
|
242
|
+
size: "sm",
|
|
243
|
+
onClick: () => setFilterOpen(true),
|
|
244
|
+
className: "shrink-0",
|
|
245
|
+
children: [
|
|
246
|
+
/* @__PURE__ */ jsx(SlidersHorizontal, { className: "mr-2 h-4 w-4" }),
|
|
247
|
+
filterTitle,
|
|
248
|
+
search.hasActiveFilters && /* @__PURE__ */ jsx(Badge, {
|
|
249
|
+
variant: "default",
|
|
250
|
+
className: "ml-1.5 h-5 min-w-5 px-1",
|
|
251
|
+
children: "!"
|
|
252
|
+
})
|
|
253
|
+
]
|
|
254
|
+
}), /* @__PURE__ */ jsxs(SheetContent, {
|
|
255
|
+
side: "bottom",
|
|
256
|
+
children: [
|
|
257
|
+
/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: filterTitle }) }),
|
|
258
|
+
/* @__PURE__ */ jsx("div", {
|
|
259
|
+
className: "py-4 space-y-4",
|
|
260
|
+
children: filterContent
|
|
261
|
+
}),
|
|
262
|
+
/* @__PURE__ */ jsxs("div", {
|
|
263
|
+
className: "flex gap-2 pt-2",
|
|
264
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
265
|
+
variant: "outline",
|
|
266
|
+
className: "flex-1",
|
|
267
|
+
onClick: () => {
|
|
268
|
+
search.clearSearch();
|
|
269
|
+
setFilterOpen(false);
|
|
270
|
+
},
|
|
271
|
+
children: "Clear"
|
|
272
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
273
|
+
className: "flex-1",
|
|
274
|
+
onClick: () => {
|
|
275
|
+
search.handleSearch();
|
|
276
|
+
setFilterOpen(false);
|
|
277
|
+
},
|
|
278
|
+
children: "Apply"
|
|
279
|
+
})]
|
|
280
|
+
})
|
|
281
|
+
]
|
|
282
|
+
})]
|
|
283
|
+
}) : /* @__PURE__ */ jsxs(Popover, {
|
|
284
|
+
open: filterOpen,
|
|
285
|
+
onOpenChange: setFilterOpen,
|
|
286
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsxs(Button, {
|
|
287
|
+
variant: "outline",
|
|
288
|
+
size: "sm",
|
|
289
|
+
className: "shrink-0",
|
|
290
|
+
children: [
|
|
291
|
+
/* @__PURE__ */ jsx(SlidersHorizontal, { className: "mr-2 h-4 w-4" }),
|
|
292
|
+
filterTitle,
|
|
293
|
+
search.hasActiveFilters && /* @__PURE__ */ jsx(Badge, {
|
|
294
|
+
variant: "default",
|
|
295
|
+
className: "ml-1.5 h-5 min-w-5 px-1",
|
|
296
|
+
children: "!"
|
|
297
|
+
})
|
|
298
|
+
]
|
|
299
|
+
}) }), /* @__PURE__ */ jsx(PopoverContent, {
|
|
300
|
+
className: "w-80",
|
|
301
|
+
align: "end",
|
|
302
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
303
|
+
className: "space-y-4",
|
|
304
|
+
children: [
|
|
305
|
+
/* @__PURE__ */ jsx("h4", {
|
|
306
|
+
className: "font-medium text-sm",
|
|
307
|
+
children: filterTitle
|
|
308
|
+
}),
|
|
309
|
+
filterContent,
|
|
310
|
+
/* @__PURE__ */ jsxs("div", {
|
|
311
|
+
className: "flex gap-2 pt-2",
|
|
312
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
313
|
+
variant: "outline",
|
|
314
|
+
size: "sm",
|
|
315
|
+
className: "flex-1",
|
|
316
|
+
onClick: () => {
|
|
317
|
+
search.clearSearch();
|
|
318
|
+
setFilterOpen(false);
|
|
319
|
+
},
|
|
320
|
+
children: "Clear"
|
|
321
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
322
|
+
size: "sm",
|
|
323
|
+
className: "flex-1",
|
|
324
|
+
onClick: () => {
|
|
325
|
+
search.handleSearch();
|
|
326
|
+
setFilterOpen(false);
|
|
327
|
+
},
|
|
328
|
+
children: "Apply"
|
|
329
|
+
})]
|
|
330
|
+
})
|
|
331
|
+
]
|
|
332
|
+
})
|
|
333
|
+
})]
|
|
334
|
+
}) });
|
|
335
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
336
|
+
className: cn("flex items-center gap-3 py-2", className),
|
|
337
|
+
children: [
|
|
338
|
+
showSearch && /* @__PURE__ */ jsxs("div", {
|
|
339
|
+
className: "relative flex-1 max-w-sm",
|
|
340
|
+
children: [
|
|
341
|
+
/* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
|
|
342
|
+
/* @__PURE__ */ jsx(Input, {
|
|
343
|
+
placeholder: searchPlaceholder,
|
|
344
|
+
defaultValue: search.searchValue,
|
|
345
|
+
onChange: handleSearchInput,
|
|
346
|
+
className: "pl-8 h-9"
|
|
347
|
+
}),
|
|
348
|
+
search.hasActiveSearch && /* @__PURE__ */ jsx("button", {
|
|
349
|
+
type: "button",
|
|
350
|
+
onClick: search.clearSearch,
|
|
351
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
352
|
+
"aria-label": "Clear search",
|
|
353
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
354
|
+
})
|
|
355
|
+
]
|
|
356
|
+
}),
|
|
357
|
+
filterElement,
|
|
358
|
+
showResults && totalResults != null && /* @__PURE__ */ jsxs("span", {
|
|
359
|
+
className: "text-sm text-muted-foreground shrink-0",
|
|
360
|
+
children: [
|
|
361
|
+
totalResults,
|
|
362
|
+
" result",
|
|
363
|
+
totalResults !== 1 ? "s" : ""
|
|
364
|
+
]
|
|
365
|
+
}),
|
|
366
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
367
|
+
children
|
|
368
|
+
]
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
//#endregion
|
|
373
|
+
export { DataTable, DataTableToolbar };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import * as React$1 from "react";
|
|
5
|
+
import { Moon, Sun } from "lucide-react";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
|
8
|
+
import { useTheme } from "next-themes";
|
|
9
|
+
|
|
10
|
+
//#region src/components/mode-toggle.tsx
|
|
11
|
+
function ModeToggle() {
|
|
12
|
+
const { theme, setTheme } = useTheme();
|
|
13
|
+
const [mounted, setMounted] = React$1.useState(false);
|
|
14
|
+
React$1.useEffect(() => {
|
|
15
|
+
setMounted(true);
|
|
16
|
+
}, []);
|
|
17
|
+
if (!mounted) return /* @__PURE__ */ jsxs(Button, {
|
|
18
|
+
variant: "ghost",
|
|
19
|
+
size: "icon",
|
|
20
|
+
children: [/* @__PURE__ */ jsx(Sun, { className: "h-[1.2rem] w-[1.2rem]" }), /* @__PURE__ */ jsx("span", {
|
|
21
|
+
className: "sr-only",
|
|
22
|
+
children: "Toggle theme"
|
|
23
|
+
})]
|
|
24
|
+
});
|
|
25
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, {
|
|
26
|
+
modal: false,
|
|
27
|
+
children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
|
|
28
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
29
|
+
variant: "ghost",
|
|
30
|
+
size: "icon"
|
|
31
|
+
}),
|
|
32
|
+
children: [
|
|
33
|
+
/* @__PURE__ */ jsx(Sun, { className: "h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" }),
|
|
34
|
+
/* @__PURE__ */ jsx(Moon, { className: "absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" }),
|
|
35
|
+
/* @__PURE__ */ jsx("span", {
|
|
36
|
+
className: "sr-only",
|
|
37
|
+
children: "Toggle theme"
|
|
38
|
+
})
|
|
39
|
+
]
|
|
40
|
+
}), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
41
|
+
align: "end",
|
|
42
|
+
children: /* @__PURE__ */ jsxs(DropdownMenuRadioGroup, {
|
|
43
|
+
value: theme,
|
|
44
|
+
onValueChange: setTheme,
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
47
|
+
value: "light",
|
|
48
|
+
children: "Light"
|
|
49
|
+
}),
|
|
50
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
51
|
+
value: "dark",
|
|
52
|
+
children: "Dark"
|
|
53
|
+
}),
|
|
54
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
55
|
+
value: "system",
|
|
56
|
+
children: "System"
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
})
|
|
60
|
+
})]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { ModeToggle };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { n as useKeyboardShortcut } from "./use-keyboard-shortcut-_mRCh3QO.mjs";
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/components/command/command-search.d.ts
|
|
6
|
+
interface CommandSearchContextValue {
|
|
7
|
+
open: boolean;
|
|
8
|
+
setOpen: (open: boolean) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* useCommandSearch — Access command search open/close state.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const { open, setOpen } = useCommandSearch();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
declare function useCommandSearch(): CommandSearchContextValue;
|
|
19
|
+
interface CommandSearchProps {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
/** Controlled open state */
|
|
22
|
+
open?: boolean;
|
|
23
|
+
/** Open state change handler */
|
|
24
|
+
onOpenChange?: (open: boolean) => void;
|
|
25
|
+
/** Keyboard shortcut to toggle (default: "mod+k") */
|
|
26
|
+
shortcut?: string;
|
|
27
|
+
/** Whether to register the keyboard shortcut (default: true) */
|
|
28
|
+
enableShortcut?: boolean;
|
|
29
|
+
/** Dialog title for accessibility (default: "Command Palette") */
|
|
30
|
+
title?: string;
|
|
31
|
+
/** Dialog description for accessibility */
|
|
32
|
+
description?: string;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
interface CommandSearchInputProps {
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
interface CommandSearchGroupProps {
|
|
40
|
+
heading?: string;
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
interface CommandSearchItemProps {
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
/** Icon element rendered before the label */
|
|
47
|
+
icon?: ReactNode;
|
|
48
|
+
/** Keyboard shortcut displayed via Kbd component */
|
|
49
|
+
shortcut?: string;
|
|
50
|
+
/** Called when the item is selected */
|
|
51
|
+
onSelect?: (value: string) => void;
|
|
52
|
+
/** Value for filtering */
|
|
53
|
+
value?: string;
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
interface CommandSearchEmptyProps {
|
|
58
|
+
children?: ReactNode;
|
|
59
|
+
className?: string;
|
|
60
|
+
}
|
|
61
|
+
interface CommandSearchListProps {
|
|
62
|
+
children: ReactNode;
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* CommandSearch — Command palette dialog with keyboard shortcut.
|
|
67
|
+
* Wraps consumer's @/components/ui/command (Base UI shadcn).
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <CommandSearch shortcut="mod+k">
|
|
72
|
+
* <CommandSearch.Input placeholder="Type a command or search..." />
|
|
73
|
+
* <CommandSearch.List>
|
|
74
|
+
* <CommandSearch.Empty>No results found.</CommandSearch.Empty>
|
|
75
|
+
* <CommandSearch.Group heading="Navigation">
|
|
76
|
+
* <CommandSearch.Item icon={<Home />} shortcut="⌘H" onSelect={() => navigate("/")}>
|
|
77
|
+
* Home
|
|
78
|
+
* </CommandSearch.Item>
|
|
79
|
+
* </CommandSearch.Group>
|
|
80
|
+
* </CommandSearch.List>
|
|
81
|
+
* </CommandSearch>
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
declare function CommandSearchRoot({
|
|
85
|
+
children,
|
|
86
|
+
open: controlledOpen,
|
|
87
|
+
onOpenChange: controlledOnOpenChange,
|
|
88
|
+
shortcut,
|
|
89
|
+
enableShortcut,
|
|
90
|
+
title,
|
|
91
|
+
description,
|
|
92
|
+
className
|
|
93
|
+
}: CommandSearchProps): react_jsx_runtime0.JSX.Element;
|
|
94
|
+
declare function CommandSearchInput({
|
|
95
|
+
placeholder,
|
|
96
|
+
className
|
|
97
|
+
}: CommandSearchInputProps): react_jsx_runtime0.JSX.Element;
|
|
98
|
+
declare function CommandSearchList({
|
|
99
|
+
children,
|
|
100
|
+
className
|
|
101
|
+
}: CommandSearchListProps): react_jsx_runtime0.JSX.Element;
|
|
102
|
+
declare function CommandSearchGroup({
|
|
103
|
+
heading,
|
|
104
|
+
children,
|
|
105
|
+
className
|
|
106
|
+
}: CommandSearchGroupProps): react_jsx_runtime0.JSX.Element;
|
|
107
|
+
declare function CommandSearchItemComponent({
|
|
108
|
+
children,
|
|
109
|
+
icon,
|
|
110
|
+
shortcut: shortcutDisplay,
|
|
111
|
+
onSelect,
|
|
112
|
+
value,
|
|
113
|
+
disabled,
|
|
114
|
+
className
|
|
115
|
+
}: CommandSearchItemProps): react_jsx_runtime0.JSX.Element;
|
|
116
|
+
declare function CommandSearchEmpty({
|
|
117
|
+
children,
|
|
118
|
+
className
|
|
119
|
+
}: CommandSearchEmptyProps): react_jsx_runtime0.JSX.Element;
|
|
120
|
+
declare function CommandSearchSeparator({
|
|
121
|
+
className
|
|
122
|
+
}: {
|
|
123
|
+
className?: string;
|
|
124
|
+
}): react_jsx_runtime0.JSX.Element;
|
|
125
|
+
declare const CommandSearch: typeof CommandSearchRoot & {
|
|
126
|
+
Input: typeof CommandSearchInput;
|
|
127
|
+
List: typeof CommandSearchList;
|
|
128
|
+
Group: typeof CommandSearchGroup;
|
|
129
|
+
Item: typeof CommandSearchItemComponent;
|
|
130
|
+
Empty: typeof CommandSearchEmpty;
|
|
131
|
+
Separator: typeof CommandSearchSeparator;
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
export { CommandSearch, type CommandSearchEmptyProps, type CommandSearchGroupProps, type CommandSearchInputProps, type CommandSearchItemProps, type CommandSearchListProps, type CommandSearchProps, useCommandSearch, useKeyboardShortcut };
|