import {useTheme} from '../../theme/ThemeProvider'; import type {A11yProps} from '../../types'; import {useEffect, useMemo} from 'react'; import type {ViewStyle} from 'react-native'; import {Animated, StyleSheet, View} from 'react-native'; type Props = A11yProps & { size?: number; // Dots size in pixels color?: string; // Dots color dotCount?: number; // Number of dots speed?: number; // Duration (ms) of each animation cycle style?: ViewStyle; }; export const DotsLoading = (props: Props) => { const {colors} = useTheme(); const { accessibilityLabel = 'Loading', size = 10, color = colors.primary, dotCount = 3, speed = 600, style, testID, } = props; // One Animated.Value per dot, recreated only when dotCount changes. const animatedValues = useMemo( () => Array.from({length: dotCount}, () => new Animated.Value(0)), [dotCount], ); useEffect(() => { // Create looping animations with staggered delay const animations = animatedValues.map(av => Animated.loop( Animated.sequence([ Animated.timing(av, { toValue: 1, duration: Math.round(speed / 2), useNativeDriver: true, }), Animated.timing(av, { toValue: 0, duration: Math.round(speed / 2), useNativeDriver: true, }), ]), {iterations: -1}, ), ); // Start with a small staggered delay for the "wave" effect const staggered = animations.map((anim, i) => Animated.sequence([Animated.delay((speed / dotCount) * i), anim]), ); // Execute all in parallel and stop the loop on unmount or prop change. const animation = Animated.parallel(staggered); animation.start(); return () => { animation.stop(); animatedValues.forEach(av => av.setValue(0)); }; }, [animatedValues, speed, dotCount]); return ( {animatedValues.map((av, i) => { // Animate opacity and scale const scale = av.interpolate({ inputRange: [0, 1], outputRange: [0.8, 1.3], }); const opacity = av.interpolate({ inputRange: [0, 1], outputRange: [0.4, 1], }); return ( ); })} ); }; const styles = StyleSheet.create({ container: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, });