aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
614 lines (611 loc) • 24.6 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { 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 { FocusTrap } from '../../primitives/focus/FocusTrap.js';
import { LiquidGlassMaterial } from '../../primitives/LiquidGlassMaterial.js';
import { useA11yId, focusUtils, announceToScreenReader, createModalA11y } from '../../utils/a11y.js';
import { GlassButton, IconButton } from '../button/GlassButton.js';
export { GlassDialog } from './GlassDialog.js';
export { GlassDrawer } from './GlassDrawer.js';
/**
* GlassModal component
* A versatile modal with glassmorphism styling
*/
const GlassModal = /*#__PURE__*/forwardRef(({
open = true,
onClose = () => {},
title,
description,
size = "md",
variant = "default",
material = "glass",
materialProps,
closeOnBackdropClick = true,
closeOnEscape = true,
closeButton,
showCloseButton = true,
footer,
children = null,
backdrop,
backdropBlur = "md",
animation = "scale",
lockScroll = true,
zIndex = 50,
className,
role = "dialog",
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledBy,
"aria-describedby": ariaDescribedBy,
initialFocus,
restoreFocus,
contentClassName,
// Consciousness features
consciousness,
predictive,
adaptive,
eyeTracking,
spatialAudio,
trackAchievements,
...props
}, ref) => {
const [mounted, setMounted] = useState(false);
const contentRef = useRef(null);
const previousActiveElement = useRef(null);
// Consciousness state
const [interactionCount, setInteractionCount] = useState(0);
const [modalFocusTime, setModalFocusTime] = useState(0);
const [contentEngagement, setContentEngagement] = useState({
scrollDepth: 0,
timeSpent: 0,
interactions: 0
});
const [modalInsights, setModalInsights] = useState(null);
// Consciousness hooks (mock implementations)
const predictiveEngine = predictive ? {
analyzeModalEngagement: async context => ({
urgency: "low",
complexity: 0.5,
userStress: 0.3
})
} : 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;
// Generate unique IDs for accessibility
const modalId = useA11yId("glass-modal");
const titleId = title ? useA11yId("glass-modal-title") : undefined;
const descriptionId = description ? useA11yId("glass-modal-desc") : undefined;
// Create accessibility attributes
const baseA11yProps = createModalA11y({
id: modalId,
titleId: ariaLabelledBy || titleId,
descriptionId: ariaDescribedBy || descriptionId,
modal: true
});
// Create final a11y props with role override
const a11yProps = {
...baseA11yProps,
...(role && {
role
}),
...(ariaLabel && !ariaLabelledBy && !titleId && {
"aria-label": ariaLabel
})
};
useEffect(() => {
setMounted(true);
}, []);
// Focus management
useEffect(() => {
if (!mounted) return;
if (open) {
// Store the previously focused element
previousActiveElement.current = document.activeElement;
// Focus management after modal renders
setTimeout(() => {
if (initialFocus?.current) {
initialFocus.current.focus();
} else if (contentRef.current) {
// Focus first focusable element or the modal itself
focusUtils.focusFirst(contentRef.current);
if (document.activeElement === document.body) {
contentRef.current.focus();
}
}
}, 100);
// Announce modal opening to screen readers
if (role === "alertdialog") {
announceToScreenReader(`Alert dialog opened: ${title || ariaLabel || "Dialog"}`, "assertive");
} else {
announceToScreenReader(`Dialog opened: ${title || ariaLabel || "Dialog"}`, "polite");
}
} else if (previousActiveElement.current && mounted) {
// Restore focus when modal closes
if (restoreFocus?.current) {
restoreFocus.current.focus();
} else {
previousActiveElement.current.focus();
}
}
}, [open, mounted, title, ariaLabel, role, initialFocus, restoreFocus]);
useEffect(() => {
if (!mounted) return;
if (open && lockScroll) {
const scrollY = window.scrollY;
const body = document.body;
body.style.position = "fixed";
body.style.top = `-${scrollY}px`;
body.style.width = "100%";
body.style.overflow = "hidden";
return () => {
body.style.position = "";
body.style.top = "";
body.style.width = "";
body.style.overflow = "";
window.scrollTo(0, scrollY);
};
}
}, [open, lockScroll, mounted]);
// Consciousness effects
// Modal opening/closing tracking with spatial audio
useEffect(() => {
if (!mounted) return;
if (open) {
const openTime = Date.now();
setModalFocusTime(openTime);
setInteractionCount(prev => prev + 1);
// Record modal opening interaction
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("modal_open", {
title: title || "Untitled Modal",
size,
variant,
role,
timestamp: openTime
});
}
// Track achievement for modal interaction
if (trackAchievements && achievementTracker) {
achievementTracker.recordInteraction("modal_opened", {
modalType: role,
title: title || "Modal",
timestamp: openTime
});
}
// Play spatial audio for modal opening
if (spatialAudio && spatialAudioEngine) {
const audioConfig = role === "alertdialog" ? {
sound: "alert-modal-open",
volume: 0.7,
position: "center",
reverb: 0.4
} : {
sound: "modal-open",
volume: 0.5,
position: "center",
reverb: 0.3
};
spatialAudioEngine.playSound(audioConfig.sound, audioConfig);
}
} else if (modalFocusTime > 0) {
const closeTime = Date.now();
const timeSpent = closeTime - modalFocusTime;
// Record modal closing interaction
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("modal_close", {
title: title || "Untitled Modal",
timeSpent,
interactions: contentEngagement.interactions,
scrollDepth: contentEngagement.scrollDepth,
timestamp: closeTime
});
}
// Update content engagement
setContentEngagement(prev => ({
...prev,
timeSpent
}));
// Play spatial audio for modal closing
if (spatialAudio && spatialAudioEngine) {
spatialAudioEngine.playSound("modal-close", {
volume: 0.4,
position: "center",
reverb: 0.2
});
}
}
}, [open, mounted, consciousness, interactionRecorder, trackAchievements, achievementTracker, spatialAudio, spatialAudioEngine, title, size, variant, role, modalFocusTime, contentEngagement]);
// Eye tracking for modal engagement
useEffect(() => {
if (!eyeTracking || !eyeTracker || !open) return;
const handleGazeData = gazeData => {
// Track if user is looking at modal content
if (contentRef.current) {
const rect = contentRef.current.getBoundingClientRect();
const isLookingAtModal = gazeData.x >= rect.left && gazeData.x <= rect.right && gazeData.y >= rect.top && gazeData.y <= rect.bottom;
if (isLookingAtModal && trackAchievements && achievementTracker) {
achievementTracker.recordInteraction("modal_gaze_engagement", {
modalTitle: title,
gazeTime: Date.now(),
modalArea: "content"
});
}
}
};
eyeTracker.startTracking(handleGazeData);
return () => eyeTracker.stopTracking();
}, [eyeTracking, eyeTracker, open, trackAchievements, achievementTracker, title]);
// Biometric adaptation for modal behavior
useEffect(() => {
if (!adaptive || !biometricAdapter || !open) return;
const updateAdaptiveFeatures = () => {
const biometrics = biometricAdapter.getCurrentBiometrics();
// Analyze modal content complexity and user stress
setModalInsights({
urgency: role === "alertdialog" ? "high" : biometrics.stressLevel > 0.7 ? "medium" : "low",
complexity: (children?.toString().length || 0) > 500 ? 0.8 : 0.4,
userStress: biometrics.stressLevel
});
// Record biometric adaptation for achievements
if (trackAchievements && achievementTracker) {
achievementTracker.recordInteraction("modal_biometric_adaptation", {
stressLevel: biometrics.stressLevel,
modalComplexity: (children?.toString().length || 0) > 500 ? "high" : "low",
adaptations: {
urgencyLevel: role === "alertdialog" ? "high" : "normal"
}
});
}
};
const interval = setInterval(updateAdaptiveFeatures, 3000);
updateAdaptiveFeatures(); // Run immediately
return () => clearInterval(interval);
}, [adaptive, biometricAdapter, open, role, children, trackAchievements, achievementTracker]);
// Predictive modal insights
useEffect(() => {
if (!predictive || !predictiveEngine || !open) return;
const generateModalInsights = async () => {
try {
const modalContext = {
title: title || "Modal",
role,
size,
variant,
hasFooter: !!footer,
contentLength: children?.toString().length || 0,
interactionCount,
timeSpent: modalFocusTime ? Date.now() - modalFocusTime : 0
};
const insights = await predictiveEngine.analyzeModalEngagement(modalContext);
setModalInsights(prevInsights => ({
urgency: insights.urgency || prevInsights?.urgency || "low",
complexity: insights.complexity || prevInsights?.complexity || 0.5,
userStress: insights.userStress || prevInsights?.userStress || 0.3
}));
} catch (error) {
console.warn("Predictive modal analysis failed:", error);
}
};
const timeoutId = setTimeout(generateModalInsights, 1000);
return () => clearTimeout(timeoutId);
}, [predictive, predictiveEngine, open, title, role, size, variant, footer, children, interactionCount, modalFocusTime]);
// Enhanced handlers with consciousness tracking
const handleBackdropClick = useCallback(e => {
if (closeOnBackdropClick && e.target === e.currentTarget) {
// Record backdrop click interaction
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("modal_backdrop_click", {
modalTitle: title,
timestamp: Date.now()
});
}
// Play spatial audio for backdrop close
if (spatialAudio && spatialAudioEngine) {
spatialAudioEngine.playSound("modal-backdrop-close", {
volume: 0.3,
position: "left",
reverb: 0.1
});
}
onClose();
}
}, [closeOnBackdropClick, onClose, consciousness, interactionRecorder, title, spatialAudio, spatialAudioEngine]);
const handleEscapeKey = useCallback(() => {
if (closeOnEscape) {
// Record escape key close
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("modal_escape_close", {
modalTitle: title,
timestamp: Date.now()
});
}
// Play spatial audio for escape close
if (spatialAudio && spatialAudioEngine) {
spatialAudioEngine.playSound("modal-escape-close", {
volume: 0.4,
position: "right",
reverb: 0.2
});
}
onClose();
}
}, [closeOnEscape, onClose, consciousness, interactionRecorder, title, spatialAudio, spatialAudioEngine]);
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"
};
const variantClasses = {
default: "items-center justify-center glass-p-4",
centered: "items-center justify-center glass-p-4",
drawer: "items-end justify-center pb-0",
fullscreen: "items-center justify-center glass-p-0"
};
const backdropBlurClasses = {
none: "",
sm: "glass-backdrop-blur-md",
md: "glass-backdrop-blur-md",
lg: "glass-backdrop-blur-md"
};
const getAnimationPreset = () => {
switch (animation) {
case "fade":
return "fadeIn";
case "scale":
return "scaleIn";
case "slide":
return variant === "drawer" ? "slideUp" : "slideDown";
case "flip":
return "scaleIn";
default:
return "scaleIn";
}
};
if (!mounted || !open) {
return null;
}
return jsxs("div", {
"data-glass-component": true,
className: cn("fixed inset-0 flex", variantClasses[variant], backdropBlurClasses[backdropBlur], consciousness && "consciousness-modal-container", adaptive && modalInsights?.urgency === "high" && "consciousness-urgent-modal", eyeTracking && "consciousness-eye-trackable"),
style: {
zIndex
},
"data-consciousness-modal": "true",
"data-consciousness-active": String(!!consciousness),
"data-modal-title": title,
"data-modal-role": role,
"data-modal-urgency": modalInsights?.urgency,
"data-user-stress": modalInsights?.userStress,
"data-interaction-count": interactionCount,
children: [backdrop || jsx(MotionFramer, {
preset: "fadeIn",
className: 'absolute inset-0 glass-surface-dark/50',
onClick: handleBackdropClick
}), jsx(MotionFramer, {
preset: getAnimationPreset(),
className: cn("relative w-full", sizeClasses[size], variant === "fullscreen" ? "h-full" : "max-h-full", consciousness && "consciousness-modal-content", predictive && modalInsights && "consciousness-predictive-modal", adaptive && modalInsights?.urgency === "high" && "consciousness-urgent-content"),
"data-consciousness-content": "true",
"data-modal-complexity": modalInsights?.complexity,
"data-time-spent": modalFocusTime ? Date.now() - modalFocusTime : 0,
children: jsx(FocusTrap, {
active: open,
onEscape: handleEscapeKey,
lockScroll: false,
children: material === "liquid" ? jsxs(LiquidGlassMaterial, {
ior: materialProps?.ior || 1.52,
thickness: materialProps?.thickness || 12,
tint: materialProps?.tint || {
r: 0,
g: 0,
b: 0,
a: 0.1
},
variant: materialProps?.variant || "regular",
quality: materialProps?.quality || "high",
environmentAdaptation: true,
motionResponsive: true,
ref: ref,
tabIndex: -1,
...a11yProps,
className: cn("w-full flex flex-col liquid-glass-modal-surface", "focus:outline-none", variant === "fullscreen" ? "h-full" : "max-h-full overflow-hidden", consciousness && "consciousness-modal-glass", eyeTracking && "consciousness-eye-trackable-content", adaptive && modalInsights?.urgency === "high" && "consciousness-urgent-glass", predictive && modalInsights && "consciousness-predictive-glass", className),
style: {
"--liquid-glass-depth-offset": variant === "drawer" ? "8px" : "12px",
"--liquid-glass-tint-adaptive": modalInsights?.urgency === "high" ? "rgba(220, 38, 38, 0.15)" : "rgba(var(--glass-color-black) / var(--glass-opacity-10))"
},
"data-liquid-glass-modal": "true",
"data-modal-urgency": modalInsights?.urgency,
...props,
children: [(title || description || showCloseButton) && jsx("div", {
className: "glass-flex-shrink-0 glass-p-6 glass-border-b glass-border-glass-border/20",
children: jsxs("div", {
className: "glass-flex glass-items-start glass-justify-between",
children: [jsxs("div", {
className: "glass-flex-1 glass-min-w-0",
children: [title && jsx("h2", {
id: titleId,
className: 'glass-text-lg font-semibold text-primary',
children: title
}), description && jsx("p", {
id: descriptionId,
className: 'glass-text-sm text-muted-foreground glass-mt-1',
children: description
})]
}), showCloseButton && jsx(GlassButton, {
variant: "ghost",
size: "sm",
iconOnly: true,
onClick: onClose,
"aria-label": "Close modal",
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-flex-shrink-0 glass-p-6 glass-border-t glass-border-glass-border/20",
children: footer
})]
}) : jsxs(OptimizedGlassCore, {
intent: "neutral",
elevation: "level4",
intensity: "strong",
depth: 2,
tint: "neutral",
border: "subtle",
animation: "none",
performanceMode: "medium",
ref: ref,
tabIndex: -1,
...a11yProps,
className: cn("w-full flex flex-col glass-overlay-noise glass-edge glass-overlay-specular glass-typography-reset", "focus:outline-none", variant === "fullscreen" ? "h-full" : "max-h-full overflow-hidden", consciousness && "consciousness-modal-glass", eyeTracking && "consciousness-eye-trackable-content", adaptive && modalInsights?.urgency === "high" && "consciousness-urgent-glass", predictive && modalInsights && "consciousness-predictive-glass", className),
...props,
children: [(title || description || showCloseButton) && jsx("div", {
className: "glass-flex-shrink-0 glass-p-6 glass-border-b glass-border-glass-border/20",
children: jsxs("div", {
className: "glass-flex glass-items-start glass-justify-between",
children: [jsxs("div", {
className: "glass-flex-1 glass-min-w-0",
children: [title && jsx("h2", {
id: titleId,
className: 'glass-text-lg font-semibold text-primary',
children: title
}), description && jsx("p", {
id: descriptionId,
className: "glass-mt-1 glass-text-sm glass-text-secondary",
children: description
})]
}), showCloseButton && jsx("div", {
className: "glass-flex-shrink-0 glass-ml-4",
children: closeButton || jsx(IconButton, {
icon: jsx("svg", {
className: 'w-4 h-4',
fill: "none",
stroke: "currentColor",
viewBox: "0 0 24 24",
children: jsx("path", {
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M6 18L18 6M6 6l12 12"
})
}),
variant: "ghost",
size: "sm",
onClick: () => {
// Record close button interaction
if (consciousness && interactionRecorder) {
interactionRecorder.recordInteraction("modal_close_button", {
modalTitle: title,
timestamp: Date.now()
});
}
// Play spatial audio for button close
if (spatialAudio && spatialAudioEngine) {
spatialAudioEngine.playSound("modal-button-close", {
volume: 0.3,
position: "right",
reverb: 0.1
});
}
onClose();
},
"aria-label": "Close modal",
className: cn(consciousness && "consciousness-close-button", adaptive && modalInsights?.urgency === "high" && "consciousness-urgent-close"),
"data-consciousness-close": "true"
})
})]
})
}), jsxs("div", {
ref: contentRef,
className: cn("flex-1 overflow-y-auto glass-p-6", consciousness && "consciousness-modal-body", eyeTracking && "consciousness-eye-trackable-body", adaptive && modalInsights?.urgency === "high" && "consciousness-urgent-body"),
tabIndex: 0,
"data-consciousness-body": "true",
"data-content-complexity": modalInsights?.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
}));
interactionRecorder.recordInteraction("modal_scroll", {
modalTitle: title,
scrollDepth,
timestamp: Date.now()
});
}
},
onClick: e => {
if (consciousness && interactionRecorder) {
setContentEngagement(prev => ({
...prev,
interactions: prev.interactions + 1
}));
interactionRecorder.recordInteraction("modal_content_click", {
modalTitle: title,
timestamp: Date.now()
});
}
},
children: [children, predictive && modalInsights && 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: "Modal Insights"
}), jsxs("div", {
className: "glass-flex glass-gap-2",
children: [jsxs("span", {
className: cn("glass-px-2 glass-py-1 glass-radius-md", modalInsights.urgency === "high" ? "bg-red-500/20 text-red-300" : modalInsights.urgency === "medium" ? "bg-yellow-500/20 text-yellow-300" : "bg-green-500/20 text-green-300"),
children: [modalInsights.urgency, " urgency"]
}), modalInsights.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-flex-shrink-0 glass-p-6 glass-border-t glass-border-glass-border/20",
children: footer
})]
})
})
})]
});
});
GlassModal.displayName = "GlassModal";
export { GlassModal };
//# sourceMappingURL=GlassModal.js.map