UNPKG

aura-glass

Version:

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

229 lines (226 loc) 9.28 kB
'use client'; import { jsx, jsxs, Fragment } 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'; import { createPortal } from 'react-dom'; const GlassSpotlightSearch = /*#__PURE__*/forwardRef(({ open, onClose, actions, placeholder = "Search for actions...", maxResults = 10, elevation = "level5", showRecent = true, maxRecent = 5, fuzzySearch = true, groupByCategory = true, className, ...props }, ref) => { const [query, setQuery] = useState(""); const [selectedIndex, setSelectedIndex] = useState(0); const [recentActions, setRecentActions] = useState([]); const inputRef = useRef(null); const resultsRef = useRef(null); useEffect(() => { if (open) { setQuery(""); setSelectedIndex(0); setTimeout(() => inputRef.current?.focus(), 100); } }, [open]); useEffect(() => { if (!open) return; const handleKeyDown = e => { switch (e.key) { case "Escape": e.preventDefault(); onClose(); break; case "ArrowDown": e.preventDefault(); setSelectedIndex(prev => Math.min(prev + 1, filteredActions.length - 1)); break; case "ArrowUp": e.preventDefault(); setSelectedIndex(prev => Math.max(prev - 1, 0)); break; case "Enter": e.preventDefault(); if (filteredActions[selectedIndex]) { handleActionSelect(filteredActions[selectedIndex]); } break; } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [open, selectedIndex, onClose]); useEffect(() => { if (resultsRef.current && open) { const selectedElement = resultsRef.current.children[selectedIndex]; if (selectedElement) { selectedElement.scrollIntoView({ block: "nearest", behavior: "smooth" }); } } }, [selectedIndex, open]); const fuzzyMatch = useCallback((text, query) => { const textLower = text.toLowerCase(); const queryLower = query.toLowerCase(); if (textLower.includes(queryLower)) return true; let queryIndex = 0; for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) { if (textLower[i] === queryLower[queryIndex]) { queryIndex++; } } return queryIndex === queryLower.length; }, []); const filteredActions = useMemo(() => { if (!query) { if (showRecent && recentActions.length > 0) { return actions.filter(action => recentActions.includes(action.id)).slice(0, maxRecent); } return actions.slice(0, maxResults); } const matches = actions.filter(action => { const searchText = [action.title, action.description || "", ...(action.keywords || [])].join(" "); return fuzzySearch ? fuzzyMatch(searchText, query) : searchText.toLowerCase().includes(query.toLowerCase()); }); return matches.slice(0, maxResults); }, [query, actions, showRecent, recentActions, maxRecent, maxResults, fuzzySearch, fuzzyMatch]); const groupedActions = useMemo(() => { if (!groupByCategory) return { "": filteredActions }; return filteredActions.reduce((acc, action) => { const category = action.category || "Other"; if (!acc[category]) { acc[category] = []; } acc[category].push(action); return acc; }, {}); }, [filteredActions, groupByCategory]); const handleActionSelect = useCallback(action => { action.onAction(); setRecentActions(prev => { const updated = [action.id, ...prev.filter(id => id !== action.id)].slice(0, maxRecent); localStorage.setItem("spotlight-recent", JSON.stringify(updated)); return updated; }); onClose(); }, [onClose, maxRecent]); useEffect(() => { if (typeof window !== "undefined") { const stored = localStorage.getItem("spotlight-recent"); if (stored) { try { setRecentActions(JSON.parse(stored)); } catch (e) { console.error("Failed to parse recent actions", e); } } } }, []); if (!open) return null; const content = jsx("div", { className: 'fixed inset-0 z-50 glass-flex glass-items-start glass-justify-center glass-p-4 pt-24 glass-surface-dark/40 glass-glass-glass-backdrop-blur-sm glass-contrast-guard', onClick: onClose, role: "presentation", children: jsxs(OptimizedGlassCore, { ref: ref, elevation: elevation, className: cn("w-full max-w-2xl overflow-hidden glass-radius-2xl", "transform transition-all duration-200 ease-out", open ? "scale-100 opacity-100" : "scale-95 opacity-0", className), onClick: e => e.stopPropagation(), role: "dialog", "aria-modal": "true", "aria-label": "Command search", ...props, children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-3 glass-p-4 glass-border-b glass-border-subtle", children: [jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: 'w-5 h-5 glass-text-secondary glass-flex-shrink-0', children: jsx("path", { d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), jsx("input", { ref: inputRef, type: "text", value: query, onChange: e => { setQuery(e.target.value); setSelectedIndex(0); }, placeholder: placeholder, className: 'glass-flex-1 bg-transparent glass-text-base glass-text-primary outline-none placeholder:glass-text-secondary glass-focus glass-touch-target glass-contrast-guard' }), jsx("kbd", { className: "glass-px-2 glass-py-1 glass-text-xs glass-text-secondary glass-surface-subtle/10 glass-radius-sm", children: "ESC" })] }), jsx("div", { ref: resultsRef, className: 'max-h-96 overflow-y-auto glass-p-2', children: filteredActions.length === 0 ? jsx("div", { className: 'glass-p-8 text-center glass-text-secondary', children: query ? "No results found" : "No actions available" }) : jsxs(Fragment, { children: [!query && showRecent && recentActions.length > 0 && jsx("div", { className: 'glass-px-3 glass-py-2 glass-text-xs font-semibold glass-text-secondary uppercase tracking-wider', children: "Recent" }), Object.entries(groupedActions).map(([category, categoryActions]) => jsxs("div", { children: [groupByCategory && category && jsx("div", { className: 'glass-px-3 glass-py-2 glass-text-xs font-semibold glass-text-secondary uppercase tracking-wider', children: category }), categoryActions.map((action, index) => { const globalIndex = filteredActions.indexOf(action); const isSelected = globalIndex === selectedIndex; return jsxs("button", { "data-glass-component": true, type: "button", onClick: () => handleActionSelect(action), onMouseEnter: () => setSelectedIndex(globalIndex), className: cn("w-full flex items-center gap-3 glass-p-3 glass-radius-lg", "transition-all duration-200", "text-left glass-focus glass-touch-target glass-contrast-guard", isSelected ? "bg-white/15 shadow-lg" : "hover:bg-white/5"), children: [action.icon && jsx("div", { className: "glass-flex-shrink-0 glass-text-primary", children: action.icon }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsx("div", { className: 'glass-text-sm font-medium glass-text-primary truncate', children: action.title }), action.description && jsx("div", { className: 'glass-text-xs glass-text-secondary truncate', children: action.description })] }), action.shortcut && jsx("kbd", { className: "glass-px-2 glass-py-1 glass-text-xs glass-text-secondary glass-surface-subtle/10 glass-radius-sm glass-flex-shrink-0", children: action.shortcut })] }, action.id); })] }, category))] }) })] }) }); return /*#__PURE__*/createPortal(content, document.body); }); GlassSpotlightSearch.displayName = "GlassSpotlightSearch"; export { GlassSpotlightSearch, GlassSpotlightSearch as default }; //# sourceMappingURL=GlassSpotlightSearch.js.map