UNPKG

aura-glass

Version:

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

626 lines (623 loc) 19.4 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { motion, useMotionValue, useSpring } from 'framer-motion'; import { useContext, useState, useCallback, useMemo, useEffect, useRef, createContext } from 'react'; import { cn } from '../../lib/utilsComprehensive.js'; import { createGlassStyle } from '../../core/mixins/glassMixins.js'; const defaultConfig = { opacity: { base: 0.1, hover: 0.15, active: 0.2 }, blur: { base: 20, hover: 15, active: 10 }, brightness: { base: 1, hover: 1.1, active: 1.2 }, tinting: { enabled: true, intensity: 0.3, adaptiveColor: true }, texture: { type: "smooth", intensity: 0.5, animated: false }, environment: { weatherReactive: true, timeReactive: true, temperatureReactive: true } }; const GlassEngineContext = /*#__PURE__*/createContext(null); const useGlassEngine = () => { useReducedMotion(); const context = useContext(GlassEngineContext); if (!context) { throw new Error("useGlassEngine must be used within GlassEngineProvider"); } return context; }; const generateTextureCSS = (type, intensity) => { const patterns = { smooth: `linear-gradient(135deg, rgba(255,255,255,${0.1 * intensity}), transparent)`, frosted: ` radial-gradient(circle at 20% 30%, rgba(255,255,255,${0.15 * intensity}) 1px, transparent 1px), radial-gradient(circle at 70% 80%, rgba(255,255,255,${0.1 * intensity}) 1px, transparent 1px), linear-gradient(135deg, rgba(255,255,255,${0.05 * intensity}), transparent) `, rippled: ` repeating-linear-gradient( 45deg, rgba(255,255,255,${0.08 * intensity}), rgba(255,255,255,${0.08 * intensity}) 2px, transparent 2px, transparent 8px ) `, crystalline: ` conic-gradient(from 0deg at 50% 50%, rgba(255,255,255,${0.2 * intensity}) 0deg, transparent 60deg, rgba(255,255,255,${0.1 * intensity}) 120deg, transparent 180deg, rgba(255,255,255,${0.15 * intensity}) 240deg, transparent 300deg ) `, liquid: ` radial-gradient(ellipse at top, rgba(255,255,255,${0.12 * intensity}), transparent), radial-gradient(ellipse at bottom, rgba(255,255,255,${0.08 * intensity}), transparent) ` }; return patterns[type] || patterns.smooth; }; const GlassEngineProvider = ({ children, initialConfig }) => { const [config, setConfig] = useState({ ...defaultConfig, ...initialConfig }); const updateConfig = useCallback(newConfig => { setConfig(prev => ({ ...prev, ...newConfig, opacity: { ...prev.opacity, ...(newConfig.opacity || {}) }, blur: { ...prev.blur, ...(newConfig.blur || {}) }, brightness: { ...prev.brightness, ...(newConfig.brightness || {}) }, tinting: { ...prev.tinting, ...(newConfig.tinting || {}) }, texture: { ...prev.texture, ...(newConfig.texture || {}) }, environment: { ...prev.environment, ...(newConfig.environment || {}) } })); }, []); const buildGlassEngineStyle = useCallback((variant = "base", customProps) => { const effectiveConfig = customProps ? { ...config, ...customProps } : config; const { opacity, blur, brightness, texture } = effectiveConfig; opacity[variant] || opacity.base; blur[variant] || blur.base; brightness[variant] || brightness.base; return createGlassStyle({ intent: "neutral", elevation: "level2" }); }, [config]); const getTexturePattern = useCallback(type => { return generateTextureCSS(type, config.texture.intensity); }, [config.texture.intensity]); const adaptToEnvironment = useCallback(conditions => { // Use functional update to avoid closing over `config` and to keep // this callback stable. Also avoid mutating nested state. setConfig(prev => { // If none of the environment reactions are enabled, no change. if (!prev.environment.weatherReactive && !prev.environment.timeReactive && !prev.environment.temperatureReactive) { return prev; } // Start from clones of nested objects to avoid mutating previous state. let next = { ...prev, opacity: { ...prev.opacity }, blur: { ...prev.blur }, brightness: { ...prev.brightness }, texture: { ...prev.texture } }; // Weather adaptations if (prev.environment.weatherReactive) { switch (conditions.weather) { case "rainy": next.texture = { ...next.texture, type: "rippled", animated: true }; next.opacity.base = Math.min(0.25, next.opacity.base + 0.05); next.blur.base = Math.max(10, next.blur.base - 5); break; case "foggy": next.opacity.base = Math.max(0.05, next.opacity.base - 0.03); next.blur.base = Math.min(30, next.blur.base + 8); break; case "snowy": next.texture = { ...next.texture, type: "crystalline" }; next.brightness.base = Math.min(1.3, next.brightness.base + 0.1); break; case "sunny": next.brightness.base = Math.min(1.4, next.brightness.base + 0.15); next.opacity.base = Math.min(0.18, next.opacity.base + 0.02); break; } } // Time adaptations if (prev.environment.timeReactive) { const hour = conditions.timeOfDay; if (hour >= 20 || hour <= 6) { // Night time - more subtle effects next.opacity.base = Math.max(0.05, next.opacity.base - 0.02); next.blur.base = Math.min(25, next.blur.base + 3); } else if (hour >= 12 && hour <= 16) { // Midday - stronger effects next.brightness.base = Math.min(1.2, next.brightness.base + 0.05); } } // Temperature adaptations if (prev.environment.temperatureReactive) { if (conditions.temperature < 0) { next.texture = { ...next.texture, type: "frosted" }; } else if (conditions.temperature > 30) { next.texture = { ...next.texture, type: "liquid", animated: true }; } } return next; }); }, []); const contextValue = { config, updateConfig, createGlassStyle: buildGlassEngineStyle, getTexturePattern, adaptToEnvironment }; return jsx(GlassEngineContext.Provider, { value: contextValue, children: children }); }; const AdaptiveGlass = ({ children, variant = "base", textureOverride, environmentalAware = true, className = "", as: Component = "div", ...props }) => { const prefersReducedMotion = useReducedMotion(); const { createGlassStyle, config, adaptToEnvironment } = useGlassEngine(); const [currentVariant, setCurrentVariant] = useState(variant); const glassStyle = useMemo(() => { const customConfig = textureOverride ? { texture: { ...config.texture, type: textureOverride } } : undefined; return createGlassStyle(currentVariant, customConfig); }, [createGlassStyle, currentVariant, textureOverride, config.texture]); // Simulate environmental conditions (in real app, would come from APIs) useEffect(() => { if (environmentalAware) { const mockConditions = { weather: "sunny", temperature: 20, timeOfDay: new Date().getHours(), humidity: 60, season: "spring" }; adaptToEnvironment(mockConditions); } }, [environmentalAware, adaptToEnvironment]); return jsx(motion.div, { className: `relative ${className}`, style: glassStyle, onMouseEnter: () => setCurrentVariant("hover"), onMouseLeave: () => setCurrentVariant(variant), onMouseDown: () => setCurrentVariant("active"), onMouseUp: () => setCurrentVariant("hover"), transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.2 }, ...props, children: children }); }; const GlassOpacityEngine = ({ children, dynamicOpacity = true, opacityRange = [0.05, 0.3], trigger = "hover", className = "" }) => { const { createGlassStyle, updateConfig } = useGlassEngine(); const [opacity, setOpacity] = useState(opacityRange[0]); useMotionValue(0); const springOpacity = useSpring(opacity, { stiffness: 300, damping: 30 }); useEffect(() => { if (!dynamicOpacity) return; let interval; switch (trigger) { case "time": interval = setInterval(() => { const hour = new Date().getHours(); const timeOpacity = 0.1 + (Math.sin(hour / 24 * Math.PI * 2) + 1) * 0.1; setOpacity(Math.max(opacityRange[0], Math.min(opacityRange[1], timeOpacity))); }, 60000); break; case "scroll": const handleScroll = () => { const scrollProgress = window.scrollY / (document.body.scrollHeight - window.innerHeight); const scrollOpacity = opacityRange[0] + scrollProgress * (opacityRange[1] - opacityRange[0]); setOpacity(scrollOpacity); }; window.addEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll); } return () => { if (interval) clearInterval(interval); }; }, [dynamicOpacity, trigger, opacityRange]); return jsx(motion.div, { className: className, style: { ...createGlassStyle("base"), backgroundColor: `rgba(255, 255, 255, ${springOpacity})` }, onMouseEnter: trigger === "hover" ? () => setOpacity(opacityRange[1]) : undefined, onMouseLeave: trigger === "hover" ? () => setOpacity(opacityRange[0]) : undefined, children: children }); }; const GlassColorTinting = ({ children, contentAware = true, tintColor, intensity = 0.3, className = "" }) => { const { createGlassStyle } = useGlassEngine(); const [adaptiveTint, setAdaptiveTint] = useState(tintColor || "var(--glass-bg-default)"); const containerRef = useRef(null); const analyzeContent = useCallback(() => { if (!contentAware || !containerRef.current) return; // Simple content analysis - in production, would be more sophisticated const images = containerRef.current.querySelectorAll("img"); if (images.length > 0) { // Simulate extracting dominant color from first image const colors = ["var(--glass-color-primary)", "var(--glass-color-danger)", "var(--glass-color-success)", "var(--glass-color-warning)", "#8b5cf6"]; const randomColor = colors[Math.floor(Math.random() * colors.length)]; const r = parseInt(randomColor.slice(1, 3), 16); const g = parseInt(randomColor.slice(3, 5), 16); const b = parseInt(randomColor.slice(5, 7), 16); setAdaptiveTint(`rgba(${r}, ${g}, ${b}, ${intensity})`); } }, [contentAware, intensity]); useEffect(() => { if (contentAware) { analyzeContent(); } }, [analyzeContent, contentAware]); const tintedStyle = useMemo(() => ({ ...createGlassStyle("base"), backgroundColor: adaptiveTint }), [createGlassStyle, adaptiveTint]); return jsx("div", { ref: containerRef, className: className, style: tintedStyle, children: children }); }; const GlassTextureVariations = ({ children, contentType = "text", autoAdapt = true, className = "" }) => { const { createGlassStyle, updateConfig } = useGlassEngine(); const [currentTexture, setCurrentTexture] = useState("smooth"); useEffect(() => { if (!autoAdapt) return; const textureMap = { text: "smooth", image: "crystalline", video: "liquid", code: "frosted", data: "rippled" }; const newTexture = textureMap[contentType]; setCurrentTexture(newTexture); updateConfig({ texture: { type: newTexture, intensity: 0.6, animated: contentType === "video" } }); }, [contentType, autoAdapt, updateConfig]); return jsx("div", { className: className, style: createGlassStyle("base"), children: children }); }; const EnvironmentalGlass = ({ children, weatherAPI = false, timeSync = true, className = "" }) => { const prefersReducedMotion = useReducedMotion(); const { adaptToEnvironment, createGlassStyle } = useGlassEngine(); const [conditions, setConditions] = useState({ weather: "sunny", temperature: 20, timeOfDay: new Date().getHours(), humidity: 50, season: "spring" }); useEffect(() => { if (timeSync) { const updateTime = () => { setConditions(prev => ({ ...prev, timeOfDay: new Date().getHours() })); }; updateTime(); const interval = setInterval(updateTime, 60000); return () => clearInterval(interval); } }, [timeSync]); useEffect(() => { if (weatherAPI) { // Simulate weather API call const mockWeatherConditions = { ...conditions, weather: ["sunny", "cloudy", "rainy"][Math.floor(Math.random() * 3)], temperature: Math.random() * 30 + 5, humidity: Math.random() * 80 + 20 }; setConditions(mockWeatherConditions); } }, [weatherAPI]); useEffect(() => { adaptToEnvironment(conditions); }, [conditions, adaptToEnvironment]); return jsx(motion.div, { className: className, style: createGlassStyle("base"), animate: { filter: `hue-rotate(${conditions.timeOfDay * 15}deg) brightness(${1 + (conditions.timeOfDay > 12 ? (24 - conditions.timeOfDay) / 24 : conditions.timeOfDay / 24) * 0.2})` }, transition: prefersReducedMotion ? { duration: 0 } : { duration: 2 }, children: children }); }; const GlassEngineDemo = () => { const { config, updateConfig, createGlassStyle } = useGlassEngine(); return jsxs("div", { className: 'space-y-6 glass-p-6', children: [jsx("div", { className: 'glass-grid glass-grid-cols-2 md:grid-cols-4 glass-gap-4', children: ["smooth", "frosted", "rippled", "crystalline"].map(texture => jsxs(AdaptiveGlass, { textureOverride: texture, className: 'glass-p-4 text-center', children: [jsx("h3", { className: cn("glass-text-primary glass-font-medium glass-capitalize glass-mb-2"), children: texture }), jsx("p", { className: cn("glass-text-secondary glass-text-sm"), children: "Glass texture variation" })] }, texture)) }), jsxs("div", { className: 'glass-grid glass-grid-cols-1 md:grid-cols-3 glass-gap-4', children: [jsxs(GlassOpacityEngine, { trigger: "hover", className: "glass-p-4", children: [jsx("h3", { className: cn("glass-text-primary glass-font-medium glass-mb-2"), children: "Dynamic Opacity" }), jsx("p", { className: cn("glass-text-secondary glass-text-sm"), children: "Hover to see opacity change" })] }), jsxs(GlassColorTinting, { contentAware: true, className: "glass-p-4", children: [jsx("h3", { className: cn("glass-text-primary glass-font-medium glass-mb-2"), children: "Content-Aware Tinting" }), jsx("p", { className: cn("glass-text-secondary glass-text-sm"), children: "Adapts to content colors" })] }), jsxs(EnvironmentalGlass, { timeSync: true, className: "glass-p-4", children: [jsx("h3", { className: cn("glass-text-primary glass-font-medium glass-mb-2"), children: "Environmental" }), jsx("p", { className: cn("glass-text-secondary glass-text-sm"), children: "Reacts to time and weather" })] })] }), jsxs("div", { className: "glass-p-4", style: createGlassStyle("base"), children: [jsx("h3", { className: cn("glass-text-primary glass-font-medium glass-mb-4"), children: "Glass Engine Controls" }), jsxs("div", { className: 'glass-grid glass-grid-cols-1 md:grid-cols-2 glass-gap-4', children: [jsxs("div", { children: [jsx("label", { className: cn("glass-display-block glass-text-secondary glass-text-sm glass-mb-2"), children: "Base Opacity" }), jsx("input", { type: "range", min: "0.05", max: "0.3", step: "0.01", value: config.opacity.base, onChange: e => updateConfig({ opacity: { ...config.opacity, base: parseFloat(e.target.value) } }), className: "glass-w-full glass-focus glass-touch-target glass-contrast-guard" })] }), jsxs("div", { children: [jsx("label", { className: cn("glass-display-block glass-text-secondary glass-text-sm glass-mb-2"), children: "Blur Intensity" }), jsx("input", { type: "range", min: "5", max: "30", value: config.blur.base, onChange: e => updateConfig({ blur: { ...config.blur, base: parseInt(e.target.value) } }), className: "glass-w-full glass-focus glass-touch-target glass-contrast-guard" })] })] }), jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mt-4', children: [jsx("span", { className: cn("glass-text-secondary"), children: "Environmental Reactions" }), jsxs("label", { className: 'glass-flex glass-items-center space-x-2', children: [jsx("input", { type: "checkbox", checked: config.environment.weatherReactive, onChange: e => updateConfig({ environment: { ...config.environment, weatherReactive: e.target.checked } }), className: "glass-focus glass-touch-target glass-contrast-guard" }), jsx("span", { className: cn("glass-text-primary glass-text-sm"), children: "Weather" })] })] })] })] }); }; const GlassEngine = ({ initialConfig, renderDemo = true, children, className, ...rest }) => { return jsx(GlassEngineProvider, { initialConfig: initialConfig, children: jsx("div", { className: cn("glass-engine-wrapper glass-space-y-6", className), ...rest, children: children ?? (renderDemo ? jsx(GlassEngineDemo, {}) : null) }) }); }; export { AdaptiveGlass, EnvironmentalGlass, GlassColorTinting, GlassEngine, GlassEngineDemo, GlassEngineProvider, GlassOpacityEngine, GlassTextureVariations, GlassEngine as default, useGlassEngine }; //# sourceMappingURL=GlassEngine.js.map