aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
415 lines (412 loc) • 12.4 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { useReducedMotion } from '../../hooks/useReducedMotion.js';
import React, { forwardRef, useRef, useState, useEffect, useCallback } from 'react';
import { useMotionValue, useSpring, useTransform, motion, AnimatePresence } from 'framer-motion';
import { cn } from '../../lib/utilsComprehensive.js';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import '../../primitives/motion/MotionFramer.js';
const GlassLiquidTransition = /*#__PURE__*/forwardRef(({
children,
className,
variant = "morph",
duration = 1000,
stiffness = 100,
damping = 10,
tension = 200,
mass = 1,
interactive = true,
trigger = "hover",
intensity = 1,
...restProps
}, ref) => {
useReducedMotion();
const containerRef = useRef(null);
const [containerElement, setContainerElement] = useState(null);
const [isActive, setIsActive] = useState(false);
const [mousePos, setMousePos] = useState({
x: 0,
y: 0
});
// Default values for missing props
`liquid-transition-${Math.random().toString(36).substr(2, 9)}`;
// Motion values for liquid effect
const liquidX = useMotionValue(0);
const liquidY = useMotionValue(0);
const liquidScale = useMotionValue(1);
const liquidRotate = useMotionValue(0);
// Spring physics
const springConfig = {
stiffness,
damping,
mass
};
const smoothX = useSpring(liquidX, springConfig);
const smoothY = useSpring(liquidY, springConfig);
const smoothScale = useSpring(liquidScale, {
...springConfig,
stiffness: stiffness * 2
});
const smoothRotate = useSpring(liquidRotate, springConfig);
// Transform values for different effects
const morphX = useTransform(smoothX, [-1, 1], [-20 * intensity, 20 * intensity]);
const morphY = useTransform(smoothY, [-1, 1], [-20 * intensity, 20 * intensity]);
const flowScale = useTransform(smoothScale, [0.8, 1.2], [0.95, 1.05]);
// Update containerElement from ref
useEffect(() => {
if (containerRef.current) {
setContainerElement(containerRef.current);
}
}, []);
// Handle mouse movement for interactive effects
const handleMouseMove = useCallback(e => {
if (!interactive || !containerElement) return;
const rect = containerElement.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
setMousePos({
x,
y
});
if (variant === "morph" || variant === "flow") {
liquidX.set(x * 2);
liquidY.set(y * 2);
}
if (variant === "ripple") {
const distance = Math.sqrt(x * x + y * y);
liquidScale.set(1 + distance * 0.5);
liquidRotate.set(Math.atan2(y, x) * 180 / Math.PI);
}
}, [interactive, variant, liquidX, liquidY, liquidScale, liquidRotate]);
// Handle trigger events
const handleTrigger = useCallback(() => {
if (trigger === "click") {
setIsActive(!isActive);
}
}, [trigger, isActive]);
// Auto trigger effect
useEffect(() => {
if (trigger === "auto") {
const interval = setInterval(() => {
setIsActive(prev => !prev);
}, duration * 2);
return () => clearInterval(interval);
}
}, [trigger, duration]);
// Scroll trigger effect
useEffect(() => {
if (trigger === "scroll") {
const handleScroll = () => {
const scrollProgress = window.scrollY / (document.body.scrollHeight - window.innerHeight);
liquidScale.set(1 + scrollProgress * 0.2);
liquidRotate.set(scrollProgress * 360);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}
}, [trigger, liquidScale, liquidRotate]);
// Get variant-specific animation
const getVariantAnimation = () => {
switch (variant) {
case "morph":
return {
initial: {
borderRadius: "12px",
scale: 1
},
animate: {
borderRadius: isActive ? "50%" : "12px",
scale: isActive ? 1.1 : 1
},
transition: {
type: "spring",
tension,
friction: 20
},
style: {
x: morphX,
y: morphY
}
};
case "flow":
return {
initial: {
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)"
},
animate: {
clipPath: isActive ? "polygon(5% 5%, 95% 10%, 90% 95%, 10% 90%)" : "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)"
},
transition: {
duration: duration / 1000,
ease: "easeInOut"
},
style: {
scale: flowScale
}
};
case "ripple":
return {
initial: {
scale: 1,
opacity: 1
},
animate: {
scale: [1, 1.2, 1],
opacity: [1, 0.8, 1]
},
transition: {
duration: duration / 1000,
repeat: isActive ? Infinity : 0,
ease: "easeInOut"
}
};
case "dissolve":
return {
initial: {
filter: "blur(0px)",
opacity: 1
},
animate: {
filter: isActive ? "blur(var(--glass-blur-md))" : "blur(0px)",
opacity: isActive ? 0.7 : 1,
scale: isActive ? 1.05 : 1
},
transition: {
duration: duration / 1000,
ease: "easeInOut"
}
};
case "splash":
return {
initial: {
scale: 0,
opacity: 0,
rotate: 0
},
animate: {
scale: isActive ? [0, 1.5, 1] : 0,
opacity: isActive ? [0, 1, 0.8] : 0,
rotate: isActive ? [0, 180, 360] : 0
},
transition: {
duration: duration / 1000,
times: [0, 0.5, 1],
ease: "easeOut"
}
};
default:
return {};
}
};
const animationProps = getVariantAnimation();
return jsxs(motion.div, {
ref: containerRef,
className: cn("relative overflow-hidden", "transform-gpu will-change-transform", className),
onMouseMove: handleMouseMove,
onMouseEnter: () => trigger === "hover" && setIsActive(true),
onMouseLeave: () => trigger === "hover" && setIsActive(false),
onClick: handleTrigger,
...animationProps,
children: [jsx(motion.div, {
className: 'absolute inset-0 pointer-events-none',
style: {
background: `radial-gradient(
circle at ${50 + mousePos.x * 50}% ${50 + mousePos.y * 50}%,
rgba(var(--glass-color-primary) / 0.3) 0%,
transparent 70%
)`,
x: smoothX,
y: smoothY,
scale: smoothScale,
rotate: smoothRotate
}
}), jsx("div", {
className: 'relative z-10',
children: children
}), variant === "ripple" && isActive && jsx(LiquidRipples, {
x: mousePos.x,
y: mousePos.y,
intensity: intensity,
duration: duration
}), variant === "splash" && isActive && jsx(LiquidSplash, {
x: mousePos.x,
y: mousePos.y,
intensity: intensity,
duration: duration
})]
});
});
GlassLiquidTransition.displayName = "GlassLiquidTransition";
function LiquidRipples({
x,
y,
intensity,
duration
}) {
const prefersReducedMotion = useReducedMotion();
const [ripples, setRipples] = useState([]);
useEffect(() => {
const ripple = {
id: Date.now(),
x,
y
};
setRipples(prev => [...prev, ripple]);
setTimeout(() => {
setRipples(prev => prev.filter(r => r.id !== ripple.id));
}, duration);
}, [x, y, duration]);
return jsx(AnimatePresence, {
children: ripples.map(ripple => jsx(motion.div, {
className: 'absolute pointer-events-none',
initial: {
scale: 0,
opacity: 1
},
animate: prefersReducedMotion ? {} : {
scale: 3 * intensity,
opacity: 0
},
exit: {
opacity: 0
},
transition: prefersReducedMotion ? {
duration: 0
} : {
duration: duration / 1000,
ease: "easeOut"
},
style: {
left: `${50 + ripple.x * 50}%`,
top: `${50 + ripple.y * 50}%`,
x: "-50%",
y: "-50%"
},
children: jsx(OptimizedGlassCore, {
intensity: "medium",
blur: "strong",
className: 'w-32 h-32 glass-radius-full glass-border-2 glass-border-white/30'
})
}, ripple.id))
});
}
function LiquidSplash({
x,
y,
intensity,
duration
}) {
const prefersReducedMotion = useReducedMotion();
const particleCount = Math.floor(8 * intensity);
return jsx(AnimatePresence, {
children: Array.from({
length: particleCount
}).map((_, i) => {
const angle = i / particleCount * Math.PI * 2;
const distance = 100 * intensity;
return jsx(motion.div, {
className: 'absolute pointer-events-none',
initial: {
x: `${50 + x * 50}%`,
y: `${50 + y * 50}%`,
scale: 0,
opacity: 1
},
animate: {
x: `${50 + x * 50 + Math.cos(angle) * distance}%`,
y: `${50 + y * 50 + Math.sin(angle) * distance}%`,
scale: [0, 1, 0],
opacity: [1, 0.5, 0]
},
exit: {
opacity: 0
},
transition: prefersReducedMotion ? {
duration: 0
} : {
duration: duration / 1000,
ease: "easeOut"
},
style: {
translateX: "-50%",
translateY: "-50%"
},
children: jsx("div", {
className: 'w-4 h-4 glass-radius-full glass-surface-primary'
})
}, i);
})
});
}
const GlassLiquidContainer = /*#__PURE__*/React.forwardRef(({
children,
className,
morphOnScroll = false,
morphIntensity = 1,
...restProps
}, ref) => {
const containerRef = useRef(null);
const [containerElement, setContainerElement] = useState(null);
const [morphAmount, setMorphAmount] = useState(0);
// Default values for missing props
const containerId = `liquid-container-${Math.random().toString(36).substr(2, 9)}`;
const role = "region";
const ariaLabel = "Liquid morphing container";
useEffect(() => {
if (!morphOnScroll) return;
const handleScroll = () => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const elementCenter = rect.top + rect.height / 2;
const distanceFromCenter = Math.abs(viewportHeight / 2 - elementCenter);
const maxDistance = viewportHeight / 2;
const morph = Math.max(0, 1 - distanceFromCenter / maxDistance);
setMorphAmount(morph * morphIntensity);
};
window.addEventListener("scroll", handleScroll);
handleScroll(); // Initial calculation
return () => window.removeEventListener("scroll", handleScroll);
}, [morphOnScroll, morphIntensity, containerRef]);
return jsx(motion.div, {
ref: node => {
// Handle forwarded ref
if (ref) {
if (typeof ref === "function") {
ref(node);
} else if (ref && "current" in ref && Object.getOwnPropertyDescriptor(ref, "current")?.writable !== false) {
ref.current = node;
}
}
// Update internal state
setContainerElement(node);
},
className: cn("relative", className),
id: containerId,
role: role,
"aria-label": ariaLabel,
style: {
borderRadius: `${12 + morphAmount * 38}px`,
transform: `scale(${1 + morphAmount * 0.05})`
} ,
transition: {
type: "spring",
stiffness: 100,
damping: 20
} ,
...restProps,
children: jsx(OptimizedGlassCore, {
intensity: "subtle",
blur: "medium",
className: "glass-w-full glass-h-full",
children: children
})
});
});
GlassLiquidContainer.displayName = "GlassLiquidContainer";
export { GlassLiquidContainer, GlassLiquidTransition };
//# sourceMappingURL=GlassLiquidTransition.js.map