@classytic/fluid 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/core.d.mts +14 -18
- package/dist/client/core.mjs +105 -279
- package/dist/client/hooks.mjs +52 -26
- package/dist/client/table.d.mts +41 -5
- package/dist/client/table.mjs +54 -7
- package/dist/dashboard.d.mts +2 -0
- package/dist/dashboard.mjs +1 -1
- package/dist/dropdown-wrapper-B86u9Fri.mjs +357 -0
- package/dist/forms.mjs +22 -7
- package/package.json +1 -1
- package/dist/api-pagination-DBTE0yk4.mjs +0 -190
package/dist/client/table.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as ApiPaginationData } from "../api-pagination-CJ0vR_w6.mjs";
|
|
2
2
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
-
import React, { ReactNode } from "react";
|
|
4
|
-
import { ColumnDef } from "@tanstack/react-table";
|
|
3
|
+
import React$1, { ReactNode } from "react";
|
|
4
|
+
import { ColumnDef, Table as TanstackTable, VisibilityState, VisibilityState as VisibilityState$1 } from "@tanstack/react-table";
|
|
5
5
|
|
|
6
6
|
//#region src/components/data-table.d.ts
|
|
7
7
|
interface DataTablePaginationProps extends Partial<ApiPaginationData> {
|
|
@@ -15,11 +15,15 @@ interface DataTableProps<TData, TValue> {
|
|
|
15
15
|
enableSorting?: boolean;
|
|
16
16
|
enableRowSelection?: boolean;
|
|
17
17
|
onRowSelectionChange?: (selectedRows: TData[]) => void;
|
|
18
|
+
/** Controlled column visibility state. Keys are column IDs, values are booleans. */
|
|
19
|
+
columnVisibility?: VisibilityState$1;
|
|
20
|
+
/** Callback when column visibility changes. */
|
|
21
|
+
onColumnVisibilityChange?: (visibility: VisibilityState$1) => void;
|
|
18
22
|
className?: string;
|
|
19
23
|
/** Custom loading state renderer */
|
|
20
|
-
loadingState?: React.ReactNode;
|
|
24
|
+
loadingState?: React$1.ReactNode;
|
|
21
25
|
/** Custom empty state renderer */
|
|
22
|
-
emptyState?: React.ReactNode;
|
|
26
|
+
emptyState?: React$1.ReactNode;
|
|
23
27
|
}
|
|
24
28
|
declare function DataTable<TData, TValue>({
|
|
25
29
|
columns,
|
|
@@ -29,6 +33,8 @@ declare function DataTable<TData, TValue>({
|
|
|
29
33
|
enableSorting,
|
|
30
34
|
enableRowSelection,
|
|
31
35
|
onRowSelectionChange,
|
|
36
|
+
columnVisibility: controlledVisibility,
|
|
37
|
+
onColumnVisibilityChange,
|
|
32
38
|
className,
|
|
33
39
|
loadingState: customLoadingState,
|
|
34
40
|
emptyState: customEmptyState
|
|
@@ -81,4 +87,34 @@ declare function DataTableToolbar({
|
|
|
81
87
|
showResultCount
|
|
82
88
|
}: DataTableToolbarProps): react_jsx_runtime0.JSX.Element;
|
|
83
89
|
//#endregion
|
|
84
|
-
|
|
90
|
+
//#region src/components/data-table-column-toggle.d.ts
|
|
91
|
+
interface ToggleableColumn {
|
|
92
|
+
/** Column ID (must match the column key used in VisibilityState) */
|
|
93
|
+
id: string;
|
|
94
|
+
/** Display name shown in the toggle list */
|
|
95
|
+
header: string;
|
|
96
|
+
}
|
|
97
|
+
interface DataTableColumnToggleProps {
|
|
98
|
+
/** List of columns that can be toggled. Derive from your ColumnDef array. */
|
|
99
|
+
columns: ToggleableColumn[];
|
|
100
|
+
/** Controlled visibility state. Keys are column IDs, values are booleans. */
|
|
101
|
+
columnVisibility: VisibilityState$1;
|
|
102
|
+
/** Callback when visibility changes. */
|
|
103
|
+
onColumnVisibilityChange: (visibility: VisibilityState$1) => void;
|
|
104
|
+
/** Custom trigger element. Defaults to a Settings2 icon button. */
|
|
105
|
+
trigger?: React.ReactNode;
|
|
106
|
+
/** Label shown at the top of the dropdown */
|
|
107
|
+
label?: string;
|
|
108
|
+
/** Additional className for the trigger button */
|
|
109
|
+
className?: string;
|
|
110
|
+
}
|
|
111
|
+
declare function DataTableColumnToggle({
|
|
112
|
+
columns,
|
|
113
|
+
columnVisibility,
|
|
114
|
+
onColumnVisibilityChange,
|
|
115
|
+
trigger,
|
|
116
|
+
label,
|
|
117
|
+
className
|
|
118
|
+
}: DataTableColumnToggleProps): react_jsx_runtime0.JSX.Element;
|
|
119
|
+
//#endregion
|
|
120
|
+
export { DataTable, DataTableColumnToggle, type DataTableColumnToggleProps, type DataTablePaginationProps, type DataTableProps, DataTableToolbar, type DataTableToolbarProps, type TanstackTable, type ToggleableColumn, type VisibilityState };
|
package/dist/client/table.mjs
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
import { t as cn } from "../utils-DQ5SCVoW.mjs";
|
|
4
4
|
import { t as useIsMobile } from "../use-mobile-BX3SQVo2.mjs";
|
|
5
5
|
import { t as useScrollDetection } from "../use-scroll-detection-CsgsQYvy.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { o as ApiPagination, r as DropdownWrapper } from "../dropdown-wrapper-B86u9Fri.mjs";
|
|
7
7
|
import { n as useSearch } from "../search-context-DR7DBs7S.mjs";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
10
10
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
-
import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, Search, SlidersHorizontal, X } from "lucide-react";
|
|
11
|
+
import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, Search, Settings2, SlidersHorizontal, X } from "lucide-react";
|
|
12
12
|
import { Button } from "@/components/ui/button";
|
|
13
13
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
14
14
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
@@ -16,6 +16,7 @@ import { Badge } from "@/components/ui/badge";
|
|
|
16
16
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
|
17
17
|
import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
|
18
18
|
import { Input } from "@/components/ui/input";
|
|
19
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
19
20
|
|
|
20
21
|
//#region src/components/data-table.tsx
|
|
21
22
|
const ScrollButton = React.memo(function ScrollButton({ direction, onClick, visible, className }) {
|
|
@@ -34,11 +35,14 @@ const ScrollButton = React.memo(function ScrollButton({ direction, onClick, visi
|
|
|
34
35
|
children: direction === "left" ? /* @__PURE__ */ jsx(ChevronLeft, { className: "size-5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-5" })
|
|
35
36
|
});
|
|
36
37
|
});
|
|
37
|
-
function DataTable({ columns, data, isLoading = false, pagination, enableSorting = false, enableRowSelection = false, onRowSelectionChange, className, loadingState: customLoadingState, emptyState: customEmptyState }) {
|
|
38
|
+
function DataTable({ columns, data, isLoading = false, pagination, enableSorting = false, enableRowSelection = false, onRowSelectionChange, columnVisibility: controlledVisibility, onColumnVisibilityChange, className, loadingState: customLoadingState, emptyState: customEmptyState }) {
|
|
38
39
|
const [sorting, setSorting] = useState([]);
|
|
39
40
|
const [rowSelection, setRowSelection] = useState({});
|
|
41
|
+
const [internalVisibility, setInternalVisibility] = useState({});
|
|
40
42
|
const scrollAreaRef = useRef(null);
|
|
41
43
|
const { canScrollLeft, canScrollRight, isScrollable, checkScroll } = useScrollDetection(scrollAreaRef);
|
|
44
|
+
const visibility = controlledVisibility ?? internalVisibility;
|
|
45
|
+
const setVisibility = onColumnVisibilityChange ?? setInternalVisibility;
|
|
42
46
|
const { total = 0, limit = 10, pages = 1, page = 1, hasNext = false, hasPrev = false, onPageChange = () => {} } = pagination ?? {};
|
|
43
47
|
const table = useReactTable({
|
|
44
48
|
data,
|
|
@@ -46,6 +50,9 @@ function DataTable({ columns, data, isLoading = false, pagination, enableSorting
|
|
|
46
50
|
getCoreRowModel: getCoreRowModel(),
|
|
47
51
|
getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
|
|
48
52
|
onSortingChange: setSorting,
|
|
53
|
+
onColumnVisibilityChange: (updater) => {
|
|
54
|
+
setVisibility(typeof updater === "function" ? updater(visibility) : updater);
|
|
55
|
+
},
|
|
49
56
|
onRowSelectionChange: enableRowSelection ? (updater) => {
|
|
50
57
|
setRowSelection(updater);
|
|
51
58
|
if (onRowSelectionChange) {
|
|
@@ -55,6 +62,7 @@ function DataTable({ columns, data, isLoading = false, pagination, enableSorting
|
|
|
55
62
|
} : void 0,
|
|
56
63
|
state: {
|
|
57
64
|
sorting,
|
|
65
|
+
columnVisibility: visibility,
|
|
58
66
|
rowSelection: enableRowSelection ? rowSelection : void 0
|
|
59
67
|
},
|
|
60
68
|
enableRowSelection
|
|
@@ -106,8 +114,9 @@ function DataTable({ columns, data, isLoading = false, pagination, enableSorting
|
|
|
106
114
|
})]
|
|
107
115
|
})
|
|
108
116
|
}), []);
|
|
117
|
+
const visibleColumnCount = table.getVisibleLeafColumns().length;
|
|
109
118
|
const defaultEmptyState = useMemo(() => /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
110
|
-
colSpan:
|
|
119
|
+
colSpan: visibleColumnCount,
|
|
111
120
|
className: "h-32 text-center",
|
|
112
121
|
children: /* @__PURE__ */ jsxs("div", {
|
|
113
122
|
className: "flex flex-col items-center justify-center py-8",
|
|
@@ -126,7 +135,7 @@ function DataTable({ columns, data, isLoading = false, pagination, enableSorting
|
|
|
126
135
|
})
|
|
127
136
|
]
|
|
128
137
|
})
|
|
129
|
-
}) }), [
|
|
138
|
+
}) }), [visibleColumnCount]);
|
|
130
139
|
if (isLoading) return /* @__PURE__ */ jsx(Fragment, { children: customLoadingState || defaultLoadingState });
|
|
131
140
|
return /* @__PURE__ */ jsxs("div", {
|
|
132
141
|
className: cn("flex flex-col h-full gap-4", className),
|
|
@@ -159,7 +168,7 @@ function DataTable({ columns, data, isLoading = false, pagination, enableSorting
|
|
|
159
168
|
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
160
169
|
}, cell.id))
|
|
161
170
|
}, row.id)) : customEmptyState ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
162
|
-
colSpan:
|
|
171
|
+
colSpan: visibleColumnCount,
|
|
163
172
|
className: "h-32 text-center",
|
|
164
173
|
children: customEmptyState
|
|
165
174
|
}) }) : defaultEmptyState })] })
|
|
@@ -370,4 +379,42 @@ function DataTableToolbar({ children, className, showSearch = true, searchPlaceh
|
|
|
370
379
|
}
|
|
371
380
|
|
|
372
381
|
//#endregion
|
|
373
|
-
|
|
382
|
+
//#region src/components/data-table-column-toggle.tsx
|
|
383
|
+
function DataTableColumnToggle({ columns, columnVisibility, onColumnVisibilityChange, trigger, label = "Columns", className }) {
|
|
384
|
+
const defaultTrigger = /* @__PURE__ */ jsxs(Button, {
|
|
385
|
+
variant: "outline",
|
|
386
|
+
size: "sm",
|
|
387
|
+
className: cn("shrink-0", className),
|
|
388
|
+
children: [/* @__PURE__ */ jsx(Settings2, { className: "mr-2 h-4 w-4" }), label]
|
|
389
|
+
});
|
|
390
|
+
return /* @__PURE__ */ jsxs(DropdownWrapper, {
|
|
391
|
+
trigger: trigger ?? defaultTrigger,
|
|
392
|
+
contentClassName: "min-w-[150px]",
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ jsx("div", {
|
|
395
|
+
className: "px-1.5 py-1 text-xs font-medium text-muted-foreground",
|
|
396
|
+
children: label
|
|
397
|
+
}),
|
|
398
|
+
/* @__PURE__ */ jsx("div", { className: "bg-border -mx-1 my-1 h-px" }),
|
|
399
|
+
columns.map((col) => {
|
|
400
|
+
const isVisible = columnVisibility[col.id] !== false;
|
|
401
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
402
|
+
role: "menuitemcheckbox",
|
|
403
|
+
"aria-checked": isVisible,
|
|
404
|
+
className: "flex items-center gap-2 rounded-md px-2 py-1.5 text-sm cursor-pointer hover:bg-accent select-none",
|
|
405
|
+
onClick: () => onColumnVisibilityChange({
|
|
406
|
+
...columnVisibility,
|
|
407
|
+
[col.id]: !isVisible
|
|
408
|
+
}),
|
|
409
|
+
children: [/* @__PURE__ */ jsx(Checkbox, {
|
|
410
|
+
checked: isVisible,
|
|
411
|
+
className: "pointer-events-none"
|
|
412
|
+
}), col.header]
|
|
413
|
+
}, col.id);
|
|
414
|
+
})
|
|
415
|
+
]
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
//#endregion
|
|
420
|
+
export { DataTable, DataTableColumnToggle, DataTableToolbar };
|
package/dist/dashboard.d.mts
CHANGED
|
@@ -49,6 +49,8 @@ interface NavItem extends NavPermissions {
|
|
|
49
49
|
url: string;
|
|
50
50
|
icon?: LucideIcon;
|
|
51
51
|
isActive?: boolean;
|
|
52
|
+
/** When true, the collapsible sub-menu is expanded by default (even when not active). */
|
|
53
|
+
defaultOpen?: boolean;
|
|
52
54
|
badge?: string | number;
|
|
53
55
|
items?: NavSubItem[];
|
|
54
56
|
}
|
package/dist/dashboard.mjs
CHANGED
|
@@ -379,7 +379,7 @@ function SidebarBrand({ title, icon, href = "/", className, tooltip }) {
|
|
|
379
379
|
function SidebarNavItem({ item, onClick }) {
|
|
380
380
|
const hasSubItems = item.items && item.items.length > 0;
|
|
381
381
|
const Icon = item.icon;
|
|
382
|
-
const [open, setOpen] = useState(item.isActive ?? false);
|
|
382
|
+
const [open, setOpen] = useState(item.defaultOpen ?? item.isActive ?? false);
|
|
383
383
|
if (hasSubItems) return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Collapsible, {
|
|
384
384
|
open,
|
|
385
385
|
onOpenChange: setOpen,
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { t as cn } from "./utils-DQ5SCVoW.mjs";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { ChevronDown, MoreHorizontal } from "lucide-react";
|
|
5
|
+
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
|
8
|
+
|
|
9
|
+
//#region src/components/custom-pagination.tsx
|
|
10
|
+
function CustomPagination({ page, onPageChange, pages, hasPrev, hasNext }) {
|
|
11
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
12
|
+
const currentPageNum = Number(page);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const checkMobile = () => {
|
|
15
|
+
setIsMobile(window.innerWidth < 640);
|
|
16
|
+
};
|
|
17
|
+
checkMobile();
|
|
18
|
+
window.addEventListener("resize", checkMobile);
|
|
19
|
+
return () => window.removeEventListener("resize", checkMobile);
|
|
20
|
+
}, []);
|
|
21
|
+
if (pages <= 1) return null;
|
|
22
|
+
const getPageNumbers = () => {
|
|
23
|
+
const delta = isMobile ? 1 : 2;
|
|
24
|
+
const range = [];
|
|
25
|
+
const rangeWithDots = [];
|
|
26
|
+
for (let i = Math.max(2, currentPageNum - delta); i <= Math.min(pages - 1, currentPageNum + delta); i++) range.push(i);
|
|
27
|
+
if (currentPageNum - delta > 2) rangeWithDots.push(1, "...");
|
|
28
|
+
else rangeWithDots.push(1);
|
|
29
|
+
rangeWithDots.push(...range);
|
|
30
|
+
if (currentPageNum + delta < pages - 1) rangeWithDots.push("...", pages);
|
|
31
|
+
else if (pages > 1) {
|
|
32
|
+
if (!range.includes(pages)) rangeWithDots.push(pages);
|
|
33
|
+
}
|
|
34
|
+
return [...new Set(rangeWithDots)];
|
|
35
|
+
};
|
|
36
|
+
if (isMobile && pages > 5) return /* @__PURE__ */ jsx(Pagination, {
|
|
37
|
+
className: "mx-0 w-auto",
|
|
38
|
+
children: /* @__PURE__ */ jsxs(PaginationContent, {
|
|
39
|
+
className: "gap-1",
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationPrevious, {
|
|
42
|
+
onClick: () => hasPrev ? onPageChange(currentPageNum - 1) : void 0,
|
|
43
|
+
className: cn("cursor-pointer transition-colors h-8 px-2 text-xs", !hasPrev && "pointer-events-none opacity-50 cursor-not-allowed", hasPrev && "hover:bg-accent hover:text-accent-foreground"),
|
|
44
|
+
"aria-disabled": !hasPrev
|
|
45
|
+
}) }),
|
|
46
|
+
/* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsxs("div", {
|
|
47
|
+
className: "flex h-8 items-center justify-center px-3 text-xs font-medium bg-primary text-primary-foreground rounded-md",
|
|
48
|
+
children: [
|
|
49
|
+
currentPageNum,
|
|
50
|
+
" / ",
|
|
51
|
+
pages
|
|
52
|
+
]
|
|
53
|
+
}) }),
|
|
54
|
+
/* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationNext, {
|
|
55
|
+
onClick: () => hasNext ? onPageChange(currentPageNum + 1) : void 0,
|
|
56
|
+
className: cn("cursor-pointer transition-colors h-8 px-2 text-xs", !hasNext && "pointer-events-none opacity-50 cursor-not-allowed", hasNext && "hover:bg-accent hover:text-accent-foreground"),
|
|
57
|
+
"aria-disabled": !hasNext
|
|
58
|
+
}) })
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
return /* @__PURE__ */ jsx(Pagination, {
|
|
63
|
+
className: "mx-0 w-auto",
|
|
64
|
+
children: /* @__PURE__ */ jsxs(PaginationContent, {
|
|
65
|
+
className: "gap-1 flex-wrap",
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationPrevious, {
|
|
68
|
+
onClick: () => hasPrev ? onPageChange(currentPageNum - 1) : void 0,
|
|
69
|
+
className: cn("cursor-pointer transition-colors", isMobile ? "h-8 px-2 text-xs" : "h-9 px-3 text-sm", !hasPrev && "pointer-events-none opacity-50 cursor-not-allowed", hasPrev && "hover:bg-accent hover:text-accent-foreground"),
|
|
70
|
+
"aria-disabled": !hasPrev
|
|
71
|
+
}) }),
|
|
72
|
+
getPageNumbers().map((pageNum, index) => /* @__PURE__ */ jsx(PaginationItem, { children: pageNum === "..." ? /* @__PURE__ */ jsx("span", {
|
|
73
|
+
className: cn("flex items-center justify-center text-muted-foreground", isMobile ? "h-8 w-8 text-xs" : "h-9 w-9 text-sm"),
|
|
74
|
+
children: "..."
|
|
75
|
+
}) : /* @__PURE__ */ jsx(PaginationLink, {
|
|
76
|
+
className: cn("cursor-pointer transition-colors", isMobile ? "h-8 w-8 text-xs p-0" : "h-9 w-9 text-sm p-0", "hover:bg-accent hover:text-accent-foreground", Number(currentPageNum) === Number(pageNum) && "bg-primary text-primary-foreground hover:bg-primary/90"),
|
|
77
|
+
onClick: () => typeof pageNum === "number" ? onPageChange(pageNum) : void 0,
|
|
78
|
+
isActive: Number(currentPageNum) === Number(pageNum),
|
|
79
|
+
"aria-label": `Go to page ${pageNum}`,
|
|
80
|
+
"aria-current": Number(currentPageNum) === Number(pageNum) ? "page" : void 0,
|
|
81
|
+
children: pageNum
|
|
82
|
+
}) }, `pagination-page-${pageNum}-${index}`)),
|
|
83
|
+
/* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationNext, {
|
|
84
|
+
onClick: () => hasNext ? onPageChange(currentPageNum + 1) : void 0,
|
|
85
|
+
className: cn("cursor-pointer transition-colors", isMobile ? "h-8 px-2 text-xs" : "h-9 px-3 text-sm", !hasNext && "pointer-events-none opacity-50 cursor-not-allowed", hasNext && "hover:bg-accent hover:text-accent-foreground"),
|
|
86
|
+
"aria-disabled": !hasNext
|
|
87
|
+
}) })
|
|
88
|
+
]
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function PaginationInfo({ page, total, limit = 10 }) {
|
|
93
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const checkMobile = () => {
|
|
96
|
+
setIsMobile(window.innerWidth < 640);
|
|
97
|
+
};
|
|
98
|
+
checkMobile();
|
|
99
|
+
window.addEventListener("resize", checkMobile);
|
|
100
|
+
return () => window.removeEventListener("resize", checkMobile);
|
|
101
|
+
}, []);
|
|
102
|
+
if (total === 0) return /* @__PURE__ */ jsx("div", {
|
|
103
|
+
className: "flex-1 text-sm text-muted-foreground",
|
|
104
|
+
children: /* @__PURE__ */ jsx("p", { children: "No entries found" })
|
|
105
|
+
});
|
|
106
|
+
const startEntry = (page - 1) * limit + 1;
|
|
107
|
+
const endEntry = Math.min(page * limit, total);
|
|
108
|
+
return /* @__PURE__ */ jsx("div", {
|
|
109
|
+
className: "flex-1 text-sm text-muted-foreground",
|
|
110
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
111
|
+
className: cn("whitespace-nowrap", isMobile && "text-xs"),
|
|
112
|
+
children: isMobile ? /* @__PURE__ */ jsxs("span", { children: [
|
|
113
|
+
startEntry.toLocaleString(),
|
|
114
|
+
"-",
|
|
115
|
+
endEntry.toLocaleString(),
|
|
116
|
+
" of",
|
|
117
|
+
" ",
|
|
118
|
+
total.toLocaleString()
|
|
119
|
+
] }) : /* @__PURE__ */ jsxs("span", { children: [
|
|
120
|
+
"Showing",
|
|
121
|
+
" ",
|
|
122
|
+
/* @__PURE__ */ jsx("span", {
|
|
123
|
+
className: "font-medium text-foreground",
|
|
124
|
+
children: startEntry.toLocaleString()
|
|
125
|
+
}),
|
|
126
|
+
" ",
|
|
127
|
+
"to",
|
|
128
|
+
" ",
|
|
129
|
+
/* @__PURE__ */ jsx("span", {
|
|
130
|
+
className: "font-medium text-foreground",
|
|
131
|
+
children: endEntry.toLocaleString()
|
|
132
|
+
}),
|
|
133
|
+
" ",
|
|
134
|
+
"of",
|
|
135
|
+
" ",
|
|
136
|
+
/* @__PURE__ */ jsx("span", {
|
|
137
|
+
className: "font-medium text-foreground",
|
|
138
|
+
children: total.toLocaleString()
|
|
139
|
+
}),
|
|
140
|
+
" ",
|
|
141
|
+
total === 1 ? "result" : "results"
|
|
142
|
+
] })
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/components/api-pagination.tsx
|
|
149
|
+
/**
|
|
150
|
+
* ApiPagination - A reusable pagination component for API-driven data
|
|
151
|
+
*/
|
|
152
|
+
function ApiPagination({ total = 0, limit = 10, pages = 1, page = 1, hasNext = false, hasPrev = false, onPageChange = () => {}, className, showInfo = true, infoPosition = "left" }) {
|
|
153
|
+
const infoComponent = showInfo && /* @__PURE__ */ jsx("div", {
|
|
154
|
+
className: "shrink-0",
|
|
155
|
+
children: /* @__PURE__ */ jsx(PaginationInfo, {
|
|
156
|
+
total,
|
|
157
|
+
page,
|
|
158
|
+
limit
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
const paginationComponent = /* @__PURE__ */ jsx("div", {
|
|
162
|
+
className: "flex justify-center sm:justify-end",
|
|
163
|
+
children: /* @__PURE__ */ jsx(CustomPagination, {
|
|
164
|
+
page,
|
|
165
|
+
pages,
|
|
166
|
+
hasPrev,
|
|
167
|
+
hasNext,
|
|
168
|
+
onPageChange
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
return /* @__PURE__ */ jsx("div", {
|
|
172
|
+
className: cn("shrink-0 bg-muted/30 rounded-lg border border-border p-3 mb-2", className),
|
|
173
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
174
|
+
className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4",
|
|
175
|
+
children: infoPosition === "left" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
176
|
+
className: "order-2 sm:order-1",
|
|
177
|
+
children: infoComponent
|
|
178
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
179
|
+
className: "order-1 sm:order-2",
|
|
180
|
+
children: paginationComponent
|
|
181
|
+
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
182
|
+
className: "order-1 sm:order-1",
|
|
183
|
+
children: paginationComponent
|
|
184
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
185
|
+
className: "order-2 sm:order-2",
|
|
186
|
+
children: infoComponent
|
|
187
|
+
})] })
|
|
188
|
+
})
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/components/dropdown-wrapper.tsx
|
|
194
|
+
function DropdownWrapper({ trigger, children, align = "end", side = "bottom", sideOffset = 4, className, contentClassName, ...props }) {
|
|
195
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, {
|
|
196
|
+
...props,
|
|
197
|
+
children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: trigger }), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
198
|
+
align,
|
|
199
|
+
side,
|
|
200
|
+
sideOffset,
|
|
201
|
+
className: cn("min-w-[160px]", contentClassName),
|
|
202
|
+
children
|
|
203
|
+
})]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function ActionDropdown({ items = [], triggerIcon: TriggerIcon = MoreHorizontal, triggerVariant = "ghost", triggerSize = "sm", triggerClassName, triggerLabel, showOnHover = false, align = "end", contentClassName, onOpenChange, stopPropagation = true, ...props }) {
|
|
207
|
+
const handleTriggerClick = (e) => {
|
|
208
|
+
if (stopPropagation) e.stopPropagation();
|
|
209
|
+
};
|
|
210
|
+
const trigger = /* @__PURE__ */ jsxs(Button, {
|
|
211
|
+
variant: triggerVariant,
|
|
212
|
+
size: triggerSize,
|
|
213
|
+
onClick: handleTriggerClick,
|
|
214
|
+
className: cn(showOnHover && "opacity-0 group-hover:opacity-100 transition-opacity", triggerClassName),
|
|
215
|
+
children: [
|
|
216
|
+
/* @__PURE__ */ jsx(TriggerIcon, { className: "h-4 w-4" }),
|
|
217
|
+
triggerLabel && /* @__PURE__ */ jsx("span", {
|
|
218
|
+
className: "ml-2",
|
|
219
|
+
children: triggerLabel
|
|
220
|
+
}),
|
|
221
|
+
/* @__PURE__ */ jsx("span", {
|
|
222
|
+
className: "sr-only",
|
|
223
|
+
children: "Open menu"
|
|
224
|
+
})
|
|
225
|
+
]
|
|
226
|
+
});
|
|
227
|
+
const handleItemClick = (item) => (e) => {
|
|
228
|
+
if (stopPropagation) e.stopPropagation();
|
|
229
|
+
item.onClick?.(e);
|
|
230
|
+
};
|
|
231
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, {
|
|
232
|
+
onOpenChange,
|
|
233
|
+
...props,
|
|
234
|
+
children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: trigger }), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
235
|
+
align,
|
|
236
|
+
className: cn("min-w-[180px]", contentClassName),
|
|
237
|
+
children: items.map((item, index) => {
|
|
238
|
+
if (item.hidden) return null;
|
|
239
|
+
if (item.type === "separator") return /* @__PURE__ */ jsx(DropdownMenuSeparator, {}, item.key || `separator-${index}`);
|
|
240
|
+
if (item.type === "label") return /* @__PURE__ */ jsx(DropdownMenuGroup, { children: /* @__PURE__ */ jsx(DropdownMenuLabel, {
|
|
241
|
+
className: item.className,
|
|
242
|
+
children: item.label
|
|
243
|
+
}) }, item.key || `label-${index}`);
|
|
244
|
+
if (item.type === "group") return /* @__PURE__ */ jsx(DropdownMenuGroup, { children: item.items?.map((groupItem, groupIndex) => {
|
|
245
|
+
if (groupItem.hidden) return null;
|
|
246
|
+
const GroupItemIcon = groupItem.icon;
|
|
247
|
+
const groupItemLabel = typeof groupItem.label === "function" ? groupItem.label() : groupItem.label;
|
|
248
|
+
return /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
249
|
+
onClick: handleItemClick(groupItem),
|
|
250
|
+
disabled: groupItem.disabled,
|
|
251
|
+
className: cn(groupItem.variant === "destructive" && "text-destructive focus:text-destructive", groupItem.className),
|
|
252
|
+
children: [
|
|
253
|
+
GroupItemIcon && /* @__PURE__ */ jsx(GroupItemIcon, { className: "h-4 w-4" }),
|
|
254
|
+
/* @__PURE__ */ jsx("span", { children: groupItemLabel }),
|
|
255
|
+
groupItem.shortcut && /* @__PURE__ */ jsx("span", {
|
|
256
|
+
className: "ml-auto text-xs tracking-widest text-muted-foreground",
|
|
257
|
+
children: groupItem.shortcut
|
|
258
|
+
})
|
|
259
|
+
]
|
|
260
|
+
}, groupItem.key || `group-item-${groupIndex}`);
|
|
261
|
+
}) }, item.key || `group-${index}`);
|
|
262
|
+
const ItemIcon = item.icon;
|
|
263
|
+
const displayLabel = typeof item.label === "function" ? item.label() : item.label;
|
|
264
|
+
return /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
265
|
+
onClick: handleItemClick(item),
|
|
266
|
+
disabled: item.disabled,
|
|
267
|
+
className: cn(item.variant === "destructive" && "text-destructive focus:text-destructive", item.className),
|
|
268
|
+
children: [
|
|
269
|
+
ItemIcon && /* @__PURE__ */ jsx(ItemIcon, { className: "h-4 w-4" }),
|
|
270
|
+
/* @__PURE__ */ jsx("span", { children: displayLabel }),
|
|
271
|
+
item.shortcut && /* @__PURE__ */ jsx("span", {
|
|
272
|
+
className: "ml-auto text-xs tracking-widest text-muted-foreground",
|
|
273
|
+
children: item.shortcut
|
|
274
|
+
})
|
|
275
|
+
]
|
|
276
|
+
}, item.key || `item-${index}`);
|
|
277
|
+
})
|
|
278
|
+
})]
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function SelectDropdown({ value, onValueChange, placeholder = "Select option...", options = [], triggerClassName, contentClassName, disabled = false, ...props }) {
|
|
282
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
283
|
+
const trigger = /* @__PURE__ */ jsxs(Button, {
|
|
284
|
+
variant: "outline",
|
|
285
|
+
disabled,
|
|
286
|
+
className: cn("w-full justify-between", !selectedOption && "text-muted-foreground", triggerClassName),
|
|
287
|
+
children: [selectedOption ? selectedOption.label : placeholder, /* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })]
|
|
288
|
+
});
|
|
289
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, {
|
|
290
|
+
...props,
|
|
291
|
+
children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: trigger }), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
292
|
+
align: "start",
|
|
293
|
+
className: cn("w-full min-w-(--anchor-width)", contentClassName),
|
|
294
|
+
children: options.map((option) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
295
|
+
onClick: () => onValueChange?.(option.value),
|
|
296
|
+
className: cn("cursor-pointer", value === option.value && "bg-accent"),
|
|
297
|
+
children: [option.icon && /* @__PURE__ */ jsx(option.icon, { className: "mr-2 h-4 w-4" }), option.label]
|
|
298
|
+
}, option.value))
|
|
299
|
+
})]
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function CheckboxDropdown({ values = [], onValuesChange, placeholder = "Select options...", options = [], triggerClassName, contentClassName, disabled = false, showSelectedCount = true, ...props }) {
|
|
303
|
+
const selectedOptions = options.filter((option) => values.includes(option.value));
|
|
304
|
+
const displayText = selectedOptions.length > 0 ? showSelectedCount ? `${selectedOptions.length} selected` : selectedOptions.map((opt) => opt.label).join(", ") : placeholder;
|
|
305
|
+
const trigger = /* @__PURE__ */ jsxs(Button, {
|
|
306
|
+
variant: "outline",
|
|
307
|
+
disabled,
|
|
308
|
+
className: cn("w-full justify-between", selectedOptions.length === 0 && "text-muted-foreground", triggerClassName),
|
|
309
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
310
|
+
className: "truncate",
|
|
311
|
+
children: displayText
|
|
312
|
+
}), /* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })]
|
|
313
|
+
});
|
|
314
|
+
const handleCheckedChange = (optionValue, checked) => {
|
|
315
|
+
if (checked) onValuesChange?.([...values, optionValue]);
|
|
316
|
+
else onValuesChange?.(values.filter((value) => value !== optionValue));
|
|
317
|
+
};
|
|
318
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, {
|
|
319
|
+
...props,
|
|
320
|
+
children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: trigger }), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
321
|
+
align: "start",
|
|
322
|
+
className: cn("w-full min-w-(--anchor-width)", contentClassName),
|
|
323
|
+
children: options.map((option) => /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
|
|
324
|
+
checked: values.includes(option.value),
|
|
325
|
+
onCheckedChange: (checked) => handleCheckedChange(option.value, checked),
|
|
326
|
+
children: [option.icon && /* @__PURE__ */ jsx(option.icon, { className: "mr-2 h-4 w-4" }), option.label]
|
|
327
|
+
}, option.value))
|
|
328
|
+
})]
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
function RadioDropdown({ value, onValueChange, placeholder = "Select option...", options = [], triggerClassName, contentClassName, disabled = false, ...props }) {
|
|
332
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
333
|
+
const trigger = /* @__PURE__ */ jsxs(Button, {
|
|
334
|
+
variant: "outline",
|
|
335
|
+
disabled,
|
|
336
|
+
className: cn("w-full justify-between", !selectedOption && "text-muted-foreground", triggerClassName),
|
|
337
|
+
children: [selectedOption ? selectedOption.label : placeholder, /* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })]
|
|
338
|
+
});
|
|
339
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, {
|
|
340
|
+
...props,
|
|
341
|
+
children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: trigger }), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
342
|
+
align: "start",
|
|
343
|
+
className: cn("w-full min-w-(--anchor-width)", contentClassName),
|
|
344
|
+
children: /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
|
|
345
|
+
value,
|
|
346
|
+
onValueChange,
|
|
347
|
+
children: options.map((option) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, {
|
|
348
|
+
value: option.value,
|
|
349
|
+
children: [option.icon && /* @__PURE__ */ jsx(option.icon, { className: "mr-2 h-4 w-4" }), option.label]
|
|
350
|
+
}, option.value))
|
|
351
|
+
})
|
|
352
|
+
})]
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
//#endregion
|
|
357
|
+
export { SelectDropdown as a, PaginationInfo as c, RadioDropdown as i, CheckboxDropdown as n, ApiPagination as o, DropdownWrapper as r, CustomPagination as s, ActionDropdown as t };
|
package/dist/forms.mjs
CHANGED
|
@@ -12,12 +12,12 @@ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGr
|
|
|
12
12
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
13
13
|
import { Badge } from "@/components/ui/badge";
|
|
14
14
|
import { Input } from "@/components/ui/input";
|
|
15
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
15
16
|
import { format, isValid, set } from "date-fns";
|
|
16
17
|
import { Controller } from "react-hook-form";
|
|
17
18
|
import { Field, FieldContent, FieldDescription, FieldError as FieldError$1, FieldLabel } from "@/components/ui/field";
|
|
18
19
|
import { Textarea } from "@/components/ui/textarea";
|
|
19
20
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
20
|
-
import { Checkbox } from "@/components/ui/checkbox";
|
|
21
21
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
22
22
|
import { Switch } from "@/components/ui/switch";
|
|
23
23
|
import { Calendar } from "@/components/ui/calendar";
|
|
@@ -372,6 +372,7 @@ function SelectInput({ control, items = [], groups = [], name, label, placeholde
|
|
|
372
372
|
const rawValue = field ? field.value?.toString() : localValue;
|
|
373
373
|
const handleChange = (newValue) => {
|
|
374
374
|
const actualValue = newValue === CLEAR_VALUE ? "" : newValue;
|
|
375
|
+
if ((field ? String(field.value ?? "") : localValue) === actualValue) return;
|
|
375
376
|
if (field) field.onChange(actualValue);
|
|
376
377
|
else setLocalValue(actualValue);
|
|
377
378
|
onValueChange?.(actualValue);
|
|
@@ -1475,18 +1476,32 @@ TagChoiceInput.displayName = "TagChoiceInput";
|
|
|
1475
1476
|
*/
|
|
1476
1477
|
function ComboboxInput({ control, name, label, placeholder = "Select...", emptyText = "No items found.", description, helperText, required, disabled, items = [], className, labelClassName, inputClassName, onValueChange, renderOption, value: propValue, onChange: propOnChange }) {
|
|
1477
1478
|
const descriptionText = description || helperText;
|
|
1478
|
-
const
|
|
1479
|
+
const resolvedMapRef = useRef(/* @__PURE__ */ new Map());
|
|
1480
|
+
const currentValueRef = useRef("");
|
|
1481
|
+
useEffect(() => {
|
|
1482
|
+
resolvedMapRef.current = new Map(items.map((item) => [item.value, item]));
|
|
1483
|
+
}, [items]);
|
|
1484
|
+
const onValueChangeRef = useRef(onValueChange);
|
|
1485
|
+
onValueChangeRef.current = onValueChange;
|
|
1486
|
+
const propOnChangeRef = useRef(propOnChange);
|
|
1487
|
+
propOnChangeRef.current = propOnChange;
|
|
1488
|
+
const fieldRef = useRef();
|
|
1489
|
+
const handleValueChange = useCallback((newItem) => {
|
|
1479
1490
|
const safeValue = newItem?.value || "";
|
|
1491
|
+
if (safeValue === currentValueRef.current) return;
|
|
1492
|
+
const field = fieldRef.current;
|
|
1480
1493
|
if (field) field.onChange(safeValue);
|
|
1481
|
-
else if (
|
|
1482
|
-
|
|
1483
|
-
}, [
|
|
1494
|
+
else if (propOnChangeRef.current) propOnChangeRef.current(safeValue);
|
|
1495
|
+
onValueChangeRef.current?.(safeValue);
|
|
1496
|
+
}, []);
|
|
1484
1497
|
const renderCombobox = (currentValue, field, isDisabled, fieldState) => {
|
|
1498
|
+
fieldRef.current = field;
|
|
1499
|
+
currentValueRef.current = currentValue ?? "";
|
|
1485
1500
|
const ariaDescribedBy = [descriptionText ? `${name}-description` : void 0, fieldState?.invalid ? `${name}-error` : void 0].filter(Boolean).join(" ") || void 0;
|
|
1486
1501
|
return /* @__PURE__ */ jsxs(Combobox, {
|
|
1487
1502
|
items,
|
|
1488
|
-
value: currentValue ? items.find((item) => item.value === currentValue) ?? null : null,
|
|
1489
|
-
onValueChange:
|
|
1503
|
+
value: currentValue ? resolvedMapRef.current.get(currentValue) ?? items.find((item) => item.value === currentValue) ?? null : null,
|
|
1504
|
+
onValueChange: handleValueChange,
|
|
1490
1505
|
disabled: isDisabled,
|
|
1491
1506
|
children: [/* @__PURE__ */ jsx(ComboboxInput$1, {
|
|
1492
1507
|
placeholder,
|