aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
533 lines (530 loc) • 18 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { useReducedMotion } from '../../hooks/useReducedMotion.js';
import { useAnimation, useSpring, motion } from 'framer-motion';
import { useRef, useState, useCallback, useEffect } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
// Environmental glass property mappings
const ENVIRONMENTAL_GLASS_CONFIGS = {
// Time of day variations
'timeOfDay-dawn': {
opacity: 0.85,
blur: 8,
temperature: 0.3,
iridescence: 0.7,
chromatic: 0.4
},
'timeOfDay-morning': {
opacity: 0.9,
blur: 6,
temperature: 0.1,
reflection: 0.8,
chromatic: 0.2
},
'timeOfDay-afternoon': {
opacity: 0.75,
blur: 4,
temperature: -0.1,
caustic: 0.6,
chromatic: 0.1
},
'timeOfDay-evening': {
opacity: 0.8,
blur: 10,
temperature: 0.4,
iridescence: 0.9,
chromatic: 0.5
},
'timeOfDay-night': {
opacity: 0.95,
blur: 12,
temperature: 0.6,
crystalline: 0.3,
chromatic: 0.7
},
// Weather variations
'weather-sunny': {
caustic: 0.8,
reflection: 0.9,
chromatic: 0.3,
iridescence: 0.4
},
'weather-cloudy': {
opacity: 0.9,
blur: 8,
refraction: 0.6,
chromatic: 0.2
},
'weather-rainy': {
viscosity: 0.7,
blur: 15,
caustic: 0.9,
chromatic: 0.6
},
'weather-stormy': {
opacity: 0.7,
blur: 20,
viscosity: 0.9,
crystalline: 0.1
},
'weather-snowy': {
crystalline: 0.8,
opacity: 0.95,
blur: 6,
temperature: 1.0
},
'weather-foggy': {
blur: 25,
opacity: 0.6,
viscosity: 0.4,
chromatic: 0.1
},
// Seasonal variations
'season-spring': {
iridescence: 0.6,
chromatic: 0.4,
temperature: 0.2,
viscosity: 0.3
},
'season-summer': {
caustic: 0.7,
reflection: 0.8,
temperature: -0.2,
chromatic: 0.2
},
'season-autumn': {
temperature: 0.3,
iridescence: 0.8,
chromatic: 0.5,
crystalline: 0.2
},
'season-winter': {
crystalline: 0.9,
temperature: 0.8,
opacity: 0.9,
chromatic: 0.3
},
// User activity adaptations
'activity-focused': {
opacity: 0.85,
blur: 4,
refraction: 0.3,
chromatic: 0.1
},
'activity-browsing': {
opacity: 0.8,
blur: 6,
iridescence: 0.4,
chromatic: 0.3
},
'activity-searching': {
blur: 8,
caustic: 0.5,
chromatic: 0.4,
viscosity: 0.3
},
'activity-creating': {
iridescence: 0.7,
chromatic: 0.6,
caustic: 0.4,
temperature: 0.2
},
'activity-analyzing': {
crystalline: 0.5,
reflection: 0.7,
chromatic: 0.2,
opacity: 0.9
},
'activity-idle': {
blur: 12,
viscosity: 0.6,
iridescence: 0.8,
chromatic: 0.5
},
// Content type adaptations
'content-text': {
opacity: 0.9,
blur: 3,
chromatic: 0.1,
reflection: 0.4
},
'content-data': {
crystalline: 0.6,
reflection: 0.8,
chromatic: 0.2,
opacity: 0.85
},
'content-media': {
caustic: 0.7,
iridescence: 0.6,
chromatic: 0.5,
refraction: 0.7
},
'content-interactive': {
viscosity: 0.4,
caustic: 0.5,
iridescence: 0.5,
chromatic: 0.4
},
'content-dashboard': {
opacity: 0.8,
blur: 5,
crystalline: 0.4,
reflection: 0.6
},
'content-form': {
opacity: 0.9,
blur: 4,
chromatic: 0.1,
viscosity: 0.2
}
};
const defaultGlassProperties = {
opacity: 0.8,
blur: 6,
refraction: 0.5,
reflection: 0.5,
chromatic: 0.3,
caustic: 0.4,
temperature: 0.0,
viscosity: 0.3,
crystalline: 0.2,
iridescence: 0.4
};
const GlassMorphingEngine = ({
children,
className = '',
environmentalContext = {},
userActivity = 'browsing',
contentType = 'text',
intensity = 1,
adaptationSpeed = 1000,
enableRealTimeAdaptation = true,
onMorphingChange
}) => {
useReducedMotion();
const containerRef = useRef(null);
const isMountedRef = useRef(true);
const timeoutIdsRef = useRef(new Set());
const isTestEnv = typeof process !== 'undefined' && process.env?.JEST_WORKER_ID !== undefined;
const [currentProperties, setCurrentProperties] = useState(defaultGlassProperties);
const [isTransitioning, setIsTransitioning] = useState(false);
const lastTargetRef = useRef(null);
const controls = useAnimation();
// Motion values for smooth transitions
const opacityMotion = useSpring(currentProperties.opacity, {
stiffness: 100,
damping: 30
});
const blurMotion = useSpring(currentProperties.blur, {
stiffness: 80,
damping: 25
});
const refractionMotion = useSpring(currentProperties.refraction, {
stiffness: 120,
damping: 35
});
const reflectionMotion = useSpring(currentProperties.reflection, {
stiffness: 110,
damping: 32
});
const chromaticMotion = useSpring(currentProperties.chromatic, {
stiffness: 90,
damping: 28
});
const causticMotion = useSpring(currentProperties.caustic, {
stiffness: 100,
damping: 30
});
const temperatureMotion = useSpring(currentProperties.temperature, {
stiffness: 70,
damping: 20
});
const viscosityMotion = useSpring(currentProperties.viscosity, {
stiffness: 85,
damping: 25
});
const crystallineMotion = useSpring(currentProperties.crystalline, {
stiffness: 95,
damping: 30
});
const iridescenceMotion = useSpring(currentProperties.iridescence, {
stiffness: 105,
damping: 32
});
const clearScheduledTasks = useCallback(() => {
timeoutIdsRef.current.forEach(id => clearTimeout(id));
timeoutIdsRef.current.clear();
}, []);
useEffect(() => {
return () => {
isMountedRef.current = false;
clearScheduledTasks();
};
}, [clearScheduledTasks]);
const scheduleTask = useCallback((callback, delay = 0) => {
if (isTestEnv) {
callback();
return;
}
const id = window.setTimeout(() => {
timeoutIdsRef.current.delete(id);
callback();
}, delay);
timeoutIdsRef.current.add(id);
}, [isTestEnv]);
const resolvedRealTime = enableRealTimeAdaptation && !isTestEnv;
// Calculate target glass properties based on context
const calculateTargetProperties = useCallback((environmental, activity, content, intensityFactor) => {
let targetProperties = {
...defaultGlassProperties
};
// Apply environmental adaptations
if (environmental.timeOfDay) {
const config = ENVIRONMENTAL_GLASS_CONFIGS[`timeOfDay-${environmental.timeOfDay}`];
targetProperties = {
...targetProperties,
...config
};
}
if (environmental.weather) {
const config = ENVIRONMENTAL_GLASS_CONFIGS[`weather-${environmental.weather}`];
targetProperties = {
...targetProperties,
...config
};
}
if (environmental.season) {
const config = ENVIRONMENTAL_GLASS_CONFIGS[`season-${environmental.season}`];
targetProperties = {
...targetProperties,
...config
};
}
// Apply user activity adaptations
const activityConfig = ENVIRONMENTAL_GLASS_CONFIGS[`activity-${activity}`];
if (activityConfig) {
targetProperties = {
...targetProperties,
...activityConfig
};
}
// Apply content type adaptations
const contentConfig = ENVIRONMENTAL_GLASS_CONFIGS[`content-${content}`];
if (contentConfig) {
targetProperties = {
...targetProperties,
...contentConfig
};
}
// Apply environmental sensor data
if (environmental.temperature !== undefined) {
const tempFactor = (environmental.temperature - 20) / 30; // Normalize around 20°C
targetProperties.temperature += tempFactor * 0.3;
targetProperties.crystalline += Math.max(0, -tempFactor * 0.4);
targetProperties.viscosity += tempFactor * 0.2;
}
if (environmental.humidity !== undefined) {
const humidityFactor = environmental.humidity / 100;
targetProperties.viscosity += humidityFactor * 0.3;
targetProperties.blur += humidityFactor * 3;
targetProperties.caustic += humidityFactor * 0.2;
}
if (environmental.lightLevel !== undefined) {
const lightFactor = environmental.lightLevel / 100;
targetProperties.reflection *= 0.5 + lightFactor * 0.5;
targetProperties.caustic *= lightFactor;
targetProperties.iridescence *= 0.3 + lightFactor * 0.7;
}
// Apply intensity scaling
Object.keys(targetProperties).forEach(key => {
const prop = key;
if (prop !== 'opacity') {
targetProperties[prop] = targetProperties[prop] * intensityFactor;
} else {
// Opacity scaling is inverse for better visibility
targetProperties[prop] = 1 - (1 - targetProperties[prop]) * intensityFactor;
}
});
// Clamp values to valid ranges
targetProperties.opacity = Math.max(0.1, Math.min(1, targetProperties.opacity));
targetProperties.blur = Math.max(0, Math.min(30, targetProperties.blur));
targetProperties.refraction = Math.max(0, Math.min(1, targetProperties.refraction));
targetProperties.reflection = Math.max(0, Math.min(1, targetProperties.reflection));
targetProperties.chromatic = Math.max(0, Math.min(1, targetProperties.chromatic));
targetProperties.caustic = Math.max(0, Math.min(1, targetProperties.caustic));
targetProperties.temperature = Math.max(-1, Math.min(1, targetProperties.temperature));
targetProperties.viscosity = Math.max(0, Math.min(1, targetProperties.viscosity));
targetProperties.crystalline = Math.max(0, Math.min(1, targetProperties.crystalline));
targetProperties.iridescence = Math.max(0, Math.min(1, targetProperties.iridescence));
return targetProperties;
}, []);
// Real-time environmental data fetching
const fetchEnvironmentalData = useCallback(async () => {
// In a real implementation, this would fetch from weather APIs, device sensors, etc.
const now = new Date();
const hour = now.getHours();
let timeOfDay = 'morning';
if (hour >= 5 && hour < 8) timeOfDay = 'dawn';else if (hour >= 8 && hour < 12) timeOfDay = 'morning';else if (hour >= 12 && hour < 17) timeOfDay = 'afternoon';else if (hour >= 17 && hour < 20) timeOfDay = 'evening';else timeOfDay = 'night';
const month = now.getMonth();
let season = 'spring';
if (month >= 2 && month <= 4) season = 'spring';else if (month >= 5 && month <= 7) season = 'summer';else if (month >= 8 && month <= 10) season = 'autumn';else season = 'winter';
// Mock environmental data - in real app, would use actual sensors/APIs
return {
timeOfDay,
season,
weather: 'sunny',
// Would come from weather API
temperature: 22,
// Would come from device sensors or location-based weather
humidity: 45,
lightLevel: 75
};
}, []);
// Morphing transition effect
const performMorphingTransition = useCallback(async targetProperties => {
if (!isMountedRef.current) return;
setIsTransitioning(true);
// Animate all properties smoothly
opacityMotion.set(targetProperties.opacity);
blurMotion.set(targetProperties.blur);
refractionMotion.set(targetProperties.refraction);
reflectionMotion.set(targetProperties.reflection);
chromaticMotion.set(targetProperties.chromatic);
causticMotion.set(targetProperties.caustic);
temperatureMotion.set(targetProperties.temperature);
viscosityMotion.set(targetProperties.viscosity);
crystallineMotion.set(targetProperties.crystalline);
iridescenceMotion.set(targetProperties.iridescence);
// Update state
if (isMountedRef.current) {
setCurrentProperties(targetProperties);
}
// Notify parent component
onMorphingChange?.(targetProperties);
// End transition after animation completes
scheduleTask(() => {
if (isMountedRef.current) {
setIsTransitioning(false);
}
}, adaptationSpeed);
}, [opacityMotion, blurMotion, refractionMotion, reflectionMotion, chromaticMotion, causticMotion, temperatureMotion, viscosityMotion, crystallineMotion, iridescenceMotion, adaptationSpeed, onMorphingChange, scheduleTask]);
// Update glass properties when context changes
useEffect(() => {
const updateGlassProperties = async () => {
let finalEnvironmentalContext = {
...environmentalContext
};
// Fetch real-time data if enabled
if (resolvedRealTime) {
const realTimeData = await fetchEnvironmentalData();
finalEnvironmentalContext = {
...finalEnvironmentalContext,
...realTimeData
};
}
const targetProperties = calculateTargetProperties(finalEnvironmentalContext, userActivity, contentType, intensity);
const previousTarget = lastTargetRef.current;
const hasChanged = !previousTarget || Object.keys(targetProperties).some(key => targetProperties[key] !== previousTarget[key]);
if (!hasChanged) {
return;
}
lastTargetRef.current = targetProperties;
performMorphingTransition(targetProperties);
};
updateGlassProperties();
}, [environmentalContext, userActivity, contentType, intensity, resolvedRealTime, calculateTargetProperties, performMorphingTransition, fetchEnvironmentalData]);
// Auto-update environmental data periodically
useEffect(() => {
if (!resolvedRealTime) return;
const interval = window.setInterval(async () => {
const realTimeData = await fetchEnvironmentalData();
const targetProperties = calculateTargetProperties({
...environmentalContext,
...realTimeData
}, userActivity, contentType, intensity);
const previousTarget = lastTargetRef.current;
const hasChanged = !previousTarget || Object.keys(targetProperties).some(key => targetProperties[key] !== previousTarget[key]);
if (!hasChanged) {
return;
}
lastTargetRef.current = targetProperties;
performMorphingTransition(targetProperties);
}, 60000); // Update every minute
return () => clearInterval(interval);
}, [resolvedRealTime, environmentalContext, userActivity, contentType, intensity, calculateTargetProperties, performMorphingTransition, fetchEnvironmentalData]);
// Generate dynamic CSS for glass effects
// Bind motion values to CSS variables on the container element
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const subs = [];
subs.push(opacityMotion.on('change', v => el.style.setProperty('--gm-opacity', String(v))));
subs.push(blurMotion.on('change', v => el.style.setProperty('--gm-blur', `${v}px`)));
subs.push(refractionMotion.on('change', v => el.style.setProperty('--gm-refraction', String(v))));
subs.push(reflectionMotion.on('change', v => el.style.setProperty('--gm-reflection', String(v))));
subs.push(chromaticMotion.on('change', v => el.style.setProperty('--gm-chromatic', String(v))));
subs.push(causticMotion.on('change', v => el.style.setProperty('--gm-caustic', String(v))));
subs.push(temperatureMotion.on('change', v => {
el.style.setProperty('--gm-temperature', String(v));
// Map temperature [-1..1] -> hue around blue (200) to warm (+/- 60)
const hue = Math.round(v * 60 + 200);
el.style.setProperty('--gm-hue', String(hue));
}));
subs.push(viscosityMotion.on('change', v => el.style.setProperty('--gm-viscosity', String(v))));
subs.push(crystallineMotion.on('change', v => el.style.setProperty('--gm-crystalline', String(v))));
subs.push(iridescenceMotion.on('change', v => el.style.setProperty('--gm-iridescence', String(v))));
// Prime initial values
const prime = () => {
el.style.setProperty('--gm-opacity', String(opacityMotion.get()));
el.style.setProperty('--gm-blur', `${blurMotion.get()}px`);
el.style.setProperty('--gm-refraction', String(refractionMotion.get()));
el.style.setProperty('--gm-reflection', String(reflectionMotion.get()));
el.style.setProperty('--gm-chromatic', String(chromaticMotion.get()));
el.style.setProperty('--gm-caustic', String(causticMotion.get()));
const t = temperatureMotion.get();
el.style.setProperty('--gm-temperature', String(t));
el.style.setProperty('--gm-hue', String(Math.round(t * 60 + 200)));
el.style.setProperty('--gm-viscosity', String(viscosityMotion.get()));
el.style.setProperty('--gm-crystalline', String(crystallineMotion.get()));
el.style.setProperty('--gm-iridescence', String(iridescenceMotion.get()));
};
prime();
return () => {
subs.forEach(unsub => unsub());
};
}, [containerRef, opacityMotion, blurMotion, refractionMotion, reflectionMotion, chromaticMotion, causticMotion, temperatureMotion, viscosityMotion, crystallineMotion, iridescenceMotion]);
return jsxs(motion.div, {
ref: containerRef,
className: cn('glass-morphing-container', className),
animate: controls,
children: [jsxs("div", {
className: cn('glass-morphing-effects'),
children: [jsx("div", {
className: cn('glass-layer glass-base')
}), jsx("div", {
className: cn('glass-layer glass-refraction')
}), jsx("div", {
className: cn('glass-layer glass-reflection')
}), jsx("div", {
className: cn('glass-layer glass-chromatic')
}), jsx("div", {
className: cn('glass-layer glass-caustic')
}), jsx("div", {
className: cn('glass-layer glass-crystalline')
}), jsx("div", {
className: cn('glass-layer glass-viscosity')
})]
}), jsx("div", {
className: cn('glass-morphing-content'),
children: children
}), isTransitioning && jsx("div", {
className: cn("glass-morphing-transition-indicator"),
children: jsx("div", {
className: cn("transition-shimmer")
})
})]
});
};
export { GlassMorphingEngine, GlassMorphingEngine as default };
//# sourceMappingURL=GlassMorphingEngine.js.map