niva-ui
Version:
A beautiful, minimalistic React + Tailwind UI framework for SaaS applications
1,205 lines (1,192 loc) • 89.7 kB
JavaScript
"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