UNPKG

aura-glass

Version:

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

720 lines (717 loc) 26.6 kB
'use client'; import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; import { GlassButton } from '../button/GlassButton.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { X } from 'lucide-react'; import React, { forwardRef, useState, useRef, useEffect, useCallback } 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'; import { LiquidGlassMaterial } from '../../primitives/LiquidGlassMaterial.js'; import { useAchievements } from '../advanced/GlassAchievementSystem.js'; import { useBiometricAdaptation } from '../advanced/GlassBiometricAdaptation.js'; import { useEyeTracking } from '../advanced/GlassEyeTracking.js'; import { usePredictiveEngine, useInteractionRecorder } from '../advanced/GlassPredictiveEngine.js'; import { useSpatialAudio } from '../advanced/GlassSpatialAudio.js'; /** * GlassDrawer component * Slide-out panel with glassmorphism styling and comprehensive functionality */ const GlassDrawer = /*#__PURE__*/forwardRef(({ open = true, onOpenChange, position = "right", size = "md", material = "glass", materialProps, title, description, children = null, closeOnBackdropClick = true, closeOnEscape = true, showCloseButton = true, header, footer, modal = true, backdropBlur = true, elevation = "modal", zIndex = 50, showOverlay = true, animationDuration = 300, resizable = false, className, contentClassName, // Consciousness features consciousness = false, predictive = false, adaptive = false, eyeTracking = false, spatialAudio = false, trackAchievements = false, ...props }, ref) => { const [isVisible, setIsVisible] = useState(open); const contentRef = useRef(null); // Consciousness state const [interactionCount, setInteractionCount] = useState(0); const [drawerFocusTime, setDrawerFocusTime] = useState(0); const [contentEngagement, setContentEngagement] = useState({ scrollDepth: 0, timeSpent: 0, interactions: 0 }); const [drawerInsights, setDrawerInsights] = useState(null); // Consciousness hooks const predictiveEngine = predictive ? usePredictiveEngine() : null; const eyeTracker = eyeTracking ? useEyeTracking() : null; const biometricAdapter = adaptive ? useBiometricAdaptation() : null; const spatialAudioEngine = spatialAudio ? useSpatialAudio() : null; const interactionRecorder = consciousness ? useInteractionRecorder() : null; const achievementTracker = trackAchievements ? useAchievements() : null; // Handle escape key with consciousness tracking useEffect(() => { if (!closeOnEscape || !open) return; const handleEscape = event => { if (event.key === "Escape") { // Record escape key close if (consciousness && interactionRecorder) { // Create synthetic event for escape interaction const syntheticEvent = { currentTarget: { id: "drawer-escape" }, type: "keydown" }; interactionRecorder.recordFocus(syntheticEvent); } // Play spatial audio for escape close if (spatialAudio && spatialAudioEngine) { const audioPosition = position === "left" ? { x: -1, y: 0, z: 0 } : position === "right" ? { x: 1, y: 0, z: 0 } : { x: 0, y: 0, z: 0 }; spatialAudioEngine.playGlassSound("drawer-escape-close", audioPosition, { volume: 0.4 }); } onOpenChange?.(false); } }; document.addEventListener("keydown", handleEscape); return () => document.removeEventListener("keydown", handleEscape); }, [closeOnEscape, open, onOpenChange, consciousness, interactionRecorder, title, position, spatialAudio, spatialAudioEngine]); // Handle body scroll lock useEffect(() => { if (modal && open) { document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = ""; }; } }, [modal, open]); // Handle visibility state useEffect(() => { if (open) { setIsVisible(true); } else { // Delay hiding to allow exit animation const timer = setTimeout(() => setIsVisible(false), animationDuration); return () => clearTimeout(timer); } }, [open, animationDuration]); // Consciousness effects // Drawer opening/closing tracking with spatial audio useEffect(() => { if (open) { const openTime = Date.now(); setDrawerFocusTime(openTime); setInteractionCount(prev => prev + 1); // Record drawer opening interaction if (consciousness && interactionRecorder) { // Create synthetic event for drawer open const syntheticEvent = { currentTarget: { id: "drawer-open" }, type: "click" }; interactionRecorder.recordClick(syntheticEvent); } // Track achievement for drawer interaction if (trackAchievements && achievementTracker) { achievementTracker.recordAction("drawer_opened", { drawerPosition: position, drawerSize: size, title: title || "Drawer", timestamp: openTime }); } // Play spatial audio for drawer opening if (spatialAudio && spatialAudioEngine) { const audioConfig = { sound: `drawer-${position}-open`, volume: 0.6, position: position === "left" ? { x: -1, y: 0, z: 0 } : position === "right" ? { x: 1, y: 0, z: 0 } : { x: 0, y: 0, z: 0 }, reverb: 0.3 }; spatialAudioEngine.playGlassSound(audioConfig.sound, audioConfig.position, { volume: audioConfig.volume }); } } else if (drawerFocusTime > 0) { const closeTime = Date.now(); const timeSpent = closeTime - drawerFocusTime; // Record drawer closing interaction if (consciousness && interactionRecorder) { // Create synthetic event for drawer close const syntheticEvent = { currentTarget: { id: "drawer-close" }, type: "click" }; interactionRecorder.recordClick(syntheticEvent); } // Update content engagement setContentEngagement(prev => ({ ...prev, timeSpent })); // Play spatial audio for drawer closing if (spatialAudio && spatialAudioEngine) { const audioConfig = { sound: `drawer-${position}-close`, volume: 0.4, position: position === "left" ? { x: -1, y: 0, z: 0 } : position === "right" ? { x: 1, y: 0, z: 0 } : { x: 0, y: 0, z: 0 }, reverb: 0.2 }; spatialAudioEngine.playGlassSound(audioConfig.sound, audioConfig.position, { volume: audioConfig.volume }); } } }, [open, consciousness, interactionRecorder, trackAchievements, achievementTracker, spatialAudio, spatialAudioEngine, title, position, size, modal, drawerFocusTime, contentEngagement]); // Eye tracking for drawer engagement useEffect(() => { if (!eyeTracking || !eyeTracker || !open) return; // Eye tracking methods not available in current implementation // eyeTracker.startTracking(handleGazeData); // return () => eyeTracker.stopTracking(); }, [eyeTracking, eyeTracker, open, trackAchievements, achievementTracker, title, position]); // Biometric adaptation for drawer behavior useEffect(() => { if (!adaptive || !biometricAdapter || !open) return; const updateAdaptiveFeatures = () => { const stressLevel = biometricAdapter.currentStressLevel; // Analyze drawer content complexity and user stress setDrawerInsights({ urgency: stressLevel > 0.7 ? "high" : stressLevel > 0.4 ? "medium" : "low", complexity: (children?.toString().length || 0) > 500 ? 0.8 : 0.4, userStress: stressLevel }); // Record biometric adaptation for achievements if (trackAchievements && achievementTracker) { const stressLevel = biometricAdapter.currentStressLevel; achievementTracker.recordAction("drawer_biometric_adaptation", { stressLevel: stressLevel, drawerComplexity: (children?.toString().length || 0) > 500 ? "high" : "low", position, adaptations: { urgencyLevel: stressLevel > 0.7 ? "high" : "normal" } }); } }; const interval = setInterval(updateAdaptiveFeatures, 3000); updateAdaptiveFeatures(); // Run immediately return () => clearInterval(interval); }, [adaptive, biometricAdapter, open, children, trackAchievements, achievementTracker, position]); // Predictive drawer insights useEffect(() => { if (!predictive || !predictiveEngine || !open) return; const generateDrawerInsights = async () => { try { const drawerContext = { title: title || "Drawer", position, size, modal, hasFooter: !!footer, contentLength: children?.toString().length || 0, interactionCount, timeSpent: drawerFocusTime ? Date.now() - drawerFocusTime : 0 }; const insights = predictiveEngine.insights; // Transform insights array to expected object format const transformedInsights = insights && insights.length > 0 ? { urgency: insights.find(i => i.type === "urgency" && i.metadata)?.metadata?.urgency || "medium", complexity: insights.find(i => i.type === "complexity" && i.metadata)?.metadata?.complexity || 0, userStress: insights.find(i => i.type === "stress" && i.metadata)?.metadata?.userStress || 0 } : null; setDrawerInsights(prevInsights => { if (transformedInsights) { return transformedInsights; } return prevInsights; }); } catch (error) { console.warn("Predictive drawer analysis failed:", error); } }; const timeoutId = setTimeout(generateDrawerInsights, 1000); return () => clearTimeout(timeoutId); }, [predictive, predictiveEngine, open, title, position, size, modal, footer, children, interactionCount, drawerFocusTime]); // Enhanced handlers with consciousness tracking const handleBackdropClick = useCallback(event => { if (closeOnBackdropClick && event.target === event.currentTarget) { // Record backdrop click interaction if (consciousness && interactionRecorder) { // Create synthetic event for backdrop click const syntheticEvent = { currentTarget: { id: "drawer-backdrop" }, type: "click" }; interactionRecorder.recordClick(syntheticEvent); } // Play spatial audio for backdrop close if (spatialAudio && spatialAudioEngine) { const audioPosition = position === "left" ? { x: -1, y: 0, z: 0 } : position === "right" ? { x: 1, y: 0, z: 0 } : { x: 0, y: 0, z: 0 }; spatialAudioEngine.playGlassSound("drawer-backdrop-close", audioPosition, { volume: 0.3 }); } onOpenChange?.(false); } }, [closeOnBackdropClick, onOpenChange, consciousness, interactionRecorder, title, position, spatialAudio, spatialAudioEngine]); // Handle close const handleClose = useCallback(() => { // Record close button interaction if (consciousness && interactionRecorder) { // Create synthetic event for close button const syntheticEvent = { currentTarget: { id: "drawer-close-button" }, type: "click" }; interactionRecorder.recordClick(syntheticEvent); } // Play spatial audio for button close if (spatialAudio && spatialAudioEngine) { const audioPosition = position === "left" ? { x: -1, y: 0, z: 0 } : position === "right" ? { x: 1, y: 0, z: 0 } : { x: 0, y: 0, z: 0 }; spatialAudioEngine.playGlassSound("drawer-button-close", audioPosition, { volume: 0.3 }); } onOpenChange?.(false); }, [onOpenChange, consciousness, interactionRecorder, title, position, spatialAudio, spatialAudioEngine]); // Size classes based on position const getSizeClasses = () => { const isVertical = position === "top" || position === "bottom"; if (isVertical) { // Height for top/bottom drawers switch (size) { case "xs": return "h-32"; case "sm": return "h-48"; case "md": return "h-64"; case "lg": return "h-80"; case "xl": return "h-96"; case "full": return "h-full"; default: return "h-64"; } } else { // Width for left/right drawers switch (size) { case "xs": return "w-64"; case "sm": return "w-80"; case "md": return "w-96"; case "lg": return "w-[28rem]"; case "xl": return "w-[32rem]"; case "full": return "w-full"; default: return "w-96"; } } }; // Position classes const getPositionClasses = () => { switch (position) { case "top": return "top-0 left-0 right-0"; case "right": return "top-0 right-0 bottom-0"; case "bottom": return "bottom-0 left-0 right-0"; case "left": return "top-0 left-0 bottom-0"; default: return "top-0 right-0 bottom-0"; } }; // Animation direction based on position const getAnimationPreset = () => { switch (position) { case "top": return "slideDown"; case "right": return "slideLeft"; case "bottom": return "slideUp"; case "left": return "slideRight"; default: return "slideLeft"; } }; // Border radius based on position const getBorderRadius = () => { switch (position) { case "top": return "rounded-b-xl"; case "right": return "rounded-l-xl"; case "bottom": return "rounded-t-xl"; case "left": return "rounded-r-xl"; default: return "rounded-l-xl"; } }; if (!isVisible) return null; return jsxs("div", { "data-glass-component": true, className: cn("fixed inset-0", `z-${zIndex}`, consciousness && "consciousness-drawer-container", adaptive && drawerInsights?.urgency === "high" && "consciousness-urgent-drawer", eyeTracking && "consciousness-eye-trackable"), role: "dialog", "aria-modal": modal, "aria-labelledby": title ? "drawer-title" : undefined, "aria-describedby": description ? "drawer-description" : undefined, "data-consciousness-drawer": "true", "data-consciousness-active": String(!!consciousness), "data-drawer-title": title, "data-drawer-position": position, "data-drawer-size": size, "data-drawer-urgency": drawerInsights?.urgency, "data-user-stress": drawerInsights?.userStress, "data-interaction-count": interactionCount, children: [showOverlay && jsx(MotionFramer, { preset: "fadeIn", duration: animationDuration, className: cn("absolute inset-0 bg-black/20 cursor-pointer", backdropBlur && "glass-backdrop-blur-md", consciousness && "consciousness-drawer-backdrop", adaptive && drawerInsights?.urgency === "high" && "consciousness-urgent-backdrop"), onClick: handleBackdropClick, "data-consciousness-backdrop": "true", "data-drawer-backdrop-position": position }), jsx(MotionFramer, { preset: getAnimationPreset(), duration: animationDuration, className: cn("absolute flex flex-col", getPositionClasses(), getSizeClasses(), consciousness && "consciousness-drawer-content", predictive && drawerInsights && "consciousness-predictive-drawer", adaptive && drawerInsights?.urgency === "high" && "consciousness-urgent-content"), "data-consciousness-content": "true", "data-drawer-complexity": drawerInsights?.complexity, "data-time-spent": drawerFocusTime ? Date.now() - drawerFocusTime : 0, children: material === "liquid" ? jsxs(LiquidGlassMaterial, { ior: materialProps?.ior || 1.45, thickness: materialProps?.thickness || 8, tint: materialProps?.tint || { r: 0, g: 0, b: 0, a: 0.06 }, variant: materialProps?.variant || "regular", quality: materialProps?.quality || "balanced", environmentAdaptation: true, motionResponsive: true, ref: ref, className: cn("h-full flex flex-col liquid-glass-drawer-surface", getBorderRadius(), resizable && "resize overflow-auto", consciousness && "consciousness-drawer-glass", eyeTracking && "consciousness-eye-trackable-content", adaptive && drawerInsights?.urgency === "high" && "consciousness-urgent-glass", predictive && drawerInsights && "consciousness-predictive-glass", className), style: { "--liquid-glass-drawer-density": position === "top" || position === "bottom" ? "0.85" : "0.9", "--liquid-glass-motion-factor": "0.6", "--liquid-glass-adaptive-tint": drawerInsights?.urgency === "high" ? "rgba(220, 38, 38, 0.1)" : "rgba(0, 0, 0, 0.06)" }, "data-liquid-glass-drawer": "true", "data-drawer-position": position, "data-drawer-urgency": drawerInsights?.urgency, ...props, children: [(header || title || description || showCloseButton) && jsxs("div", { className: "glass-flex glass-items-start glass-justify-between glass-p-6 glass-border-b glass-border-glass-border/10 glass-flex-shrink-0", children: [jsx("div", { className: "glass-flex-1 glass-min-w-0", children: header || jsxs(Fragment, { children: [title && jsx("h2", { id: "drawer-title", className: 'glass-text-lg font-semibold text-primary mb-1', children: title }), description && jsx("p", { id: "drawer-description", className: 'glass-text-sm text-muted-foreground', children: description })] }) }), showCloseButton && jsx(GlassButton, { variant: "ghost", size: "sm", iconOnly: true, onClick: handleClose, "aria-label": "Close drawer", className: "glass-flex-shrink-0 glass-ml-4", children: "\u00D7" })] }), jsx("div", { className: cn("flex-1 overflow-y-auto glass-p-6", contentClassName), children: children }), footer && jsx("div", { className: "glass-p-6 glass-border-t glass-border-glass-border/10 glass-flex-shrink-0", children: footer })] }) : jsxs(OptimizedGlassCore, { intent: "neutral", elevation: "level2", intensity: "medium", depth: 2, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", ref: ref, liftOnHover: true, hoverSheen: true, className: cn("h-full flex flex-col border border-border/20 glass-radial-reveal", getBorderRadius(), resizable && "resize overflow-auto", consciousness && "consciousness-drawer-glass", eyeTracking && "consciousness-eye-trackable-content", adaptive && drawerInsights?.urgency === "high" && "consciousness-urgent-glass", predictive && drawerInsights && "consciousness-predictive-glass", className), ...props, children: [(header || title || description || showCloseButton) && jsxs("div", { className: "glass-flex glass-items-start glass-justify-between glass-p-6 glass-border-b glass-border-glass-border/10 glass-flex-shrink-0", children: [jsx("div", { className: "glass-flex-1 glass-min-w-0", children: header || jsxs(Fragment, { children: [title && jsx("h2", { id: "drawer-title", className: 'glass-text-lg font-semibold text-primary mb-1', children: title }), description && jsx("p", { id: "drawer-description", className: "glass-text-sm glass-text-secondary", children: description })] }) }), showCloseButton && jsx(GlassButton, { type: "button", className: cn("glass-ml-4 glass-p-2 glass-radius-lg glass-text-secondary hover:text-foreground hover:bg-muted/20 transition-colors", consciousness && "consciousness-close-button", adaptive && drawerInsights?.urgency === "high" && "consciousness-urgent-close"), onClick: handleClose, "aria-label": "Close drawer", "data-consciousness-close": "true", children: jsx(X, { className: 'w-4 h-4' }) })] }), children && jsxs("div", { ref: contentRef, className: cn("flex-1 overflow-y-auto glass-p-6", consciousness && "consciousness-drawer-body", eyeTracking && "consciousness-eye-trackable-body", adaptive && drawerInsights?.urgency === "high" && "consciousness-urgent-body"), tabIndex: 0, "data-consciousness-body": "true", "data-content-complexity": drawerInsights?.complexity, "data-scroll-tracking": String(!!consciousness), onScroll: e => { if (consciousness && interactionRecorder) { const scrollElement = e.target; const scrollDepth = scrollElement.scrollTop / (scrollElement.scrollHeight - scrollElement.clientHeight) || 0; setContentEngagement(prev => ({ ...prev, scrollDepth: Math.max(prev.scrollDepth, scrollDepth), interactions: prev.interactions + 1 })); // Create synthetic event for scroll const syntheticEvent = { currentTarget: { id: "drawer-content" }, type: "scroll" }; interactionRecorder.recordFocus(syntheticEvent); } }, onClick: e => { if (consciousness && interactionRecorder) { setContentEngagement(prev => ({ ...prev, interactions: prev.interactions + 1 })); interactionRecorder.recordClick(e); } }, children: [children, predictive && drawerInsights && jsx("div", { className: "glass-mt-4 glass-p-3 glass-surface-primary/10 glass-radius-lg glass-border glass-border-primary/20 glass-text-xs", children: jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsx("span", { className: 'text-primary', children: "Drawer Insights" }), jsxs("div", { className: "glass-flex glass-gap-2", children: [jsxs("span", { className: cn("glass-px-2 glass-py-1 glass-radius-md", drawerInsights.urgency === "high" ? "bg-red-500/20 text-red-300" : drawerInsights.urgency === "medium" ? "bg-yellow-500/20 text-yellow-300" : "bg-green-500/20 text-green-300"), children: [drawerInsights.urgency, " urgency"] }), drawerInsights.userStress > 0.7 && jsx("span", { className: "glass-px-2 glass-py-1 glass-radius-md glass-surface-primary/20 glass-text-secondary", children: "high stress" }), jsxs("span", { className: "glass-px-2 glass-py-1 glass-radius-md glass-surface-blue/20 glass-text-secondary", children: [position, " drawer"] })] })] }) })] }), footer && jsx("div", { className: "glass-p-6 glass-border-t glass-border-glass-border/10 glass-flex-shrink-0", children: footer })] }) })] }); }); GlassDrawer.displayName = "GlassDrawer"; const DrawerTrigger = /*#__PURE__*/forwardRef(({ children, asChild = false, ...props }, ref) => { if (asChild) { return /*#__PURE__*/React.cloneElement(children, { ref, ...props }); } return jsx(GlassButton, { ref: ref, ...props, children: children }); }); DrawerTrigger.displayName = "DrawerTrigger"; const DrawerHeader = /*#__PURE__*/forwardRef(({ children, className, ...props }, ref) => { return jsx("div", { ref: ref, className: cn("flex flex-col glass-gap-1.5", className), ...props, children: children }); }); DrawerHeader.displayName = "DrawerHeader"; const DrawerTitle = /*#__PURE__*/forwardRef(({ children, className, ...props }, ref) => { return jsx("h2", { ref: ref, className: cn("glass-text-lg font-semibold leading-none tracking-tight", className), ...props, children: children }); }); DrawerTitle.displayName = "DrawerTitle"; const DrawerDescription = /*#__PURE__*/forwardRef(({ children, className, ...props }, ref) => { return jsx("p", { ref: ref, className: cn("glass-text-sm glass-text-secondary", className), ...props, children: children }); }); DrawerDescription.displayName = "DrawerDescription"; const DrawerFooter = /*#__PURE__*/forwardRef(({ children, className, ...props }, ref) => { return jsx("div", { ref: ref, className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:glass-gap-2", className), ...props, children: children }); }); DrawerFooter.displayName = "DrawerFooter"; export { DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, GlassDrawer }; //# sourceMappingURL=GlassDrawer.js.map