aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
180 lines (177 loc) • 7.63 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { useMotionValue, useSpring, useScroll, useTransform, motion } from 'framer-motion';
import { forwardRef, useRef, useState, 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 '../../primitives/motion/MotionFramer.js';
import { useA11yId } from '../../utils/a11y.js';
const GlassParallaxLayers = /*#__PURE__*/forwardRef(({
layers,
className,
mouseIntensity = 0.5,
scrollIntensity = 0.3,
perspective = 1200,
autoRotate = false,
rotateSpeed = 0.5,
interactive = true,
debug = false,
"aria-label": ariaLabel,
respectMotionPreference = true
}, ref) => {
const containerRef = useRef(null);
const [isHovered, setIsHovered] = useState(false);
const componentId = useA11yId("parallax-layers");
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
// Check for reduced motion preference
useEffect(() => {
if (!respectMotionPreference) return;
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
setPrefersReducedMotion(mediaQuery.matches);
const handleChange = e => setPrefersReducedMotion(e.matches);
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, [respectMotionPreference]);
// Motion values for mouse position
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
// Smooth spring animation
const springConfig = {
damping: 25,
stiffness: 150
};
const smoothMouseX = useSpring(mouseX, springConfig);
const smoothMouseY = useSpring(mouseY, springConfig);
// Scroll progress
const {
scrollYProgress
} = useScroll({
target: containerRef,
offset: ["start end", "end start"]
});
// Auto rotation
const [rotation, setRotation] = useState(0);
useEffect(() => {
if (!autoRotate) return;
const interval = setInterval(() => {
setRotation(prev => prev + rotateSpeed);
}, 16);
return () => clearInterval(interval);
}, [autoRotate, rotateSpeed]);
// Handle mouse movement
const handleMouseMove = useCallback(e => {
if (!interactive || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
mouseX.set(x);
mouseY.set(y);
}, [interactive, mouseX, mouseY]);
const handleMouseLeave = useCallback(() => {
mouseX.set(0);
mouseY.set(0);
setIsHovered(false);
}, [mouseX, mouseY]);
const handleMouseEnter = useCallback(() => {
setIsHovered(true);
}, []);
// Sort layers by depth (furthest first for proper stacking)
const sortedLayers = [...layers].sort((a, b) => b.depth - a.depth);
return jsxs("div", {
ref: ref || containerRef,
id: componentId,
className: cn("relative overflow-hidden", "transform-gpu will-change-transform", className),
style: {
perspective: prefersReducedMotion ? "none" : `${perspective}px`,
transformStyle: prefersReducedMotion ? "flat" : "preserve-3d"
},
onMouseMove: prefersReducedMotion ? undefined : handleMouseMove,
onMouseEnter: prefersReducedMotion ? undefined : handleMouseEnter,
onMouseLeave: prefersReducedMotion ? undefined : handleMouseLeave,
role: "presentation",
"aria-label": ariaLabel || "Parallax layers with interactive effects",
"aria-hidden": !ariaLabel,
children: [sortedLayers.map((layer, index) => {
const depthFactor = layer.depth / 10;
const parallaxFactor = 1 - depthFactor;
// Calculate transforms based on depth
const xTransform = useTransform(smoothMouseX, [-0.5, 0.5], [-50 * parallaxFactor * mouseIntensity, 50 * parallaxFactor * mouseIntensity]);
useTransform(smoothMouseY, [-0.5, 0.5], [-30 * parallaxFactor * mouseIntensity, 30 * parallaxFactor * mouseIntensity]);
const scrollY = useTransform(scrollYProgress, [0, 1], [100 * depthFactor * scrollIntensity, -100 * depthFactor * scrollIntensity]);
const rotateXTransform = useTransform(smoothMouseY, [-0.5, 0.5], [15 * parallaxFactor * mouseIntensity, -15 * parallaxFactor * mouseIntensity]);
const rotateYTransform = useTransform(smoothMouseX, [-0.5, 0.5], [-15 * parallaxFactor * mouseIntensity, 15 * parallaxFactor * mouseIntensity]);
// Blur based on depth
const blurAmount = Math.round(depthFactor * 4);
const autoBlur = blurAmount === 0 ? "none" : blurAmount === 1 ? "subtle" : blurAmount === 2 ? "medium" : blurAmount === 3 ? "strong" : "intense";
// Map layer blur values to OptimizedGlass compatible values
const mapBlurValue = blur => {
switch (blur) {
case "sm":
return "subtle";
case "md":
return "medium";
case "lg":
return "strong";
case "xl":
return "intense";
default:
return blur;
}
};
const finalBlur = mapBlurValue(layer.blur) || autoBlur;
return jsx(motion.div, {
initial: {
opacity: 0,
scale: prefersReducedMotion ? 1 : 0.9
},
animate: {
opacity: layer.opacity || 1 - depthFactor * 0.2,
scale: layer.scale || 1 - depthFactor * 0.1
},
transition: {
duration: prefersReducedMotion ? 0 : 0.6,
delay: prefersReducedMotion ? 0 : index * 0.1
},
style: {
x: prefersReducedMotion ? 0 : xTransform,
y: prefersReducedMotion ? 0 : scrollY,
rotateX: prefersReducedMotion ? 0 : rotateXTransform,
rotateY: prefersReducedMotion ? 0 : autoRotate ? rotation : rotateYTransform,
z: prefersReducedMotion ? 0 : layer.depth * 50,
scale: layer.scale || 1 - depthFactor * 0.1,
opacity: layer.opacity || 1 - depthFactor * 0.2,
transformStyle: prefersReducedMotion ? "flat" : "preserve-3d"
},
children: jsxs(OptimizedGlassCore, {
intent: "neutral",
elevation: isHovered ? "level3" : "level1",
intensity: "medium",
glassBlur: finalBlur,
depth: Math.min(layer.depth, 5),
className: cn("absolute inset-0", "transition-all duration-300", layer.className),
children: [layer.content, debug && jsxs("div", {
className: 'absolute glass-top-2 left-2 glass-surface-dark/20 glass-glass-glass-backdrop-blur-sm glass-contrast-guard glass-p-2 glass-radius-sm glass-text-xs text-primary/90 glass-contrast-guard',
children: ["Layer ", index + 1, " | Depth: ", layer.depth]
})]
})
}, `layer-${index}`);
}), interactive && !prefersReducedMotion && jsx(motion.div, {
className: 'absolute bottom-4 right-4 text-primary/60 glass-text-xs glass-surface-dark/20 glass-glass-glass-backdrop-blur-sm glass-contrast-guard glass-p-2 glass-radius-md glass-contrast-guard',
initial: {
opacity: 0
},
animate: {
opacity: isHovered ? 1 : 0.5
},
role: "status",
"aria-live": "polite",
children: isHovered ? "Move mouse to parallax" : "Hover to interact"
})]
});
});
export { GlassParallaxLayers };
//# sourceMappingURL=GlassParallaxLayers.js.map