aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
447 lines (444 loc) • 16 kB
JavaScript
'use client';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { forwardRef, useState, useRef, useMemo, useEffect, useCallback } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
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 { useReducedMotion } from '../../hooks/useReducedMotion.js';
import { useAnimationContext } from '../../contexts/AnimationContext.js';
import { SpringPresets } from '../../animations/physics/springPhysics.js';
import { useA11yId, createNavigationA11y } from '../../utils/a11y.js';
import { useKeyboardNavigation } from '../../utils/a11yHooks.js';
// Helper components
const Badge = ({
children
}) => jsx("span", {
className: 'glass-inline-flex glass-items-center glass-justify-center min-w-[18px] h-[18px] glass-px-1.5 glass-text-xs font-semibold text-primary glass-surface-blue glass-radius-full',
children: children
});
const Icon = ({
name,
size = 24
}) => jsx("span", {
style: {
fontSize: size
},
"aria-hidden": "true",
children: "\uD83D\uDD39"
});
const Tooltip = ({
children,
title
}) => jsx("div", {
title: title,
children: children
});
// Simple animation hook for indicator position
const INITIAL_INDICATOR_STYLE = {
left: 0,
top: 0,
width: 0,
height: 0,
opacity: 0
};
const useIndicatorAnimation = _prefersReducedMotion => {
const [indicatorStyle, setIndicatorStyle] = useState(INITIAL_INDICATOR_STYLE);
const animateIndicator = useCallback(nextStyle => {
setIndicatorStyle(prev => {
if (prev.left === nextStyle.left && prev.top === nextStyle.top && prev.width === nextStyle.width && prev.height === nextStyle.height && prev.opacity === nextStyle.opacity) {
return prev;
}
return nextStyle;
});
}, []);
return {
indicatorStyle,
animateIndicator
};
};
// Navigation component with glass styling
// Remove styled components
/**
* A glass-styled navigation component with various layout options
*/
const GlassNavigation = /*#__PURE__*/forwardRef(({
items = [],
activeItem,
onItemClick,
onMenuToggle,
position = "top",
variant = "standard",
className,
style,
logo,
actions,
showDivider = false,
glassIntensity = 0.7,
sticky = false,
maxWidth,
compact = false,
centered = false,
zIndex = 100,
width,
initialExpandedItems = [],
collapsible = false,
initialCollapsed = false,
...rest
}, ref) => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [expandedItems, setExpandedItems] = useState(initialExpandedItems);
const [collapsed, setCollapsed] = useState(initialCollapsed);
const prefersReducedMotion = useReducedMotion();
const navItemsRef = useRef(null);
const itemRefs = useRef({});
const {
defaultSpring
} = useAnimationContext();
// Enhanced keyboard navigation with accessibility
const navigationItems = items.filter(item => !item.disabled).map(item => ({
id: item.id || item.key,
disabled: item.disabled
}));
const {
focusedIndex,
selectedIds,
handleKeyDown: handleNavKeyDown,
registerItem,
focusItem
} = useKeyboardNavigation({
items: navigationItems,
orientation: position === "left" || position === "right" ? "vertical" : "horizontal",
loop: true,
homeEndKeys: true,
onActivate: id => {
const item = items.find(item => (item.id || item.key) === id);
if (item) {
handleItemClick(id, item);
}
}
});
const navId = useA11yId("glass-navigation");
useMemo(() => {
const baseFallback = SpringPresets.default;
let resolvedConfig;
if (typeof defaultSpring === "object" && defaultSpring !== null) {
resolvedConfig = {
...baseFallback,
...defaultSpring
};
} else if (typeof defaultSpring === "string" && defaultSpring in SpringPresets) {
resolvedConfig = SpringPresets?.[defaultSpring];
} else {
resolvedConfig = baseFallback;
}
return resolvedConfig;
}, [defaultSpring]);
const disableChildAnimation = useMemo(() => {
return prefersReducedMotion;
}, [prefersReducedMotion]);
const {
indicatorStyle,
animateIndicator
} = useIndicatorAnimation();
useEffect(() => {
if (disableChildAnimation) {
animateIndicator(INITIAL_INDICATOR_STYLE);
return;
}
const activeElement = activeItem ? itemRefs.current?.[activeItem] : null;
const containerElement = navItemsRef.current;
if (activeElement && containerElement) {
const itemRect = activeElement.getBoundingClientRect();
const containerRect = containerElement.getBoundingClientRect();
let newStyle;
if (position === "left" || position === "right") {
newStyle = {
left: position === "left" ? 0 : containerRect.width - 3,
top: itemRect.top - containerRect.top,
width: 3,
height: itemRect.height,
opacity: 1
};
} else {
newStyle = {
left: itemRect.left - containerRect.left,
top: position === "top" ? containerRect.height - 3 : 0,
width: itemRect.width,
height: 3,
opacity: 1
};
}
animateIndicator(newStyle);
} else {
animateIndicator(INITIAL_INDICATOR_STYLE);
}
}, [activeItem, position, collapsed, disableChildAnimation, animateIndicator]);
const toggleMobileMenu = useCallback(() => {
const newState = !mobileMenuOpen;
setMobileMenuOpen(newState);
if (onMenuToggle) {
onMenuToggle(newState);
}
}, [mobileMenuOpen, onMenuToggle]);
const handleItemClick = useCallback((id, item) => {
if (item?.onClick) {
item?.onClick();
}
if (onItemClick) {
onItemClick(item);
}
if (item?.children && item?.children.length > 0) {
setExpandedItems(prev => prev.includes(id) ? prev.filter(itemId => itemId !== id) : [...prev, id]);
}
if (mobileMenuOpen) {
setMobileMenuOpen(false);
if (onMenuToggle) {
onMenuToggle(false);
}
}
}, [onItemClick, mobileMenuOpen, onMenuToggle]);
const toggleCollapsed = useCallback(() => {
setCollapsed(prev => !prev);
}, []);
// Navigation positioning and styling classes
const getPositionClasses = position => {
const base = "flex items-center box-border";
switch (position) {
case "left":
case "right":
return cn(base, "flex-col h-full");
default:
return cn(base, "flex-row w-full");
}
};
const getPositionStyles = position => {
switch (position) {
case "left":
return {
left: 0,
height: "100vh"
};
case "right":
return {
right: 0,
height: "100vh"
};
case "bottom":
return {
bottom: 0,
width: "100%"
};
case "top":
default:
return {
top: 0,
width: "100%"
};
}
};
const renderNavItem = useCallback((item, level = 0) => {
const isActive = Boolean(activeItem === item?.id || item?.active);
const hasChildren = item?.children && item?.children.length > 0;
const isExpanded = item?.id ? expandedItems.includes(item?.id) : false;
const assignRef = el => {
if (item?.id) {
if (itemRefs.current) itemRefs.current[item?.id] = el;
}
};
if (item?.customElement) {
return jsx("li", {
ref: assignRef,
className: cn("relative z-10", item?.className),
children: item?.customElement
}, item?.id || item?.key);
}
const content = jsxs(Fragment, {
children: [item?.icon && jsx("span", {
className: 'nav-item-icon',
"aria-hidden": "true",
children: item?.icon
}), (!collapsed || level > 0) && jsx("span", {
className: 'nav-item-label',
children: item?.label
}), item?.badge && jsx(Badge, {
children: String(item?.badge)
}), hasChildren && !collapsed && jsx("span", {
className: 'nav-item-expand-icon',
"aria-hidden": "true",
children: jsx(Icon, {
name: isExpanded ? "expand_less" : "expand_more"
})
})]
});
const itemClasses = cn("flex items-center glass-gap-2 text-decoration-none border-none bg-transparent cursor-pointer transition-all duration-200", "glass-radius-md border-0 outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50", "glass-focus glass-touch-target glass-contrast-guard", {
"glass-px-2 glass-py-1.5 glass-text-sm": variant === "minimal",
"glass-px-4 glass-py-2.5 glass-text-base": variant !== "minimal",
"text-blue-600 font-semibold bg-blue-50/50": isActive,
"glass-text-secondary font-normal hover:bg-white/10": !isActive,
"opacity-50 pointer-events-none": item?.disabled,
"active:scale-[0.98]": !item?.disabled
});
// Create accessibility attributes for navigation items
const itemA11yProps = createNavigationA11y({
id: item?.id || item?.key,
current: isActive ? "page" : undefined,
expanded: hasChildren ? isExpanded : undefined,
controls: hasChildren ? `${item?.id || item?.key}-submenu` : undefined,
posinset: level === 0 ? items.findIndex(i => i.id === item?.id) + 1 : undefined,
setsize: level === 0 ? items.length : undefined
});
const navItem = item?.href ? jsx("a", {
href: item?.href,
target: item?.external ? "_blank" : undefined,
rel: item?.external ? "noopener noreferrer" : undefined,
className: itemClasses,
onClick: e => {
if (item?.disabled) {
e.preventDefault();
return;
}
handleItemClick(item?.id || item?.key, item);
},
onKeyDown: handleNavKeyDown,
...itemA11yProps,
"aria-disabled": item?.disabled,
ref: el => {
registerItem(item?.id || item?.key, el);
assignRef(el);
},
children: content
}) : jsx("button", {
type: "button",
className: itemClasses,
onClick: e => handleItemClick(item?.id || item?.key, item),
onKeyDown: handleNavKeyDown,
disabled: item?.disabled,
...itemA11yProps,
ref: el => {
registerItem(item?.id || item?.key, el);
assignRef(el);
},
children: content
});
return jsxs("li", {
ref: assignRef,
className: cn("relative z-10", item?.className),
children: [item?.tooltip && !collapsed ? jsx(Tooltip, {
title: item?.tooltip,
children: navItem
}) : collapsed && level === 0 ? jsx(Tooltip, {
title: item?.label,
children: navItem
}) : navItem, hasChildren && jsx("ul", {
className: cn("list-none m-0 glass-p-2 glass-mt-1 glass-radius-md bg-white/5 overflow-hidden transition-all duration-300", {
"opacity-100 max-h-[500px] visible": isExpanded && !collapsed,
"opacity-0 max-h-0 invisible": !isExpanded || collapsed
}),
children: item?.children?.map(child => renderNavItem(child, level + 1))
})]
}, item?.id || item?.key);
}, [activeItem, expandedItems, collapsed, variant, handleItemClick]);
// Determine glass elevation based on variant
const glassElevation = variant === "minimal" ? "level1" : "level2";
const glassTier = variant === "minimal" ? "low" : "medium";
return jsx("nav", {
children: jsxs(OptimizedGlassCore, {
ref: ref,
intent: "neutral",
elevation: glassElevation,
tier: glassTier,
intensity: "medium",
depth: variant === "prominent" ? 3 : 2,
tint: "neutral",
border: "subtle",
animation: prefersReducedMotion ? "none" : "gentle",
performanceMode: "medium",
id: navId,
role: "navigation",
"aria-label": "Main navigation",
onKeyDown: handleNavKeyDown,
className: cn(
// Base navigation styles
getPositionClasses(position), {
"justify-center": centered,
"justify-between": !centered,
"glass-p-2 glass-px-4": compact,
"glass-p-3 glass-px-6": !compact,
"sticky top-0": sticky
},
// Position-specific styling
position === "left" || position === "right" ? "w-60" : "w-full", position === "left" || position === "right" ? "h-screen" : "h-auto",
// Mobile responsive
"md:glass-p-2 md:glass-px-4", className),
style: {
...getPositionStyles(position),
...style,
zIndex,
width: width ? typeof width === "number" ? `${width}px` : width : position === "left" || position === "right" ? "240px" : "100%",
maxWidth: maxWidth ? typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth : undefined
},
...rest,
children: [jsx("button", {
onClick: toggleMobileMenu,
className: cn("hidden md:hidden lg:hidden", "bg-transparent border-none cursor-pointer glass-p-2 text-inherit", "flex items-center justify-center", "glass-focus glass-touch-target glass-contrast-guard", "max-md:flex"),
"aria-label": mobileMenuOpen ? "Close menu" : "Open menu",
"aria-expanded": mobileMenuOpen,
children: jsx(Icon, {
name: mobileMenuOpen ? "close" : "menu"
})
}), logo && jsx("div", {
className: cn("flex items-center", {
"justify-center w-full glass-p-4": position === "left" || position === "right"
}),
children: logo
}), jsxs("ul", {
ref: navItemsRef,
className: cn("flex list-none m-0 glass-p-0 flex-1 items-center relative", {
"flex-col glass-gap-4": position === "left" || position === "right",
"flex-row glass-gap-4": position === "top" || position === "bottom",
"glass-gap-3": variant === "minimal",
"mt-6 w-full": position === "left" || position === "right",
"mobile-open": mobileMenuOpen
}),
role: "menubar",
"aria-orientation": position === "left" || position === "right" ? "vertical" : "horizontal",
children: [!prefersReducedMotion && jsx("div", {
className: 'absolute glass-surface-blue glass-radius-sm z-0 pointer-events-none transition-all duration-300 ease-out',
style: {
left: `${indicatorStyle.left}px`,
top: `${indicatorStyle.top}px`,
width: `${indicatorStyle.width}px`,
height: `${indicatorStyle.height}px`,
opacity: indicatorStyle.opacity
}
}), items.map(item => renderNavItem(item))]
}), showDivider && jsx("div", {
className: cn("bg-white/20", {
"w-4/5 h-px my-3 mx-auto": position === "left" || position === "right",
"w-px h-6 mx-3": position === "top" || position === "bottom"
})
}), actions && jsx("div", {
className: "glass-flex glass-items-center glass-gap-3",
children: actions
}), collapsible && (position === "left" || position === "right") && jsx("button", {
onClick: toggleCollapsed,
title: collapsed ? "Expand" : "Collapse",
"aria-label": collapsed ? "Expand navigation" : "Collapse navigation",
className: cn("absolute top-1/2 -translate-y-1/2 w-6 h-6 glass-radius-full", "flex items-center justify-center cursor-pointer", "bg-white/10 border border-white/20 transition-all duration-200", "glass-focus glass-touch-target glass-contrast-guard", "hover:bg-white/20", {
"-right-3": !collapsed,
"-left-3": collapsed
}),
children: jsx(Icon, {
name: position === "left" ? collapsed ? "chevron_right" : "chevron_left" : collapsed ? "chevron_left" : "chevron_right"
})
})]
})
});
});
GlassNavigation.displayName = "GlassNavigation";
export { GlassNavigation };
//# sourceMappingURL=GlassNavigation.js.map