import React, { useEffect, useRef, useState } from 'react'; import { EntranceAnimationDirection } from '../constants'; import { Styles } from '@cleartrip/ct-design-types'; export interface EntranceAnimationProps { direction: `${EntranceAnimationDirection}`; index?: number; children: React.ReactNode; staggerDelay?: number; maxStaggerIndex?: number; initialOffset?: number; duration?: number; containerStyle?: Styles; fillContainer?: boolean; opacityAnimation?: boolean; startAnimation?: boolean; onAnimationComplete?: () => void; } const EntranceAnimation: React.FC = ({ direction, index = 0, children, staggerDelay = 80, maxStaggerIndex = 5, initialOffset = 200, duration = 200, containerStyle, fillContainer = false, opacityAnimation = true, startAnimation = true, onAnimationComplete = () => void 0, }) => { // Wrap index for staggered effect const wrappedIndex = index % (maxStaggerIndex + 1); // Track if animation has already been executed const hasAnimated = useRef(false); const [shouldAnimate, setShouldAnimate] = useState(false); // Generate unique keyframe names based on direction and offset const keyframeName = `entrance-${direction.toLowerCase()}-${initialOffset}`; useEffect(() => { if (!hasAnimated.current && startAnimation) { hasAnimated.current = true; // Start animation after stagger delay const timer = setTimeout(() => { setShouldAnimate(true); // Call onAnimationComplete after animation finishes const animationCompleteTimer = setTimeout( () => { onAnimationComplete(); }, duration + (opacityAnimation ? duration * 0.5 : 0), ); return () => clearTimeout(animationCompleteTimer); }, wrappedIndex * staggerDelay); return () => clearTimeout(timer); } }, [wrappedIndex, staggerDelay, duration, direction, startAnimation, onAnimationComplete, opacityAnimation]); // Create dynamic styles including keyframes const dynamicStyles = React.useMemo(() => { const keyframes = direction === EntranceAnimationDirection.B2T ? ` @keyframes ${keyframeName} { from { transform: translateY(${initialOffset}px); } to { transform: translateY(0); } } ` : ` @keyframes ${keyframeName} { from { transform: translateX(-${initialOffset}px); } to { transform: translateX(0); } } `; const opacityKeyframes = opacityAnimation ? ` @keyframes ${keyframeName}-opacity { from { opacity: 0.5; } to { opacity: 1; } } ` : ''; return ` `; }, [direction, initialOffset, keyframeName, opacityAnimation]); const animationStyle = { ...(fillContainer && { flex: 1 }), ...containerStyle, ...(shouldAnimate && { animation: `${keyframeName} ${duration}ms ease-out forwards${opacityAnimation ? `, ${keyframeName}-opacity ${duration * 0.5}ms ease-out ${duration}ms forwards` : ''}`, }), ...(opacityAnimation && !shouldAnimate && { opacity: 0.5 }), ...(direction === EntranceAnimationDirection.B2T && !shouldAnimate && { transform: `translateY(${initialOffset}px)`, }), ...(direction === EntranceAnimationDirection.L2R && !shouldAnimate && { transform: `translateX(-${initialOffset}px)`, }), }; return ( <>
{children}
); }; export default EntranceAnimation;