Files

110 lines
2.7 KiB
TypeScript
Raw Permalink Normal View History

2025-05-14 14:35:15 +02:00
"use client"
import React from "react"
import { useEffect, useRef, useState } from "react"
import { motion, useAnimation, useInView } from "framer-motion"
type StaggerProps = {
children: React.ReactNode
delay?: number
staggerDelay?: number
duration?: number
className?: string
once?: boolean
threshold?: number
animation?: "fadeIn" | "fadeInUp" | "fadeInDown" | "fadeInLeft" | "fadeInRight" | "zoom"
}
const animations = {
fadeIn: {
hidden: { opacity: 0 },
visible: { opacity: 1 },
},
fadeInUp: {
hidden: { opacity: 0, y: 50 },
visible: { opacity: 1, y: 0 },
},
fadeInDown: {
hidden: { opacity: 0, y: -50 },
visible: { opacity: 1, y: 0 },
},
fadeInLeft: {
hidden: { opacity: 0, x: -50 },
visible: { opacity: 1, x: 0 },
},
fadeInRight: {
hidden: { opacity: 0, x: 50 },
visible: { opacity: 1, x: 0 },
},
zoom: {
hidden: { opacity: 0, scale: 0.8 },
visible: { opacity: 1, scale: 1 },
},
}
export function Stagger({
children,
delay = 0,
staggerDelay = 0.1,
duration = 0.5,
className = "",
once = true,
threshold = 0.1,
animation = "fadeInUp",
}: StaggerProps) {
const controls = useAnimation()
const ref = useRef(null)
const isInView = useInView(ref, { once, threshold })
const [shouldReduceMotion, setShouldReduceMotion] = useState(false)
// Check for prefers-reduced-motion
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)")
setShouldReduceMotion(mediaQuery.matches)
const handleChange = () => setShouldReduceMotion(mediaQuery.matches)
mediaQuery.addEventListener("change", handleChange)
return () => mediaQuery.removeEventListener("change", handleChange)
}, [])
useEffect(() => {
if (isInView) {
controls.start("visible")
} else if (!once) {
controls.start("hidden")
}
}, [isInView, controls, once])
const selectedAnimation = animations[animation]
// If user prefers reduced motion, use a simpler animation
const variants = shouldReduceMotion
? {
hidden: { opacity: 0 },
visible: { opacity: 1 },
}
: selectedAnimation
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: shouldReduceMotion ? 0 : staggerDelay,
delayChildren: delay,
},
},
}
return (
<motion.div ref={ref} initial="hidden" animate={controls} variants={containerVariants} className={className}>
{React.Children.map(children, (child) => (
<motion.div variants={variants} transition={{ duration, ease: "easeOut" }}>
{child}
</motion.div>
))}
</motion.div>
)
}