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