UNPKG

niva-ui

Version:

A beautiful, minimalistic React + Tailwind UI framework for SaaS applications

1,205 lines (1,192 loc) 89.7 kB
"use client"; // src/components/ui/button.tsx import * as React2 from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva } from "class-variance-authority"; // src/lib/utils.ts import { clsx } from "clsx"; import { twMerge } from "tailwind-merge"; function cn(...inputs) { return twMerge(clsx(inputs)); } // src/components/ui/button.tsx var buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10" } }, defaultVariants: { variant: "default", size: "default" } } ); var Button = React2.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button"; return /* @__PURE__ */ React2.createElement( Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props } ); } ); Button.displayName = "Button"; // src/components/ui/badge.tsx import * as React3 from "react"; import { cva as cva2 } from "class-variance-authority"; var badgeVariants = cva2( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground" } }, defaultVariants: { variant: "default" } } ); function Badge({ className, variant, ...props }) { return /* @__PURE__ */ React3.createElement("div", { className: cn(badgeVariants({ variant }), className), ...props }); } // src/components/ui/card.tsx import * as React4 from "react"; var Card = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( "div", { ref, className: cn( "rounded-lg border bg-card text-card-foreground shadow-sm", className ), ...props } )); Card.displayName = "Card"; var CardHeader = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( "div", { ref, className: cn("flex flex-col space-y-1.5 p-6", className), ...props } )); CardHeader.displayName = "CardHeader"; var CardTitle = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( "h3", { ref, className: cn( "text-2xl font-semibold leading-none tracking-tight", className ), ...props } )); CardTitle.displayName = "CardTitle"; var CardDescription = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( "p", { ref, className: cn("text-sm text-muted-foreground", className), ...props } )); CardDescription.displayName = "CardDescription"; var CardContent = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement("div", { ref, className: cn("p-6 pt-0", className), ...props })); CardContent.displayName = "CardContent"; var CardFooter = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React4.createElement( "div", { ref, className: cn("flex items-center p-6 pt-0", className), ...props } )); CardFooter.displayName = "CardFooter"; // src/components/ui/input.tsx import * as React5 from "react"; import { cva as cva3 } from "class-variance-authority"; var inputVariants = cva3( "flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", { variants: { variant: { default: "border-input", destructive: "border-destructive focus-visible:ring-destructive", ghost: "border-transparent bg-transparent hover:bg-accent" }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3 text-sm", lg: "h-11 px-4 py-3" } }, defaultVariants: { variant: "default", size: "default" } } ); var Input = React5.forwardRef( ({ className, variant, size, type, label, error, icon, suffix, ...props }, ref) => { return /* @__PURE__ */ React5.createElement("div", { className: "grid w-full gap-1.5" }, label && /* @__PURE__ */ React5.createElement("label", { className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" }, label), /* @__PURE__ */ React5.createElement("div", { className: "relative" }, icon && /* @__PURE__ */ React5.createElement("div", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" }, icon), /* @__PURE__ */ React5.createElement( "input", { type, className: cn( inputVariants({ variant, size, className }), icon && "pl-10", suffix && "pr-10", error && "border-destructive focus-visible:ring-destructive" ), ref, ...props } ), suffix && /* @__PURE__ */ React5.createElement("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground" }, suffix)), error && /* @__PURE__ */ React5.createElement("p", { className: "text-sm text-destructive" }, error)); } ); Input.displayName = "Input"; // src/components/ui/select.tsx import * as React6 from "react"; import { ChevronDown, Check } from "lucide-react"; import { cva as cva4 } from "class-variance-authority"; var selectVariants = cva4( "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", { variants: { variant: { default: "border-input", destructive: "border-destructive focus:ring-destructive" }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3 text-sm", lg: "h-11 px-4 py-3" } }, defaultVariants: { variant: "default", size: "default" } } ); var Select = React6.forwardRef( ({ className, variant, size, options, value, onValueChange, placeholder = "Select an option...", label, error, searchable = false, disabled, ...props }, ref) => { const [isOpen, setIsOpen] = React6.useState(false); const [searchTerm, setSearchTerm] = React6.useState(""); const dropdownRef = React6.useRef(null); const selectedOption = options.find((option) => option.value === value); const filteredOptions = React6.useMemo(() => { if (!searchable || !searchTerm) return options; return options.filter( (option) => option.label.toLowerCase().includes(searchTerm.toLowerCase()) ); }, [options, searchTerm, searchable]); React6.useEffect(() => { const handleClickOutside = (event) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { setIsOpen(false); setSearchTerm(""); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const handleSelect = (optionValue) => { onValueChange?.(optionValue); setIsOpen(false); setSearchTerm(""); }; return /* @__PURE__ */ React6.createElement("div", { className: "grid w-full gap-1.5" }, label && /* @__PURE__ */ React6.createElement("label", { className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" }, label), /* @__PURE__ */ React6.createElement("div", { className: "relative", ref: dropdownRef }, /* @__PURE__ */ React6.createElement( "button", { type: "button", ref, className: cn( selectVariants({ variant, size, className }), error && "border-destructive focus:ring-destructive" ), onClick: () => !disabled && setIsOpen(!isOpen), disabled, ...props }, /* @__PURE__ */ React6.createElement("span", { className: cn( "block truncate text-left", !selectedOption && "text-muted-foreground" ) }, selectedOption ? selectedOption.label : placeholder), /* @__PURE__ */ React6.createElement(ChevronDown, { className: cn( "h-4 w-4 transition-transform", isOpen && "rotate-180" ) }) ), isOpen && /* @__PURE__ */ React6.createElement("div", { className: "absolute z-50 mt-1 w-full rounded-md border bg-popover p-1 shadow-md" }, searchable && /* @__PURE__ */ React6.createElement("div", { className: "p-1" }, /* @__PURE__ */ React6.createElement( "input", { type: "text", placeholder: "Search...", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), className: "w-full rounded-sm border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" } )), /* @__PURE__ */ React6.createElement("div", { className: "max-h-60 overflow-auto" }, filteredOptions.length === 0 ? /* @__PURE__ */ React6.createElement("div", { className: "py-6 text-center text-sm text-muted-foreground" }, "No options found.") : filteredOptions.map((option) => /* @__PURE__ */ React6.createElement( "button", { key: option.value, type: "button", className: cn( "relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground", option.disabled && "cursor-not-allowed opacity-50", value === option.value && "bg-accent text-accent-foreground" ), onClick: () => !option.disabled && handleSelect(option.value), disabled: option.disabled }, /* @__PURE__ */ React6.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, value === option.value && /* @__PURE__ */ React6.createElement(Check, { className: "h-4 w-4" })), option.label ))))), error && /* @__PURE__ */ React6.createElement("p", { className: "text-sm text-destructive" }, error)); } ); Select.displayName = "Select"; // src/components/ui/avatar.tsx import * as React7 from "react"; import { cva as cva5 } from "class-variance-authority"; var avatarVariants = cva5( "relative flex shrink-0 overflow-hidden rounded-full", { variants: { size: { sm: "h-6 w-6", default: "h-10 w-10", lg: "h-12 w-12", xl: "h-16 w-16", "2xl": "h-20 w-20" } }, defaultVariants: { size: "default" } } ); var avatarImageVariants = cva5("aspect-square h-full w-full object-cover"); var avatarFallbackVariants = cva5( "flex h-full w-full items-center justify-center rounded-full bg-muted text-muted-foreground font-medium", { variants: { size: { sm: "text-xs", default: "text-sm", lg: "text-base", xl: "text-lg", "2xl": "text-xl" } }, defaultVariants: { size: "default" } } ); var Avatar = React7.forwardRef( ({ className, size, src, alt, fallback, status, ...props }, ref) => { const [imageLoaded, setImageLoaded] = React7.useState(false); const [imageFailed, setImageFailed] = React7.useState(false); const initials = React7.useMemo(() => { if (fallback) return fallback; if (alt) { const names = alt.split(" "); return names.length > 1 ? `${names[0][0]}${names[names.length - 1][0]}`.toUpperCase() : names[0].slice(0, 2).toUpperCase(); } return "??"; }, [fallback, alt]); return /* @__PURE__ */ React7.createElement("div", { className: cn(avatarVariants({ size }), className), ref, ...props }, src && !imageFailed && /* @__PURE__ */ React7.createElement( "img", { src, alt, className: cn(avatarImageVariants()), onLoad: () => setImageLoaded(true), onError: () => setImageFailed(true) } ), (!src || imageFailed || !imageLoaded) && /* @__PURE__ */ React7.createElement("div", { className: cn(avatarFallbackVariants({ size })) }, initials), status && /* @__PURE__ */ React7.createElement("div", { className: cn( "absolute -bottom-0 -right-0 rounded-full border-2 border-background", size === "sm" && "h-2 w-2", size === "default" && "h-3 w-3", size === "lg" && "h-3.5 w-3.5", size === "xl" && "h-4 w-4", size === "2xl" && "h-5 w-5", status === "online" && "bg-green-500", status === "offline" && "bg-gray-400", status === "away" && "bg-yellow-500", status === "busy" && "bg-red-500" ) })); } ); Avatar.displayName = "Avatar"; // src/components/ui/table.tsx import * as React8 from "react"; import { ChevronUp, ChevronDown as ChevronDown2, ChevronsUpDown } from "lucide-react"; var Table = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React8.createElement("div", { className: "relative w-full overflow-auto" }, /* @__PURE__ */ React8.createElement( "table", { ref, className: cn("w-full caption-bottom text-sm", className), ...props } ))); Table.displayName = "Table"; var TableHeader = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React8.createElement("thead", { ref, className: cn("[&_tr]:border-b", className), ...props })); TableHeader.displayName = "TableHeader"; var TableBody = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React8.createElement( "tbody", { ref, className: cn("[&_tr:last-child]:border-0", className), ...props } )); TableBody.displayName = "TableBody"; var TableFooter = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React8.createElement( "tfoot", { ref, className: cn( "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className ), ...props } )); TableFooter.displayName = "TableFooter"; var TableRow = React8.forwardRef(({ className, hover = true, ...props }, ref) => /* @__PURE__ */ React8.createElement( "tr", { ref, className: cn( "border-b transition-colors", hover && "hover:bg-muted/50", "data-[state=selected]:bg-muted", className ), ...props } )); TableRow.displayName = "TableRow"; var TableHead = React8.forwardRef( ({ className, sortable, sortDirection, onSort, children, ...props }, ref) => /* @__PURE__ */ React8.createElement( "th", { ref, className: cn( "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", sortable && "cursor-pointer hover:text-foreground", className ), onClick: sortable ? onSort : void 0, ...props }, /* @__PURE__ */ React8.createElement("div", { className: "flex items-center space-x-2" }, /* @__PURE__ */ React8.createElement("span", null, children), sortable && /* @__PURE__ */ React8.createElement("span", { className: "flex items-center" }, sortDirection === "asc" && /* @__PURE__ */ React8.createElement(ChevronUp, { className: "h-4 w-4" }), sortDirection === "desc" && /* @__PURE__ */ React8.createElement(ChevronDown2, { className: "h-4 w-4" }), sortDirection === null && /* @__PURE__ */ React8.createElement(ChevronsUpDown, { className: "h-4 w-4" }))) ) ); TableHead.displayName = "TableHead"; var TableCell = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React8.createElement( "td", { ref, className: cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className), ...props } )); TableCell.displayName = "TableCell"; var TableCaption = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React8.createElement( "caption", { ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props } )); TableCaption.displayName = "TableCaption"; function DataTable({ data, columns, loading = false, emptyMessage = "No data available", onRowClick, selectable = false, selectedRows = [], onSelectionChange }) { const [sortConfig, setSortConfig] = React8.useState({ key: null, direction: "asc" }); const sortedData = React8.useMemo(() => { if (!sortConfig.key) return data; return [...data].sort((a, b) => { const aValue = a[sortConfig.key]; const bValue = b[sortConfig.key]; if (aValue < bValue) { return sortConfig.direction === "asc" ? -1 : 1; } if (aValue > bValue) { return sortConfig.direction === "asc" ? 1 : -1; } return 0; }); }, [data, sortConfig]); const handleSort = (key) => { setSortConfig((prev) => ({ key, direction: prev.key === key && prev.direction === "asc" ? "desc" : "asc" })); }; const isSelected = (row) => selectedRows.some((selected) => selected === row); const toggleSelection = (row) => { if (!onSelectionChange) return; const newSelection = isSelected(row) ? selectedRows.filter((selected) => selected !== row) : [...selectedRows, row]; onSelectionChange(newSelection); }; const toggleAllSelection = () => { if (!onSelectionChange) return; const allSelected = selectedRows.length === data.length; onSelectionChange(allSelected ? [] : [...data]); }; if (loading) { return /* @__PURE__ */ React8.createElement("div", { className: "w-full p-8 text-center" }, /* @__PURE__ */ React8.createElement("div", { className: "text-sm text-muted-foreground" }, "Loading...")); } return /* @__PURE__ */ React8.createElement(Table, null, /* @__PURE__ */ React8.createElement(TableHeader, null, /* @__PURE__ */ React8.createElement(TableRow, null, selectable && /* @__PURE__ */ React8.createElement(TableHead, { className: "w-12" }, /* @__PURE__ */ React8.createElement( "input", { type: "checkbox", checked: selectedRows.length === data.length && data.length > 0, onChange: toggleAllSelection, className: "rounded" } )), columns.map((column) => /* @__PURE__ */ React8.createElement( TableHead, { key: String(column.key), sortable: column.sortable, sortDirection: sortConfig.key === column.key ? sortConfig.direction : null, onSort: () => column.sortable && handleSort(column.key), style: { width: column.width } }, column.header )))), /* @__PURE__ */ React8.createElement(TableBody, null, sortedData.length === 0 ? /* @__PURE__ */ React8.createElement(TableRow, null, /* @__PURE__ */ React8.createElement(TableCell, { colSpan: columns.length + (selectable ? 1 : 0), className: "text-center py-8" }, /* @__PURE__ */ React8.createElement("div", { className: "text-muted-foreground" }, emptyMessage))) : sortedData.map((row, index) => /* @__PURE__ */ React8.createElement( TableRow, { key: index, className: cn( onRowClick && "cursor-pointer", isSelected(row) && "bg-muted/50" ), onClick: () => onRowClick?.(row) }, selectable && /* @__PURE__ */ React8.createElement(TableCell, null, /* @__PURE__ */ React8.createElement( "input", { type: "checkbox", checked: isSelected(row), onChange: () => toggleSelection(row), onClick: (e) => e.stopPropagation(), className: "rounded" } )), columns.map((column) => /* @__PURE__ */ React8.createElement(TableCell, { key: String(column.key) }, column.render ? column.render(row[column.key], row) : String(row[column.key] ?? ""))) )))); } // src/components/ui/toast.tsx import * as React9 from "react"; import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from "lucide-react"; import { cva as cva6 } from "class-variance-authority"; var toastVariants = cva6( "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", { variants: { variant: { default: "border bg-background text-foreground", success: "border-green-200 bg-green-50 text-green-900 dark:border-green-800 dark:bg-green-950 dark:text-green-50", error: "border-red-200 bg-red-50 text-red-900 dark:border-red-800 dark:bg-red-950 dark:text-red-50", warning: "border-yellow-200 bg-yellow-50 text-yellow-900 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-50", info: "border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-50" } }, defaultVariants: { variant: "default" } } ); var Toast = React9.forwardRef( ({ className, variant, title, description, action, onClose, duration = 5e3, ...props }, ref) => { const [isVisible, setIsVisible] = React9.useState(true); React9.useEffect(() => { if (duration && duration > 0) { const timer = setTimeout(() => { setIsVisible(false); setTimeout(() => onClose?.(), 150); }, duration); return () => clearTimeout(timer); } }, [duration, onClose]); const getIcon = () => { switch (variant) { case "success": return /* @__PURE__ */ React9.createElement(CheckCircle, { className: "h-5 w-5 text-green-600 dark:text-green-400" }); case "error": return /* @__PURE__ */ React9.createElement(AlertCircle, { className: "h-5 w-5 text-red-600 dark:text-red-400" }); case "warning": return /* @__PURE__ */ React9.createElement(AlertTriangle, { className: "h-5 w-5 text-yellow-600 dark:text-yellow-400" }); case "info": return /* @__PURE__ */ React9.createElement(Info, { className: "h-5 w-5 text-blue-600 dark:text-blue-400" }); default: return null; } }; if (!isVisible) return null; return /* @__PURE__ */ React9.createElement( "div", { ref, className: cn(toastVariants({ variant }), className), ...props }, /* @__PURE__ */ React9.createElement("div", { className: "flex items-start space-x-3" }, /* @__PURE__ */ React9.createElement("div", { className: "flex-shrink-0 mt-0.5" }, getIcon()), /* @__PURE__ */ React9.createElement("div", { className: "flex-1 min-w-0" }, title && /* @__PURE__ */ React9.createElement("div", { className: "text-sm font-semibold" }, title), description && /* @__PURE__ */ React9.createElement("div", { className: "mt-1 text-sm opacity-90" }, description), action && /* @__PURE__ */ React9.createElement("div", { className: "mt-2" }, action))), /* @__PURE__ */ React9.createElement( "button", { onClick: () => { setIsVisible(false); setTimeout(() => onClose?.(), 150); }, className: "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100" }, /* @__PURE__ */ React9.createElement(X, { className: "h-4 w-4" }) ) ); } ); Toast.displayName = "Toast"; var ToastContainer = ({ position = "top-right" }) => { const positionClasses = { "top-right": "top-0 right-0", "top-left": "top-0 left-0", "bottom-right": "bottom-0 right-0", "bottom-left": "bottom-0 left-0", "top-center": "top-0 left-1/2 -translate-x-1/2", "bottom-center": "bottom-0 left-1/2 -translate-x-1/2" }; return /* @__PURE__ */ React9.createElement( "div", { className: cn( "fixed z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:flex-col md:max-w-[420px]", positionClasses[position] ), id: "toast-container" } ); }; var ToastContext = React9.createContext(void 0); function ToastProvider({ children }) { const [toasts, setToasts] = React9.useState([]); const toast2 = React9.useCallback((options) => { const id = options.id || Math.random().toString(36).substring(7); setToasts((prev) => [...prev, { ...options, id }]); return id; }, []); const dismiss = React9.useCallback((id) => { setToasts((prev) => prev.filter((toast3) => toast3.id !== id)); }, []); const clear = React9.useCallback(() => { setToasts([]); }, []); return /* @__PURE__ */ React9.createElement(ToastContext.Provider, { value: { toasts, toast: toast2, dismiss, clear } }, children, /* @__PURE__ */ React9.createElement(ToastContainer, null), toasts.map(({ id, ...toastProps }) => /* @__PURE__ */ React9.createElement( Toast, { key: id, ...toastProps, onClose: () => dismiss(id) } ))); } function useToast() { const context = React9.useContext(ToastContext); if (!context) { throw new Error("useToast must be used within a ToastProvider"); } return context; } var toast = { success: (message, options) => { if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("toast", { detail: { variant: "success", description: message, ...options } })); } }, error: (message, options) => { if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("toast", { detail: { variant: "error", description: message, ...options } })); } }, warning: (message, options) => { if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("toast", { detail: { variant: "warning", description: message, ...options } })); } }, info: (message, options) => { if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("toast", { detail: { variant: "info", description: message, ...options } })); } } }; // src/components/ui/modal.tsx import * as React10 from "react"; import { X as X2 } from "lucide-react"; import { cva as cva7 } from "class-variance-authority"; var modalVariants = cva7( "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", { variants: { size: { sm: "max-w-sm", default: "max-w-lg", lg: "max-w-2xl", xl: "max-w-4xl", full: "max-w-[95vw] max-h-[95vh]" } }, defaultVariants: { size: "default" } } ); var Modal = React10.forwardRef( ({ className, size, isOpen, onClose, title, description, showCloseButton = true, closeOnOverlayClick = true, closeOnEscape = true, children, ...props }, ref) => { const modalRef = React10.useRef(null); React10.useEffect(() => { if (!closeOnEscape || !isOpen) return; const handleEscape = (e) => { if (e.key === "Escape") { onClose(); } }; document.addEventListener("keydown", handleEscape); return () => document.removeEventListener("keydown", handleEscape); }, [isOpen, closeOnEscape, onClose]); const handleOverlayClick = (e) => { if (closeOnOverlayClick && e.target === e.currentTarget) { onClose(); } }; React10.useEffect(() => { if (isOpen) { const previousActiveElement = document.activeElement; modalRef.current?.focus(); return () => { previousActiveElement?.focus(); }; } }, [isOpen]); React10.useEffect(() => { if (isOpen) { document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = "unset"; }; } }, [isOpen]); if (!isOpen) return null; return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement( "div", { className: "fixed inset-0 z-40 bg-black/50 backdrop-blur-sm", onClick: handleOverlayClick } ), /* @__PURE__ */ React10.createElement( "div", { ref: (node) => { modalRef.current = node; if (typeof ref === "function") { ref(node); } else if (ref) { ref.current = node; } }, className: cn(modalVariants({ size }), className), role: "dialog", "aria-modal": "true", "aria-labelledby": title ? "modal-title" : void 0, "aria-describedby": description ? "modal-description" : void 0, tabIndex: -1, ...props }, showCloseButton && /* @__PURE__ */ React10.createElement( "button", { onClick: onClose, className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground" }, /* @__PURE__ */ React10.createElement(X2, { className: "h-4 w-4" }), /* @__PURE__ */ React10.createElement("span", { className: "sr-only" }, "Close") ), (title || description) && /* @__PURE__ */ React10.createElement("div", { className: "flex flex-col space-y-1.5" }, title && /* @__PURE__ */ React10.createElement("h2", { id: "modal-title", className: "text-lg font-semibold leading-none tracking-tight" }, title), description && /* @__PURE__ */ React10.createElement("p", { id: "modal-description", className: "text-sm text-muted-foreground" }, description)), /* @__PURE__ */ React10.createElement("div", { className: "flex-1" }, children) )); } ); Modal.displayName = "Modal"; var ModalHeader = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React10.createElement( "div", { ref, className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props } )); ModalHeader.displayName = "ModalHeader"; var ModalTitle = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React10.createElement( "h3", { ref, className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props } )); ModalTitle.displayName = "ModalTitle"; var ModalDescription = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React10.createElement( "p", { ref, className: cn("text-sm text-muted-foreground", className), ...props } )); ModalDescription.displayName = "ModalDescription"; var ModalContent = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React10.createElement("div", { ref, className: cn("py-4", className), ...props })); ModalContent.displayName = "ModalContent"; var ModalFooter = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React10.createElement( "div", { ref, className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props } )); ModalFooter.displayName = "ModalFooter"; function useConfirmModal() { const [isOpen, setIsOpen] = React10.useState(false); const [options, setOptions] = React10.useState({}); const resolveRef = React10.useRef(null); const confirm = React10.useCallback((opts = {}) => { setOptions({ title: opts.title || "Are you sure?", description: opts.description || "This action cannot be undone.", confirmText: opts.confirmText || "Confirm", cancelText: opts.cancelText || "Cancel", variant: opts.variant || "default", ...opts }); setIsOpen(true); return new Promise((resolve) => { resolveRef.current = resolve; }); }, []); const handleConfirm = () => { resolveRef.current?.(true); setIsOpen(false); }; const handleCancel = () => { resolveRef.current?.(false); setIsOpen(false); }; const ConfirmModal = () => /* @__PURE__ */ React10.createElement( Modal, { isOpen, onClose: handleCancel, title: options.title, description: options.description }, /* @__PURE__ */ React10.createElement(ModalFooter, null, /* @__PURE__ */ React10.createElement( "button", { onClick: handleCancel, className: cn( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", "border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2" ) }, options.cancelText ), /* @__PURE__ */ React10.createElement( "button", { onClick: handleConfirm, className: cn( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", options.variant === "destructive" ? "bg-destructive text-destructive-foreground hover:bg-destructive/90 h-10 px-4 py-2" : "bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2" ) }, options.confirmText )) ); return { confirm, ConfirmModal }; } // src/components/ui/navbar.tsx import * as React11 from "react"; import { Menu, X as X3, ChevronDown as ChevronDown3 } from "lucide-react"; import { cva as cva8 } from "class-variance-authority"; var navbarVariants = cva8( "flex w-full items-center justify-between border-b bg-background px-4", { variants: { size: { default: "h-16", sm: "h-12", lg: "h-20" }, variant: { default: "border-border", minimal: "border-transparent", elevated: "shadow-sm border-border" } }, defaultVariants: { size: "default", variant: "default" } } ); var Navbar = React11.forwardRef( ({ className, size, variant, logo, items = [], actions, mobileMenuButton = true, onMobileMenuToggle, isMobileMenuOpen = false, ...props }, ref) => { const [openDropdown, setOpenDropdown] = React11.useState(null); const handleDropdownToggle = (label) => { setOpenDropdown(openDropdown === label ? null : label); }; const handleItemClick = (item) => { if (item.disabled) return; if (item.onClick) { item.onClick(); } else if (item.href) { window.location.href = item.href; } setOpenDropdown(null); }; return /* @__PURE__ */ React11.createElement( "nav", { ref, className: cn(navbarVariants({ size, variant }), className), ...props }, /* @__PURE__ */ React11.createElement("div", { className: "flex items-center space-x-4" }, logo), /* @__PURE__ */ React11.createElement("div", { className: "hidden md:flex items-center space-x-1" }, items.map((item) => /* @__PURE__ */ React11.createElement("div", { key: item.label, className: "relative" }, item.items && item.items.length > 0 ? ( // Dropdown Menu /* @__PURE__ */ React11.createElement("div", { className: "relative" }, /* @__PURE__ */ React11.createElement( "button", { onClick: () => handleDropdownToggle(item.label), className: cn( "flex items-center space-x-1 px-3 py-2 text-sm font-medium rounded-md transition-colors", "hover:bg-accent hover:text-accent-foreground", "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", item.disabled && "opacity-50 cursor-not-allowed", openDropdown === item.label && "bg-accent text-accent-foreground" ), disabled: item.disabled }, item.icon, /* @__PURE__ */ React11.createElement("span", null, item.label), item.badge && /* @__PURE__ */ React11.createElement("span", { className: "ml-1 px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full" }, item.badge), /* @__PURE__ */ React11.createElement(ChevronDown3, { className: cn( "h-4 w-4 transition-transform", openDropdown === item.label && "rotate-180" ) }) ), openDropdown === item.label && /* @__PURE__ */ React11.createElement("div", { className: "absolute top-full left-0 mt-1 w-48 rounded-md border bg-popover p-1 shadow-md z-50" }, item.items.map((subItem) => /* @__PURE__ */ React11.createElement( "button", { key: subItem.label, onClick: () => handleItemClick(subItem), className: cn( "flex w-full items-center space-x-2 px-3 py-2 text-sm rounded-sm transition-colors text-left", "hover:bg-accent hover:text-accent-foreground", "focus:outline-none focus:ring-2 focus:ring-ring", subItem.disabled && "opacity-50 cursor-not-allowed" ), disabled: subItem.disabled }, subItem.icon, /* @__PURE__ */ React11.createElement("span", null, subItem.label), subItem.badge && /* @__PURE__ */ React11.createElement("span", { className: "ml-auto px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full" }, subItem.badge) )))) ) : ( // Regular Menu Item /* @__PURE__ */ React11.createElement( "button", { onClick: () => handleItemClick(item), className: cn( "flex items-center space-x-2 px-3 py-2 text-sm font-medium rounded-md transition-colors", "hover:bg-accent hover:text-accent-foreground", "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", item.disabled && "opacity-50 cursor-not-allowed" ), disabled: item.disabled }, item.icon, /* @__PURE__ */ React11.createElement("span", null, item.label), item.badge && /* @__PURE__ */ React11.createElement("span", { className: "ml-1 px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full" }, item.badge) ) )))), /* @__PURE__ */ React11.createElement("div", { className: "flex items-center space-x-2" }, actions, mobileMenuButton && /* @__PURE__ */ React11.createElement( "button", { onClick: onMobileMenuToggle, className: "md:hidden p-2 rounded-md hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2" }, isMobileMenuOpen ? /* @__PURE__ */ React11.createElement(X3, { className: "h-5 w-5" }) : /* @__PURE__ */ React11.createElement(Menu, { className: "h-5 w-5" }) )), isMobileMenuOpen && /* @__PURE__ */ React11.createElement("div", { className: "absolute top-full left-0 right-0 md:hidden border-b bg-background shadow-lg z-40" }, /* @__PURE__ */ React11.createElement("div", { className: "px-4 py-2 space-y-1" }, items.map((item) => /* @__PURE__ */ React11.createElement("div", { key: item.label }, item.items && item.items.length > 0 ? /* @__PURE__ */ React11.createElement("div", null, /* @__PURE__ */ React11.createElement( "button", { onClick: () => handleDropdownToggle(item.label), className: cn( "flex w-full items-center justify-between px-3 py-2 text-sm font-medium rounded-md transition-colors", "hover:bg-accent hover:text-accent-foreground", item.disabled && "opacity-50 cursor-not-allowed" ), disabled: item.disabled }, /* @__PURE__ */ React11.createElement("div", { className: "flex items-center space-x-2" }, item.icon, /* @__PURE__ */ React11.createElement("span", null, item.label), item.badge && /* @__PURE__ */ React11.createElement("span", { className: "px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full" }, item.badge)), /* @__PURE__ */ React11.createElement(ChevronDown3, { className: cn( "h-4 w-4 transition-transform", openDropdown === item.label && "rotate-180" ) }) ), openDropdown === item.label && /* @__PURE__ */ React11.createElement("div", { className: "ml-4 mt-1 space-y-1" }, item.items.map((subItem) => /* @__PURE__ */ React11.createElement( "button", { key: subItem.label, onClick: () => handleItemClick(subItem), className: cn( "flex w-full items-center space-x-2 px-3 py-2 text-sm rounded-md transition-colors text-left", "hover:bg-accent hover:text-accent-foreground", subItem.disabled && "opacity-50 cursor-not-allowed" ), disabled: subItem.disabled }, subItem.icon, /* @__PURE__ */ React11.createElement("span", null, subItem.label), subItem.badge && /* @__PURE__ */ React11.createElement("span", { className: "ml-auto px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full" }, subItem.badge) )))) : /* @__PURE__ */ React11.createElement( "button", { onClick: () => handleItemClick(item), className: cn( "flex w-full items-center space-x-2 px-3 py-2 text-sm font-medium rounded-md transition-colors text-left", "hover:bg-accent hover:text-accent-foreground", item.disabled && "opacity-50 cursor-not-allowed" ), disabled: item.disabled }, item.icon, /* @__PURE__ */ React11.createElement("span", null, item.label), item.badge && /* @__PURE__ */ React11.createElement("span", { className: "ml-auto px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full" }, item.badge) ))))) ); } ); Navbar.displayName = "Navbar"; // src/components/ui/sidebar.tsx import * as React12 from "react"; import { ChevronLeft, ChevronRight, ChevronDown as ChevronDown4 } from "lucide-react"; import { cva as cva9 } from "class-variance-authority"; var sidebarVariants = cva9( "flex flex-col border-r bg-background transition-all duration-300 ease-in-out", { variants: { variant: { default: "border-border", minimal: "border-transparent", elevated: "shadow-lg border-border" }, size: { sm: "w-16", default: "w-64", lg: "w-80" } }, defaultVariants: { variant: "default", size: "default" } } ); var Sidebar = React12.forwardRef( ({ className, variant, size, header, footer, sections = [], collapsible = false, collapsed = false, onCollapse, showCollapseButton = true, ...props }, ref) => { const [expandedItems, setExpandedItems] = React12.useState(/* @__PURE__ */ new Set()); const [internalCollapsed, setInternalCollapsed] = React12.useState(collapsed); const isCollapsed = collapsible ? internalCollapsed : false; React12.useEffect(() => { setInternalCollapsed(collapsed); }, [collapsed]); const toggleCollapse = () => { const newCollapsed = !internalCollapsed; setInternalCollapsed(newCollapsed); onCollapse?.(newCollapsed); }; const toggleExpanded = (label) => { const newExpanded = new Set(expandedItems); if (newExpanded.has(label)) { newExpanded.delete(label); } else { newExpanded.add(label); } setExpandedItems(newExpanded); }; const handleItemClick = (item) => { if (item.disabled) return; if (item.items && item.items.length > 0) { toggleExpanded(item.label); } else if (item.onClick) { item.onClick(); } else if (item.href) { window.location.href = item.href; } }; const renderItem = (item, level = 0) => { const hasChildren = item.items && item.items.length > 0; const isExpanded = expandedItems.has(item.label); return /* @__PURE__ */ React12.createElement("div", { key: item.label }, /* @__PURE__ */ React12.createElement( "button", { onClick: () => handleItemClick(item), className: cn( "flex w-full items-center justify-between px-3 py-2 text-sm font-medium rounded-lg transition-colors group", "hover:bg-accent hover:text-accent-foreground", "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", level > 0 && "ml-4 pl-6", item.active && "bg-accent text-accent-foreground", item.disabled && "opacity-50 cursor-not-allowed", isCollapsed && "justify-center px-2" ), disabled: item.disabled, title: isCollapsed ? item.label : void 0 }, /* @__PURE__ */ React12.createElement("div", { className: "flex items-center space-x-3" }, item.icon && /* @__PURE__ */ React12.createElement("div", { className: "flex-shrink-0" }, item.icon), !isCollapsed && /* @__PURE__ */ React12.createElement("span", { className: "truncate" }, item.label)), !isCollapsed && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center space-x-2" }, item.badge && /* @__PURE__ */ React12.createElement("span", { className: "px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full" }, item.badge), hasChildren && /* @__PURE__ */ React12.createElement(ChevronDown4, { className: cn( "h-4 w-4 transition-transform", isExpanded && "rotate-180" ) })) ), hasChildren && isExpanded && !isCollapsed && /* @__PURE__ */ React12.createElement("div", { className: "mt-1 space-y-1" }, item.items.map((subItem) => renderItem(subItem, level + 1)))); }; return /* @__PURE__ */ React12.createElement( "div", { ref, className: cn( sidebarVariants({ variant, size: isCollapsed ? "sm" : size }), className ), ...props }, header && /* @__PURE__ */ React12.createElement("div", { className: cn( "flex items-center p-4 border-b border-border", isCollapsed && "justify-center px-2" ) }, header), /* @__PURE__ */ React12.createElement("div", { className: "flex-1 overflow-y-auto p-4 space-y-2" }, sections.map((section, sectionIndex) => /* @__PURE__ */ React12.createElement("div", { key: `section-${sectionIndex}` }, section.title && !isCollapsed && /* @__PURE__ */ React12.createElement("div", { className: "px-3 mb-2" }, /* @__PURE__ */ React12.createElement("h3", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider" }, section.title)), /* @__PURE__ */ React12.createElement("div", { className: "space-y-1" }, section.items.map((item) => renderItem(item))), sectionIndex < sections.length - 1 && /* @__PURE__ */ React12.createElement("div", { className: "my-4 border-t border-border" })))), footer && /* @__PURE__ */ React12.createElement("div", { className: cn( "p-4 border-t border-border", isCollapsed && "px-2" ) }, footer), collapsible && showCollapseButton && /* @__PURE__ */ React12.createElement("div", { className: "p-2 border-t border-border" }, /* @__PURE__ */ React12.createElement( "button", { onClick: toggleCollapse, className: "flex w-full items-center justify-center p-2 rounded-lg hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", title: isCollapsed ? "Expand sidebar" : "Collapse sidebar" }, isCollapsed ? /* @__PURE__ */ React12.createElement(ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ React12.createElement(ChevronLeft, { className: "h-4 w-4" }) )) ); } ); Sidebar.displayName = "Sidebar"; function