aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
514 lines (511 loc) • 15.3 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { createContext, useState, useRef, useCallback, useEffect, useContext } from 'react';
import { motion } from 'framer-motion';
import { cn } from '../../lib/utilsComprehensive.js';
import { detectDevice } from '../../utils/deviceCapabilities.js';
// Quality tier definitions
const qualityTiers = {
minimal: {
name: "Minimal Glass",
level: "minimal",
capabilities: {
backdropFilter: false,
boxShadow: true,
borderRadius: true,
transforms: false,
animations: false,
particles: false,
webgl: false,
complexGradients: false,
highResolution: false
},
limits: {
maxBlur: 0,
maxElements: 10,
animationDuration: 0,
particleCount: 0
}
},
basic: {
name: "Basic Glass",
level: "basic",
capabilities: {
backdropFilter: true,
boxShadow: true,
borderRadius: true,
transforms: true,
animations: false,
particles: false,
webgl: false,
complexGradients: false,
highResolution: false
},
limits: {
maxBlur: 8,
maxElements: 20,
animationDuration: 0,
particleCount: 0
}
},
standard: {
name: "Standard Glass",
level: "standard",
capabilities: {
backdropFilter: true,
boxShadow: true,
borderRadius: true,
transforms: true,
animations: true,
particles: false,
webgl: false,
complexGradients: true,
highResolution: false
},
limits: {
maxBlur: 16,
maxElements: 50,
animationDuration: 1,
particleCount: 0
}
},
premium: {
name: "Premium Glass",
level: "premium",
capabilities: {
backdropFilter: true,
boxShadow: true,
borderRadius: true,
transforms: true,
animations: true,
particles: true,
webgl: false,
complexGradients: true,
highResolution: true
},
limits: {
maxBlur: 24,
maxElements: 100,
animationDuration: 1,
particleCount: 25
}
},
ultra: {
name: "Ultra Glass",
level: "ultra",
capabilities: {
backdropFilter: true,
boxShadow: true,
borderRadius: true,
transforms: true,
animations: true,
particles: true,
webgl: true,
complexGradients: true,
highResolution: true
},
limits: {
maxBlur: 32,
maxElements: 200,
animationDuration: 1,
particleCount: 100
}
}
};
// Context for sharing quality settings
const GlassQualityContext = /*#__PURE__*/createContext({
tier: qualityTiers.standard,
capabilities: null,
updateTier: () => {}
});
function GlassProgressiveEnhancement({
children,
className,
autoDetect = true,
forcedTier,
adaptToNetwork = true,
adaptToBattery = true,
respectUserPreferences = true,
monitorPerformance = true,
onTierChange,
onCapabilitiesDetected
}) {
const [capabilities, setCapabilities] = useState(null);
const [currentTier, setCurrentTier] = useState(forcedTier ? qualityTiers[forcedTier] : qualityTiers.standard);
const performanceMonitor = useRef({
frameCount: 0,
lastTime: 0,
fps: 60
});
// Detect device capabilities
const detectCapabilities = useCallback(async () => {
const capabilities = {
gpu: await detectGPUCapabilities(),
cpu: detectCPUCapabilities(),
memory: detectMemoryCapabilities(),
display: detectDisplayCapabilities(),
network: detectNetworkCapabilities(),
battery: await detectBatteryCapabilities(),
preferences: detectUserPreferences()
};
return capabilities;
}, []);
// GPU detection
async function detectGPUCapabilities() {
const device = detectDevice();
// If no WebGL support, avoid creating any context
if (!device.capabilities.webgl) {
return {
renderer: "unknown",
vendor: "unknown",
tier: "low",
webglSupport: false,
webgl2Support: false
};
}
// Create a minimal WebGL context only to get renderer/vendor info, then release it
const canvas = document.createElement("canvas");
canvas.width = 1;
canvas.height = 1;
const attrs = {
alpha: false,
antialias: false,
depth: false,
stencil: false,
preserveDrawingBuffer: false,
desynchronized: true,
failIfMajorPerformanceCaveat: true,
powerPreference: "low-power"
};
const gl = canvas.getContext("webgl", attrs) || canvas.getContext("experimental-webgl", attrs);
let renderer = "unknown";
let vendor = "unknown";
if (gl) {
try {
const debugInfo = gl.getExtension && gl.getExtension("WEBGL_debug_renderer_info");
if (debugInfo) {
renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || "unknown";
vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || "unknown";
}
} catch {}
// Explicitly release the context
try {
const lose = gl.getExtension && gl.getExtension("WEBGL_lose_context");
lose && lose.loseContext && lose.loseContext();
} catch {}
}
try {
canvas.width = 0;
canvas.height = 0;
if (canvas.parentNode) canvas.parentNode.removeChild(canvas);
} catch {}
// Simplified GPU tier detection
let tier = "mid";
if (renderer.includes("Intel HD") || renderer.includes("Intel(R) UHD")) {
tier = "low";
} else if (renderer.includes("RTX") || renderer.includes("RX ") || renderer.includes("Pro")) {
tier = "high";
}
return {
renderer,
vendor,
tier,
webglSupport: true,
webgl2Support: device.capabilities.webgl2
};
}
// CPU detection
function detectCPUCapabilities() {
const cores = navigator.hardwareConcurrency || 4;
// Estimate performance based on cores and user agent
let estimatedPerformance = "mid";
if (cores <= 2) {
estimatedPerformance = "low";
} else if (cores >= 8) {
estimatedPerformance = "high";
}
return {
cores,
estimatedPerformance
};
}
// Memory detection
function detectMemoryCapabilities() {
const deviceMemory = navigator.deviceMemory;
let estimatedTier = "mid";
if (deviceMemory) {
if (deviceMemory <= 2) estimatedTier = "low";else if (deviceMemory >= 8) estimatedTier = "high";
}
return {
deviceMemory,
estimatedTier
};
}
// Display detection
function detectDisplayCapabilities() {
const pixelRatio = window.devicePixelRatio || 1;
const resolution = {
width: window.screen.width,
height: window.screen.height
};
// Detect color gamut support
let colorGamut = "srgb";
if (window.matchMedia("(color-gamut: p3)").matches) {
colorGamut = "p3";
}
if (window.matchMedia("(color-gamut: rec2020)").matches) {
colorGamut = "rec2020";
}
const hdr = window.matchMedia("(dynamic-range: high)").matches;
return {
pixelRatio,
resolution,
colorGamut,
hdr
};
}
// Network detection
function detectNetworkCapabilities() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
return {
effectiveType: connection?.effectiveType || "unknown",
saveData: connection?.saveData || false
};
}
// Battery detection
async function detectBatteryCapabilities() {
try {
if ("getBattery" in navigator) {
const battery = await navigator.getBattery();
return {
level: battery.level,
charging: battery.charging
};
}
} catch (error) {
// Battery API not supported
}
return {
level: 1,
charging: true
};
}
// User preferences detection
function detectUserPreferences() {
return {
reducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
highContrast: window.matchMedia("(prefers-contrast: high)").matches,
forcedColors: window.matchMedia("(forced-colors: active)").matches
};
}
// Calculate optimal quality tier
const calculateOptimalTier = useCallback(caps => {
if (forcedTier) return forcedTier;
let score = 50; // Base score
// GPU scoring
switch (caps.gpu.tier) {
case "high":
score += 30;
break;
case "mid":
score += 15;
break;
case "low":
score -= 15;
break;
}
if (caps.gpu.webgl2Support) score += 10;
if (!caps.gpu.webglSupport) score -= 20;
// CPU scoring
switch (caps.cpu.estimatedPerformance) {
case "high":
score += 20;
break;
case "mid":
score += 10;
break;
case "low":
score -= 10;
break;
}
// Memory scoring
switch (caps.memory.estimatedTier) {
case "high":
score += 15;
break;
case "mid":
score += 5;
break;
case "low":
score -= 10;
break;
}
// Display scoring
if (caps.display.pixelRatio > 2) score += 10;
if (caps.display.hdr) score += 5;
// Network adaptation
if (adaptToNetwork) {
switch (caps.network.effectiveType) {
case "5g":
score += 10;
break;
case "4g":
score += 5;
break;
case "3g":
score -= 10;
break;
case "2g":
score -= 20;
break;
}
if (caps.network.saveData) score -= 15;
}
// Battery adaptation
if (adaptToBattery && caps.battery.level < 0.2 && !caps.battery.charging) {
score -= 20;
}
// User preferences
if (respectUserPreferences) {
if (caps.preferences.reducedMotion) score -= 25;
if (caps.preferences.forcedColors) score -= 15;
}
// Determine tier based on score
if (score >= 90) return "ultra";
if (score >= 70) return "premium";
if (score >= 50) return "standard";
if (score >= 30) return "basic";
return "minimal";
}, [forcedTier, adaptToNetwork, adaptToBattery, respectUserPreferences]);
// Performance monitoring
const monitorFrameRate = useCallback(() => {
if (!monitorPerformance) return;
const monitor = () => {
const now = performance.now();
performanceMonitor.current.frameCount++;
if (now - performanceMonitor.current.lastTime >= 1000) {
performanceMonitor.current.fps = performanceMonitor.current.frameCount * 1000 / (now - performanceMonitor.current.lastTime);
// Downgrade tier if performance is poor
if (performanceMonitor.current.fps < 30 && currentTier.level !== "minimal") {
const currentIndex = Object.keys(qualityTiers).indexOf(currentTier.level);
if (currentIndex > 0) {
const newTierKey = Object.keys(qualityTiers)[currentIndex - 1];
setCurrentTier(qualityTiers[newTierKey]);
}
}
performanceMonitor.current.frameCount = 0;
performanceMonitor.current.lastTime = now;
}
requestAnimationFrame(monitor);
};
requestAnimationFrame(monitor);
}, [monitorPerformance, currentTier]);
// Initialize capabilities detection
useEffect(() => {
if (!autoDetect && !forcedTier) return;
detectCapabilities().then(caps => {
setCapabilities(caps);
onCapabilitiesDetected?.(caps);
if (!forcedTier) {
const optimalTier = calculateOptimalTier(caps);
setCurrentTier(qualityTiers[optimalTier]);
}
});
}, [autoDetect, forcedTier, detectCapabilities, calculateOptimalTier, onCapabilitiesDetected]);
// Start performance monitoring
useEffect(() => {
monitorFrameRate();
}, [monitorFrameRate]);
// Notify tier changes
useEffect(() => {
onTierChange?.(currentTier);
}, [currentTier, onTierChange]);
// Update CSS variables based on tier
useEffect(() => {
const root = document.documentElement;
root.style.setProperty("--glass-max-blur", `${currentTier.limits.maxBlur}px`);
root.style.setProperty("--glass-animation-duration", `${currentTier.limits.animationDuration}s`);
root.style.setProperty("--glass-max-elements", String(currentTier.limits.maxElements));
root.style.setProperty("--glass-particle-count", String(currentTier.limits.particleCount));
// Set capability flags
Object.entries(currentTier.capabilities).forEach(([key, value]) => {
root.style.setProperty(`--glass-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`, value ? "1" : "0");
});
// Apply tier-specific class
document.body.className = document.body.className.replace(/glass-tier-\w+/g, "");
document.body.classList.add(`glass-tier-${currentTier.level}`);
}, [currentTier]);
const updateTier = useCallback(tier => {
setCurrentTier(qualityTiers[tier]);
}, []);
const contextValue = {
tier: currentTier,
capabilities,
updateTier
};
return jsx(GlassQualityContext.Provider, {
value: contextValue,
children: jsxs("div", {
className: cn("glass-progressive-enhancement", className),
children: [children, process.env.NODE_ENV === "development" && jsxs("div", {
className: 'fixed bottom-2 left-2 glass-surface-primary glass-p-2 glass-radius-sm glass-text-xs opacity-50 z-50',
children: [jsxs("div", {
children: ["Quality: ", currentTier.name]
}), jsxs("div", {
children: ["FPS: ", Math.round(performanceMonitor.current.fps)]
}), capabilities && jsxs("div", {
children: ["GPU: ", capabilities.gpu.tier]
})]
})]
})
});
}
// Hook to access quality context
function useGlassQuality() {
const context = useContext(GlassQualityContext);
if (!context) {
throw new Error("useGlassQuality must be used within GlassProgressiveEnhancement");
}
return context;
}
// Enhanced glass component that respects quality settings
function EnhancedGlass({
children,
className,
blur = 16,
particles = false,
animation = true,
webgl = false,
...props
}) {
const {
tier
} = useGlassQuality();
// Adjust properties based on quality tier
Math.min(blur, tier.limits.maxBlur);
const enableParticles = particles && tier.capabilities.particles;
const enableAnimation = animation && tier.capabilities.animations;
const enableWebGL = webgl && tier.capabilities.webgl;
const enableBackdropFilter = tier.capabilities.backdropFilter;
return jsxs(motion.div, {
className: cn("relative", enableBackdropFilter && "OptimizedGlass intensity={0.2} glassBlur={6}", !enableBackdropFilter && "glass-surface-fallback", className),
animate: enableAnimation ? {
scale: [1, 1.01, 1]
} : undefined,
transition: enableAnimation ? {
duration: tier.limits.animationDuration,
repeat: Infinity
} : undefined,
style: {
// Use createGlassStyle() instead,
},
...props,
children: [children, enableParticles && jsx("div", {
className: 'absolute inset-0 pointer-events-none'
}), enableWebGL && jsx("canvas", {
className: 'absolute inset-0 pointer-events-none'
})]
});
}
export { EnhancedGlass, GlassProgressiveEnhancement, qualityTiers, useGlassQuality };
//# sourceMappingURL=GlassProgressiveEnhancement.js.map