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