UNPKG

niva-ui

Version:

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

1 lines 176 kB
{"version":3,"sources":["../src/components/ui/button.tsx","../src/lib/utils.ts","../src/components/ui/badge.tsx","../src/components/ui/card.tsx","../src/components/ui/input.tsx","../src/components/ui/select.tsx","../src/components/ui/avatar.tsx","../src/components/ui/table.tsx","../src/components/ui/toast.tsx","../src/components/ui/modal.tsx","../src/components/ui/navbar.tsx","../src/components/ui/sidebar.tsx","../src/components/ui/form.tsx","../src/components/ui/dropdown.tsx","../src/components/ui/alert.tsx","../src/components/ui/loader.tsx","../src/components/ui/chip.tsx","../src/components/ui/pagination.tsx","../src/components/ui/search-bar.tsx","../src/components/ui/stat-card.tsx","../src/components/ui/code-block.tsx","../src/components/theme-provider.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n \"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\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 rounded-md px-3\",\n lg: \"h-11 rounded-md px-8\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\"\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n )\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\n/**\n * Utility function to merge class names with tailwind-merge\n * This ensures that conflicting Tailwind classes are properly resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\n/**\n * Format currency values for display in SaaS applications\n */\nexport function formatCurrency(\n amount: number,\n currency: string = 'USD',\n locale: string = 'en-US'\n): string {\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n }).format(amount)\n}\n\n/**\n * Format large numbers with compact notation (1.2K, 1.5M, etc.)\n */\nexport function formatCompactNumber(\n num: number,\n locale: string = 'en-US'\n): string {\n return new Intl.NumberFormat(locale, {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(num)\n}\n\n/**\n * Calculate percentage change between two values\n */\nexport function calculatePercentageChange(\n current: number,\n previous: number\n): number {\n if (previous === 0) return current > 0 ? 100 : 0\n return ((current - previous) / previous) * 100\n}\n\n/**\n * Generate initials from a full name\n */\nexport function getInitials(name: string): string {\n return name\n .split(' ')\n .map((part) => part.charAt(0))\n .join('')\n .toUpperCase()\n .slice(0, 2)\n}\n\n/**\n * Debounce function for search inputs and other frequent operations\n */\nexport function debounce<T extends (...args: any[]) => any>(\n func: T,\n wait: number\n): (...args: Parameters<T>) => void {\n let timeout: NodeJS.Timeout\n\n return function executedFunction(...args: Parameters<T>) {\n const later = () => {\n clearTimeout(timeout)\n func(...args)\n }\n\n clearTimeout(timeout)\n timeout = setTimeout(later, wait)\n }\n}\n\n/**\n * Generate a random color for avatars, badges, etc.\n */\nexport function generateRandomColor(): string {\n const colors = [\n 'bg-red-500',\n 'bg-blue-500',\n 'bg-green-500',\n 'bg-yellow-500',\n 'bg-purple-500',\n 'bg-pink-500',\n 'bg-indigo-500',\n 'bg-cyan-500',\n 'bg-orange-500',\n 'bg-emerald-500',\n ]\n return colors[Math.floor(Math.random() * colors.length)]\n}\n\n/**\n * Check if a string is a valid email\n */\nexport function isValidEmail(email: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(email)\n}\n\n/**\n * Truncate text with ellipsis\n */\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n return text.slice(0, maxLength) + '...'\n}\n\n/**\n * Sleep utility for async operations\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n","import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n \"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\",\n {\n variants: {\n variant: {\n default:\n \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary:\n \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive:\n \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n outline: \"text-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nexport interface BadgeProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof badgeVariants> { }\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n return (\n <div className={cn(badgeVariants({ variant }), className)} {...props} />\n )\n}\n\nexport { Badge, badgeVariants }\n","import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"rounded-lg border bg-card text-card-foreground shadow-sm\",\n className\n )}\n {...props}\n />\n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n {...props}\n />\n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n <h3\n ref={ref}\n className={cn(\n \"text-2xl font-semibold leading-none tracking-tight\",\n className\n )}\n {...props}\n />\n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n <p\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex items-center p-6 pt-0\", className)}\n {...props}\n />\n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n","\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\n\nconst inputVariants = cva(\n \"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\",\n {\n variants: {\n variant: {\n default: \"border-input\",\n destructive: \"border-destructive focus-visible:ring-destructive\",\n ghost: \"border-transparent bg-transparent hover:bg-accent\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 px-3 text-sm\",\n lg: \"h-11 px-4 py-3\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,\n VariantProps<typeof inputVariants> {\n label?: string\n error?: string\n icon?: React.ReactNode\n suffix?: React.ReactNode\n}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, variant, size, type, label, error, icon, suffix, ...props }, ref) => {\n return (\n <div className=\"grid w-full gap-1.5\">\n {label && (\n <label className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n {label}\n </label>\n )}\n <div className=\"relative\">\n {icon && (\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\">\n {icon}\n </div>\n )}\n <input\n type={type}\n className={cn(\n inputVariants({ variant, size, className }),\n icon && \"pl-10\",\n suffix && \"pr-10\",\n error && \"border-destructive focus-visible:ring-destructive\"\n )}\n ref={ref}\n {...props}\n />\n {suffix && (\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground\">\n {suffix}\n </div>\n )}\n </div>\n {error && (\n <p className=\"text-sm text-destructive\">{error}</p>\n )}\n </div>\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input, inputVariants }\n","\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDown, Check } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\n\nconst selectVariants = cva(\n \"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\",\n {\n variants: {\n variant: {\n default: \"border-input\",\n destructive: \"border-destructive focus:ring-destructive\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 px-3 text-sm\",\n lg: \"h-11 px-4 py-3\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nexport interface SelectOption {\n value: string\n label: string\n disabled?: boolean\n}\n\nexport interface SelectProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'>,\n VariantProps<typeof selectVariants> {\n options: SelectOption[]\n value?: string\n onValueChange?: (value: string) => void\n placeholder?: string\n label?: string\n error?: string\n searchable?: boolean\n}\n\nconst Select = React.forwardRef<HTMLButtonElement, SelectProps>(\n ({\n className,\n variant,\n size,\n options,\n value,\n onValueChange,\n placeholder = \"Select an option...\",\n label,\n error,\n searchable = false,\n disabled,\n ...props\n }, ref) => {\n const [isOpen, setIsOpen] = React.useState(false)\n const [searchTerm, setSearchTerm] = React.useState(\"\")\n const dropdownRef = React.useRef<HTMLDivElement>(null)\n\n const selectedOption = options.find(option => option.value === value)\n\n const filteredOptions = React.useMemo(() => {\n if (!searchable || !searchTerm) return options\n return options.filter(option =>\n option.label.toLowerCase().includes(searchTerm.toLowerCase())\n )\n }, [options, searchTerm, searchable])\n\n React.useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false)\n setSearchTerm(\"\")\n }\n }\n\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [])\n\n const handleSelect = (optionValue: string) => {\n onValueChange?.(optionValue)\n setIsOpen(false)\n setSearchTerm(\"\")\n }\n\n return (\n <div className=\"grid w-full gap-1.5\">\n {label && (\n <label className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n {label}\n </label>\n )}\n <div className=\"relative\" ref={dropdownRef}>\n <button\n type=\"button\"\n ref={ref}\n className={cn(\n selectVariants({ variant, size, className }),\n error && \"border-destructive focus:ring-destructive\"\n )}\n onClick={() => !disabled && setIsOpen(!isOpen)}\n disabled={disabled}\n {...props}\n >\n <span className={cn(\n \"block truncate text-left\",\n !selectedOption && \"text-muted-foreground\"\n )}>\n {selectedOption ? selectedOption.label : placeholder}\n </span>\n <ChevronDown className={cn(\n \"h-4 w-4 transition-transform\",\n isOpen && \"rotate-180\"\n )} />\n </button>\n\n {isOpen && (\n <div className=\"absolute z-50 mt-1 w-full rounded-md border bg-popover p-1 shadow-md\">\n {searchable && (\n <div className=\"p-1\">\n <input\n type=\"text\"\n placeholder=\"Search...\"\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n 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\"\n />\n </div>\n )}\n <div className=\"max-h-60 overflow-auto\">\n {filteredOptions.length === 0 ? (\n <div className=\"py-6 text-center text-sm text-muted-foreground\">\n No options found.\n </div>\n ) : (\n filteredOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n className={cn(\n \"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\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n value === option.value && \"bg-accent text-accent-foreground\"\n )}\n onClick={() => !option.disabled && handleSelect(option.value)}\n disabled={option.disabled}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n {value === option.value && <Check className=\"h-4 w-4\" />}\n </span>\n {option.label}\n </button>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n {error && (\n <p className=\"text-sm text-destructive\">{error}</p>\n )}\n </div>\n )\n }\n)\nSelect.displayName = \"Select\"\n\nexport { Select, selectVariants }\n","\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\n\nconst avatarVariants = cva(\n \"relative flex shrink-0 overflow-hidden rounded-full\",\n {\n variants: {\n size: {\n sm: \"h-6 w-6\",\n default: \"h-10 w-10\",\n lg: \"h-12 w-12\",\n xl: \"h-16 w-16\",\n \"2xl\": \"h-20 w-20\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n }\n)\n\nconst avatarImageVariants = cva(\"aspect-square h-full w-full object-cover\")\n\nconst avatarFallbackVariants = cva(\n \"flex h-full w-full items-center justify-center rounded-full bg-muted text-muted-foreground font-medium\",\n {\n variants: {\n size: {\n sm: \"text-xs\",\n default: \"text-sm\",\n lg: \"text-base\",\n xl: \"text-lg\",\n \"2xl\": \"text-xl\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n }\n)\n\nexport interface AvatarProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof avatarVariants> {\n src?: string\n alt?: string\n fallback?: string\n status?: \"online\" | \"offline\" | \"away\" | \"busy\"\n}\n\nconst Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(\n ({ className, size, src, alt, fallback, status, ...props }, ref) => {\n const [imageLoaded, setImageLoaded] = React.useState(false)\n const [imageFailed, setImageFailed] = React.useState(false)\n\n const initials = React.useMemo(() => {\n if (fallback) return fallback\n if (alt) {\n const names = alt.split(' ')\n return names.length > 1\n ? `${names[0][0]}${names[names.length - 1][0]}`.toUpperCase()\n : names[0].slice(0, 2).toUpperCase()\n }\n return '??'\n }, [fallback, alt])\n\n return (\n <div className={cn(avatarVariants({ size }), className)} ref={ref} {...props}>\n {src && !imageFailed && (\n <img\n src={src}\n alt={alt}\n className={cn(avatarImageVariants())}\n onLoad={() => setImageLoaded(true)}\n onError={() => setImageFailed(true)}\n />\n )}\n {(!src || imageFailed || !imageLoaded) && (\n <div className={cn(avatarFallbackVariants({ size }))}>\n {initials}\n </div>\n )}\n {status && (\n <div className={cn(\n \"absolute -bottom-0 -right-0 rounded-full border-2 border-background\",\n size === \"sm\" && \"h-2 w-2\",\n size === \"default\" && \"h-3 w-3\",\n size === \"lg\" && \"h-3.5 w-3.5\",\n size === \"xl\" && \"h-4 w-4\",\n size === \"2xl\" && \"h-5 w-5\",\n status === \"online\" && \"bg-green-500\",\n status === \"offline\" && \"bg-gray-400\",\n status === \"away\" && \"bg-yellow-500\",\n status === \"busy\" && \"bg-red-500\"\n )} />\n )}\n </div>\n )\n }\n)\nAvatar.displayName = \"Avatar\"\n\nexport { Avatar, avatarVariants }\n","\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronUp, ChevronDown, ChevronsUpDown } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nconst Table = React.forwardRef<\n HTMLTableElement,\n React.HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n <div className=\"relative w-full overflow-auto\">\n <table\n ref={ref}\n className={cn(\"w-full caption-bottom text-sm\", className)}\n {...props}\n />\n </div>\n))\nTable.displayName = \"Table\"\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n))\nTableHeader.displayName = \"TableHeader\"\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tbody\n ref={ref}\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n))\nTableBody.displayName = \"TableBody\"\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tfoot\n ref={ref}\n className={cn(\n \"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n className\n )}\n {...props}\n />\n))\nTableFooter.displayName = \"TableFooter\"\n\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes<HTMLTableRowElement> & {\n hover?: boolean\n }\n>(({ className, hover = true, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\n \"border-b transition-colors\",\n hover && \"hover:bg-muted/50\",\n \"data-[state=selected]:bg-muted\",\n className\n )}\n {...props}\n />\n))\nTableRow.displayName = \"TableRow\"\n\ninterface TableHeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {\n sortable?: boolean\n sortDirection?: \"asc\" | \"desc\" | null\n onSort?: () => void\n}\n\nconst TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(\n ({ className, sortable, sortDirection, onSort, children, ...props }, ref) => (\n <th\n ref={ref}\n className={cn(\n \"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n sortable && \"cursor-pointer hover:text-foreground\",\n className\n )}\n onClick={sortable ? onSort : undefined}\n {...props}\n >\n <div className=\"flex items-center space-x-2\">\n <span>{children}</span>\n {sortable && (\n <span className=\"flex items-center\">\n {sortDirection === \"asc\" && <ChevronUp className=\"h-4 w-4\" />}\n {sortDirection === \"desc\" && <ChevronDown className=\"h-4 w-4\" />}\n {sortDirection === null && <ChevronsUpDown className=\"h-4 w-4\" />}\n </span>\n )}\n </div>\n </th>\n )\n)\nTableHead.displayName = \"TableHead\"\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n <td\n ref={ref}\n className={cn(\"p-4 align-middle [&:has([role=checkbox])]:pr-0\", className)}\n {...props}\n />\n))\nTableCell.displayName = \"TableCell\"\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n <caption\n ref={ref}\n className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\n {...props}\n />\n))\nTableCaption.displayName = \"TableCaption\"\n\n// Enhanced DataTable component for SaaS applications\nexport interface Column<T> {\n key: keyof T\n header: string\n sortable?: boolean\n render?: (value: T[keyof T], row: T) => React.ReactNode\n width?: string\n}\n\nexport interface DataTableProps<T> {\n data: T[]\n columns: Column<T>[]\n loading?: boolean\n emptyMessage?: string\n onRowClick?: (row: T) => void\n selectable?: boolean\n selectedRows?: T[]\n onSelectionChange?: (rows: T[]) => void\n}\n\nfunction DataTable<T extends Record<string, any>>({\n data,\n columns,\n loading = false,\n emptyMessage = \"No data available\",\n onRowClick,\n selectable = false,\n selectedRows = [],\n onSelectionChange,\n}: DataTableProps<T>) {\n const [sortConfig, setSortConfig] = React.useState<{\n key: keyof T | null\n direction: \"asc\" | \"desc\"\n }>({ key: null, direction: \"asc\" })\n\n const sortedData = React.useMemo(() => {\n if (!sortConfig.key) return data\n\n return [...data].sort((a, b) => {\n const aValue = a[sortConfig.key!]\n const bValue = b[sortConfig.key!]\n\n if (aValue < bValue) {\n return sortConfig.direction === \"asc\" ? -1 : 1\n }\n if (aValue > bValue) {\n return sortConfig.direction === \"asc\" ? 1 : -1\n }\n return 0\n })\n }, [data, sortConfig])\n\n const handleSort = (key: keyof T) => {\n setSortConfig(prev => ({\n key,\n direction: prev.key === key && prev.direction === \"asc\" ? \"desc\" : \"asc\"\n }))\n }\n\n const isSelected = (row: T) => selectedRows.some(selected => selected === row)\n\n const toggleSelection = (row: T) => {\n if (!onSelectionChange) return\n\n const newSelection = isSelected(row)\n ? selectedRows.filter(selected => selected !== row)\n : [...selectedRows, row]\n\n onSelectionChange(newSelection)\n }\n\n const toggleAllSelection = () => {\n if (!onSelectionChange) return\n\n const allSelected = selectedRows.length === data.length\n onSelectionChange(allSelected ? [] : [...data])\n }\n\n if (loading) {\n return (\n <div className=\"w-full p-8 text-center\">\n <div className=\"text-sm text-muted-foreground\">Loading...</div>\n </div>\n )\n }\n\n return (\n <Table>\n <TableHeader>\n <TableRow>\n {selectable && (\n <TableHead className=\"w-12\">\n <input\n type=\"checkbox\"\n checked={selectedRows.length === data.length && data.length > 0}\n onChange={toggleAllSelection}\n className=\"rounded\"\n />\n </TableHead>\n )}\n {columns.map((column) => (\n <TableHead\n key={String(column.key)}\n sortable={column.sortable}\n sortDirection={\n sortConfig.key === column.key\n ? sortConfig.direction\n : null\n }\n onSort={() => column.sortable && handleSort(column.key)}\n style={{ width: column.width }}\n >\n {column.header}\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {sortedData.length === 0 ? (\n <TableRow>\n <TableCell colSpan={columns.length + (selectable ? 1 : 0)} className=\"text-center py-8\">\n <div className=\"text-muted-foreground\">{emptyMessage}</div>\n </TableCell>\n </TableRow>\n ) : (\n sortedData.map((row, index) => (\n <TableRow\n key={index}\n className={cn(\n onRowClick && \"cursor-pointer\",\n isSelected(row) && \"bg-muted/50\"\n )}\n onClick={() => onRowClick?.(row)}\n >\n {selectable && (\n <TableCell>\n <input\n type=\"checkbox\"\n checked={isSelected(row)}\n onChange={() => toggleSelection(row)}\n onClick={(e) => e.stopPropagation()}\n className=\"rounded\"\n />\n </TableCell>\n )}\n {columns.map((column) => (\n <TableCell key={String(column.key)}>\n {column.render\n ? column.render(row[column.key], row)\n : String(row[column.key] ?? \"\")}\n </TableCell>\n ))}\n </TableRow>\n ))\n )}\n </TableBody>\n </Table>\n )\n}\n\nexport {\n Table,\n TableHeader,\n TableBody,\n TableFooter,\n TableHead,\n TableRow,\n TableCell,\n TableCaption,\n DataTable,\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { X, CheckCircle, AlertCircle, Info, AlertTriangle } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\n\nconst toastVariants = cva(\n \"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\",\n {\n variants: {\n variant: {\n default: \"border bg-background text-foreground\",\n success: \"border-green-200 bg-green-50 text-green-900 dark:border-green-800 dark:bg-green-950 dark:text-green-50\",\n error: \"border-red-200 bg-red-50 text-red-900 dark:border-red-800 dark:bg-red-950 dark:text-red-50\",\n warning: \"border-yellow-200 bg-yellow-50 text-yellow-900 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-50\",\n info: \"border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-50\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nexport interface ToastProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof toastVariants> {\n title?: string\n description?: string\n action?: React.ReactNode\n onClose?: () => void\n duration?: number\n}\n\nconst Toast = React.forwardRef<HTMLDivElement, ToastProps>(\n ({ className, variant, title, description, action, onClose, duration = 5000, ...props }, ref) => {\n const [isVisible, setIsVisible] = React.useState(true)\n\n React.useEffect(() => {\n if (duration && duration > 0) {\n const timer = setTimeout(() => {\n setIsVisible(false)\n setTimeout(() => onClose?.(), 150) // Allow time for exit animation\n }, duration)\n\n return () => clearTimeout(timer)\n }\n }, [duration, onClose])\n\n const getIcon = () => {\n switch (variant) {\n case \"success\":\n return <CheckCircle className=\"h-5 w-5 text-green-600 dark:text-green-400\" />\n case \"error\":\n return <AlertCircle className=\"h-5 w-5 text-red-600 dark:text-red-400\" />\n case \"warning\":\n return <AlertTriangle className=\"h-5 w-5 text-yellow-600 dark:text-yellow-400\" />\n case \"info\":\n return <Info className=\"h-5 w-5 text-blue-600 dark:text-blue-400\" />\n default:\n return null\n }\n }\n\n if (!isVisible) return null\n\n return (\n <div\n ref={ref}\n className={cn(toastVariants({ variant }), className)}\n {...props}\n >\n <div className=\"flex items-start space-x-3\">\n <div className=\"flex-shrink-0 mt-0.5\">\n {getIcon()}\n </div>\n <div className=\"flex-1 min-w-0\">\n {title && (\n <div className=\"text-sm font-semibold\">\n {title}\n </div>\n )}\n {description && (\n <div className=\"mt-1 text-sm opacity-90\">\n {description}\n </div>\n )}\n {action && (\n <div className=\"mt-2\">\n {action}\n </div>\n )}\n </div>\n </div>\n <button\n onClick={() => {\n setIsVisible(false)\n setTimeout(() => onClose?.(), 150)\n }}\n 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\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n )\n }\n)\nToast.displayName = \"Toast\"\n\n// Toast Container\ninterface ToastContainerProps {\n position?: \"top-right\" | \"top-left\" | \"bottom-right\" | \"bottom-left\" | \"top-center\" | \"bottom-center\"\n}\n\nconst ToastContainer: React.FC<ToastContainerProps> = ({\n position = \"top-right\"\n}) => {\n const positionClasses = {\n \"top-right\": \"top-0 right-0\",\n \"top-left\": \"top-0 left-0\",\n \"bottom-right\": \"bottom-0 right-0\",\n \"bottom-left\": \"bottom-0 left-0\",\n \"top-center\": \"top-0 left-1/2 -translate-x-1/2\",\n \"bottom-center\": \"bottom-0 left-1/2 -translate-x-1/2\",\n }\n\n return (\n <div\n className={cn(\n \"fixed z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:flex-col md:max-w-[420px]\",\n positionClasses[position]\n )}\n id=\"toast-container\"\n />\n )\n}\n\n// Toast Hook and Context\ninterface ToastOptions extends Omit<ToastProps, 'onClose'> {\n id?: string\n}\n\ninterface ToastContextType {\n toasts: (ToastOptions & { id: string })[]\n toast: (options: ToastOptions) => string\n dismiss: (id: string) => void\n clear: () => void\n}\n\nconst ToastContext = React.createContext<ToastContextType | undefined>(undefined)\n\nexport function ToastProvider({ children }: { children: React.ReactNode }) {\n const [toasts, setToasts] = React.useState<(ToastOptions & { id: string })[]>([])\n\n const toast = React.useCallback((options: ToastOptions) => {\n const id = options.id || Math.random().toString(36).substring(7)\n\n setToasts(prev => [...prev, { ...options, id }])\n\n return id\n }, [])\n\n const dismiss = React.useCallback((id: string) => {\n setToasts(prev => prev.filter(toast => toast.id !== id))\n }, [])\n\n const clear = React.useCallback(() => {\n setToasts([])\n }, [])\n\n return (\n <ToastContext.Provider value={{ toasts, toast, dismiss, clear }}>\n {children}\n <ToastContainer />\n {toasts.map(({ id, ...toastProps }) => (\n <Toast\n key={id}\n {...toastProps}\n onClose={() => dismiss(id)}\n />\n ))}\n </ToastContext.Provider>\n )\n}\n\nexport function useToast() {\n const context = React.useContext(ToastContext)\n if (!context) {\n throw new Error(\"useToast must be used within a ToastProvider\")\n }\n return context\n}\n\n// Convenience functions\nexport const toast = {\n success: (message: string, options?: Omit<ToastOptions, 'variant'>) => {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('toast', {\n detail: { variant: 'success', description: message, ...options }\n }))\n }\n },\n error: (message: string, options?: Omit<ToastOptions, 'variant'>) => {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('toast', {\n detail: { variant: 'error', description: message, ...options }\n }))\n }\n },\n warning: (message: string, options?: Omit<ToastOptions, 'variant'>) => {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('toast', {\n detail: { variant: 'warning', description: message, ...options }\n }))\n }\n },\n info: (message: string, options?: Omit<ToastOptions, 'variant'>) => {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('toast', {\n detail: { variant: 'info', description: message, ...options }\n }))\n }\n },\n}\n\nexport { Toast, ToastContainer, toastVariants }\n","\"use client\"\n\nimport * as React from \"react\"\nimport { X } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\n\nconst modalVariants = cva(\n \"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\",\n {\n variants: {\n size: {\n sm: \"max-w-sm\",\n default: \"max-w-lg\",\n lg: \"max-w-2xl\",\n xl: \"max-w-4xl\",\n full: \"max-w-[95vw] max-h-[95vh]\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n }\n)\n\nexport interface ModalProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof modalVariants> {\n isOpen: boolean\n onClose: () => void\n title?: string\n description?: string\n showCloseButton?: boolean\n closeOnOverlayClick?: boolean\n closeOnEscape?: boolean\n}\n\nconst Modal = React.forwardRef<HTMLDivElement, ModalProps>(\n ({\n className,\n size,\n isOpen,\n onClose,\n title,\n description,\n showCloseButton = true,\n closeOnOverlayClick = true,\n closeOnEscape = true,\n children,\n ...props\n }, ref) => {\n const modalRef = React.useRef<HTMLDivElement>(null)\n\n // Handle escape key\n React.useEffect(() => {\n if (!closeOnEscape || !isOpen) return\n\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose()\n }\n }\n\n document.addEventListener('keydown', handleEscape)\n return () => document.removeEventListener('keydown', handleEscape)\n }, [isOpen, closeOnEscape, onClose])\n\n // Handle overlay click\n const handleOverlayClick = (e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose()\n }\n }\n\n // Focus management\n React.useEffect(() => {\n if (isOpen) {\n const previousActiveElement = document.activeElement as HTMLElement\n modalRef.current?.focus()\n\n return () => {\n previousActiveElement?.focus()\n }\n }\n }, [isOpen])\n\n // Prevent body scroll when modal is open\n React.useEffect(() => {\n if (isOpen) {\n document.body.style.overflow = 'hidden'\n return () => {\n document.body.style.overflow = 'unset'\n }\n }\n }, [isOpen])\n\n if (!isOpen) return null\n\n return (\n <>\n {/* Overlay */}\n <div\n className=\"fixed inset-0 z-40 bg-black/50 backdrop-blur-sm\"\n onClick={handleOverlayClick}\n />\n\n {/* Modal */}\n <div\n ref={(node) => {\n modalRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n }}\n className={cn(modalVariants({ size }), className)}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? \"modal-title\" : undefined}\n aria-describedby={description ? \"modal-description\" : undefined}\n tabIndex={-1}\n {...props}\n >\n {/* Close button */}\n {showCloseButton && (\n <button\n onClick={onClose}\n 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\"\n >\n <X className=\"h-4 w-4\" />\n <span className=\"sr-only\">Close</span>\n </button>\n )}\n\n {/* Header */}\n {(title || description) && (\n <div className=\"flex flex-col space-y-1.5\">\n {title && (\n <h2 id=\"modal-title\" className=\"text-lg font-semibold leading-none tracking-tight\">\n {title}\n </h2>\n )}\n {description && (\n <p id=\"modal-description\" className=\"text-sm text-muted-foreground\">\n {description}\n </p>\n )}\n </div>\n )}\n\n {/* Content */}\n <div className=\"flex-1\">\n {children}\n </div>\n </div>\n </>\n )\n }\n)\nModal.displayName = \"Modal\"\n\n// Modal Components for composition\nconst ModalHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex flex-col space-y-1.5 text-center sm:text-left\", className)}\n {...props}\n />\n))\nModalHeader.displayName = \"ModalHeader\"\n\nconst ModalTitle = React.forwardRef<\n HTMLHeadingElement,\n React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n <h3\n ref={ref}\n className={cn(\"text-lg font-semibold leading-none tracking-tight\", className)}\n {...props}\n />\n))\nModalTitle.displayName = \"ModalTitle\"\n\nconst ModalDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n <p\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n))\nModalDescription.displayName = \"ModalDescription\"\n\nconst ModalContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"py-4\", className)} {...props} />\n))\nModalContent.displayName = \"ModalContent\"\n\nconst ModalFooter = React.forwardRef<\n HTMLDivElement,\n React.HT