import React, { useEffect, useMemo } from "react"; import { View, StyleSheet, ViewStyle } from "react-native"; import Animated, { useSharedValue, useAnimatedStyle, withRepeat, withTiming, Easing, cancelAnimation, } from "react-native-reanimated"; const DEFAULT_ANIMATION_SPEED_MS = 15000; type ReusableMarqueeProps = { data: T[]; renderItem: (item: T, index: number) => React.ReactElement; itemWidth: number; itemHeight: number; itemSpacing: number; animationSpeedMs?: number; style?: ViewStyle; contentContainerStyle?: ViewStyle; moveTo?: "left" | "right"; }; const ReusableMarquee = ({ data: baseData, renderItem, itemWidth, itemHeight, itemSpacing, animationSpeedMs = DEFAULT_ANIMATION_SPEED_MS, style, contentContainerStyle, moveTo = "left", }: ReusableMarqueeProps): React.ReactElement | null => { const translateX = useSharedValue(0); const { ONE_SET_WIDTH, DATA } = useMemo(() => { if (!baseData || baseData.length === 0) { return { ONE_SET_WIDTH: 0, DATA: [] }; } // Calculate width of one set of items including spacing const calculatedWidth = baseData.reduce( (sum) => sum + itemWidth + itemSpacing, 0 ); // Duplicate data for seamless looping const duplicatedData = [...baseData, ...baseData]; return { ONE_SET_WIDTH: calculatedWidth, DATA: duplicatedData }; }, [baseData, itemWidth, itemSpacing]); useEffect(() => { if (DATA.length > 0 && ONE_SET_WIDTH > 0) { const initialTranslateX = moveTo === "left" ? 0 : -ONE_SET_WIDTH; const targetTranslateX = moveTo === "left" ? -ONE_SET_WIDTH : 0; translateX.value = initialTranslateX; const animation = withRepeat( withTiming(targetTranslateX, { duration: animationSpeedMs, easing: Easing.linear, }), -1, false ); translateX.value = animation; return () => { cancelAnimation(translateX); }; } else { cancelAnimation(translateX); translateX.value = 0; return undefined; } }, [translateX, ONE_SET_WIDTH, DATA, animationSpeedMs, moveTo]); // const animatedStyle = useAnimatedStyle(() => { return { transform: [{ translateX: translateX.value }], }; }); if (DATA.length === 0) { return null; } return ( {DATA.map((item, index) => ( {renderItem(item, index % baseData.length)} ))} ); }; const styles = StyleSheet.create({ container: { width: "100%", overflow: "hidden", }, animatedContainer: { flexDirection: "row", }, itemWrapper: { }, }); export default ReusableMarquee;