UNPKG

aura-glass

Version:

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

267 lines (264 loc) 10.4 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { forwardRef, useRef, useState, useEffect, useCallback } from 'react'; import { cn } from '../../lib/utilsComprehensive.js'; import { ZLayer } from '../../core/zspace.js'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { createGlassStyle } from '../../core/mixins/glassMixins.js'; import styles from './GlassDynamicAtmosphere.module.css.js'; const clamp01 = value => Math.min(1, Math.max(0, value)); const applyAlpha = (color, alpha) => { if (!color) return `rgba(0, 0, 0, ${clamp01(alpha)})`; if (color.startsWith('#')) { const normalized = color.replace('#', ''); if (normalized.length === 3) { const r = parseInt(normalized[0] + normalized[0], 16); const g = parseInt(normalized[1] + normalized[1], 16); const b = parseInt(normalized[2] + normalized[2], 16); return `rgba(${r}, ${g}, ${b}, ${clamp01(alpha)})`; } if (normalized.length === 6) { const r = parseInt(normalized.slice(0, 2), 16); const g = parseInt(normalized.slice(2, 4), 16); const b = parseInt(normalized.slice(4, 6), 16); return `rgba(${r}, ${g}, ${b}, ${clamp01(alpha)})`; } } return color; }; /** * DynamicAtmosphere Component * * A component that creates dynamic atmospheric background effects. */ const DynamicAtmosphere = /*#__PURE__*/forwardRef((props, ref) => { const { type = 'subtle', primaryColor = '#6366F1', // Primary (purple) secondaryColor = 'var(--glass-color-primary)', // Secondary (blue) accentColor = 'var(--glass-color-success)', // Accent (green) intensity = 0.5, speed = 1, interactionMode = 'none', interactionSensitivity = 0.5, fullSize = true, width = '100%', height = '100%', style, className, zIndex = ZLayer.Background, position = 'absolute', respectReducedMotion = true, particleCount = 20, blur = false, blurStrength = 5, noise = false, ...rest } = props; const prefersReducedMotion = useReducedMotion(); const shouldReduceMotion = respectReducedMotion && prefersReducedMotion; const containerRef = useRef(null); const [transform, setTransform] = useState(''); // Compute and inject CSS variables for backgrounds to avoid inline glass literals useEffect(() => { const el = containerRef.current; if (!el) return; const i = intensity; const pc = primaryColor; const sc = secondaryColor; const ac = accentColor; let bg = ''; if (type === 'subtle') { bg = `radial-gradient(circle at 50% 50%, ${applyAlpha(pc, i * 0.4)}, transparent 70%)`; } else if (type === 'nebula') { bg = `radial-gradient(circle at 30% 50%, ${applyAlpha(pc, i * 0.6)}, transparent 50%), radial-gradient(circle at 70% 50%, ${applyAlpha(sc, i * 0.6)}, transparent 50%)`; } else if (type === 'waves') { bg = `linear-gradient(135deg, ${applyAlpha(pc, i * 0.5)}, ${applyAlpha(sc, i * 0.5)}, ${applyAlpha(ac, i * 0.5)}, ${applyAlpha(pc, i * 0.5)})`; } else if (type === 'gradient') { bg = `linear-gradient(-45deg, ${applyAlpha(pc, i * 0.4)}, ${applyAlpha(sc, i * 0.4)}, ${applyAlpha(ac, i * 0.4)}, ${applyAlpha(pc, i * 0.4)})`; } if (bg) el.style.setProperty('--atmosphere-bg', bg); if (type === 'aurora') { const aur = `linear-gradient(90deg, ${applyAlpha(pc, 0)}, ${applyAlpha(pc, i * 0.6)}, ${applyAlpha(sc, i * 0.6)}, ${applyAlpha(ac, i * 0.6)}, ${applyAlpha(pc, 0)})`; el.style.setProperty('--atmosphere-aurora-bg', aur); } }, [type, intensity, primaryColor, secondaryColor, accentColor]); // Convert width and height to string const widthValue = typeof width === 'number' ? `${width}px` : width; const heightValue = typeof height === 'number' ? `${height}px` : height; const safeSpeed = Math.max(speed, 0.1); const baseAnimationDuration = `${30 / safeSpeed}s`; const particleAnimationDuration = `${15 / safeSpeed}s`; const containerStyle = { position, width: fullSize ? '100%' : widthValue, height: fullSize ? '100%' : heightValue, zIndex, overflow: 'hidden', pointerEvents: 'none', ...style }; if (!fullSize) { containerStyle.width = widthValue; containerStyle.height = heightValue; } if (position === 'absolute' || position === 'fixed') { containerStyle.top = 0; containerStyle.left = 0; containerStyle.right = 0; containerStyle.bottom = 0; } if (blur) { Object.assign(containerStyle, createGlassStyle({ intent: 'neutral', elevation: 'level2' })); containerStyle.backdropFilter = `blur(${blurStrength}px)`; containerStyle.WebkitBackdropFilter = `blur(${blurStrength}px)`; } const effectStyle = { transform }; const intensityValue = clamp01(intensity); const typeClassMap = { subtle: styles.typeSubtle, nebula: styles.typeNebula, aurora: styles.typeAurora, particles: styles.typeDefault, waves: styles.typeWaves, gradient: styles.typeGradient, ambient: styles.typeAmbient, custom: styles.typeDefault }; const animationClassMap = { subtle: styles.animateSubtle, nebula: styles.animateNebula, aurora: styles.animateAurora, waves: styles.animateWaves, gradient: styles.animateGradient }; if (!shouldReduceMotion) { effectStyle['--atmosphere-animation-duration'] = baseAnimationDuration; } switch (type) { case 'subtle': effectStyle.opacity = intensityValue * 0.7 + 0.3; break; case 'nebula': effectStyle.opacity = intensityValue * 0.8 + 0.2; break; case 'aurora': effectStyle['--atmosphere-aurora-opacity'] = intensityValue * 0.8 + 0.2; break; case 'waves': effectStyle.opacity = intensityValue * 0.7 + 0.3; break; case 'gradient': effectStyle.opacity = intensityValue * 0.7 + 0.3; break; case 'ambient': effectStyle.opacity = intensityValue * 0.6 + 0.4; effectStyle.background = `radial-gradient(circle at 20% 30%, ${applyAlpha(primaryColor, intensityValue * 0.5)}, transparent 50%), radial-gradient(circle at 80% 70%, ${applyAlpha(secondaryColor, intensityValue * 0.5)}, transparent 50%), radial-gradient(circle at 50% 50%, ${applyAlpha(accentColor, intensityValue * 0.4)}, transparent 70%)`; break; default: effectStyle.opacity = intensityValue * 0.5 + 0.5; effectStyle.backgroundColor = applyAlpha(primaryColor, intensityValue * 0.3); break; } const effectClasses = cn(styles.effect, typeClassMap[type] ?? styles.typeDefault, noise && styles.noise, !shouldReduceMotion && animationClassMap[type], shouldReduceMotion && styles.reduceMotion); // Generate particles const renderParticles = () => { if (type !== 'particles') return null; return jsx("div", { className: styles.particleContainer, children: Array.from({ length: particleCount }).map((_, index) => { const size = Math.random() * 8 + 2; const positionX = Math.random() * 100; const positionY = Math.random() * 100; const delay = Math.random() * 5; const particleStyle = { backgroundColor: primaryColor, width: `${size}px`, height: `${size}px`, top: `${positionY}%`, left: `${positionX}%` }; if (!shouldReduceMotion) { particleStyle['--particle-animation-duration'] = particleAnimationDuration; particleStyle['--particle-animation-delay'] = `${delay}s`; } return jsx("div", { className: cn(styles.particle, !shouldReduceMotion && styles.particleAnimated, shouldReduceMotion && styles.reduceMotion), style: particleStyle }, `particle-${index}`); }) }); }; // Handle mouse movement interaction const handleMouseMove = useCallback(e => { if (interactionMode !== 'mouse' || !containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; const y = (e.clientY - rect.top) / rect.height; // Calculate offset based on mouse position and sensitivity const offsetX = (x - 0.5) * interactionSensitivity * 20; const offsetY = (y - 0.5) * interactionSensitivity * 20; setTransform(`translate(${offsetX}px, ${offsetY}px)`); }, [interactionMode, interactionSensitivity, shouldReduceMotion]); // Handle scroll interaction const handleScroll = useCallback(() => { if (interactionMode !== 'scroll' || !containerRef.current) return; const scrollY = window.scrollY; const windowHeight = window.innerHeight; // Calculate how far the element is in the viewport const rect = containerRef.current.getBoundingClientRect(); const elementTop = rect.top + scrollY; const elementVisible = Math.min(windowHeight, Math.max(0, scrollY + windowHeight - elementTop)) / windowHeight; // Apply transform based on scroll position const offsetY = (elementVisible - 0.5) * interactionSensitivity * 30; setTransform(`translateY(${offsetY}px)`); }, [interactionMode, interactionSensitivity, shouldReduceMotion]); // Set up event listeners useEffect(() => { if (shouldReduceMotion) return; if (interactionMode === 'mouse') { window.addEventListener('mousemove', handleMouseMove); } else if (interactionMode === 'scroll') { window.addEventListener('scroll', handleScroll); // Initial calculation handleScroll(); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('scroll', handleScroll); }; }, [interactionMode, handleMouseMove, handleScroll, shouldReduceMotion]); const setContainerRef = node => { if (containerRef.current !== node) { containerRef.current = node; } if (typeof ref === 'function') { ref(node); } else if (ref) { ref.current = node; } }; return jsxs("div", { ref: setContainerRef, className: cn('glass-dynamic-atmosphere', styles.container, className), style: containerStyle, ...rest, children: [jsx("div", { className: effectClasses, style: effectStyle }), renderParticles()] }); }); DynamicAtmosphere.displayName = 'GlassDynamicAtmosphere'; export { DynamicAtmosphere, DynamicAtmosphere as default }; //# sourceMappingURL=GlassDynamicAtmosphere.js.map