UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

246 lines (243 loc) 9.52 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { forwardRef, useState, useRef, useEffect, useCallback, useMemo } from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import '../../primitives/motion/MotionFramer.js'; const GlassTreeSelect = /*#__PURE__*/forwardRef(({ data: incomingData = [], value: incomingValue = [], onChange = () => {}, placeholder = "Select...", multiple = false, searchable = true, searchPlaceholder = "Search...", showCheckbox, defaultExpanded = false, maxHeight = "300px", elevation = "level3", disabled = false, className, ...props }, ref) => { const data = Array.isArray(incomingData) ? incomingData : []; const value = Array.isArray(incomingValue) ? incomingValue : []; const [isOpen, setIsOpen] = useState(false); const [search, setSearch] = useState(""); const [expandedNodes, setExpandedNodes] = useState(new Set()); const containerRef = useRef(null); const shouldShowCheckbox = showCheckbox ?? multiple; useEffect(() => { if (defaultExpanded) { const allIds = new Set(); const collectIds = (nodes = []) => { (Array.isArray(nodes) ? nodes : []).forEach(node => { const hasChildren = Array.isArray(node.children) && node.children.length > 0; if (hasChildren) { allIds.add(node.id); collectIds(node.children); } }); }; collectIds(data); setExpandedNodes(allIds); } }, [data, defaultExpanded]); useEffect(() => { const handleClickOutside = event => { if (containerRef.current && !containerRef.current.contains(event.target)) { setIsOpen(false); } }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isOpen]); const flattenTree = useCallback((nodes = [], parentId) => { const safeNodes = Array.isArray(nodes) ? nodes : []; return safeNodes.reduce((acc, node) => { const nodeWithParent = { ...node, parentId }; acc.push(nodeWithParent); if (Array.isArray(node.children) && node.children.length > 0) { acc.push(...flattenTree(node.children, node.id)); } return acc; }, []); }, []); const flatNodes = useMemo(() => flattenTree(data), [data, flattenTree]); const getNodeById = useCallback(id => { return flatNodes.find(node => node.id === id); }, [flatNodes]); const getSelectedLabels = useCallback(() => { return value.map(id => getNodeById(id)?.label).filter(Boolean); }, [value, getNodeById]); const filteredData = useMemo(() => { if (!search) return data; const matchingIds = new Set(); const filterNodes = nodes => { const safeNodes = Array.isArray(nodes) ? nodes : []; return safeNodes.map(node => { const matches = node.label.toLowerCase().includes(search.toLowerCase()); const filteredChildren = Array.isArray(node.children) ? filterNodes(node.children) : []; if (matches || filteredChildren.length > 0) { matchingIds.add(node.id); if (matches) { setExpandedNodes(prev => new Set([...prev, node.id])); } return { ...node, children: filteredChildren.length > 0 ? filteredChildren : node.children }; } return null; }).filter(node => node !== null); }; return filterNodes(data); }, [data, search]); const toggleExpand = useCallback(id => { setExpandedNodes(prev => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }, []); const handleSelect = useCallback(id => { if (disabled) return; const node = getNodeById(id); if (node?.disabled) return; if (multiple) { const newValue = value.includes(id) ? value.filter(v => v !== id) : [...value, id]; onChange?.(newValue); } else { onChange?.([id]); setIsOpen(false); } }, [disabled, multiple, value, onChange, getNodeById]); const renderTreeNode = (node, level = 0) => { const hasChildren = Array.isArray(node.children) && node.children.length > 0; const isExpanded = expandedNodes.has(node.id); const isSelected = value.includes(node.id); return jsxs("div", { "data-glass-component": true, children: [jsxs("div", { className: cn("flex items-center gap-2 glass-p-2 glass-radius-md cursor-pointer", "transition-all duration-200", "hover:bg-white/5", "glass-focus glass-touch-target glass-contrast-guard", isSelected && "bg-white/10", node.disabled && "opacity-50 cursor-not-allowed"), style: { paddingLeft: `${level * 1.5 + 0.5}rem` }, onClick: () => !node.disabled && handleSelect(node.id), onKeyDown: e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); if (!node.disabled) handleSelect(node.id); } }, tabIndex: 0, role: "button", children: [hasChildren && jsx("button", { type: "button", onClick: e => { e.stopPropagation(); toggleExpand(node.id); }, className: 'glass-flex-shrink-0 glass-text-secondary hover:glass-text-primary transition-colors glass-focus glass-touch-target glass-radius-sm glass-p-0.5', children: jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: cn("w-4 h-4 transition-transform duration-200", isExpanded && "rotate-90"), children: jsx("path", { d: "M9 6l6 6-6 6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), !hasChildren && jsx("div", { className: 'w-4' }), shouldShowCheckbox && jsx("input", { type: "checkbox", checked: isSelected, onChange: () => handleSelect(node.id), disabled: disabled || node.disabled, onClick: e => e.stopPropagation(), className: "glass-flex-shrink-0 glass-focus" }), node.icon && jsx("span", { className: "glass-flex-shrink-0", children: node.icon }), jsx("span", { className: 'glass-flex-1 glass-text-sm glass-text-primary truncate', children: node.label })] }), hasChildren && isExpanded && Array.isArray(node.children) && jsx("div", { children: node.children.map(child => renderTreeNode(child, level + 1)) })] }, node.id); }; const selectedLabels = getSelectedLabels(); return jsxs("div", { ref: containerRef, className: cn("relative", className), ...props, children: [jsxs("button", { type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: cn("w-full flex items-center justify-between gap-2", "glass-p-3 glass-radius-md", "bg-white/5 border glass-border-subtle", "transition-all duration-200", "hover:bg-white/10", "focus:outline-none glass-focus glass-touch-target glass-contrast-guard", "disabled:opacity-50 disabled:cursor-not-allowed", isOpen && "ring-2 ring-blue-500/50"), children: [jsx("span", { className: cn("glass-text-sm truncate", selectedLabels.length === 0 && "glass-text-secondary"), children: selectedLabels.length > 0 ? selectedLabels.join(", ") : placeholder }), jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: cn("w-4 h-4 glass-text-secondary flex-shrink-0 transition-transform duration-200", isOpen && "rotate-180"), children: jsx("path", { d: "M6 9l6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })] }), isOpen && jsxs(OptimizedGlassCore, { elevation: elevation, className: cn("absolute top-full left-0 right-0 mt-2 z-50", "glass-radius-lg overflow-hidden"), children: [searchable && jsx("div", { className: "glass-p-2 glass-border-b glass-border-subtle", children: jsx("input", { type: "text", value: search, onChange: e => setSearch(e.target.value), placeholder: searchPlaceholder, className: cn("w-full glass-p-2 glass-radius-md", "glass-text-sm glass-text-primary", "bg-white/5 border glass-border-subtle", "focus:outline-none glass-focus"), autoFocus: true }) }), jsx("div", { className: 'overflow-y-auto glass-p-2', style: { maxHeight }, children: filteredData.length === 0 ? jsx("div", { className: 'glass-p-4 text-center glass-text-sm glass-text-secondary', children: "No results found" }) : filteredData.map(node => renderTreeNode(node)) })] })] }); }); GlassTreeSelect.displayName = "GlassTreeSelect"; export { GlassTreeSelect, GlassTreeSelect as default }; //# sourceMappingURL=GlassTreeSelect.js.map