aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
246 lines (243 loc) • 9.52 kB
JavaScript
'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