aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
549 lines (546 loc) • 20.7 kB
JavaScript
'use client';
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
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 { GlassButton } from '../button/GlassButton.js';
import { trapFocus } from '../../utils/focus.js';
/**
* GlassDialog component
* Modal dialog with glassmorphism styling and comprehensive functionality
*/
const GlassDialog = /*#__PURE__*/forwardRef(({
open = false,
onOpenChange,
title,
description,
children,
size = "md",
variant = "default",
material = "glass",
materialProps,
closeOnBackdropClick = true,
closeOnEscape = true,
showCloseButton = true,
animation = "scale",
footer,
header,
modal = true,
backdropBlur = true,
elevation = "modal",
zIndex = 50,
className,
contentClassName,
// Consciousness features
consciousness,
predictive,
adaptive,
eyeTracking,
spatialAudio,
trackAchievements,
...props
}, ref) => {
const [isVisible, setIsVisible] = useState(open);
const dialogRef = useRef(null);
const previouslyFocusedRef = useRef(null);
// Consciousness state
const [interactionCount, setInteractionCount] = useState(0);
const [dialogEngagement, setDialogEngagement] = useState({
timeSpent: 0,
contentScrolled: false,
interactionCount: 0
});
const [dialogInsights, setDialogInsights] = useState(null);
const eyeTracker = eyeTracking ? {
startTracking: handler => {},
stopTracking: () => {}
} : null;
const biometricAdapter = adaptive ? {
getCurrentBiometrics: () => ({
stressLevel: 0.3
})
} : null;
const spatialAudioEngine = spatialAudio ? {
playSound: (sound, config) => {}
} : null;
const interactionRecorder = consciousness ? {
recordInteraction: (type, data) => {}
} : null;
const achievementTracker = trackAchievements ? {
recordInteraction: (type, data) => {}
} : null;
// Handle escape key
useEffect(() => {
if (!closeOnEscape || !open) return;
const handleEscape = event => {
if (event.key === "Escape") {
onOpenChange?.(false);
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [closeOnEscape, open, onOpenChange]);
// Handle body scroll lock
useEffect(() => {
if (modal && open) {
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "";
};
}
}, [modal, open]);
// Focus management: trap focus and restore on close
useEffect(() => {
if (!open || !dialogRef.current) return;
// Store previously focused element
previouslyFocusedRef.current = document.activeElement;
// Set up focus trap
const releaseFocus = trapFocus(dialogRef.current, {
returnFocus: false,
// We'll handle this manually for better control
escapeDeactivates: false,
// We handle escape separately
allowOutsideClick: true // We handle backdrop clicks separately
});
return () => {
releaseFocus();
// Restore focus to previously focused element
if (previouslyFocusedRef.current) {
setTimeout(() => {
previouslyFocusedRef.current?.focus();
}, 0);
}
};
}, [open]);
// Handle visibility state
useEffect(() => {
if (open) {
setIsVisible(true);
} else {
// Delay hiding to allow exit animation
const timer = setTimeout(() => setIsVisible(false), 200);
return () => clearTimeout(timer);
}
}, [open]);
// Consciousness effects
// Dialog lifecycle tracking with spatial audio
useEffect(() => {
if (open) {
const openTime = Date.now();
setInteractionCount(prev => prev + 1);
// Record dialog opening
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("dialog_open", {
title: title?.toString() || "Dialog",
size,
variant,
timestamp: openTime
});
}
// Track achievement for dialog interaction
if (trackAchievements && achievementTracker) {
achievementTracker.recordInteraction("dialog_opened", {
dialogTitle: title?.toString() || "Dialog",
timestamp: openTime
});
}
// Play spatial audio for dialog opening
if (spatialAudio && spatialAudioEngine) {
spatialAudioEngine.playSound("dialog-open", {
volume: 0.5,
position: "center",
reverb: 0.3
});
}
// Start tracking time
const timeTracker = setInterval(() => {
setDialogEngagement(prev => ({
...prev,
timeSpent: Date.now() - openTime
}));
}, 1000);
return () => clearInterval(timeTracker);
} else {
// Record dialog closing
if (consciousness && interactionRecorder && dialogEngagement.timeSpent > 0) {
interactionRecorder.recordInteraction("dialog_close", {
title: title?.toString() || "Dialog",
timeSpent: dialogEngagement.timeSpent,
interactions: dialogEngagement.interactionCount,
contentScrolled: dialogEngagement.contentScrolled,
timestamp: Date.now()
});
}
// Play spatial audio for dialog closing
if (spatialAudio && spatialAudioEngine) {
spatialAudioEngine.playSound("dialog-close", {
volume: 0.4,
position: "center",
reverb: 0.2
});
}
}
}, [open, consciousness, interactionRecorder, trackAchievements, achievementTracker, spatialAudio, spatialAudioEngine, title, size, variant, dialogEngagement]);
// Eye tracking for dialog engagement
useEffect(() => {
if (!eyeTracking || !eyeTracker || !open) return;
const handleGazeData = gazeData => {
// Track if user is looking at dialog content
const dialogElement = document.querySelector('[data-consciousness-dialog="true"]');
if (dialogElement) {
const rect = dialogElement.getBoundingClientRect();
const isLookingAtDialog = gazeData.x >= rect.left && gazeData.x <= rect.right && gazeData.y >= rect.top && gazeData.y <= rect.bottom;
if (isLookingAtDialog && trackAchievements && achievementTracker) {
achievementTracker.recordInteraction("dialog_gaze_engagement", {
dialogTitle: title?.toString(),
gazeTime: Date.now()
});
}
}
};
eyeTracker.startTracking(handleGazeData);
return () => eyeTracker.stopTracking();
}, [eyeTracking, eyeTracker, open, trackAchievements, achievementTracker, title]);
// Biometric adaptation for dialog behavior
useEffect(() => {
if (!adaptive || !biometricAdapter || !open) return;
const updateAdaptiveFeatures = () => {
const biometrics = biometricAdapter.getCurrentBiometrics();
setDialogInsights({
urgency: biometrics.stressLevel > 0.7 ? "high" : biometrics.stressLevel > 0.4 ? "medium" : "low",
complexity: (children?.toString().length || 0) > 300 ? 0.8 : 0.4,
userStress: biometrics.stressLevel
});
if (trackAchievements && achievementTracker) {
achievementTracker.recordInteraction("dialog_biometric_adaptation", {
stressLevel: biometrics.stressLevel,
dialogComplexity: (children?.toString().length || 0) > 300 ? "high" : "low"
});
}
};
const interval = setInterval(updateAdaptiveFeatures, 3000);
updateAdaptiveFeatures();
return () => clearInterval(interval);
}, [adaptive, biometricAdapter, open, children, trackAchievements, achievementTracker]);
// Enhanced handlers with consciousness tracking
const handleBackdropClick = useCallback(event => {
if (closeOnBackdropClick && event.target === event.currentTarget) {
// Record backdrop click interaction
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("dialog_backdrop_click", {
dialogTitle: title?.toString(),
timestamp: Date.now()
});
}
// Play spatial audio for backdrop close
if (spatialAudio && spatialAudioEngine) {
spatialAudioEngine.playSound("dialog-backdrop-close", {
volume: 0.3,
position: "left",
reverb: 0.1
});
}
onOpenChange?.(false);
props.onClose?.();
}
}, [closeOnBackdropClick, onOpenChange, props, consciousness, interactionRecorder, title, spatialAudio, spatialAudioEngine]);
const handleClose = useCallback(() => {
// Record close button interaction
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("dialog_close_button", {
dialogTitle: title?.toString(),
timestamp: Date.now()
});
}
// Play spatial audio for button close
if (spatialAudio && spatialAudioEngine) {
spatialAudioEngine.playSound("dialog-button-close", {
volume: 0.3,
position: "right",
reverb: 0.1
});
}
onOpenChange?.(false);
props.onClose?.();
}, [onOpenChange, props, consciousness, interactionRecorder, title, spatialAudio, spatialAudioEngine]);
// Size classes
const sizeClasses = {
xs: "max-w-xs",
sm: "max-w-sm",
md: "max-w-md",
lg: "max-w-lg",
xl: "max-w-xl",
"2xl": "max-w-2xl",
full: "max-w-full glass-mx-4"
};
// Animation presets
const getAnimationPreset = () => {
switch (animation) {
case "fade":
return "fadeIn";
case "scale":
return "scaleIn";
case "slide":
return "slideUp";
case "flip":
return "rotateIn";
default:
return "scaleIn";
}
};
if (!isVisible) return null;
return jsxs("div", {
"data-glass-component": true,
className: cn("fixed inset-0 flex items-center justify-center", variant === "fullscreen" ? "glass-p-0" : "glass-p-4", `z-${zIndex}`, consciousness && "consciousness-dialog-container", adaptive && dialogInsights?.urgency === "high" && "consciousness-urgent-dialog", eyeTracking && "consciousness-eye-trackable"),
onClick: handleBackdropClick,
role: "dialog",
"aria-modal": modal,
"aria-labelledby": title ? "dialog-title" : undefined,
"aria-describedby": description ? "dialog-description" : undefined,
"data-consciousness-dialog": "true",
"data-consciousness-active": String(!!consciousness),
"data-dialog-title": title?.toString(),
"data-dialog-urgency": dialogInsights?.urgency,
"data-user-stress": dialogInsights?.userStress,
"data-interaction-count": interactionCount,
children: [jsx(MotionFramer, {
preset: "fadeIn",
duration: 200,
className: cn("absolute inset-0 bg-black/20", backdropBlur && "glass-backdrop-blur-md")
}), jsx(MotionFramer, {
ref: dialogRef,
preset: getAnimationPreset(),
duration: 200,
className: cn("relative w-full", variant === "fullscreen" ? "h-full" : "max-h-[90vh]", sizeClasses[size]),
children: material === "liquid" ? jsxs(LiquidGlassMaterial, {
ior: materialProps?.ior || 1.48,
thickness: materialProps?.thickness || 10,
tint: materialProps?.tint || {
r: 0,
g: 0,
b: 0,
a: 0.08
},
variant: materialProps?.variant || "regular",
quality: materialProps?.quality || "high",
environmentAdaptation: true,
motionResponsive: true,
ref: ref,
className: cn("w-full overflow-hidden liquid-glass-dialog-surface", variant === "fullscreen" && "h-full rounded-none", consciousness && "consciousness-dialog-glass", eyeTracking && "consciousness-eye-trackable-content", adaptive && dialogInsights?.urgency === "high" && "consciousness-urgent-glass", predictive && dialogInsights && "consciousness-predictive-glass", className),
style: {
"--liquid-glass-dialog-density": "0.9",
"--liquid-glass-adaptive-tint": dialogInsights?.urgency === "high" ? "rgba(220, 38, 38, 0.12)" : "rgba(0, 0, 0, 0.08)"
},
"data-liquid-glass-dialog": "true",
"data-dialog-urgency": dialogInsights?.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",
children: [jsx("div", {
className: "glass-flex-1 glass-min-w-0",
children: header || jsxs(Fragment, {
children: [title && jsx("h2", {
id: "dialog-title",
className: 'glass-text-lg font-semibold text-primary mb-1',
children: title
}), description && jsx("p", {
id: "dialog-description",
className: 'glass-text-sm text-muted-foreground',
children: description
})]
})
}), showCloseButton && jsx(GlassButton, {
variant: "ghost",
size: "sm",
iconOnly: true,
onClick: handleClose,
"aria-label": "Close dialog",
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",
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("w-full overflow-hidden border border-border/20 glass-radial-reveal", variant === "fullscreen" && "h-full rounded-none", consciousness && "consciousness-dialog-glass", eyeTracking && "consciousness-eye-trackable-content", adaptive && dialogInsights?.urgency === "high" && "consciousness-urgent-glass", predictive && dialogInsights && "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",
children: [jsx("div", {
className: "glass-flex-1 glass-min-w-0",
children: header || jsxs(Fragment, {
children: [title && jsx("h2", {
id: "dialog-title",
className: 'glass-text-lg font-semibold text-primary mb-1',
children: title
}), description && jsx("p", {
id: "dialog-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 && dialogInsights?.urgency === "high" && "consciousness-urgent-close"),
onClick: handleClose,
"aria-label": "Close dialog",
consciousness: consciousness,
adaptive: adaptive,
spatialAudio: spatialAudio,
trackAchievements: trackAchievements,
"data-consciousness-close": "true",
children: jsx(X, {
className: 'w-4 h-4'
})
})]
}), children && jsxs("div", {
className: cn("flex-1 overflow-y-auto glass-p-6", consciousness && "consciousness-dialog-body", eyeTracking && "consciousness-eye-trackable-body", adaptive && dialogInsights?.urgency === "high" && "consciousness-urgent-body"),
"data-consciousness-body": "true",
"data-content-complexity": dialogInsights?.complexity,
onScroll: e => {
setDialogEngagement(prev => ({
...prev,
contentScrolled: true,
interactionCount: prev.interactionCount + 1
}));
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("dialog_scroll", {
dialogTitle: title?.toString(),
timestamp: Date.now()
});
}
},
onClick: e => {
setDialogEngagement(prev => ({
...prev,
interactionCount: prev.interactionCount + 1
}));
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("dialog_content_click", {
dialogTitle: title?.toString(),
timestamp: Date.now()
});
}
},
children: [children, predictive && dialogInsights && 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: "Dialog Insights"
}), jsxs("div", {
className: "glass-flex glass-gap-2",
children: [jsxs("span", {
className: cn("glass-px-2 glass-py-1 glass-radius-md", dialogInsights.urgency === "high" ? "bg-red-500/20 text-red-300" : dialogInsights.urgency === "medium" ? "bg-yellow-500/20 text-yellow-300" : "bg-green-500/20 text-green-300"),
children: [dialogInsights.urgency, " urgency"]
}), dialogInsights.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"
})]
})]
})
})]
}), footer && jsx("div", {
className: "glass-p-6 glass-border-t glass-border-glass-border/10",
children: footer
})]
})
})]
});
});
GlassDialog.displayName = "GlassDialog";
const DialogTrigger = /*#__PURE__*/forwardRef(({
children,
asChild = false,
...props
}, ref) => {
if (asChild) {
return /*#__PURE__*/React.cloneElement(children, {
ref,
...props
});
}
return jsx(GlassButton, {
ref: ref,
...props,
children: children
});
});
DialogTrigger.displayName = "DialogTrigger";
const DialogHeader = /*#__PURE__*/forwardRef(({
children,
className,
...props
}, ref) => {
return jsx("div", {
ref: ref,
className: cn("flex flex-col glass-gap-1.5 text-center sm:text-left", className),
...props,
children: children
});
});
DialogHeader.displayName = "DialogHeader";
const DialogTitle = /*#__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
});
});
DialogTitle.displayName = "DialogTitle";
const DialogDescription = /*#__PURE__*/forwardRef(({
children,
className,
...props
}, ref) => {
return jsx("p", {
ref: ref,
className: cn("glass-text-sm glass-text-secondary", className),
...props,
children: children
});
});
DialogDescription.displayName = "DialogDescription";
const DialogFooter = /*#__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
});
});
DialogFooter.displayName = "DialogFooter";
export { DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, GlassDialog };
//# sourceMappingURL=GlassDialog.js.map