aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
361 lines (358 loc) • 14.5 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { forwardRef, useRef, useState, useMemo, useCallback, useEffect } from 'react';
import { useReducedMotion } from '../hooks/useReducedMotion.js';
import { cn } from '../lib/utilsComprehensive.js';
import { LIQUID_GLASS, liquidGlassUtils } from '../tokens/glass.js';
import { useContrastGuard, contrastGuard } from '../utils/contrastGuard.js';
/**
* LiquidGlassMaterial - Core primitive for Apple Liquid Glass parity
*
* Features:
* - IOR-based refraction physics
* - Environmental content awareness
* - Motion-responsive effects
* - Automatic contrast enforcement
* - GPU-accelerated rendering with fallbacks
* - Performance-optimized across devices
*/
const LiquidGlassMaterial = /*#__PURE__*/forwardRef(({
as: asProp = "div",
material = "liquid",
variant = "regular",
intent = "neutral",
elevation = "level2",
ior,
thickness,
sheen,
tintMode = "adaptive",
adaptToContent = true,
adaptToMotion = true,
contrastLevel = "AA",
performanceLevel = "high",
enableRefraction,
enableReflection,
enableParallax,
enableMicroInteractions,
tint,
quality,
environmentAdaptation,
motionResponsive,
className,
radius = "lg",
interactive = false,
disabled = false,
children,
onContrastAdjustment,
onBackdropAnalysis,
contentProps,
style,
...props
}, ref) => {
const elementRef = useRef(null);
const [isHovered, setIsHovered] = useState(false);
const [isPressed, setIsPressed] = useState(false);
const [deviceTilt, setDeviceTilt] = useState({
x: 0,
y: 0
});
const [backdropSample, setBackdropSample] = useState(null);
const prefersReducedMotion = useReducedMotion();
const supportsAdvancedRendering = useMemo(() => {
if (typeof document === "undefined") return true;
try {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
return !!context;
} catch {
return false;
}
}, []);
const qualityTier = quality || performanceLevel;
const isMotionResponsiveEnabled = (motionResponsive ?? adaptToMotion) && !prefersReducedMotion;
const isEnvironmentAdaptive = environmentAdaptation ?? adaptToContent;
// Merge refs
const combinedRef = useCallback(node => {
elementRef.current = node;
if (typeof ref === "function") {
ref(node);
} else if (ref && "current" in ref) {
ref.current = node;
}
}, [ref]);
// Get performance settings
const performanceConfig = useMemo(() => LIQUID_GLASS.performance[performanceLevel], [performanceLevel]);
// Determine effective feature flags based on performance and props
const effectiveFlags = useMemo(() => {
const allowAdvanced = supportsAdvancedRendering;
return {
refraction: allowAdvanced && (enableRefraction ?? (performanceConfig.enableRefraction && material === "liquid")),
reflection: allowAdvanced && (enableReflection ?? (performanceConfig.enableReflection && material === "liquid")),
parallax: allowAdvanced && (enableParallax ?? (performanceConfig.enableParallax && material === "liquid")),
microInteractions: enableMicroInteractions ?? (performanceConfig.enableMicroInteractions && interactive)
};
}, [enableRefraction, enableReflection, enableParallax, enableMicroInteractions, performanceConfig, material, interactive, supportsAdvancedRendering]);
// Get material specification
const materialSpec = useMemo(() => {
const spec = liquidGlassUtils.getLiquidSurface(intent, elevation, material, variant);
// Apply prop overrides
if (ior !== undefined) spec.ior = ior;
if (thickness !== undefined) spec.thickness = thickness;
if (sheen !== undefined) spec.sheen = sheen;
if (tintMode !== undefined) spec.tintMode = tintMode;
return spec;
}, [intent, elevation, material, variant, ior, thickness, sheen, tintMode]);
// Use contrast guard for accessibility
const contrastAdjustment = useContrastGuard(elementRef, {
targetLevel: contrastLevel,
material,
variant,
textColor: materialSpec.text.primary,
onAdjustment: onContrastAdjustment
});
// Device orientation tracking for motion effects
useEffect(() => {
if (!isMotionResponsiveEnabled || !effectiveFlags.microInteractions) {
return;
}
const handleOrientation = event => {
if (event.beta !== null && event.gamma !== null) {
const {
sensitivity
} = LIQUID_GLASS.motionFluency.tilt;
setDeviceTilt({
x: Math.max(-1, Math.min(1, event.gamma * sensitivity)),
y: Math.max(-1, Math.min(1, event.beta * sensitivity))
});
}
};
if (typeof DeviceOrientationEvent !== "undefined") {
window.addEventListener("deviceorientation", handleOrientation);
return () => window.removeEventListener("deviceorientation", handleOrientation);
}
}, [isMotionResponsiveEnabled, effectiveFlags.microInteractions]);
// Backdrop sampling for environmental adaptation
useEffect(() => {
if (!isEnvironmentAdaptive || !elementRef.current || material !== "liquid") {
return;
}
const element = elementRef.current;
const sampleBackdrop = async () => {
try {
const sample = await contrastGuard.sampleBackdrop(element);
setBackdropSample(sample);
onBackdropAnalysis?.(sample);
} catch (error) {
console.warn("LiquidGlassMaterial: Backdrop sampling failed", error);
}
};
// Initial sample
sampleBackdrop();
// Periodic updates (throttled)
const interval = setInterval(sampleBackdrop, LIQUID_GLASS.tinting.adaptive.updateThrottle);
return () => clearInterval(interval);
}, [isEnvironmentAdaptive, material, onBackdropAnalysis]);
// Generate dynamic styles based on current state
const dynamicStyles = useMemo(() => {
const baseStyles = liquidGlassUtils.buildLiquidGlassStyles(intent, elevation, material, variant, performanceLevel);
let styles = {
...baseStyles
};
// Apply contrast adjustments
if (contrastAdjustment?.modifications) {
const {
modifications
} = contrastAdjustment;
if (modifications.opacity !== undefined) {
styles.opacity = modifications.opacity;
}
if (modifications.tint && materialSpec.tintMode === "adaptive") {
styles.background = `${styles.background}, ${modifications.tint}`;
}
if (modifications.backdropBlur !== undefined) {
const currentBlur = materialSpec.backdropBlur.px;
const adjustedBlur = currentBlur * modifications.backdropBlur;
const backdropFilter = styles.backdropFilter?.replace(/blur\([\d.]+px\)/, `blur(${adjustedBlur}px)`);
styles.backdropFilter = backdropFilter;
styles.WebkitBackdropFilter = backdropFilter;
}
}
// Apply IOR-enhanced backdrop filter
if (effectiveFlags.refraction && materialSpec.ior > 1) {
const iorMultiplier = 1 + (materialSpec.ior - 1) * 0.3;
const saturation = 1.8 * iorMultiplier;
const brightness = 1.15 + (materialSpec.ior - 1) * 0.1;
const contrast = 1.08 + materialSpec.sheen * 0.02;
const backdropFilter = `blur(${materialSpec.backdropBlur.px}px) saturate(${saturation}) brightness(${brightness}) contrast(${contrast})`;
styles.backdropFilter = backdropFilter;
styles.WebkitBackdropFilter = backdropFilter;
}
// Apply thickness-based enhancements
if (materialSpec.thickness > 2) {
const thicknessOpacity = 0.1 + materialSpec.sheen * 0.05;
styles.boxShadow = `${styles.boxShadow}, inset 0 1px ${materialSpec.thickness}px rgba(255,255,255,${thicknessOpacity})`;
}
// Motion responsiveness
if (effectiveFlags.microInteractions && !prefersReducedMotion) {
const {
hover,
press
} = LIQUID_GLASS.motionFluency;
if (isHovered) {
styles.transform = `${styles.transform || ""} translateY(-1px) scale(1.005)`.trim();
styles.transition = `all ${hover.duration}ms ${hover.easing}`;
}
if (isPressed) {
styles.transform = `${styles.transform || ""} translateY(1px) scale(0.998)`.trim();
styles.transition = `all ${press.duration}ms ${press.easing}`;
}
// Device tilt responsiveness
if (Math.abs(deviceTilt.x) > 0.1 || Math.abs(deviceTilt.y) > 0.1) {
const tiltX = deviceTilt.x * LIQUID_GLASS.motionFluency.tilt.maxTilt;
const tiltY = deviceTilt.y * LIQUID_GLASS.motionFluency.tilt.maxTilt;
styles.transform = `${styles.transform || ""} rotateX(${tiltY}deg) rotateY(${tiltX}deg)`.trim();
}
}
// Parallax depth effects
if (effectiveFlags.parallax && materialSpec.thickness > 3) {
styles.transformStyle = "preserve-3d";
styles.transform = `${styles.transform || ""} translateZ(${materialSpec.thickness * 2}px)`.trim();
}
// Border radius
const radiusMap = {
none: "0px",
sm: "4px",
md: "8px",
lg: "12px",
xl: "16px",
"2xl": "20px",
full: "9999px"
};
styles.borderRadius = radiusMap[radius];
// Performance optimizations
if (effectiveFlags.microInteractions || effectiveFlags.parallax) {
styles.willChange = "transform, opacity, backdrop-filter";
styles.contain = "layout style paint";
}
return styles;
}, [intent, elevation, material, variant, performanceLevel, materialSpec, contrastAdjustment, effectiveFlags, isHovered, isPressed, deviceTilt, prefersReducedMotion, radius]);
// Event handlers for micro-interactions
const handleMouseEnter = useCallback(() => {
if (effectiveFlags.microInteractions && !disabled) {
setIsHovered(true);
}
}, [effectiveFlags.microInteractions, disabled]);
const handleMouseLeave = useCallback(() => {
if (effectiveFlags.microInteractions) {
setIsHovered(false);
}
}, [effectiveFlags.microInteractions]);
const handleMouseDown = useCallback(() => {
if (effectiveFlags.microInteractions && !disabled) {
setIsPressed(true);
}
}, [effectiveFlags.microInteractions, disabled]);
const handleMouseUp = useCallback(() => {
if (effectiveFlags.microInteractions) {
setIsPressed(false);
}
}, [effectiveFlags.microInteractions]);
// Build CSS classes
const classes = cn(
// Base glass foundation
"liquid-glass-material", "relative", "overflow-hidden",
// Material type
material === "liquid" && "liquid-glass-advanced",
// Variant
`liquid-glass-variant-${variant}`, variant && `liquid-glass-${variant}`, qualityTier && `liquid-glass-quality-${qualityTier}`,
// Intent
intent !== "neutral" && `liquid-glass-${intent}`,
// Interactive states
interactive && "cursor-pointer", disabled && "pointer-events-none opacity-60",
// Accessibility
contrastAdjustment?.modifications.fallbackMode && "liquid-glass-contrast-fallback", !supportsAdvancedRendering && "liquid-glass-css-fallback",
// Feature flags
effectiveFlags.refraction && "liquid-glass-refraction", effectiveFlags.reflection && "liquid-glass-reflection", effectiveFlags.parallax && "liquid-glass-parallax", effectiveFlags.microInteractions && "liquid-glass-interactive", isMotionResponsiveEnabled && "liquid-glass-motion-responsive", isEnvironmentAdaptive && "liquid-glass-environment-adaptive",
// Reduced motion
prefersReducedMotion && "liquid-glass-reduced-motion", prefersReducedMotion && "liquid-glass-motion-reduced",
// Custom classes
className);
const cssVariableStyles = useMemo(() => {
const variables = {};
if (materialSpec.ior !== undefined) {
variables["--liquid-glass-ior"] = materialSpec.ior.toString();
}
if (materialSpec.thickness !== undefined) {
variables["--liquid-glass-thickness"] = `${materialSpec.thickness}px`;
}
if (tint) {
const {
r,
g,
b,
a
} = tint;
variables["--liquid-glass-tint"] = `rgba(${r}, ${g}, ${b}, ${a ?? 1})`;
}
return variables;
}, [materialSpec, tint]);
// Combine custom styles
const combinedStyles = {
...cssVariableStyles,
...dynamicStyles,
...style
};
const {
className: contentClassName,
style: contentStyle,
...restContentProps
} = contentProps ?? {};
const ComponentTag = asProp || "div";
return jsxs(ComponentTag, {
ref: combinedRef,
className: classes,
style: combinedStyles,
"data-liquid-glass-material": "true",
"data-liquid-glass-quality": qualityTier || undefined,
"data-liquid-glass-motion-responsive": isMotionResponsiveEnabled || undefined,
"data-liquid-glass-environment-adaptive": isEnvironmentAdaptive || undefined,
"data-liquid-glass-css-fallback": !supportsAdvancedRendering || undefined,
onMouseEnter: interactive ? handleMouseEnter : undefined,
onMouseLeave: interactive ? handleMouseLeave : undefined,
onMouseDown: interactive ? handleMouseDown : undefined,
onMouseUp: interactive ? handleMouseUp : undefined,
"aria-disabled": disabled || undefined,
...props,
children: [materialSpec.sheen > 0 && jsx("div", {
className: 'liquid-glass-sheen absolute inset-0 pointer-events-none',
style: {
background: `radial-gradient(60% 60% at 50% -10%, rgba(255,255,255,${0.1 + materialSpec.sheen * 0.05}) 0%, transparent 60%)`,
opacity: isHovered ? 1.2 : 1,
transition: LIQUID_GLASS.motionFluency.hover.easing
}
}), jsx("div", {
className: cn("liquid-glass-content relative z-10", contentClassName),
style: contentStyle,
...restContentProps,
children: children
}), process.env.NODE_ENV === "development" && jsxs("div", {
className: 'liquid-glass-debug absolute top-0 right-0 glass-text-xs opacity-50 pointer-events-none glass-surface-dark glass-text-primary glass-p-1 glass-radius',
children: [jsxs("div", {
children: ["Material: ", material]
}), jsxs("div", {
children: ["Variant: ", variant]
}), jsxs("div", {
children: ["IOR: ", materialSpec.ior.toFixed(2)]
}), jsxs("div", {
children: ["Thickness: ", materialSpec.thickness, "px"]
}), jsxs("div", {
children: ["Sheen: ", materialSpec.sheen]
}), contrastAdjustment && jsxs("div", {
children: ["Contrast: ", contrastAdjustment.adjustedContrast.toFixed(1), ":1"]
})]
})]
});
});
LiquidGlassMaterial.displayName = "LiquidGlassMaterial";
export { LiquidGlassMaterial };
//# sourceMappingURL=LiquidGlassMaterial.js.map