"use client"
import * as React from "react"
import { motion, Variants, Transition, UseInViewOptions } from "framer-motion"
import { cn } from "../../lib/utils"
/**
* ScrollReveal Component
*
* Reveals content with smooth animations when scrolling into view
* Built with Framer Motion for performance and flexibility
*
* @example
* ```tsx
*
* Your content
*
* ```
*/
interface ScrollRevealProps {
/** Content to reveal */
children: React.ReactNode
/** Animation direction */
direction?: "up" | "down" | "left" | "right" | "fade" | "scale" | "blur"
/** Animation delay in seconds */
delay?: number
/** Animation duration in seconds */
duration?: number
/** Distance to animate from (pixels) */
distance?: number
/** Viewport threshold for trigger (0-1) */
threshold?: number
/** Trigger animation only once */
once?: boolean
/** Deprecated: Use 'once' instead */
triggerOnce?: boolean
/** Stagger delay for multiple children (deprecated: use ScrollRevealContainer) */
stagger?: number
/** Additional CSS classes */
className?: string
/** Custom animation variants */
variants?: Variants
/** Enable/disable animations */
animate?: boolean
/** Custom styles */
style?: React.CSSProperties
/** Element ID */
id?: string
/** Viewport options for intersection observer */
viewport?: UseInViewOptions
}
// Default animation variants with professional easing
const createDefaultVariants = (direction: string, distance: number): Variants => {
const baseVariants: Record = {
up: {
hidden: { opacity: 0, y: distance },
visible: { opacity: 1, y: 0 }
},
down: {
hidden: { opacity: 0, y: -distance },
visible: { opacity: 1, y: 0 }
},
left: {
hidden: { opacity: 0, x: distance },
visible: { opacity: 1, x: 0 }
},
right: {
hidden: { opacity: 0, x: -distance },
visible: { opacity: 1, x: 0 }
},
fade: {
hidden: { opacity: 0 },
visible: { opacity: 1 }
},
scale: {
hidden: { opacity: 0, scale: 0.85 },
visible: { opacity: 1, scale: 1 }
},
blur: {
hidden: { opacity: 0, filter: "blur(10px)" },
visible: { opacity: 1, filter: "blur(0px)" }
}
}
return baseVariants[direction] || baseVariants.up
}
// Professional easing curve
const defaultTransition: Transition = {
duration: 0.5,
ease: [0.25, 0.46, 0.45, 0.94], // Custom cubic-bezier for smooth motion
}
const ScrollReveal = React.forwardRef(
({
children,
direction = "up",
delay = 0,
duration = 0.5,
distance = 50,
threshold = 0.3, // Increased from 0.1 for better visibility
once = true,
triggerOnce, // Backward compatibility
stagger = 0,
className,
variants,
animate = true,
style,
id,
viewport
}, ref) => {
// Handle backward compatibility
const shouldTriggerOnce = triggerOnce !== undefined ? triggerOnce : once
// Get animation variants
const animationVariants = React.useMemo(() => {
if (variants) return variants
return createDefaultVariants(direction, distance)
}, [direction, distance, variants])
// Transition configuration
const transition = React.useMemo(() => ({
...defaultTransition,
duration,
delay: delay + stagger,
}), [duration, delay, stagger])
// Viewport configuration
const viewportConfig = React.useMemo(() => ({
once: shouldTriggerOnce,
amount: threshold,
...viewport
}), [shouldTriggerOnce, threshold, viewport])
// If animations are disabled, render children in a regular div
if (!animate) {
return (
{children}
)
}
// Render with Framer Motion using whileInView pattern
return (
{children}
)
}
)
ScrollReveal.displayName = "ScrollReveal"
/**
* ScrollRevealContainer Component
*
* Container for orchestrating staggered scroll reveal animations
* Uses Framer Motion's staggerChildren for better performance
*
* @example
* ```tsx
*
* Item 1
* Item 2
*
* ```
*/
interface ScrollRevealContainerProps {
/** ScrollRevealItem components */
children: React.ReactNode
/** Delay between child animations (seconds) */
stagger?: number
/** Additional CSS classes */
className?: string
/** Custom styles */
style?: React.CSSProperties
/** Element ID */
id?: string
/** Viewport threshold for trigger (0-1) */
threshold?: number
/** Trigger animation only once */
once?: boolean
/** Deprecated: Use 'once' instead */
triggerOnce?: boolean
/** Viewport options for intersection observer */
viewport?: UseInViewOptions
}
const ScrollRevealContainer = React.forwardRef(
({
children,
stagger = 0.1,
className,
style,
id,
threshold = 0.3,
once = true,
triggerOnce,
viewport
}, ref) => {
// Handle backward compatibility
const shouldTriggerOnce = triggerOnce !== undefined ? triggerOnce : once
// Viewport configuration
const viewportConfig = React.useMemo(() => ({
once: shouldTriggerOnce,
amount: threshold,
...viewport
}), [shouldTriggerOnce, threshold, viewport])
// Container variants for staggered children
const containerVariants: Variants = {
hidden: {},
visible: {
transition: {
staggerChildren: stagger,
delayChildren: 0
}
}
}
return (
{children}
)
}
)
ScrollRevealContainer.displayName = "ScrollRevealContainer"
/**
* ScrollRevealItem Component
*
* Individual item within a ScrollRevealContainer
* Automatically inherits timing from parent container
*
* @example
* ```tsx
*
* Item content
*
* ```
*/
interface ScrollRevealItemProps {
/** Content to animate */
children: React.ReactNode
/** Animation direction */
direction?: "up" | "down" | "left" | "right" | "fade" | "scale" | "blur"
/** Animation duration in seconds */
duration?: number
/** Distance to animate from (pixels) */
distance?: number
/** Additional CSS classes */
className?: string
/** Custom animation variants */
variants?: Variants
/** Custom styles */
style?: React.CSSProperties
/** Element ID */
id?: string
}
const ScrollRevealItem = React.forwardRef(
({
children,
direction = "up",
duration = 0.5,
distance = 50,
className,
variants,
style,
id
}, ref) => {
// Get animation variants
const animationVariants = React.useMemo(() => {
if (variants) return variants
return createDefaultVariants(direction, distance)
}, [direction, distance, variants])
// Transition configuration
const transition = React.useMemo(() => ({
...defaultTransition,
duration,
}), [duration])
return (
{children}
)
}
)
ScrollRevealItem.displayName = "ScrollRevealItem"
export { ScrollReveal, ScrollRevealContainer, ScrollRevealItem }
export type { ScrollRevealProps, ScrollRevealContainerProps, ScrollRevealItemProps }