UNPKG

aura-glass

Version:

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

317 lines (314 loc) 11.8 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { GlassButton } from '../button/GlassButton.js'; import { cn } from '../../lib/utilsComprehensive.js'; import React, { forwardRef, useState, createContext, useContext } 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'; const TabsContext = /*#__PURE__*/createContext(null); const useTabsContext = () => { const context = useContext(TabsContext); if (!context) { throw new Error("Tabs components must be used within a GlassTabs component"); } return context; }; const GlassTabs = /*#__PURE__*/forwardRef(({ value, defaultValue, onValueChange, orientation = "horizontal", variant = "default", activationMode = "automatic", className, children, ...props }, ref) => { const [internalValue, setInternalValue] = useState(defaultValue || ""); const currentValue = value ?? internalValue; const listRef = React.useRef(null); const triggerMapRef = React.useRef(new Map()); const [ink, setInk] = React.useState({ left: 0, width: 0 }); const updateInk = React.useCallback(() => { if (!listRef.current) return; const active = triggerMapRef.current.get(currentValue); if (!active) return; const lr = listRef.current.getBoundingClientRect(); const ar = active.getBoundingClientRect(); setInk({ left: ar.left - lr.left, width: ar.width }); }, [currentValue]); const scheduleInkUpdate = React.useCallback(() => { if (typeof window === "undefined" || process.env.JEST_WORKER_ID) { updateInk(); return; } return window.requestAnimationFrame(updateInk); }, [updateInk]); const registerTrigger = (val, el) => { const map = triggerMapRef.current; if (el) map.set(val, el);else map.delete(val); // Update ink when triggers mount/unmount scheduleInkUpdate(); }; React.useEffect(() => { updateInk(); }, [updateInk]); React.useEffect(() => { const handle = () => { updateInk(); }; window.addEventListener("resize", handle); return () => window.removeEventListener("resize", handle); }, [updateInk]); const handleValueChange = newValue => { if (!value) { setInternalValue(newValue); } onValueChange?.(newValue); }; const contextValue = { value: currentValue, onValueChange: handleValueChange, orientation, variant, activationMode, registerTrigger, listRef, ink }; return jsx(TabsContext.Provider, { "data-glass-component": true, value: contextValue, children: jsx("div", { ref: ref, className: cn("glass-tabs", orientation === "vertical" ? "flex glass-gap-6" : "w-full", className), "data-orientation": orientation, ...props, children: children }) }); }); GlassTabs.displayName = "GlassTabs"; const GlassTabsList = /*#__PURE__*/forwardRef(({ className, loop = true, children, ...props }, ref) => { const { orientation, variant, listRef, ink } = useTabsContext(); const variantStyles = { default: cn("glass-tabs-list", "glass-radius-xl glass-p-1 glass-gap-2"), pills: cn("glass-tabs-list-pills", "rounded-2xl glass-p-1.5 glass-gap-1"), underline: cn("glass-tabs-list-underline", "border-b border-border/20", "gap-8 glass-px-1"), minimal: cn("glass-tabs-list-minimal", "glass-gap-4") }; const shouldUseGlass = variant === "default" || variant === "pills"; const commonA11y = { role: "tablist", "aria-orientation": orientation, onKeyDown: e => { const isHorizontal = orientation === "horizontal"; const prevKey = isHorizontal ? "ArrowLeft" : "ArrowUp"; const nextKey = isHorizontal ? "ArrowRight" : "ArrowDown"; if (e.key !== prevKey && e.key !== nextKey && e.key !== "Home" && e.key !== "End") return; const container = e.currentTarget; const tabs = Array.from(container.querySelectorAll('[role="tab"]')); if (tabs.length === 0) return; const current = document.activeElement; let index = Math.max(0, tabs.findIndex(t => t === current)); if (e.key === prevKey) index = index > 0 ? index - 1 : loop ? tabs.length - 1 : 0; if (e.key === nextKey) index = index < tabs.length - 1 ? index + 1 : loop ? 0 : tabs.length - 1; if (e.key === "Home") index = 0; if (e.key === "End") index = tabs.length - 1; const next = tabs[index]; next?.focus(); e.preventDefault(); } }; return shouldUseGlass ? jsxs(OptimizedGlassCore, { ref: node => { listRef.current = node; if (typeof ref === "function") ref(node);else if (ref) ref.current = node; }, variant: "ethereal", elevation: "level1", intensity: "subtle", depth: 1, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", className: cn("inline-flex items-center justify-start", orientation === "horizontal" ? "flex-row" : "flex-col", variantStyles[variant], variant === "pills" ? "rounded-2xl" : "glass-radius-xl", className), ...commonA11y, ...props, children: [variant === "underline" && orientation === "horizontal" && jsx("div", { className: 'absolute bottom-0 left-0 right-0 h-0-5', children: jsx("div", { className: 'absolute glass-h-full glass-surface-primary transition-all duration-200', style: { left: ink.left, width: ink.width } }) }), children] }) : jsxs("div", { ref: node => { listRef.current = node; if (typeof ref === "function") ref(node);else if (ref) ref.current = node; }, className: cn("inline-flex items-center justify-start", orientation === "horizontal" ? "flex-row" : "flex-col", variantStyles[variant], className), ...commonA11y, ...props, children: [jsx("div", { className: 'relative glass-w-full' }), children] }); }); GlassTabsList.displayName = "GlassTabsList"; const GlassTabsTrigger = /*#__PURE__*/forwardRef(({ value, icon, badge, disabled = false, className, children, onClick, ...props }, ref) => { const { value: selectedValue, onValueChange, variant, activationMode, registerTrigger } = useTabsContext(); const isSelected = selectedValue === value; const handleClick = event => { if (!disabled) { onValueChange(value); onClick?.(event); } }; const handleKeyDown = event => { const tabButton = event.currentTarget; const tablist = tabButton.closest('[role="tablist"]'); if (!tablist) return; const isHorizontal = tablist.getAttribute("aria-orientation") !== "vertical"; const prevKey = isHorizontal ? "ArrowLeft" : "ArrowUp"; const nextKey = isHorizontal ? "ArrowRight" : "ArrowDown"; const tabs = Array.from(tablist.querySelectorAll('[role="tab"]')); const index = Math.max(0, tabs.findIndex(t => t === tabButton)); let targetIndex = index; if (event.key === prevKey) targetIndex = index > 0 ? index - 1 : tabs.length - 1; if (event.key === nextKey) targetIndex = index < tabs.length - 1 ? index + 1 : 0; if (event.key === "Home") targetIndex = 0; if (event.key === "End") targetIndex = tabs.length - 1; if (targetIndex !== index) { const next = tabs[targetIndex]; next?.focus(); if (activationMode === "automatic") { onValueChange(next?.getAttribute("data-value") || ""); } event.preventDefault(); } if ((event.key === "Enter" || event.key === " ") && !disabled) { onValueChange(value); event.preventDefault(); } }; const baseStyles = cn("inline-flex items-center justify-center glass-gap-2", "whitespace-nowrap glass-radius-lg glass-px-3 glass-py-2", "glass-text-sm font-medium transition-all duration-200", "focus-visible:outline-none focus-visible:ring-2", "focus-visible:ring-primary focus-visible:ring-offset-2", "disabled:pointer-events-none disabled:opacity-50"); const variantStyles = { default: cn(isSelected ? "bg-background/90 text-foreground shadow-md border border-border/20" : "glass-text-secondary hover:text-foreground hover:bg-muted/50"), pills: cn(isSelected ? "bg-primary text-primary-foreground shadow-lg" : "glass-text-secondary hover:text-foreground hover:bg-background/50"), underline: cn("relative glass-px-1 glass-py-3 rounded-none", isSelected ? "text-primary after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-primary" : "glass-text-secondary hover:text-foreground"), minimal: cn("glass-px-2 glass-py-1 glass-radius-md", isSelected ? "text-primary bg-primary/10" : "glass-text-secondary hover:text-foreground hover:bg-muted/30") }; // Convert Booleanish ARIA attributes to boolean const buttonProps = { ...props, "aria-pressed": props["aria-pressed"] === "true" ? true : props["aria-pressed"] === "false" ? false : props["aria-pressed"] === "mixed" ? undefined : props["aria-pressed"], "aria-expanded": props["aria-expanded"] === "true" ? true : props["aria-expanded"] === "false" ? false : props["aria-expanded"] }; return jsx(MotionFramer, { preset: "scaleIn", className: 'relative', children: jsxs(GlassButton, { ref: node => { if (typeof ref === "function") ref(node);else if (ref) ref.current = node; registerTrigger(value, node); }, role: "tab", id: `trigger-${value}`, "aria-selected": !!isSelected, "aria-controls": `content-${value}`, "data-value": value, "data-state": isSelected ? "active" : "inactive", className: cn(baseStyles, variantStyles[variant], className), disabled: disabled, onClick: handleClick, onKeyDown: handleKeyDown, ...buttonProps, children: [icon && jsx("span", { className: 'shrink-0', children: icon }), children && jsx("span", { className: 'truncate', children: children }), badge && jsx("span", { className: cn("glass-ml-2 glass-radius-full glass-px-2 glass-py-0.5 glass-text-xs", isSelected ? "bg-primary-foreground/20 text-primary-foreground" : "bg-background/50 glass-text-secondary"), children: badge }), variant === "underline" && isSelected && jsx(MotionFramer, { preset: "slideUp", className: 'absolute bottom-0 left-0 right-0 h-0-5 glass-surface-primary' })] }) }); }); GlassTabsTrigger.displayName = "GlassTabsTrigger"; const GlassTabsContent = /*#__PURE__*/forwardRef(({ value, forceMount = false, className, children, ...props }, ref) => { const { value: selectedValue } = useTabsContext(); const isSelected = selectedValue === value; if (!isSelected && !forceMount) { return null; } return jsx(MotionFramer, { preset: "fadeIn", className: cn("glass-tabs-content", "mt-6 focus-visible:outline-none", "focus-visible:ring-2 focus-visible:ring-primary", "focus-visible:ring-offset-2", !isSelected && "hidden", className), children: jsx("div", { ref: ref, role: "tabpanel", "aria-labelledby": `trigger-${value}`, id: `content-${value}`, tabIndex: 0, "data-state": isSelected ? "active" : "inactive", ...props, children: children }) }); }); GlassTabsContent.displayName = "GlassTabsContent"; export { GlassTabs, GlassTabsContent, GlassTabsList, GlassTabsTrigger, GlassTabs as Tabs, GlassTabsContent as TabsContent, GlassTabsList as TabsList, GlassTabsTrigger as TabsTrigger }; //# sourceMappingURL=GlassTabs.js.map