'use client'
import * as React from 'react'
import { motion, AnimatePresence, type Variants, type HTMLMotionProps } from 'framer-motion'
import { cn } from '@/lib/utils'
// Animation variants
export const fadeIn: Variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 },
}
export const fadeInUp: Variants = {
hidden: { opacity: 0, y: 10 },
visible: { opacity: 1, y: 0 },
}
export const fadeInDown: Variants = {
hidden: { opacity: 0, y: -10 },
visible: { opacity: 1, y: 0 },
}
export const scaleIn: Variants = {
hidden: { opacity: 0, scale: 0.95 },
visible: { opacity: 1, scale: 1 },
}
export const slideInLeft: Variants = {
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0 },
}
export const slideInRight: Variants = {
hidden: { opacity: 0, x: 20 },
visible: { opacity: 1, x: 0 },
}
// Stagger container
export const staggerContainer: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.05,
delayChildren: 0.1,
},
},
}
// Stagger item
export const staggerItem: Variants = {
hidden: { opacity: 0, y: 10 },
visible: { opacity: 1, y: 0 },
}
// Motion wrapper for consistent animations
interface MotionDivProps {
children: React.ReactNode
className?: string
variant?: 'fadeIn' | 'fadeInUp' | 'fadeInDown' | 'scaleIn' | 'slideInLeft' | 'slideInRight'
delay?: number
duration?: number
}
const variantMap = {
fadeIn,
fadeInUp,
fadeInDown,
scaleIn,
slideInLeft,
slideInRight,
}
export function MotionDiv({
children,
className,
variant = 'fadeInUp',
delay = 0,
duration = 0.3,
}: MotionDivProps) {
return (
<motion.div
initial="hidden"
animate="visible"
variants={variantMap[variant]}
transition={{ duration, delay, ease: [0, 0, 0.2, 1] }}
className={className}
>
{children}
</motion.div>
)
}
// Staggered list container
interface StaggerListProps {
children: React.ReactNode
className?: string
}
export function StaggerList({ children, className }: StaggerListProps) {
return (
<motion.div
initial="hidden"
animate="visible"
variants={staggerContainer}
className={className}
>
{children}
</motion.div>
)
}
// Stagger list item
interface StaggerItemProps {
children: React.ReactNode
className?: string
}
export function StaggerItem({ children, className }: StaggerItemProps) {
return (
<motion.div
variants={staggerItem}
transition={{ duration: 0.3, ease: [0, 0, 0.2, 1] }}
className={className}
>
{children}
</motion.div>
)
}
// Page transition wrapper
interface PageTransitionProps {
children: React.ReactNode
className?: string
}
export function PageTransition({ children, className }: PageTransitionProps) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: [0, 0, 0.2, 1] }}
className={className}
>
{children}
</motion.div>
)
}
// Presence wrapper for conditional animations
interface PresenceProps {
children: React.ReactNode
show: boolean
className?: string
}
export function Presence({ children, show, className }: PresenceProps) {
return (
<AnimatePresence mode="wait">
{show && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2, ease: [0, 0, 0.2, 1] }}
className={className}
>
{children}
</motion.div>
)}
</AnimatePresence>
)
}
// Hover scale effect
interface HoverScaleProps {
children: React.ReactNode
className?: string
scale?: number
}
export function HoverScale({ children, className, scale = 1.02 }: HoverScaleProps) {
return (
<motion.div
whileHover={{ scale }}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
className={className}
>
{children}
</motion.div>
)
}
// Counter animation for numbers
interface AnimatedCounterProps {
value: number
duration?: number
className?: string
}
export function AnimatedCounter({ value, duration = 1, className }: AnimatedCounterProps) {
const [displayValue, setDisplayValue] = React.useState(0)
React.useEffect(() => {
let startTime: number
let animationFrame: number
const animate = (timestamp: number) => {
if (!startTime) startTime = timestamp
const progress = Math.min((timestamp - startTime) / (duration * 1000), 1)
setDisplayValue(Math.floor(progress * value))
if (progress < 1) {
animationFrame = requestAnimationFrame(animate)
}
}
animationFrame = requestAnimationFrame(animate)
return () => cancelAnimationFrame(animationFrame)
}, [value, duration])
return <span className={className}>{displayValue}</span>
}
export { AnimatePresence }