import React, { useEffect, useRef } from 'react'; import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withTiming } from 'react-native-reanimated'; import { EntranceAnimationDirection } from '../constants'; import { ViewStyle } from 'react-native'; export interface EntranceAnimationProps { direction: `${EntranceAnimationDirection}`; index?: number; children: React.ReactNode; staggerDelay?: number; maxStaggerIndex?: number; initialOffset?: number; duration?: number; containerStyle?: ViewStyle; 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 translate = useSharedValue(initialOffset); const opacity = useSharedValue(opacityAnimation ? 0 : 1); // Callback to emit animation complete event useEffect(() => { if (!hasAnimated.current && startAnimation) { hasAnimated.current = true; translate.value = withDelay( wrappedIndex * staggerDelay, withTiming(0, { duration, easing: Easing.inOut(Easing.ease) }), ); opacity.value = withDelay( wrappedIndex * staggerDelay, withTiming(1, { duration: duration * 0.2, easing: Easing.inOut(Easing.ease) }), ); // Emit animation complete event after animation duration const totalDelay = wrappedIndex * staggerDelay + duration + duration * 0.5; setTimeout(() => { onAnimationComplete(); }, totalDelay); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [wrappedIndex, staggerDelay, duration, direction, startAnimation, onAnimationComplete]); const animatedStyle = useAnimatedStyle(() => { const transform = direction === EntranceAnimationDirection.B2T ? [{ translateY: translate.value }] : [{ translateX: translate.value }]; const baseStyle = { transform: transform, opacity: opacity.value, }; // Add flex: 1 if fillContainer is true const layoutStyle = fillContainer ? { flex: 1 } : {}; return { ...baseStyle, ...layoutStyle, ...containerStyle, }; }); return {children}; }; export default EntranceAnimation;