aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
298 lines (295 loc) • 8.38 kB
JavaScript
'use client';
import { jsx } from 'react/jsx-runtime';
import { forwardRef, useRef, useState, useEffect } from 'react';
import { cn } from '../lib/utilsComprehensive.js';
import { useMultiSpring } from '../animations/hooks/useMultiSpringBasic.js';
const MotionNative = /*#__PURE__*/forwardRef(({
type = 'fade',
direction = 'none',
duration = 300,
delay = 0,
easing = 'ease-out',
physics = false,
springConfig,
animateOnMount = true,
animateOnHover = false,
animateOnClick = false,
loop = false,
repeat = 1,
keyframes,
fillMode = 'forwards',
respectReducedMotion = true,
className,
children,
style,
onMouseEnter,
onMouseLeave,
onClick,
...props
}, ref) => {
const elementRef = useRef(null);
const animationRef = useRef(null);
// Physics animation hook
const physicsSpring = useMultiSpring({
opacity: 1,
x: 0,
y: 0,
scale: 1,
rotation: 0
}, {
config: springConfig || {
stiffness: 200,
damping: 25,
mass: 1
}
});
// Check for reduced motion preference safely
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
useEffect(() => {
if (typeof window !== 'undefined' && window.matchMedia) {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
const updatePreference = () => setPrefersReducedMotion(mq.matches);
updatePreference();
if (mq.addEventListener) {
mq.addEventListener('change', updatePreference);
return () => mq.removeEventListener('change', updatePreference);
} else {
// @ts-ignore - Legacy API
mq.addListener(updatePreference);
return () => mq.removeListener(updatePreference);
}
}
}, []);
const shouldAnimate = !respectReducedMotion || !prefersReducedMotion;
// Generate animation keyframes based on type and direction
const generateKeyframes = () => {
if (keyframes) return keyframes;
const frames = {};
switch (type) {
case 'fade':
frames.from = {
opacity: 0
};
frames.to = {
opacity: 1
};
break;
case 'slide':
frames.from = {
opacity: 0,
transform: getDirectionalTransform(direction, 20)
};
frames.to = {
opacity: 1,
transform: 'translate(0, 0) scale(1) rotate(0deg)'
};
break;
case 'scale':
frames.from = {
opacity: 0,
transform: 'scale(0.8)'
};
frames.to = {
opacity: 1,
transform: 'scale(1)'
};
break;
case 'bounce':
frames.from = {
opacity: 0,
transform: getDirectionalTransform(direction, 30)
};
frames['60%'] = {
opacity: 1,
transform: getDirectionalTransform(direction, -10)
};
frames['80%'] = {
opacity: 1,
transform: getDirectionalTransform(direction, 5)
};
frames.to = {
opacity: 1,
transform: 'translate(0, 0) scale(1) rotate(0deg)'
};
break;
case 'elastic':
frames.from = {
opacity: 0,
transform: getDirectionalTransform(direction, 20) + ' scale(0.3)'
};
frames['55%'] = {
opacity: 1,
transform: getDirectionalTransform(direction, -5) + ' scale(1.05)'
};
frames['65%'] = {
opacity: 1,
transform: getDirectionalTransform(direction, 3) + ' scale(0.95)'
};
frames['75%'] = {
opacity: 1,
transform: getDirectionalTransform(direction, -1) + ' scale(1.02)'
};
frames.to = {
opacity: 1,
transform: 'translate(0, 0) scale(1) rotate(0deg)'
};
break;
case 'flip':
frames.from = {
opacity: 0,
transform: `rotateY(${direction === 'left' ? -90 : 90}deg)`
};
frames.to = {
opacity: 1,
transform: 'rotateY(0deg)'
};
break;
case 'rotate':
frames.from = {
opacity: 0,
transform: 'rotate(-180deg)'
};
frames.to = {
opacity: 1,
transform: 'rotate(0deg)'
};
break;
default:
frames.from = {
opacity: 0
};
frames.to = {
opacity: 1
};
}
return frames;
};
// Helper function for directional transforms
const getDirectionalTransform = (dir, distance) => {
switch (dir) {
case 'up':
return `translateY(${distance}px)`;
case 'down':
return `translateY(-${distance}px)`;
case 'left':
return `translateX(${distance}px)`;
case 'right':
return `translateX(-${distance}px)`;
default:
return 'translate(0, 0)';
}
};
// Start animation
const startAnimation = () => {
if (!elementRef.current || !shouldAnimate) return;
const element = elementRef.current;
// Cancel existing animation
if (animationRef.current) {
animationRef.current.cancel();
}
if (physics) {
// Use physics-based animation
const targetValues = generateKeyframes().to;
const springTargets = {};
if (targetValues.opacity !== undefined) springTargets.opacity = targetValues.opacity;
if (targetValues.transform) {
// Parse transform values (simplified)
const translateMatch = targetValues.transform.match(/translate\(([^,]+),\s*([^)]+)\)/);
if (translateMatch) {
springTargets.x = parseFloat(translateMatch[1]);
springTargets.y = parseFloat(translateMatch[2]);
}
}
physicsSpring.start(springTargets);
} else {
// Use CSS animation
const animationKeyframes = generateKeyframes();
const timing = {
duration,
delay,
easing,
fill: fillMode,
iterations: loop ? Infinity : repeat
};
animationRef.current = element.animate ? element.animate(animationKeyframes, timing) : null;
}
};
// Stop animation
const stopAnimation = () => {
if (animationRef.current) {
animationRef.current.cancel();
animationRef.current = null;
}
};
// Handle hover animation
const handleMouseEnter = event => {
if (animateOnHover && shouldAnimate) {
startAnimation();
}
onMouseEnter?.(event);
};
const handleMouseLeave = event => {
if (animateOnHover && shouldAnimate) {
stopAnimation();
}
onMouseLeave?.(event);
};
// Handle click animation
const handleClick = event => {
if (animateOnClick && shouldAnimate) {
startAnimation();
}
onClick?.(event);
};
// Start animation on mount if requested
useEffect(() => {
if (animateOnMount && shouldAnimate) {
const timer = setTimeout(() => {
startAnimation();
}, delay);
return () => clearTimeout(timer);
}
}, [animateOnMount, shouldAnimate, delay]);
// Combine physics styles with custom styles
const combinedStyles = {
...style,
...(physics && {
opacity: physicsSpring.values.opacity,
transform: `translate(${physicsSpring.values.x}px, ${physicsSpring.values.y}px) scale(${physicsSpring.values.scale}) rotate(${physicsSpring.values.rotation}deg)`
})
};
return jsx("div", {
ref: el => {
elementRef.current = el;
// Handle forwarded ref
if (ref) {
if (typeof ref === 'function') {
ref(el);
} else if (ref && 'current' in ref) {
ref.current = el;
}
}
},
className: cn('motion-element', {
'motion-fade': type === 'fade',
'motion-slide': type === 'slide',
'motion-scale': type === 'scale',
'motion-bounce': type === 'bounce',
'motion-elastic': type === 'elastic',
'motion-flip': type === 'flip',
'motion-rotate': type === 'rotate',
'motion-physics': physics,
'motion-hover': animateOnHover,
'motion-click': animateOnClick
}, className),
style: combinedStyles,
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onClick: handleClick,
...props,
children: children
});
});
MotionNative.displayName = 'MotionNative';
export { MotionNative, MotionNative as default };
//# sourceMappingURL=MotionNative.js.map