UNPKG

aura-glass

Version:

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

274 lines (271 loc) 9.73 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { GlassButton } from '../button/GlassButton.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { ChevronRight } from 'lucide-react'; import React, { useState, useRef, useEffect } 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 { MotionFramer } from '../../primitives/motion/MotionFramer.js'; /** * GlassMenubar component * A glassmorphism menubar with dropdown menus */ const GlassMenubar = ({ items, orientation = "horizontal", size = "md", className, disabled = false, "aria-label": ariaLabel = "Menu bar" }) => { const [openMenus, setOpenMenus] = useState(new Set()); const [hoveredItem, setHoveredItem] = useState(null); const handleItemClick = item => { if (item?.disabled) return; if (item?.children && item?.children.length > 0) { // Toggle submenu setOpenMenus(prev => { const newSet = new Set(prev); if (newSet.has(item?.id)) { newSet.delete(item?.id); } else { // Close other menus newSet.clear(); newSet.add(item?.id); } return newSet; }); } else { // Execute action and close all menus item?.action?.(); setOpenMenus(new Set()); } }; return jsx(OptimizedGlassCore, { "data-glass-component": true, intent: "neutral", elevation: "level2", intensity: "medium", depth: 2, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", className: cn("relative glass-backdrop-blur-md ring-1 ring-white/10 bg-white/5", orientation === "horizontal" ? "flex flex-row" : "flex flex-col", disabled && "opacity-50 pointer-events-none", className), role: "menubar", "aria-label": ariaLabel, "aria-orientation": orientation, "aria-disabled": disabled, children: items.map((item, index) => jsxs(React.Fragment, { children: [item?.separator && jsx("div", { className: cn("bg-white/20", orientation === "horizontal" ? "w-px h-6 glass-mx-2" : "h-px w-6 glass-my-2") }), jsx(GlassMenubarItem, { item: item, isHovered: hoveredItem === item?.id, hasSubmenuOpen: openMenus.has(item?.id), onClick: handleItemClick, onOpenSubmenu: item => setOpenMenus(prev => new Set([...prev, item?.id])), onCloseSubmenu: () => setOpenMenus(new Set()), size: size, "aria-haspopup": item?.children && item?.children.length > 0 ? "menu" : undefined, "aria-expanded": openMenus.has(item?.id) ? true : undefined }), item?.children && openMenus.has(item?.id) && jsx(GlassMenubarContent, { isOpen: true, onClose: () => setOpenMenus(new Set()), className: cn("absolute z-[9999]", orientation === "horizontal" ? "top-full left-0 glass-mt-1" : "top-0 left-full glass-ml-1"), children: jsx(GlassMenubar, { items: item?.children, orientation: "vertical", size: size, disabled: disabled }) })] }, item?.id)) }); }; /** * GlassMenubarContent component * Container for menubar dropdown content */ const GlassMenubarContent = ({ children, position, isOpen, onClose, className }) => { const contentRef = useRef(null); // Handle outside click useEffect(() => { const handleClickOutside = e => { if (contentRef.current && !contentRef.current.contains(e.target) && isOpen) { onClose(); } }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => document.removeEventListener("mousedown", handleClickOutside); }, [isOpen, onClose]); // Handle escape key useEffect(() => { const handleEscape = e => { if (e.key === "Escape" && isOpen) { onClose(); } }; if (isOpen) { document.addEventListener("keydown", handleEscape); } return () => document.removeEventListener("keydown", handleEscape); }, [isOpen, onClose]); if (!isOpen) return null; return jsx(MotionFramer, { preset: "scaleIn", duration: 150, style: position ? { left: position.x, top: position.y } : undefined, children: jsx(OptimizedGlassCore, { intent: "neutral", elevation: "level3", intensity: "strong", depth: 2, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", ref: contentRef, className: cn("glass-backdrop-blur-md bg-black/20 border border-white/20 shadow-2xl", "min-w-48 glass-py-1", className), children: children }) }); }; /** * GlassMenubarItem component * Individual menubar item */ const GlassMenubarItem = ({ item, isHovered = false, hasSubmenuOpen = false, onClick, onOpenSubmenu, onCloseSubmenu, size = "md", className, "aria-haspopup": ariaHasPopup, "aria-expanded": ariaExpanded }) => { const sizeClasses = { sm: "h-8 glass-px-3 glass-text-sm", md: "h-10 glass-px-4 glass-text-base", lg: "h-12 glass-px-6 glass-text-lg" }; const handleClick = () => { onClick(item); }; const handleMouseEnter = () => { if (item?.children && item?.children.length > 0) { onOpenSubmenu(item); } }; const handleMouseLeave = () => { onCloseSubmenu(); }; if (item?.separator) { return jsx("div", { className: 'h-px glass-surface-subtle/20 glass-mx-2 glass-my-1', role: "separator" }); } const handleKeyDown = e => { const current = e.currentTarget; const menubar = current.closest('[role="menubar"]'); if (!menubar) return; const items = Array.from(menubar.querySelectorAll('button?.[role="menuitem"]')); const index = items.indexOf(current); if (index === -1) return; if (e.key === "ArrowRight") { items[(index + 1) % (items?.length || 0)]?.focus(); e.preventDefault(); } else if (e.key === "ArrowLeft") { items[(index - 1 + (items?.length || 0)) % (items?.length || 0)]?.focus(); e.preventDefault(); } else if (e.key === "ArrowDown" && item?.children && item?.children.length > 0) { onOpenSubmenu(item); e.preventDefault(); } else if (e.key === "Escape") { onCloseSubmenu(); current.blur(); e.preventDefault(); } else if (e.key === "Home") { items[0]?.focus(); e.preventDefault(); } else if (e.key === "End") { items[items.length - 1]?.focus(); e.preventDefault(); } else if (e.key === "Enter" || e.key === " ") { handleClick(); e.preventDefault(); } }; return jsxs(GlassButton, { className: cn("relative flex items-center justify-between w-full", "glass-text-primary/80 hover:glass-text-primary transition-colors duration-200", "hover:bg-white/10 glass-radius-md hover:-translate-y-0.5", "after:absolute after:left-0 after:bottom-0 after:h-0.5 after:bg-primary after:transition-all after:duration-200", isHovered || hasSubmenuOpen ? "after:opacity-100 after:w-full" : "after:opacity-0 after:w-0", "focus:outline-none focus:ring-2 focus:ring-white/30 focus:ring-offset-2 focus:ring-offset-transparent", "disabled:opacity-50 disabled:cursor-not-allowed", sizeClasses?.[size], { "bg-white/20 glass-text-primary": isHovered || hasSubmenuOpen, "font-medium": item?.checked }, className), onClick: handleClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, disabled: item?.disabled, type: "button", role: "menuitem", "aria-haspopup": ariaHasPopup, "aria-expanded": ariaExpanded, "aria-disabled": item?.disabled, "aria-checked": item?.type === "checkbox" || item?.type === "radio" ? item?.checked : undefined, onKeyDown: handleKeyDown, children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-3", children: [item?.icon && jsx("div", { className: 'glass-flex glass-items-center glass-justify-center w-4 h-4', children: item?.icon }), item?.type === "checkbox" && jsx("div", { className: cn("w-4 h-4 border border-white/40 glass-radius-md", item?.checked && "bg-white border-white"), children: item?.checked && jsx("div", { className: "glass-w-full glass-h-full glass-flex glass-items-center glass-justify-center", children: jsx("div", { className: 'w-2 h-2 glass-surface-dark glass-radius-sm' }) }) }), item?.type === "radio" && jsx("div", { className: cn("w-4 h-4 border border-white/40 glass-radius-full", item?.checked && "border-white"), children: item?.checked && jsx("div", { className: "glass-w-full glass-h-full glass-flex glass-items-center glass-justify-center", children: jsx("div", { className: 'w-2 h-2 glass-surface-subtle glass-radius-full' }) }) }), jsx("span", { className: 'glass-flex-1 text-left truncate', children: item?.label })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [item?.shortcut && jsx("span", { className: 'text-primary/50 glass-text-xs font-mono', children: item?.shortcut }), item?.children && item?.children.length > 0 && jsx(ChevronRight, { className: 'w-4 h-4 text-primary/50' })] })] }); }; export { GlassMenubar, GlassMenubarContent, GlassMenubarItem }; //# sourceMappingURL=GlassMenubar.js.map