UNPKG

aura-glass

Version:

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

217 lines (214 loc) 7.75 kB
'use client'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { GlassInput } from '../input/GlassInput.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { Search } from 'lucide-react'; import React, { useState, useRef, useEffect, createContext } 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'; // Context for command state const CommandContext = /*#__PURE__*/createContext(null); /** * GlassCommand component * A glassmorphism command palette with search functionality */ const GlassCommand = ({ items, placeholder = "Search commands...", emptyMessage = "No commands found", loading = false, maxHeight = "300px", filterItems, groupBy, renderItem, renderEmpty, onSelect, onSearchChange }) => { const [query, setQuery] = useState(""); const [selectedIndex, setSelectedIndex] = useState(0); const [filteredItems, setFilteredItems] = useState(items); useRef(null); // Default filter function const defaultFilter = (items, query) => { if (!query) return items; const lowerQuery = query.toLowerCase(); return items.filter(item => { const searchableText = [item?.label, item?.description, ...(item?.keywords || [])].join(" ").toLowerCase(); return searchableText.includes(lowerQuery); }); }; // Filter items when query changes useEffect(() => { const filtered = filterItems ? filterItems(items, query) : defaultFilter(items, query); setFilteredItems(filtered); setSelectedIndex(0); onSearchChange?.(query); }, [query, items, filterItems, onSearchChange]); // Group items if groupBy is provided const groupedItems = React.useMemo(() => { if (!groupBy) return { "": filteredItems }; const groups = {}; filteredItems.forEach(item => { const group = groupBy(item); if (!groups[group]) groups[group] = []; groups[group].push(item); }); return groups; }, [filteredItems, groupBy]); // Handle keyboard navigation const handleKeyDown = e => { const totalItems = filteredItems?.length || 0; switch (e.key) { case "ArrowDown": e.preventDefault(); setSelectedIndex(prev => (prev + 1) % totalItems); break; case "ArrowUp": e.preventDefault(); setSelectedIndex(prev => (prev - 1 + totalItems) % totalItems); break; case "Enter": e.preventDefault(); if (filteredItems[selectedIndex]) { handleSelect(filteredItems[selectedIndex]); } break; case "Escape": e.preventDefault(); setQuery(""); setSelectedIndex(0); break; } }; const handleSelect = item => { if (item?.disabled) return; item?.action(); onSelect?.(item); }; return jsx(CommandContext.Provider, { "data-glass-component": true, value: { selectedIndex, setSelectedIndex, query, setQuery }, children: jsx(OptimizedGlassCore, { intent: "neutral", elevation: "level3", intensity: "strong", depth: 2, tint: "neutral", border: "subtle", animation: "pulse", performanceMode: "high", className: "glass-radius-lg", children: jsxs("div", { className: "glass-p-4", children: [jsx(GlassCommandInput, { placeholder: placeholder, value: query, onChange: e => setQuery(e.target.value), onKeyDown: handleKeyDown, autoFocus: true }), jsx(GlassCommandList, { maxHeight: maxHeight, children: loading ? jsx("div", { className: "glass-flex glass-items-center glass-justify-center glass-py-8", children: jsx("div", { className: 'w-6 h-6 glass-border-2 glass-border-white/30 glass-border-t-white/60 glass-radius-full animate-spin' }) }) : (filteredItems?.length || 0) === 0 ? renderEmpty ? renderEmpty() : jsx("div", { className: 'text-center glass-py-8 text-primary/50', children: emptyMessage }) : Object.entries(groupedItems).map(([groupName, groupItems]) => jsxs("div", { children: [groupName && jsx("div", { className: 'glass-px-3 glass-py-2 glass-text-xs font-medium text-primary/60 glass-border-b glass-border-white/10', children: groupName }), groupItems.map((item, itemIndex) => { const globalIndex = filteredItems.indexOf(item); const isSelected = globalIndex === selectedIndex; return jsx("div", { className: cn("flex items-center glass-px-3 glass-py-2 cursor-pointer transition-all duration-200 glass-radius-md", "hover:bg-white/10 hover:-translate-y-0.5", { "bg-white/20 glass-text-primary shadow-md ring-1 ring-white/20": isSelected, "opacity-50 cursor-not-allowed": item?.disabled }), onClick: e => handleSelect(item), children: renderItem ? renderItem(item, isSelected) : jsxs(Fragment, { children: [item?.icon && jsx("div", { className: 'glass-flex glass-items-center glass-justify-center w-5 h-5 mr-3 text-primary/70', children: item?.icon }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsx("div", { className: 'text-primary/90 font-medium truncate', children: item?.label }), item?.description && jsx("div", { className: 'text-primary/60 glass-text-sm truncate', children: item?.description })] })] }) }, item?.id); })] }, groupName)) })] }) }) }); }; /** * GlassCommandInput component * Search input for the command palette */ const GlassCommandInput = ({ className, ...props }) => { // Convert Booleanish ARIA attributes to boolean const inputProps = { ...props, "aria-required": props["aria-required"] === "true" ? true : props["aria-required"] === "false" ? false : props["aria-required"], "aria-invalid": typeof props["aria-invalid"] === "boolean" ? props["aria-invalid"] : props["aria-invalid"] === "true" ? true : props["aria-invalid"] === "false" ? false : undefined }; return jsxs("div", { className: 'relative mb-4', children: [jsx(Search, { className: 'absolute left-3 glass-top-1/2 transform -translate-y-1/2 w-4 h-4 text-primary/50' }), jsx(OptimizedGlassCore, { variant: "clear", elevation: "level1", className: "glass-glass-backdrop-blur-md glass-radius-lg glass-contrast-guard", children: jsx(GlassInput, { className: cn("w-full pl-10 pr-4 glass-py-3 bg-transparent border-0 outline-none", "glass-text-primary placeholder-white/50", "focus:ring-2 focus:ring-white/30", className), ...inputProps }) })] }); }; /** * GlassCommandList component * Scrollable list container for command items */ const GlassCommandList = ({ children, maxHeight = "300px", className }) => { return jsx("div", { className: cn("overflow-y-auto", className), style: { maxHeight }, children: children }); }; export { GlassCommand, GlassCommandInput, GlassCommandList }; //# sourceMappingURL=GlassCommand.js.map