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