2025-08-20 04:12:49 -06:00

120 lines
2.9 KiB
TypeScript

'use client';
import * as React from 'react';
import {
AnimatePresence,
motion,
useInView,
type HTMLMotionProps,
type UseInViewOptions,
type Transition,
type Variant,
} from 'motion/react';
type MotionEffectProps = HTMLMotionProps<'div'> & {
children: React.ReactNode;
className?: string;
transition?: Transition;
delay?: number;
inView?: boolean;
inViewMargin?: UseInViewOptions['margin'];
inViewOnce?: boolean;
blur?: string | boolean;
slide?:
| {
direction?: 'up' | 'down' | 'left' | 'right';
offset?: number;
}
| boolean;
fade?: { initialOpacity?: number; opacity?: number } | boolean;
zoom?:
| {
initialScale?: number;
scale?: number;
}
| boolean;
};
function MotionEffect({
ref,
children,
className,
transition = { type: 'spring', stiffness: 200, damping: 20 },
delay = 0,
inView = false,
inViewMargin = '0px',
inViewOnce = true,
blur = false,
slide = false,
fade = false,
zoom = false,
...props
}: MotionEffectProps) {
const localRef = React.useRef<HTMLDivElement>(null);
React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);
const inViewResult = useInView(localRef, {
once: inViewOnce,
margin: inViewMargin,
});
const isInView = !inView || inViewResult;
const hiddenVariant: Variant = {};
const visibleVariant: Variant = {};
if (slide) {
const offset = typeof slide === 'boolean' ? 100 : (slide.offset ?? 100);
const direction =
typeof slide === 'boolean' ? 'left' : (slide.direction ?? 'left');
const axis = direction === 'up' || direction === 'down' ? 'y' : 'x';
hiddenVariant[axis] =
direction === 'left' || direction === 'up' ? -offset : offset;
visibleVariant[axis] = 0;
}
if (fade) {
hiddenVariant.opacity =
typeof fade === 'boolean' ? 0 : (fade.initialOpacity ?? 0);
visibleVariant.opacity =
typeof fade === 'boolean' ? 1 : (fade.opacity ?? 1);
}
if (zoom) {
hiddenVariant.scale =
typeof zoom === 'boolean' ? 0.5 : (zoom.initialScale ?? 0.5);
visibleVariant.scale = typeof zoom === 'boolean' ? 1 : (zoom.scale ?? 1);
}
if (blur) {
hiddenVariant.filter =
typeof blur === 'boolean' ? 'blur(10px)' : `blur(${blur})`;
visibleVariant.filter = 'blur(0px)';
}
return (
<AnimatePresence>
<motion.div
ref={localRef}
data-slot="motion-effect"
initial="hidden"
animate={isInView ? 'visible' : 'hidden'}
exit="hidden"
variants={{
hidden: hiddenVariant,
visible: visibleVariant,
}}
transition={{
...transition,
delay: (transition?.delay ?? 0) + delay,
}}
className={className}
{...props}
>
{children}
</motion.div>
</AnimatePresence>
);
}
export { MotionEffect, type MotionEffectProps };