react-18-ui-library 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +8 -3
- package/dist/components/layout/Navbar/Navbar.d.ts +13 -1
- package/dist/components/layout/Navbar/index.d.ts +1 -1
- package/dist/components/layout/Sidebar/Sidebar.d.ts +3 -1
- package/dist/index.cjs.js +156 -82
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.esm.js +157 -83
- package/dist/index.esm.js.map +1 -1
- package/dist/styles.css +2 -1
- package/package.json +12 -4
package/dist/index.esm.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import React, { createContext, useEffect, useCallback, useState, useRef, useId, useMemo, Component, useContext } from 'react';
|
|
3
3
|
import { clsx } from 'clsx';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
|
-
import { X, Menu,
|
|
5
|
+
import { ChevronDown, X, Menu, PanelLeftOpen, PanelLeftClose, ChevronRight, ChevronsLeft, ChevronLeft, ChevronsRight, Check, Loader2, EyeOff, Eye, ChevronUp, Minus, Search, Star, Calendar, AlertCircle, Upload, Image as Image$1, FileText, File as File$1, ImageOff, User, ChevronsUpDown, TrendingUp, TrendingDown, Inbox, AlertTriangle, CheckCircle, Info, RefreshCw, Trash2, Copy, ExternalLink } from 'lucide-react';
|
|
6
6
|
import * as RadixTabs from '@radix-ui/react-tabs';
|
|
7
7
|
import { cva } from 'class-variance-authority';
|
|
8
8
|
import * as RadixSelect from '@radix-ui/react-select';
|
|
@@ -349,21 +349,48 @@ function cn(...inputs) {
|
|
|
349
349
|
return twMerge(clsx(inputs));
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
-
function
|
|
352
|
+
function getInitials$1(name) {
|
|
353
|
+
return name
|
|
354
|
+
.split(' ')
|
|
355
|
+
.filter(Boolean)
|
|
356
|
+
.slice(0, 2)
|
|
357
|
+
.map((n) => n[0].toUpperCase())
|
|
358
|
+
.join('');
|
|
359
|
+
}
|
|
360
|
+
function Navbar({ logo, links = [], actions, profile, sticky = false, fixed = false, bordered = true, className, onMenuToggle, }) {
|
|
353
361
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
362
|
+
const [profileOpen, setProfileOpen] = useState(false);
|
|
363
|
+
const profileRef = useRef(null);
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
if (!profileOpen)
|
|
366
|
+
return;
|
|
367
|
+
const handler = (e) => {
|
|
368
|
+
if (!profileRef.current?.contains(e.target)) {
|
|
369
|
+
setProfileOpen(false);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
document.addEventListener('mousedown', handler);
|
|
373
|
+
return () => document.removeEventListener('mousedown', handler);
|
|
374
|
+
}, [profileOpen]);
|
|
354
375
|
const handleMenuToggle = () => {
|
|
355
376
|
const next = !mobileOpen;
|
|
356
377
|
setMobileOpen(next);
|
|
357
378
|
onMenuToggle?.(next);
|
|
358
379
|
};
|
|
359
|
-
return (jsxs("header", { className: cn('w-full bg-navbar font-base', sticky && 'sticky top-0', fixed && 'fixed top-0 left-0 right-0', bordered && 'border-b border-border', 'z-navbar', className), style: { height: 'var(--navbar-height)' }, children: [jsxs("div", { className: "flex items-center justify-between h-full px-4 md:px-6", children: [logo && jsx("div", { className: "flex-shrink-0", children: logo }), links.length > 0 && (jsx("nav", { className: "hidden md:flex items-center gap-1 ml-6", children: links.map((link
|
|
380
|
+
return (jsxs("header", { className: cn('w-full bg-navbar font-base', sticky && 'sticky top-0', fixed && 'fixed top-0 left-0 right-0', bordered && 'border-b border-border', 'z-navbar', className), style: { height: 'var(--navbar-height)' }, children: [jsxs("div", { className: "flex items-center justify-between h-full px-4 md:px-6", children: [logo && jsx("div", { className: "flex-shrink-0", children: logo }), links.length > 0 && (jsx("nav", { className: "hidden md:flex items-center gap-1 ml-6", children: links.map((link) => (jsxs("a", { href: link.href, onClick: link.onClick, "aria-current": link.active ? 'page' : undefined, className: cn('flex items-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors cursor-pointer', link.active
|
|
360
381
|
? 'bg-primary text-primary-foreground'
|
|
361
|
-
: 'text-text-muted hover:text-text hover:bg-surface-hover'), children: [link.icon && jsx("span", { className: "flex-shrink-0", children: link.icon }), link.label] },
|
|
382
|
+
: 'text-text-muted hover:text-text hover:bg-surface-hover'), children: [link.icon && jsx("span", { className: "flex-shrink-0", children: link.icon }), link.label] }, `${link.label}-${link.href ?? ''}`))) })), jsx("div", { className: "flex-1" }), actions && (jsx("div", { className: "hidden md:flex items-center gap-2", children: actions })), profile && (jsxs("div", { ref: profileRef, className: "hidden md:flex items-center ml-2 relative", children: [jsxs("button", { type: "button", onClick: () => setProfileOpen((v) => !v), "aria-expanded": profileOpen, "aria-haspopup": "menu", className: cn('flex items-center gap-2.5 px-2 py-1.5 rounded-lg transition-colors', 'hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-border-focus'), children: [profile.avatarUrl ? (jsx("img", { src: profile.avatarUrl, alt: profile.name, className: "w-8 h-8 rounded-full object-cover flex-shrink-0 ring-2 ring-border" })) : (jsx("span", { className: "w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-xs font-semibold flex-shrink-0 ring-2 ring-border", children: getInitials$1(profile.name) })), jsxs("div", { className: "flex flex-col items-start leading-tight", children: [jsx("span", { className: "text-sm font-medium text-text max-w-[120px] truncate", children: profile.name }), profile.email && (jsx("span", { className: "text-xs text-text-muted max-w-[120px] truncate", children: profile.email }))] }), profile.menuItems && profile.menuItems.length > 0 && (jsx(ChevronDown, { size: 14, className: cn('text-text-muted transition-transform flex-shrink-0', profileOpen && 'rotate-180') }))] }), profileOpen && profile.menuItems && profile.menuItems.length > 0 && (jsx("div", { role: "menu", className: cn('absolute right-0 top-full mt-1.5 min-w-[180px] bg-surface border border-border rounded-lg shadow-lg py-1 z-dropdown', 'animate-in fade-in-0 zoom-in-95'), children: profile.menuItems.map((item) => (jsx("a", { href: item.href, role: "menuitem", onClick: () => {
|
|
383
|
+
item.onClick?.();
|
|
384
|
+
setProfileOpen(false);
|
|
385
|
+
}, className: "flex items-center px-3 py-2 text-sm text-text hover:bg-surface-hover transition-colors cursor-pointer", children: item.name }, item.name))) }))] })), jsx("button", { type: "button", className: "md:hidden p-2 rounded-md text-text-muted hover:text-text hover:bg-surface-hover transition-colors", onClick: handleMenuToggle, "aria-label": "Toggle menu", children: mobileOpen ? jsx(X, { size: 20 }) : jsx(Menu, { size: 20 }) })] }), mobileOpen && (jsxs("div", { className: "md:hidden border-t border-border bg-navbar px-4 pb-4", children: [jsx("nav", { className: "flex flex-col gap-1 mt-2", children: links.map((link) => (jsxs("a", { href: link.href, "aria-current": link.active ? 'page' : undefined, onClick: () => {
|
|
362
386
|
link.onClick?.();
|
|
363
387
|
setMobileOpen(false);
|
|
364
388
|
}, className: cn('flex items-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-colors cursor-pointer', link.active
|
|
365
389
|
? 'bg-primary text-primary-foreground'
|
|
366
|
-
: 'text-text-muted hover:text-text hover:bg-surface-hover'), children: [link.icon && jsx("span", { className: "flex-shrink-0", children: link.icon }), link.label] },
|
|
390
|
+
: 'text-text-muted hover:text-text hover:bg-surface-hover'), children: [link.icon && jsx("span", { className: "flex-shrink-0", children: link.icon }), link.label] }, `mobile-${link.label}-${link.href ?? ''}`))) }), actions && jsx("div", { className: "flex items-center gap-2 mt-3", children: actions }), profile && (jsxs("div", { className: "mt-3 pt-3 border-t border-border", children: [jsxs("div", { className: "flex items-center gap-3 px-1 mb-2", children: [profile.avatarUrl ? (jsx("img", { src: profile.avatarUrl, alt: profile.name, className: "w-9 h-9 rounded-full object-cover flex-shrink-0 ring-2 ring-border" })) : (jsx("span", { className: "w-9 h-9 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-xs font-semibold flex-shrink-0 ring-2 ring-border", children: getInitials$1(profile.name) })), jsxs("div", { className: "flex flex-col leading-tight min-w-0", children: [jsx("span", { className: "text-sm font-medium text-text truncate", children: profile.name }), profile.email && (jsx("span", { className: "text-xs text-text-muted truncate", children: profile.email }))] })] }), profile.menuItems && profile.menuItems.length > 0 && (jsx("div", { className: "flex flex-col gap-0.5", children: profile.menuItems.map((item) => (jsx("a", { href: item.href, onClick: () => {
|
|
391
|
+
item.onClick?.();
|
|
392
|
+
setMobileOpen(false);
|
|
393
|
+
}, className: "flex items-center px-3 py-2 text-sm text-text-muted hover:text-text hover:bg-surface-hover rounded-md transition-colors cursor-pointer", children: item.name }, item.name))) }))] }))] }))] }));
|
|
367
394
|
}
|
|
368
395
|
|
|
369
396
|
function SidebarItemComponent({ item, collapsed, depth = 0, }) {
|
|
@@ -374,9 +401,13 @@ function SidebarItemComponent({ item, collapsed, depth = 0, }) {
|
|
|
374
401
|
setExpanded((v) => !v);
|
|
375
402
|
item.onClick?.();
|
|
376
403
|
};
|
|
377
|
-
return (jsxs("li", { children: [jsxs("a", { href: item.href, onClick: handleClick, title: collapsed ? item.label : undefined,
|
|
404
|
+
return (jsxs("li", { className: "relative", children: [jsxs("a", { href: item.href, onClick: handleClick, title: collapsed ? item.label : undefined, "aria-current": item.active ? 'page' : undefined, className: cn('group relative flex items-center gap-3 rounded-lg cursor-pointer text-sm font-medium', 'transition-all duration-150 outline-none', collapsed
|
|
405
|
+
? 'justify-center w-10 h-10 mx-auto'
|
|
406
|
+
: 'px-3 py-2.5', item.active
|
|
407
|
+
? 'bg-white/10 text-white'
|
|
408
|
+
: 'text-sidebar-text hover:bg-white/8 hover:text-white', depth > 0 && !collapsed && 'ml-3 text-xs'), children: [item.active && !collapsed && (jsx("span", { className: "absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-5 rounded-full bg-primary" })), item.icon && (jsx("span", { className: cn('flex-shrink-0 flex items-center justify-center transition-colors', collapsed ? 'w-5 h-5' : 'w-4 h-4', item.active ? 'text-primary' : 'text-sidebar-icon group-hover:text-white'), children: item.icon })), !collapsed && (jsxs(Fragment, { children: [jsx("span", { className: "flex-1 truncate", children: item.label }), item.badge !== undefined && (jsx("span", { className: "ml-auto text-[10px] font-semibold bg-primary/20 text-primary rounded-full px-1.5 py-0.5 min-w-[1.25rem] text-center leading-4", children: item.badge })), hasChildren && (jsx(ChevronDown, { size: 13, className: cn('ml-auto flex-shrink-0 text-sidebar-icon transition-transform duration-200', expanded && 'rotate-180') }))] })), collapsed && (jsxs("span", { role: "tooltip", className: cn('pointer-events-none absolute left-full ml-3 z-50', 'whitespace-nowrap rounded-md bg-gray-900 px-2.5 py-1.5 text-xs font-medium text-white shadow-lg', 'opacity-0 group-hover:opacity-100 transition-opacity duration-150', 'before:absolute before:right-full before:top-1/2 before:-translate-y-1/2', 'before:border-4 before:border-transparent before:border-r-gray-900'), children: [item.label, item.badge !== undefined && (jsx("span", { className: "ml-1.5 bg-primary/30 text-primary rounded-full px-1.5 py-0.5 text-[10px]", children: item.badge }))] }))] }), hasChildren && expanded && !collapsed && (jsx("ul", { className: "mt-0.5 ml-3 pl-3 border-l border-white/10 space-y-0.5", children: item.children.map((child) => (jsx(SidebarItemComponent, { item: child, collapsed: collapsed, depth: depth + 1 }, child.id))) }))] }));
|
|
378
409
|
}
|
|
379
|
-
function Sidebar({ items = [], collapsed: controlledCollapsed, defaultCollapsed = false, onCollapsedChange, header, footer, overlay = false, open = true, onClose, className, }) {
|
|
410
|
+
function Sidebar({ items = [], collapsed: controlledCollapsed, defaultCollapsed = false, onCollapsedChange, header, collapsedIcon, footer, overlay = false, open = true, onClose, className, }) {
|
|
380
411
|
const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed);
|
|
381
412
|
const collapsed = controlledCollapsed ?? internalCollapsed;
|
|
382
413
|
const toggleCollapsed = () => {
|
|
@@ -384,15 +415,17 @@ function Sidebar({ items = [], collapsed: controlledCollapsed, defaultCollapsed
|
|
|
384
415
|
setInternalCollapsed(next);
|
|
385
416
|
onCollapsedChange?.(next);
|
|
386
417
|
};
|
|
387
|
-
return (jsxs(Fragment, { children: [overlay && open && (jsx("div", { className: "fixed inset-0 bg-overlay md:hidden", style: { zIndex: 'calc(var(--z-sidebar) - 1)' }, onClick: onClose })), jsxs("aside", { className: cn('flex flex-col h-full bg-sidebar transition-
|
|
418
|
+
return (jsxs(Fragment, { children: [overlay && open && (jsx("div", { className: "fixed inset-0 bg-overlay md:hidden", style: { zIndex: 'calc(var(--z-sidebar) - 1)' }, onClick: onClose })), jsxs("aside", { className: cn('relative flex flex-col h-full bg-sidebar', 'transition-[width] duration-300 ease-in-out will-change-[width]', collapsed
|
|
388
419
|
? 'w-[var(--sidebar-collapsed-width)]'
|
|
389
|
-
: 'w-[var(--sidebar-width)]', overlay && 'fixed top-0 left-0 bottom-0', overlay && !open && '-translate-x-full', 'z-sidebar', className), children: [header && (
|
|
420
|
+
: 'w-[var(--sidebar-width)]', overlay && 'fixed top-0 left-0 bottom-0', overlay && !open && '-translate-x-full', 'z-sidebar', className), children: [(header || collapsedIcon) && (jsxs("div", { className: cn('flex-shrink-0 flex items-center border-b border-white/8', collapsed ? 'justify-center h-16 px-0' : 'px-4 h-16'), children: [!collapsed && (jsx("div", { className: "flex-1 min-w-0 overflow-hidden", children: header })), collapsed && (jsx("div", { className: "flex items-center justify-center w-9 h-9 rounded-xl bg-white/8", children: collapsedIcon }))] })), jsx("nav", { className: "flex-1 overflow-y-auto overflow-x-hidden py-3 px-2", children: jsx("ul", { className: cn('space-y-0.5', collapsed && 'flex flex-col items-center'), children: items.map((item) => (jsx(SidebarItemComponent, { item: item, collapsed: collapsed }, item.id))) }) }), footer && (jsx("div", { className: cn('flex-shrink-0 border-t border-white/8 transition-all duration-300', collapsed ? 'px-2 py-3' : 'px-4 py-3'), children: jsx("div", { className: cn('overflow-hidden transition-all duration-300', collapsed ? 'opacity-0 h-0 pointer-events-none' : 'opacity-100'), children: footer }) })), jsx("button", { type: "button", onClick: toggleCollapsed, "aria-label": collapsed ? 'Expand sidebar' : 'Collapse sidebar', className: cn('absolute -right-3', (header || collapsedIcon) ? 'top-[4.5rem]' : 'top-3', 'z-10 flex items-center justify-center', 'w-6 h-6 rounded-full', 'bg-sidebar border border-white/15 shadow-md', 'text-sidebar-text hover:text-white hover:bg-primary hover:border-primary', 'transition-all duration-200 hover:scale-110', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'), children: collapsed
|
|
421
|
+
? jsx(PanelLeftOpen, { size: 12 })
|
|
422
|
+
: jsx(PanelLeftClose, { size: 12 }) })] })] }));
|
|
390
423
|
}
|
|
391
424
|
|
|
392
425
|
function AppShell({ navbar, sidebar, children, defaultSidebarCollapsed = false, className, contentClassName, }) {
|
|
393
426
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(defaultSidebarCollapsed);
|
|
394
427
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
395
|
-
return (jsxs("div", { className: cn('flex flex-col h-screen bg-background', className), children: [navbar && (jsx(Navbar, { ...navbar, sticky: true, onMenuToggle: (open) => setMobileSidebarOpen(open) })), jsxs("div", { className: "flex flex-1 overflow-hidden", children: [sidebar && (jsxs(Fragment, { children: [jsx("div", { className: "hidden md:
|
|
428
|
+
return (jsxs("div", { className: cn('flex flex-col h-screen bg-background', className), children: [navbar && (jsx(Navbar, { ...navbar, sticky: true, onMenuToggle: (open) => setMobileSidebarOpen(open) })), jsxs("div", { className: "flex flex-1 overflow-hidden", children: [sidebar && (jsxs(Fragment, { children: [jsx("div", { className: "hidden md:block flex-shrink-0 relative", children: jsx(Sidebar, { ...sidebar, collapsed: sidebarCollapsed, onCollapsedChange: setSidebarCollapsed }) }), jsx("div", { className: "md:hidden", children: jsx(Sidebar, { ...sidebar, overlay: true, open: mobileSidebarOpen, onClose: () => setMobileSidebarOpen(false) }) })] })), jsx("main", { className: cn('flex-1 overflow-auto bg-background', contentClassName), children: children })] })] }));
|
|
396
429
|
}
|
|
397
430
|
|
|
398
431
|
const sizeClasses$g = {
|
|
@@ -557,7 +590,7 @@ function Pagination({ page, pageSize = 10, total, siblingCount = 1, showFirstLas
|
|
|
557
590
|
const btnActive = 'bg-primary text-primary-foreground';
|
|
558
591
|
const btnInactive = 'text-text-muted hover:bg-surface-hover hover:text-text';
|
|
559
592
|
const btnDisabled = 'opacity-40 cursor-not-allowed pointer-events-none';
|
|
560
|
-
return (jsxs("nav", { "aria-label": "Pagination", className: cn('flex items-center gap-1', className), children: [showFirstLast && (jsx("button", { type: "button", onClick: () => onPageChange(1), disabled: page === 1, className: cn(btnBase, page === 1 ? btnDisabled : btnInactive), "aria-label": "First page", children: jsx(ChevronsLeft, { size: 14 }) })), jsx("button", { type: "button", onClick: () => onPageChange(page - 1), disabled: page === 1, className: cn(btnBase, page === 1 ? btnDisabled : btnInactive), "aria-label": "Previous page", children: jsx(ChevronLeft, { size: 14 }) }), pages.map((p, i) => p === '...' ? (jsx("span", { className: "flex items-center justify-center w-8 h-8 text-text-muted text-sm", children: "\u2026" }, `dots-${i}`)) : (jsx("button", { type: "button", onClick: () => onPageChange(p), className: cn(btnBase, page === p ? btnActive : btnInactive), "aria-label": `Page ${p}`, "aria-current": page === p ? 'page' : undefined, children: p }, p))), jsx("button", { type: "button", onClick: () => onPageChange(page + 1), disabled: page === totalPages, className: cn(btnBase, page === totalPages ? btnDisabled : btnInactive), "aria-label": "Next page", children: jsx(ChevronRight, { size: 14 }) }), showFirstLast && (jsx("button", { type: "button", onClick: () => onPageChange(totalPages), disabled: page === totalPages, className: cn(btnBase, page === totalPages ? btnDisabled : btnInactive), "aria-label": "Last page", children: jsx(ChevronsRight, { size: 14 }) }))] }));
|
|
593
|
+
return (jsxs("nav", { "aria-label": "Pagination", className: cn('flex items-center gap-1', className), children: [showFirstLast && (jsx("button", { type: "button", onClick: () => onPageChange(1), disabled: page === 1, className: cn(btnBase, page === 1 ? btnDisabled : btnInactive), "aria-label": "First page", children: jsx(ChevronsLeft, { size: 14 }) })), jsx("button", { type: "button", onClick: () => onPageChange(page - 1), disabled: page === 1, className: cn(btnBase, page === 1 ? btnDisabled : btnInactive), "aria-label": "Previous page", children: jsx(ChevronLeft, { size: 14 }) }), pages.map((p, i) => p === '...' ? (jsx("span", { className: "flex items-center justify-center w-8 h-8 text-text-muted text-sm", children: "\u2026" }, `dots-${i === 1 ? 'left' : 'right'}`)) : (jsx("button", { type: "button", onClick: () => onPageChange(p), className: cn(btnBase, page === p ? btnActive : btnInactive), "aria-label": `Page ${p}`, "aria-current": page === p ? 'page' : undefined, children: p }, p))), jsx("button", { type: "button", onClick: () => onPageChange(page + 1), disabled: page === totalPages, className: cn(btnBase, page === totalPages ? btnDisabled : btnInactive), "aria-label": "Next page", children: jsx(ChevronRight, { size: 14 }) }), showFirstLast && (jsx("button", { type: "button", onClick: () => onPageChange(totalPages), disabled: page === totalPages, className: cn(btnBase, page === totalPages ? btnDisabled : btnInactive), "aria-label": "Last page", children: jsx(ChevronsRight, { size: 14 }) }))] }));
|
|
561
594
|
}
|
|
562
595
|
|
|
563
596
|
function StepIndicator({ steps, currentStep, orientation = 'horizontal', className, }) {
|
|
@@ -679,18 +712,24 @@ const TextArea = React.forwardRef(({ label, helperText, error, prefixIcon, prefi
|
|
|
679
712
|
const generatedId = useId();
|
|
680
713
|
const id = externalId ?? generatedId;
|
|
681
714
|
const internalRef = useRef(null);
|
|
682
|
-
const
|
|
715
|
+
const setRef = useCallback((el) => {
|
|
716
|
+
internalRef.current = el;
|
|
717
|
+
if (typeof ref === 'function')
|
|
718
|
+
ref(el);
|
|
719
|
+
else if (ref)
|
|
720
|
+
ref.current = el;
|
|
721
|
+
}, [ref]);
|
|
683
722
|
const charCount = value !== undefined ? String(value).length : 0;
|
|
684
723
|
const hasPrefix = !!(prefixIcon || prefixImage);
|
|
685
724
|
const hasSuffix = !!(suffixIcon || suffixImage);
|
|
686
725
|
useEffect(() => {
|
|
687
|
-
if (!autoResize || !
|
|
726
|
+
if (!autoResize || !internalRef.current)
|
|
688
727
|
return;
|
|
689
|
-
const el =
|
|
728
|
+
const el = internalRef.current;
|
|
690
729
|
el.style.height = 'auto';
|
|
691
730
|
el.style.height = `${el.scrollHeight}px`;
|
|
692
|
-
}, [value, autoResize
|
|
693
|
-
return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { className: cn('relative', fullWidth && 'w-full'), children: [hasPrefix && (jsx("div", { className: "absolute top-2.5 left-0 flex items-center justify-center w-9 text-text-muted", children: prefixImage ? (jsx("img", { src: prefixImage, alt: "", className: "w-4 h-4 object-contain" })) : (jsx("span", { className: "flex items-center justify-center w-4 h-4", children: prefixIcon })) })), jsx("textarea", { ref:
|
|
731
|
+
}, [value, autoResize]);
|
|
732
|
+
return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { className: cn('relative', fullWidth && 'w-full'), children: [hasPrefix && (jsx("div", { className: "absolute top-2.5 left-0 flex items-center justify-center w-9 text-text-muted", children: prefixImage ? (jsx("img", { src: prefixImage, alt: "", className: "w-4 h-4 object-contain" })) : (jsx("span", { className: "flex items-center justify-center w-4 h-4", children: prefixIcon })) })), jsx("textarea", { ref: setRef, id: id, value: value, onChange: onChange, disabled: disabled, required: required, rows: rows, maxLength: maxLength, className: cn('w-full rounded-md border bg-surface text-text placeholder:text-text-muted text-sm', 'transition-colors outline-none resize-y py-2 px-3', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', 'disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-surface-hover', error ? 'border-error focus:border-error focus:ring-error/20' : 'border-border', hasPrefix && 'pl-9', hasSuffix && 'pr-9', autoResize && 'resize-none overflow-hidden', textareaClassName), "aria-invalid": !!error, "aria-describedby": error ? `${id}-error` : helperText ? `${id}-helper` : undefined, ...props }), hasSuffix && (jsx("div", { className: "absolute top-2.5 right-2 flex items-center justify-center text-text-muted", children: suffixImage ? (jsx("img", { src: suffixImage, alt: "", className: "w-4 h-4 object-contain" })) : (jsx("span", { className: "flex items-center justify-center w-4 h-4", children: suffixIcon })) }))] }), jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { children: [error && (jsx("p", { id: `${id}-error`, className: "text-xs text-error", role: "alert", children: error })), !error && helperText && (jsx("p", { id: `${id}-helper`, className: "text-xs text-text-muted", children: helperText }))] }), (showCharCount || showMaxLength) && (jsxs("p", { className: cn('text-xs text-text-muted ml-auto', maxLength && charCount >= maxLength && 'text-error'), children: [charCount, maxLength ? `/${maxLength}` : ''] }))] })] }));
|
|
694
733
|
});
|
|
695
734
|
TextArea.displayName = 'TextArea';
|
|
696
735
|
|
|
@@ -777,24 +816,25 @@ function SearchSelect(props) {
|
|
|
777
816
|
.filter((g) => g.options.length > 0)
|
|
778
817
|
: [];
|
|
779
818
|
const isSelected = (val) => selectedValues.includes(val);
|
|
819
|
+
const propsRef = React.useRef(props);
|
|
820
|
+
propsRef.current = props;
|
|
780
821
|
const toggle = useCallback((val) => {
|
|
781
|
-
|
|
782
|
-
|
|
822
|
+
const p = propsRef.current;
|
|
823
|
+
if (p.multiple) {
|
|
824
|
+
const current = p.value ?? [];
|
|
783
825
|
const next = current.includes(val)
|
|
784
826
|
? current.filter((v) => v !== val)
|
|
785
827
|
: maxSelected && current.length >= maxSelected
|
|
786
828
|
? current
|
|
787
829
|
: [...current, val];
|
|
788
|
-
|
|
830
|
+
p.onChange?.(next);
|
|
789
831
|
}
|
|
790
832
|
else {
|
|
791
|
-
|
|
833
|
+
p.onChange?.(val);
|
|
792
834
|
setOpen(false);
|
|
793
835
|
setQuery('');
|
|
794
836
|
}
|
|
795
|
-
},
|
|
796
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
797
|
-
[props.multiple, props.value, maxSelected]);
|
|
837
|
+
}, [maxSelected]);
|
|
798
838
|
const removeChip = (val, e) => {
|
|
799
839
|
e.stopPropagation();
|
|
800
840
|
if (props.multiple) {
|
|
@@ -1042,7 +1082,7 @@ const thumbSize = {
|
|
|
1042
1082
|
};
|
|
1043
1083
|
function Slider({ value, defaultValue = [0], onValueChange, onValueCommit, min = 0, max = 100, step = 1, disabled = false, orientation = 'horizontal', size = 'md', showTooltip = false, showMarks = false, formatValue = (v) => String(v), label, helperText, className, }) {
|
|
1044
1084
|
const [internalValue, setInternalValue] = React.useState(value ?? defaultValue);
|
|
1045
|
-
const [
|
|
1085
|
+
const [hoveredThumb, setHoveredThumb] = React.useState(null);
|
|
1046
1086
|
const currentValue = value ?? internalValue;
|
|
1047
1087
|
const handleValueChange = (v) => {
|
|
1048
1088
|
setInternalValue(v);
|
|
@@ -1054,7 +1094,7 @@ function Slider({ value, defaultValue = [0], onValueChange, onValueCommit, min =
|
|
|
1054
1094
|
const count = Math.floor((max - min) / step);
|
|
1055
1095
|
return Array.from({ length: count + 1 }, (_, i) => min + i * step);
|
|
1056
1096
|
}, [showMarks, min, max, step]);
|
|
1057
|
-
return (jsxs("div", { className: cn('flex flex-col gap-1.5', className), children: [label && (jsxs("div", { className: "flex items-center justify-between", children: [jsx("span", { className: "text-sm font-medium text-text", children: label }), jsx("span", { className: "text-sm text-text-muted", children: currentValue.map(formatValue).join(' – ') })] })), jsxs(RadixSlider.Root, { className: cn('relative flex items-center select-none touch-none', orientation === 'horizontal' ? 'w-full' : 'flex-col h-40 w-5', disabled && 'opacity-50 cursor-not-allowed'), value: currentValue, defaultValue: defaultValue, onValueChange: handleValueChange, onValueCommit: onValueCommit, min: min, max: max, step: step, disabled: disabled, orientation: orientation, children: [jsx(RadixSlider.Track, { className: cn('relative grow rounded-full bg-surface-hover overflow-hidden', orientation === 'horizontal' ? `w-full ${trackSize[size]}` : `h-full w-${size === 'sm' ? '1' : size === 'md' ? '1.5' : '2'}`), children: jsx(RadixSlider.Range, { className: "absolute bg-primary rounded-full h-full" }) }), currentValue.map((
|
|
1097
|
+
return (jsxs("div", { className: cn('flex flex-col gap-1.5', className), children: [label && (jsxs("div", { className: "flex items-center justify-between", children: [jsx("span", { className: "text-sm font-medium text-text", children: label }), jsx("span", { className: "text-sm text-text-muted", children: currentValue.map(formatValue).join(' – ') })] })), jsxs(RadixSlider.Root, { className: cn('relative flex items-center select-none touch-none', orientation === 'horizontal' ? 'w-full' : 'flex-col h-40 w-5', disabled && 'opacity-50 cursor-not-allowed'), value: currentValue, defaultValue: defaultValue, onValueChange: handleValueChange, onValueCommit: onValueCommit, min: min, max: max, step: step, disabled: disabled, orientation: orientation, children: [jsx(RadixSlider.Track, { className: cn('relative grow rounded-full bg-surface-hover overflow-hidden', orientation === 'horizontal' ? `w-full ${trackSize[size]}` : `h-full w-${size === 'sm' ? '1' : size === 'md' ? '1.5' : '2'}`), children: jsx(RadixSlider.Range, { className: "absolute bg-primary rounded-full h-full" }) }), currentValue.map((val, i) => (jsx(RadixSlider.Thumb, { className: cn('block rounded-full bg-white border-2 border-primary shadow-md', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40', 'transition-transform hover:scale-110', 'cursor-grab active:cursor-grabbing', thumbSize[size], disabled && 'cursor-not-allowed hover:scale-100'), onMouseEnter: () => showTooltip && setHoveredThumb(i), onMouseLeave: () => showTooltip && setHoveredThumb(null), "aria-label": `Slider thumb ${i + 1}`, children: showTooltip && hoveredThumb === i && (jsx("div", { className: "absolute -top-8 left-1/2 -translate-x-1/2 bg-text text-background text-xs px-2 py-0.5 rounded whitespace-nowrap pointer-events-none", children: formatValue(val) })) }, `thumb-${i}`)))] }), showMarks && (jsx("div", { className: "relative flex justify-between mt-1", children: marks.map((mark) => (jsx("span", { className: "text-xs text-text-muted", children: formatValue(mark) }, mark))) })), helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
|
|
1058
1098
|
}
|
|
1059
1099
|
|
|
1060
1100
|
const sizeClasses$d = {
|
|
@@ -1067,6 +1107,7 @@ const NumberInput = React.forwardRef(({ value: controlledValue, defaultValue, on
|
|
|
1067
1107
|
const id = externalId ?? generatedId;
|
|
1068
1108
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
1069
1109
|
const [inputStr, setInputStr] = useState(defaultValue !== undefined ? String(defaultValue) : '');
|
|
1110
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
1070
1111
|
const value = controlledValue !== undefined ? controlledValue : internalValue;
|
|
1071
1112
|
const sz = sizeClasses$d[size];
|
|
1072
1113
|
const clamp = (v) => {
|
|
@@ -1105,7 +1146,9 @@ const NumberInput = React.forwardRef(({ value: controlledValue, defaultValue, on
|
|
|
1105
1146
|
onChange?.(parsed);
|
|
1106
1147
|
}
|
|
1107
1148
|
};
|
|
1149
|
+
const handleFocus = useCallback(() => setIsFocused(true), []);
|
|
1108
1150
|
const handleBlur = () => {
|
|
1151
|
+
setIsFocused(false);
|
|
1109
1152
|
if (value !== undefined)
|
|
1110
1153
|
commit(clamp(value));
|
|
1111
1154
|
else
|
|
@@ -1139,12 +1182,12 @@ const NumberInput = React.forwardRef(({ value: controlledValue, defaultValue, on
|
|
|
1139
1182
|
if (!isDigit && !isMinus && !isDot)
|
|
1140
1183
|
e.preventDefault();
|
|
1141
1184
|
};
|
|
1142
|
-
const displayValue =
|
|
1185
|
+
const displayValue = isFocused
|
|
1143
1186
|
? inputStr
|
|
1144
1187
|
: value !== undefined
|
|
1145
1188
|
? (formatValue ? formatValue(value) : String(value))
|
|
1146
1189
|
: '';
|
|
1147
|
-
return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { className: cn('relative flex items-center', fullWidth && 'w-full'), children: [prefix && (jsx("span", { className: "absolute left-0 flex items-center justify-center h-full px-2.5 border-r border-border bg-surface-hover rounded-l-md text-sm text-text-muted select-none", children: prefix })), jsx("input", { ref: ref, id: id, type: "text", inputMode: "decimal", value: displayValue, onChange: handleChange, onBlur: handleBlur, onKeyDown: handleKeyDown, disabled: disabled, required: required, "aria-invalid": !!error, className: cn('w-full rounded-md border bg-surface text-text placeholder:text-text-muted', 'transition-colors outline-none', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', 'disabled:opacity-50 disabled:cursor-not-allowed', error ? 'border-error focus:border-error focus:ring-error/20' : 'border-border', sz.input, prefix && 'pl-10', (suffix || !hideControls) && 'pr-10', className), ...props }), suffix && hideControls && (jsx("span", { className: "absolute right-0 flex items-center justify-center h-full px-2.5 border-l border-border bg-surface-hover rounded-r-md text-sm text-text-muted select-none", children: suffix })), !hideControls && (jsxs("div", { className: "absolute right-0 flex flex-col h-full border-l border-border rounded-r-md overflow-hidden", children: [jsx("button", { type: "button", tabIndex: -1, onClick: increment, disabled: disabled || (max !== undefined && (value ?? -Infinity) >= max), className: cn('flex items-center justify-center flex-1 bg-surface-hover hover:bg-surface-active transition-colors disabled:opacity-40 disabled:cursor-not-allowed', sz.btn), "aria-label": "Increment", children: jsx(ChevronUp, { size: 10 }) }), jsx("button", { type: "button", tabIndex: -1, onClick: decrement, disabled: disabled || (min !== undefined && (value ?? Infinity) <= min), className: cn('flex items-center justify-center flex-1 bg-surface-hover hover:bg-surface-active transition-colors border-t border-border disabled:opacity-40 disabled:cursor-not-allowed', sz.btn), "aria-label": "Decrement", children: jsx(ChevronDown, { size: 10 }) })] }))] }), jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { children: [error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }), showMaxLength && props.maxLength !== undefined && (jsxs("p", { className: cn('text-xs text-text-muted ml-auto', displayValue.length >= (props.maxLength ?? Infinity) && 'text-error'), children: [displayValue.length, "/", props.maxLength] }))] })] }));
|
|
1190
|
+
return (jsxs("div", { className: cn('flex flex-col gap-1', fullWidth && 'w-full', containerClassName), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", children: "*" })] })), jsxs("div", { className: cn('relative flex items-center', fullWidth && 'w-full'), children: [prefix && (jsx("span", { className: "absolute left-0 flex items-center justify-center h-full px-2.5 border-r border-border bg-surface-hover rounded-l-md text-sm text-text-muted select-none", children: prefix })), jsx("input", { ref: ref, id: id, type: "text", inputMode: "decimal", value: displayValue, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, disabled: disabled, required: required, "aria-invalid": !!error, className: cn('w-full rounded-md border bg-surface text-text placeholder:text-text-muted', 'transition-colors outline-none', 'focus:border-border-focus focus:ring-2 focus:ring-border-focus/20', 'disabled:opacity-50 disabled:cursor-not-allowed', error ? 'border-error focus:border-error focus:ring-error/20' : 'border-border', sz.input, prefix && 'pl-10', (suffix || !hideControls) && 'pr-10', className), ...props }), suffix && hideControls && (jsx("span", { className: "absolute right-0 flex items-center justify-center h-full px-2.5 border-l border-border bg-surface-hover rounded-r-md text-sm text-text-muted select-none", children: suffix })), !hideControls && (jsxs("div", { className: "absolute right-0 flex flex-col h-full border-l border-border rounded-r-md overflow-hidden", children: [jsx("button", { type: "button", tabIndex: -1, onClick: increment, disabled: disabled || (max !== undefined && (value ?? -Infinity) >= max), className: cn('flex items-center justify-center flex-1 bg-surface-hover hover:bg-surface-active transition-colors disabled:opacity-40 disabled:cursor-not-allowed', sz.btn), "aria-label": "Increment", children: jsx(ChevronUp, { size: 10 }) }), jsx("button", { type: "button", tabIndex: -1, onClick: decrement, disabled: disabled || (min !== undefined && (value ?? Infinity) <= min), className: cn('flex items-center justify-center flex-1 bg-surface-hover hover:bg-surface-active transition-colors border-t border-border disabled:opacity-40 disabled:cursor-not-allowed', sz.btn), "aria-label": "Decrement", children: jsx(ChevronDown, { size: 10 }) })] }))] }), jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { children: [error && jsx("p", { className: "text-xs text-error", role: "alert", children: error }), !error && helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }), showMaxLength && props.maxLength !== undefined && (jsxs("p", { className: cn('text-xs text-text-muted ml-auto', displayValue.length >= (props.maxLength ?? Infinity) && 'text-error'), children: [displayValue.length, "/", props.maxLength] }))] })] }));
|
|
1148
1191
|
});
|
|
1149
1192
|
NumberInput.displayName = 'NumberInput';
|
|
1150
1193
|
|
|
@@ -1196,6 +1239,16 @@ const PhoneInput = React.forwardRef(({ value: controlledValue, defaultCountry =
|
|
|
1196
1239
|
const defaultCC = countryCodes.find((c) => c.code === defaultCountry) ?? countryCodes[0];
|
|
1197
1240
|
const [selectedCountry, setSelectedCountry] = useState(controlledValue?.countryCode ?? defaultCC);
|
|
1198
1241
|
const [phoneNumber, setPhoneNumber] = useState(controlledValue?.number ?? '');
|
|
1242
|
+
useEffect(() => {
|
|
1243
|
+
if (controlledValue?.countryCode) {
|
|
1244
|
+
setSelectedCountry(controlledValue.countryCode);
|
|
1245
|
+
}
|
|
1246
|
+
}, [controlledValue?.countryCode]);
|
|
1247
|
+
useEffect(() => {
|
|
1248
|
+
if (controlledValue?.number !== undefined) {
|
|
1249
|
+
setPhoneNumber(controlledValue.number);
|
|
1250
|
+
}
|
|
1251
|
+
}, [controlledValue?.number]);
|
|
1199
1252
|
const activeCountry = controlledValue?.countryCode ?? selectedCountry;
|
|
1200
1253
|
const activeNumber = controlledValue?.number ?? phoneNumber;
|
|
1201
1254
|
const buildValue = (country, number) => ({
|
|
@@ -1243,10 +1296,12 @@ function OTPInput({ length = 6, value: controlledValue, onChange, onComplete, si
|
|
|
1243
1296
|
const id = useId();
|
|
1244
1297
|
const [internalValue, setInternalValue] = useState('');
|
|
1245
1298
|
const inputsRef = useRef([]);
|
|
1246
|
-
const
|
|
1299
|
+
const isControlled = controlledValue !== undefined;
|
|
1300
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
1247
1301
|
const digits = value.split('').slice(0, length);
|
|
1248
1302
|
const update = (newVal) => {
|
|
1249
|
-
|
|
1303
|
+
if (!isControlled)
|
|
1304
|
+
setInternalValue(newVal);
|
|
1250
1305
|
onChange?.(newVal);
|
|
1251
1306
|
if (newVal.length === length)
|
|
1252
1307
|
onComplete?.(newVal);
|
|
@@ -1321,7 +1376,7 @@ function Rating({ value: controlledValue, defaultValue = 0, onChange, max = 5, s
|
|
|
1321
1376
|
return (jsxs("div", { className: cn('flex flex-col gap-1', className), children: [label && jsx("span", { className: "text-sm font-medium text-text", children: label }), jsx("div", { className: cn('flex items-center gap-0.5', interactive && 'cursor-pointer'), onMouseLeave: () => interactive && setHoverValue(null), role: interactive ? 'slider' : 'img', "aria-label": `Rating: ${value} out of ${max}`, "aria-valuenow": value, "aria-valuemin": 0, "aria-valuemax": max, children: Array.from({ length: max }, (_, i) => {
|
|
1322
1377
|
const star = i + 1;
|
|
1323
1378
|
const fill = getStarFill(star);
|
|
1324
|
-
return (jsx("button", { type: "button", onClick: () => handleClick(star), onMouseEnter: () => interactive && setHoverValue(star), disabled: disabled, className: cn('relative transition-transform focus:outline-none', interactive && 'hover:scale-110', disabled && 'cursor-not-allowed opacity-50'), "aria-label": `Rate ${star} out of ${max}`, tabIndex: interactive ? 0 : -1, children: fill === 'empty' ? (emptyIcon ?? (jsx(Star, { size: iconSize, className: "text-border fill-transparent transition-colors" }))) : fill === 'full' ? (icon ?? (jsx(Star, { size: iconSize, className:
|
|
1379
|
+
return (jsx("button", { type: "button", onClick: () => handleClick(star), onMouseEnter: () => interactive && setHoverValue(star), disabled: disabled, className: cn('relative transition-transform focus:outline-none', interactive && 'hover:scale-110', disabled && 'cursor-not-allowed opacity-50'), "aria-label": `Rate ${star} out of ${max}`, tabIndex: interactive ? 0 : -1, children: fill === 'empty' ? (emptyIcon ?? (jsx(Star, { size: iconSize, className: "text-border fill-transparent transition-colors" }))) : fill === 'full' ? (icon ?? (jsx(Star, { size: iconSize, className: "text-warning fill-warning transition-colors" }))) : (jsxs("span", { className: "relative inline-block", children: [jsx(Star, { size: iconSize, className: "text-border fill-transparent" }), jsx("span", { className: "absolute inset-0 overflow-hidden w-1/2", children: jsx(Star, { size: iconSize, className: "text-warning fill-warning" }) })] })) }, star));
|
|
1325
1380
|
}) }), helperText && jsx("p", { className: "text-xs text-text-muted", children: helperText })] }));
|
|
1326
1381
|
}
|
|
1327
1382
|
|
|
@@ -1365,10 +1420,13 @@ function MultiSelect({ options = [], groups = [], value: controlledValue, defaul
|
|
|
1365
1420
|
update([]);
|
|
1366
1421
|
};
|
|
1367
1422
|
useEffect(() => {
|
|
1368
|
-
if (open)
|
|
1369
|
-
|
|
1370
|
-
|
|
1423
|
+
if (open) {
|
|
1424
|
+
const raf = requestAnimationFrame(() => searchRef.current?.focus());
|
|
1425
|
+
return () => cancelAnimationFrame(raf);
|
|
1426
|
+
}
|
|
1427
|
+
else {
|
|
1371
1428
|
setSearch('');
|
|
1429
|
+
}
|
|
1372
1430
|
}, [open]);
|
|
1373
1431
|
useEffect(() => {
|
|
1374
1432
|
const handler = (e) => {
|
|
@@ -1567,6 +1625,41 @@ function FormField({ label, helperText, error, required, disabled, htmlFor, chil
|
|
|
1567
1625
|
return (jsxs("div", { className: cn('flex flex-col gap-1', disabled && 'opacity-60', className), children: [label && (jsxs("label", { htmlFor: id, className: "text-sm font-medium text-text", children: [label, required && jsx("span", { className: "text-error ml-1", "aria-hidden": "true", children: "*" })] })), children, error && (jsx("p", { className: "text-xs text-error", role: "alert", children: error })), !error && helperText && (jsx("p", { className: "text-xs text-text-muted", children: helperText }))] }));
|
|
1568
1626
|
}
|
|
1569
1627
|
|
|
1628
|
+
function buildRules(field) {
|
|
1629
|
+
if (field.validator) {
|
|
1630
|
+
const compiled = field.validator.compile();
|
|
1631
|
+
return {
|
|
1632
|
+
required: field.required
|
|
1633
|
+
? `${field.label ?? field.name} is required`
|
|
1634
|
+
: compiled.required,
|
|
1635
|
+
minLength: compiled.minLength,
|
|
1636
|
+
maxLength: compiled.maxLength,
|
|
1637
|
+
min: compiled.min,
|
|
1638
|
+
max: compiled.max,
|
|
1639
|
+
pattern: compiled.pattern,
|
|
1640
|
+
validate: compiled.validate,
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
return {
|
|
1644
|
+
required: field.required ? `${field.label ?? field.name} is required` : false,
|
|
1645
|
+
minLength: field.validation?.minLength
|
|
1646
|
+
? { value: field.validation.minLength, message: `Minimum ${field.validation.minLength} characters` }
|
|
1647
|
+
: undefined,
|
|
1648
|
+
maxLength: field.validation?.maxLength
|
|
1649
|
+
? { value: field.validation.maxLength, message: `Maximum ${field.validation.maxLength} characters` }
|
|
1650
|
+
: undefined,
|
|
1651
|
+
min: field.validation?.min
|
|
1652
|
+
? { value: field.validation.min, message: `Minimum value is ${field.validation.min}` }
|
|
1653
|
+
: undefined,
|
|
1654
|
+
max: field.validation?.max
|
|
1655
|
+
? { value: field.validation.max, message: `Maximum value is ${field.validation.max}` }
|
|
1656
|
+
: undefined,
|
|
1657
|
+
pattern: field.validation?.pattern
|
|
1658
|
+
? { value: field.validation.pattern, message: 'Invalid format' }
|
|
1659
|
+
: undefined,
|
|
1660
|
+
validate: field.validation?.validate,
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1570
1663
|
function JSONForm({ schema, defaultValues, onSubmit, onCancel, submitLabel = 'Submit', cancelLabel = 'Cancel', loading = false, columns = 1, className, actionsClassName, }) {
|
|
1571
1664
|
const { control, handleSubmit, formState: { errors }, } = useForm({
|
|
1572
1665
|
defaultValues: defaultValues ?? Object.fromEntries(schema.map((f) => [f.name, f.defaultValue ?? ''])),
|
|
@@ -1587,41 +1680,7 @@ function JSONForm({ schema, defaultValues, onSubmit, onCancel, submitLabel = 'Su
|
|
|
1587
1680
|
}
|
|
1588
1681
|
const errorMsg = errors[field.name]?.message;
|
|
1589
1682
|
const colSpan = field.colSpan ?? 1;
|
|
1590
|
-
return (jsx("div", { className: cn(colSpanClasses[colSpan]), children: jsx(Controller, { name: field.name, control: control, rules: (() => {
|
|
1591
|
-
if (field.validator) {
|
|
1592
|
-
const compiled = field.validator.compile();
|
|
1593
|
-
return {
|
|
1594
|
-
required: field.required
|
|
1595
|
-
? `${field.label ?? field.name} is required`
|
|
1596
|
-
: compiled.required,
|
|
1597
|
-
minLength: compiled.minLength,
|
|
1598
|
-
maxLength: compiled.maxLength,
|
|
1599
|
-
min: compiled.min,
|
|
1600
|
-
max: compiled.max,
|
|
1601
|
-
pattern: compiled.pattern,
|
|
1602
|
-
validate: compiled.validate,
|
|
1603
|
-
};
|
|
1604
|
-
}
|
|
1605
|
-
return {
|
|
1606
|
-
required: field.required ? `${field.label ?? field.name} is required` : false,
|
|
1607
|
-
minLength: field.validation?.minLength
|
|
1608
|
-
? { value: field.validation.minLength, message: `Minimum ${field.validation.minLength} characters` }
|
|
1609
|
-
: undefined,
|
|
1610
|
-
maxLength: field.validation?.maxLength
|
|
1611
|
-
? { value: field.validation.maxLength, message: `Maximum ${field.validation.maxLength} characters` }
|
|
1612
|
-
: undefined,
|
|
1613
|
-
min: field.validation?.min
|
|
1614
|
-
? { value: field.validation.min, message: `Minimum value is ${field.validation.min}` }
|
|
1615
|
-
: undefined,
|
|
1616
|
-
max: field.validation?.max
|
|
1617
|
-
? { value: field.validation.max, message: `Maximum value is ${field.validation.max}` }
|
|
1618
|
-
: undefined,
|
|
1619
|
-
pattern: field.validation?.pattern
|
|
1620
|
-
? { value: field.validation.pattern, message: 'Invalid format' }
|
|
1621
|
-
: undefined,
|
|
1622
|
-
validate: field.validation?.validate,
|
|
1623
|
-
};
|
|
1624
|
-
})(), render: ({ field: f }) => {
|
|
1683
|
+
return (jsx("div", { className: cn(colSpanClasses[colSpan]), children: jsx(Controller, { name: field.name, control: control, rules: buildRules(field), render: ({ field: f }) => {
|
|
1625
1684
|
const commonProps = {
|
|
1626
1685
|
label: field.label,
|
|
1627
1686
|
helperText: field.helperText,
|
|
@@ -2296,18 +2355,23 @@ function formatBytes(bytes) {
|
|
|
2296
2355
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
2297
2356
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
2298
2357
|
}
|
|
2299
|
-
|
|
2358
|
+
function generateFileId() {
|
|
2359
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
2360
|
+
return crypto.randomUUID();
|
|
2361
|
+
}
|
|
2362
|
+
return `file-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2363
|
+
}
|
|
2300
2364
|
function FileUpload({ accept, maxFiles, maxSize, multiple = true, disabled = false, label, helperText, error, files = [], onFilesChange, onFileRemove, showPreview = true, className, }) {
|
|
2301
2365
|
const onDrop = useCallback((acceptedFiles, rejectedFiles) => {
|
|
2302
2366
|
const newFiles = acceptedFiles.map((file) => ({
|
|
2303
2367
|
file,
|
|
2304
|
-
id:
|
|
2368
|
+
id: generateFileId(),
|
|
2305
2369
|
preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,
|
|
2306
2370
|
progress: 0,
|
|
2307
2371
|
}));
|
|
2308
2372
|
const rejectedMapped = rejectedFiles.map(({ file, errors }) => ({
|
|
2309
2373
|
file,
|
|
2310
|
-
id:
|
|
2374
|
+
id: generateFileId(),
|
|
2311
2375
|
error: errors[0]?.message ?? 'File rejected',
|
|
2312
2376
|
}));
|
|
2313
2377
|
onFilesChange?.([...files, ...newFiles, ...rejectedMapped]);
|
|
@@ -2468,7 +2532,7 @@ function AvatarGroup({ avatars, max = 4, size = 'md', className }) {
|
|
|
2468
2532
|
const visible = avatars.slice(0, max);
|
|
2469
2533
|
const overflow = avatars.length - max;
|
|
2470
2534
|
const sz = sizeClasses$8[size];
|
|
2471
|
-
return (jsxs("div", { className: cn('flex items-center', className), children: [visible.map((avatar, i) => (jsx("div", { className: "-ml-2 first:ml-0 ring-2 ring-surface rounded-full", children: jsx(Avatar, { ...avatar, size: size }) }, i))), overflow > 0 && (jsxs("div", { className: cn('-ml-2 flex items-center justify-center rounded-full bg-surface-hover border-2 border-surface text-text-muted font-medium', sz.container, sz.text), children: ["+", overflow] }))] }));
|
|
2535
|
+
return (jsxs("div", { className: cn('flex items-center', className), children: [visible.map((avatar, i) => (jsx("div", { className: "-ml-2 first:ml-0 ring-2 ring-surface rounded-full", children: jsx(Avatar, { ...avatar, size: size }) }, avatar.src ?? avatar.name ?? i))), overflow > 0 && (jsxs("div", { className: cn('-ml-2 flex items-center justify-center rounded-full bg-surface-hover border-2 border-surface text-text-muted font-medium', sz.container, sz.text), children: ["+", overflow] }))] }));
|
|
2472
2536
|
}
|
|
2473
2537
|
|
|
2474
2538
|
const badgeVariants = cva('inline-flex items-center justify-center font-medium rounded-full select-none', {
|
|
@@ -2563,6 +2627,7 @@ function SVG({ src, children, width, height, size, color, className, title, role
|
|
|
2563
2627
|
return (jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: resolvedWidth, height: resolvedHeight, viewBox: viewBox, fill: fill, stroke: stroke, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", role: role, className: cn('flex-shrink-0', className), style: { color }, "aria-label": props['aria-label'], "aria-hidden": props['aria-hidden'], children: [title && jsx("title", { children: title }), children] }));
|
|
2564
2628
|
}
|
|
2565
2629
|
|
|
2630
|
+
const SKELETON_WIDTHS = [65, 80, 55, 75, 90, 60, 70, 85, 50, 78];
|
|
2566
2631
|
function Table({ columns, data, keyExtractor, selectable = false, selectedKeys = [], onSelectionChange, sortKey, sortDirection, onSortChange, loading = false, emptyMessage = 'No data available', stickyHeader = false, striped = false, hoverable = true, bordered = false, compact = false, className, }) {
|
|
2567
2632
|
const [internalSort, setInternalSort] = useState({
|
|
2568
2633
|
key: '',
|
|
@@ -2615,7 +2680,7 @@ function Table({ columns, data, keyExtractor, selectable = false, selectedKeys =
|
|
|
2615
2680
|
}
|
|
2616
2681
|
};
|
|
2617
2682
|
const cellPad = compact ? 'px-3 py-2' : 'px-4 py-3';
|
|
2618
|
-
return (jsx("div", { className: cn('w-full overflow-auto rounded-lg', bordered && 'border border-border', className), children: jsxs("table", { className: "w-full text-sm border-collapse", children: [jsx("thead", { className: cn(stickyHeader && 'sticky top-0 z-10'), children: jsxs("tr", { className: "bg-surface-hover border-b border-border", children: [selectable && (jsx("th", { className: cn('w-10', cellPad), children: jsx(Checkbox, { checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: toggleAll, "aria-label": "Select all rows" }) })), columns.map((col) => (jsx("th", { className: cn('text-left font-semibold text-text-muted whitespace-nowrap', cellPad, col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.sortable && 'cursor-pointer select-none hover:text-text', col.className), style: { width: col.width }, onClick: col.sortable ? () => handleSort(col.key) : undefined, children: jsxs("span", { className: "inline-flex items-center gap-1", children: [col.header, col.sortable && (jsx("span", { className: "flex-shrink-0", children: effectiveSortKey === col.key ? (effectiveSortDir === 'asc' ? (jsx(ChevronUp, { size: 14 })) : effectiveSortDir === 'desc' ? (jsx(ChevronDown, { size: 14 })) : (jsx(ChevronsUpDown, { size: 14, className: "opacity-40" }))) : (jsx(ChevronsUpDown, { size: 14, className: "opacity-40" })) }))] }) }, col.key)))] }) }), jsx("tbody", { children: loading ? (Array.from({ length: 5 }).map((_, i) => (jsxs("tr", { className: "border-b border-border", children: [selectable && jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 w-4 rounded bg-surface-hover animate-pulse" }) }), columns.map((col) => (jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 rounded bg-surface-hover animate-pulse", style: { width: `${
|
|
2683
|
+
return (jsx("div", { className: cn('w-full overflow-auto rounded-lg', bordered && 'border border-border', className), children: jsxs("table", { className: "w-full text-sm border-collapse", children: [jsx("thead", { className: cn(stickyHeader && 'sticky top-0 z-10'), children: jsxs("tr", { className: "bg-surface-hover border-b border-border", children: [selectable && (jsx("th", { className: cn('w-10', cellPad), children: jsx(Checkbox, { checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: toggleAll, "aria-label": "Select all rows" }) })), columns.map((col) => (jsx("th", { className: cn('text-left font-semibold text-text-muted whitespace-nowrap', cellPad, col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.sortable && 'cursor-pointer select-none hover:text-text', col.className), style: { width: col.width }, onClick: col.sortable ? () => handleSort(col.key) : undefined, children: jsxs("span", { className: "inline-flex items-center gap-1", children: [col.header, col.sortable && (jsx("span", { className: "flex-shrink-0", children: effectiveSortKey === col.key ? (effectiveSortDir === 'asc' ? (jsx(ChevronUp, { size: 14 })) : effectiveSortDir === 'desc' ? (jsx(ChevronDown, { size: 14 })) : (jsx(ChevronsUpDown, { size: 14, className: "opacity-40" }))) : (jsx(ChevronsUpDown, { size: 14, className: "opacity-40" })) }))] }) }, col.key)))] }) }), jsx("tbody", { children: loading ? (Array.from({ length: 5 }).map((_, i) => (jsxs("tr", { className: "border-b border-border", children: [selectable && jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 w-4 rounded bg-surface-hover animate-pulse" }) }), columns.map((col, ci) => (jsx("td", { className: cellPad, children: jsx("div", { className: "h-4 rounded bg-surface-hover animate-pulse", style: { width: `${SKELETON_WIDTHS[(i * columns.length + ci) % SKELETON_WIDTHS.length]}%` } }) }, col.key)))] }, `skeleton-row-${i}`)))) : sortedData.length === 0 ? (jsx("tr", { children: jsx("td", { colSpan: columns.length + (selectable ? 1 : 0), className: "text-center py-12 text-text-muted", children: emptyMessage }) })) : (sortedData.map((row, i) => {
|
|
2619
2684
|
const key = keyExtractor(row, i);
|
|
2620
2685
|
const isSelected = selectedKeys.includes(key);
|
|
2621
2686
|
return (jsxs("tr", { className: cn('border-b border-border transition-colors', striped && i % 2 === 1 && 'bg-surface-hover/50', hoverable && 'hover:bg-surface-hover', isSelected && 'bg-primary/5'), children: [selectable && (jsx("td", { className: cellPad, children: jsx(Checkbox, { checked: isSelected, onCheckedChange: () => toggleRow(key), "aria-label": `Select row ${i + 1}` }) })), columns.map((col) => (jsx("td", { className: cn('text-text', cellPad, col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.className), children: col.accessor
|
|
@@ -3150,21 +3215,24 @@ function Popover({ content, children, placement = 'bottom', open, defaultOpen, o
|
|
|
3150
3215
|
}
|
|
3151
3216
|
|
|
3152
3217
|
const itemBase = cn('flex items-center gap-2 px-2 py-1.5 text-sm rounded-md cursor-pointer outline-none select-none', 'transition-colors', 'data-[highlighted]:bg-surface-hover data-[highlighted]:text-text', 'data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none');
|
|
3218
|
+
function stableKey(item, i) {
|
|
3219
|
+
return `${item.type ?? 'item'}-${item.label ?? ''}-${i}`;
|
|
3220
|
+
}
|
|
3153
3221
|
function renderItems(items) {
|
|
3154
3222
|
return items.map((item, i) => {
|
|
3155
3223
|
if (item.type === 'separator') {
|
|
3156
|
-
return jsx(RadixContextMenu.Separator, { className: "my-1 h-px bg-border" }, i);
|
|
3224
|
+
return jsx(RadixContextMenu.Separator, { className: "my-1 h-px bg-border" }, `separator-${i}`);
|
|
3157
3225
|
}
|
|
3158
3226
|
if (item.type === 'label') {
|
|
3159
|
-
return (jsx(RadixContextMenu.Label, { className: "px-2 py-1 text-xs font-semibold text-text-muted uppercase tracking-wide", children: item.label }, i));
|
|
3227
|
+
return (jsx(RadixContextMenu.Label, { className: "px-2 py-1 text-xs font-semibold text-text-muted uppercase tracking-wide", children: item.label }, stableKey(item, i)));
|
|
3160
3228
|
}
|
|
3161
3229
|
if (item.type === 'checkbox') {
|
|
3162
|
-
return (jsxs(RadixContextMenu.CheckboxItem, { checked: item.checked, onCheckedChange: item.onCheckedChange, disabled: item.disabled, className: cn(itemBase, 'pl-7 relative', item.destructive && 'text-error'), children: [jsx(RadixContextMenu.ItemIndicator, { className: "absolute left-2", children: jsx(Check, { size: 12 }) }), item.label] }, i));
|
|
3230
|
+
return (jsxs(RadixContextMenu.CheckboxItem, { checked: item.checked, onCheckedChange: item.onCheckedChange, disabled: item.disabled, className: cn(itemBase, 'pl-7 relative', item.destructive && 'text-error'), children: [jsx(RadixContextMenu.ItemIndicator, { className: "absolute left-2", children: jsx(Check, { size: 12 }) }), item.label] }, stableKey(item, i)));
|
|
3163
3231
|
}
|
|
3164
3232
|
if (item.children && item.children.length > 0) {
|
|
3165
|
-
return (jsxs(RadixContextMenu.Sub, { children: [jsxs(RadixContextMenu.SubTrigger, { disabled: item.disabled, className: cn(itemBase, item.destructive && 'text-error'), children: [item.icon && jsx("span", { className: "w-4 h-4 flex items-center", children: item.icon }), jsx("span", { className: "flex-1", children: item.label }), jsx(ChevronRight, { size: 12, className: "ml-auto text-text-muted" })] }), jsx(RadixContextMenu.Portal, { children: jsx(RadixContextMenu.SubContent, { className: "z-dropdown bg-surface border border-border rounded-lg shadow-lg p-1 min-w-[10rem]", sideOffset: 2, alignOffset: -4, children: renderItems(item.children) }) })] }, i));
|
|
3233
|
+
return (jsxs(RadixContextMenu.Sub, { children: [jsxs(RadixContextMenu.SubTrigger, { disabled: item.disabled, className: cn(itemBase, item.destructive && 'text-error'), children: [item.icon && jsx("span", { className: "w-4 h-4 flex items-center", children: item.icon }), jsx("span", { className: "flex-1", children: item.label }), jsx(ChevronRight, { size: 12, className: "ml-auto text-text-muted" })] }), jsx(RadixContextMenu.Portal, { children: jsx(RadixContextMenu.SubContent, { className: "z-dropdown bg-surface border border-border rounded-lg shadow-lg p-1 min-w-[10rem]", sideOffset: 2, alignOffset: -4, children: renderItems(item.children) }) })] }, stableKey(item, i)));
|
|
3166
3234
|
}
|
|
3167
|
-
return (jsxs(RadixContextMenu.Item, { disabled: item.disabled, onSelect: item.onClick, className: cn(itemBase, item.destructive ? 'text-error' : 'text-text'), children: [item.icon && jsx("span", { className: "w-4 h-4 flex items-center", children: item.icon }), jsx("span", { className: "flex-1", children: item.label }), item.shortcut && (jsx("span", { className: "ml-auto text-xs text-text-muted", children: item.shortcut }))] }, i));
|
|
3235
|
+
return (jsxs(RadixContextMenu.Item, { disabled: item.disabled, onSelect: item.onClick, className: cn(itemBase, item.destructive ? 'text-error' : 'text-text'), children: [item.icon && jsx("span", { className: "w-4 h-4 flex items-center", children: item.icon }), jsx("span", { className: "flex-1", children: item.label }), item.shortcut && (jsx("span", { className: "ml-auto text-xs text-text-muted", children: item.shortcut }))] }, stableKey(item, i)));
|
|
3168
3236
|
});
|
|
3169
3237
|
}
|
|
3170
3238
|
function ContextMenu({ items, children, className }) {
|
|
@@ -3204,6 +3272,7 @@ const variantConfig = {
|
|
|
3204
3272
|
},
|
|
3205
3273
|
};
|
|
3206
3274
|
function ConfirmDialogProvider({ children }) {
|
|
3275
|
+
const descId = useId();
|
|
3207
3276
|
const [state, setState] = useState({
|
|
3208
3277
|
open: false,
|
|
3209
3278
|
resolve: null,
|
|
@@ -3228,12 +3297,13 @@ function ConfirmDialogProvider({ children }) {
|
|
|
3228
3297
|
const confirmLabel = state.confirmLabel ?? config.defaultConfirm;
|
|
3229
3298
|
const cancelLabel = state.cancelLabel ?? 'Cancel';
|
|
3230
3299
|
return (jsxs(ConfirmDialogContext.Provider, { value: { confirm }, children: [children, jsx(RadixDialog.Root, { open: state.open, onOpenChange: (open) => { if (!open)
|
|
3231
|
-
handleResponse(false); }, children: jsxs(RadixDialog.Portal, { children: [jsx(RadixDialog.Overlay, { className: "fixed inset-0 z-[1000] bg-black/40 backdrop-blur-sm animate-in fade-in-0" }), jsxs(RadixDialog.Content, { className: cn('fixed z-[1001] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2', 'w-full max-w-md bg-surface rounded-xl shadow-2xl border border-border p-6', 'animate-in fade-in-0 zoom-in-95'), "aria-describedby": description ?
|
|
3300
|
+
handleResponse(false); }, children: jsxs(RadixDialog.Portal, { children: [jsx(RadixDialog.Overlay, { className: "fixed inset-0 z-[1000] bg-black/40 backdrop-blur-sm animate-in fade-in-0" }), jsxs(RadixDialog.Content, { className: cn('fixed z-[1001] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2', 'w-full max-w-md bg-surface rounded-xl shadow-2xl border border-border p-6', 'animate-in fade-in-0 zoom-in-95'), "aria-describedby": description ? descId : undefined, children: [jsxs("div", { className: "flex items-start gap-4", children: [jsx("div", { className: cn('flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center', config.iconBg), children: config.icon }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx(RadixDialog.Title, { className: "text-base font-semibold text-text", children: title }), description && (jsx("p", { id: descId, className: "mt-1 text-sm text-text-muted", children: description }))] }), jsx("button", { onClick: () => handleResponse(false), className: "flex-shrink-0 text-text-muted hover:text-text transition-colors focus:outline-none", "aria-label": "Close", children: jsx(X, { size: 16 }) })] }), jsxs("div", { className: "flex items-center justify-end gap-3 mt-6", children: [jsx(Button, { variant: "outline", size: "sm", onClick: () => handleResponse(false), children: cancelLabel }), jsx(Button, { variant: config.confirmVariant, size: "sm", className: config.confirmClassName, onClick: () => handleResponse(true), children: confirmLabel })] })] })] }) })] }));
|
|
3232
3301
|
}
|
|
3233
3302
|
function ConfirmDialog({ open, onConfirm, onCancel, title, description, confirmLabel, cancelLabel, variant = 'default', }) {
|
|
3303
|
+
const staticDescId = useId();
|
|
3234
3304
|
const config = variantConfig[variant];
|
|
3235
3305
|
return (jsx(RadixDialog.Root, { open: open, onOpenChange: (o) => { if (!o)
|
|
3236
|
-
onCancel(); }, children: jsxs(RadixDialog.Portal, { children: [jsx(RadixDialog.Overlay, { className: "fixed inset-0 z-[1000] bg-black/40 backdrop-blur-sm animate-in fade-in-0" }), jsxs(RadixDialog.Content, { className: cn('fixed z-[1001] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2', 'w-full max-w-md bg-surface rounded-xl shadow-2xl border border-border p-6', 'animate-in fade-in-0 zoom-in-95'), "aria-describedby": description ?
|
|
3306
|
+
onCancel(); }, children: jsxs(RadixDialog.Portal, { children: [jsx(RadixDialog.Overlay, { className: "fixed inset-0 z-[1000] bg-black/40 backdrop-blur-sm animate-in fade-in-0" }), jsxs(RadixDialog.Content, { className: cn('fixed z-[1001] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2', 'w-full max-w-md bg-surface rounded-xl shadow-2xl border border-border p-6', 'animate-in fade-in-0 zoom-in-95'), "aria-describedby": description ? staticDescId : undefined, children: [jsxs("div", { className: "flex items-start gap-4", children: [jsx("div", { className: cn('flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center', config.iconBg), children: config.icon }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx(RadixDialog.Title, { className: "text-base font-semibold text-text", children: title ?? config.defaultTitle }), description && (jsx("p", { id: staticDescId, className: "mt-1 text-sm text-text-muted", children: description }))] }), jsx("button", { onClick: onCancel, className: "flex-shrink-0 text-text-muted hover:text-text transition-colors focus:outline-none", "aria-label": "Close", children: jsx(X, { size: 16 }) })] }), jsxs("div", { className: "flex items-center justify-end gap-3 mt-6", children: [jsx(Button, { variant: "outline", size: "sm", onClick: onCancel, children: cancelLabel ?? 'Cancel' }), jsx(Button, { variant: config.confirmVariant, size: "sm", className: config.confirmClassName, onClick: onConfirm, children: confirmLabel ?? config.defaultConfirm })] })] })] }) }));
|
|
3237
3307
|
}
|
|
3238
3308
|
|
|
3239
3309
|
const sizeClasses$4 = {
|
|
@@ -3333,11 +3403,15 @@ function CommandPalette({ open, onClose, items = [], groups = [], placeholder =
|
|
|
3333
3403
|
return null;
|
|
3334
3404
|
if (typeof document === 'undefined')
|
|
3335
3405
|
return null;
|
|
3336
|
-
|
|
3406
|
+
// Build a flat ordered list for index-based rendering — stable, not mutated during render
|
|
3407
|
+
const orderedItems = [
|
|
3408
|
+
...filteredItems,
|
|
3409
|
+
...filteredGroups.flatMap((g) => g.items),
|
|
3410
|
+
];
|
|
3337
3411
|
const renderItem = (item) => {
|
|
3338
|
-
const idx =
|
|
3412
|
+
const idx = orderedItems.indexOf(item);
|
|
3339
3413
|
const isActive = idx === activeIndex;
|
|
3340
|
-
return (jsxs("div", { "data-active": isActive, role: "option", "aria-selected": isActive, "aria-disabled": item.disabled, onClick: () => !item.disabled && handleSelect(item), onMouseEnter: () => setActiveIndex(idx), className: cn('flex items-center gap-3 px-3 py-2.5 rounded-md cursor-pointer transition-colors', isActive ? 'bg-primary text-primary-foreground' : 'text-text hover:bg-surface-hover', item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none'), children: [item.icon && (jsx("span", { className: cn('flex-shrink-0', isActive ? 'text-primary-foreground' : 'text-text-muted'), children: item.icon })), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("div", { className: "text-sm font-medium truncate", children: highlight(item.label, query) }), item.description && (jsx("div", { className: cn('text-xs truncate', isActive ? 'text-primary-foreground/70' : 'text-text-muted'), children: highlight(item.description, query) }))] }), item.shortcut && (jsx("div", { className: "flex items-center gap-1 flex-shrink-0", children: item.shortcut.map((k
|
|
3414
|
+
return (jsxs("div", { "data-active": isActive, role: "option", "aria-selected": isActive, "aria-disabled": item.disabled, onClick: () => !item.disabled && handleSelect(item), onMouseEnter: () => setActiveIndex(idx), className: cn('flex items-center gap-3 px-3 py-2.5 rounded-md cursor-pointer transition-colors', isActive ? 'bg-primary text-primary-foreground' : 'text-text hover:bg-surface-hover', item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none'), children: [item.icon && (jsx("span", { className: cn('flex-shrink-0', isActive ? 'text-primary-foreground' : 'text-text-muted'), children: item.icon })), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("div", { className: "text-sm font-medium truncate", children: highlight(item.label, query) }), item.description && (jsx("div", { className: cn('text-xs truncate', isActive ? 'text-primary-foreground/70' : 'text-text-muted'), children: highlight(item.description, query) }))] }), item.shortcut && (jsx("div", { className: "flex items-center gap-1 flex-shrink-0", children: item.shortcut.map((k) => (jsx(Kbd, { size: "sm", children: k }, k))) })), jsx(ChevronRight, { size: 14, className: cn('flex-shrink-0', isActive ? 'text-primary-foreground/70' : 'text-text-muted') })] }, item.id));
|
|
3341
3415
|
};
|
|
3342
3416
|
return createPortal(jsxs("div", { className: "fixed inset-0 z-[9999] flex items-start justify-center pt-[15vh] px-4", role: "dialog", "aria-modal": "true", "aria-label": "Command palette", onClick: (e) => e.target === e.currentTarget && onClose(), children: [jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: onClose }), jsxs("div", { className: cn('relative w-full max-w-lg bg-surface rounded-xl shadow-2xl border border-border overflow-hidden', className), onKeyDown: handleKeyDown, children: [jsxs("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-border", children: [loading ? (jsx(Loader2, { size: 18, className: "text-text-muted animate-spin flex-shrink-0" })) : (jsx(Search, { size: 18, className: "text-text-muted flex-shrink-0" })), jsx("input", { ref: inputRef, id: `${id}-input`, type: "text", role: "combobox", "aria-expanded": true, "aria-controls": `${id}-list`, "aria-autocomplete": "list", value: query, onChange: (e) => setQuery(e.target.value), placeholder: placeholder, className: "flex-1 bg-transparent text-text placeholder:text-text-muted outline-none text-sm" }), query && (jsx("button", { type: "button", onClick: () => setQuery(''), className: "text-text-muted hover:text-text transition-colors", "aria-label": "Clear search", children: jsx(X, { size: 16 }) })), jsx(Kbd, { size: "sm", children: "Esc" })] }), jsx("div", { ref: listRef, id: `${id}-list`, role: "listbox", className: "overflow-y-auto p-2", style: { maxHeight }, children: loading && !hasResults ? (jsxs("div", { className: "flex items-center justify-center py-8 text-text-muted text-sm gap-2", children: [jsx(Loader2, { size: 16, className: "animate-spin" }), "Searching\u2026"] })) : !hasResults ? (jsx("div", { className: "py-8 text-center text-text-muted text-sm", children: emptyMessage })) : (jsxs(Fragment, { children: [filteredItems.map(renderItem), filteredGroups.map((group) => (jsxs("div", { children: [jsx("div", { className: "px-3 py-1.5 text-xs font-semibold text-text-muted uppercase tracking-wider", children: group.label }), group.items.map(renderItem)] }, group.id)))] })) }), jsxs("div", { className: "flex items-center gap-3 px-4 py-2 border-t border-border text-xs text-text-muted", children: [jsxs("span", { className: "flex items-center gap-1", children: [jsx(Kbd, { size: "sm", children: "\u2191" }), jsx(Kbd, { size: "sm", children: "\u2193" }), " Navigate"] }), jsxs("span", { className: "flex items-center gap-1", children: [jsx(Kbd, { size: "sm", children: "\u21B5" }), " Select"] }), jsxs("span", { className: "flex items-center gap-1", children: [jsx(Kbd, { size: "sm", children: "Esc" }), " Close"] })] })] })] }), document.body);
|
|
3343
3417
|
}
|