import { useEffect, useState } from "react"; import { View } from "react-native"; import Animated, { cancelAnimation, Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming, } from "react-native-reanimated"; const INDICATOR_WIDTH_FRACTION = 0.3; const MIN_INDICATOR_WIDTH = 48; function LoadingStripFrame(props: { readonly children: React.ReactNode; readonly onLayout?: (width: number) => void; }) { return ( { props.onLayout?.(event.nativeEvent.layout.width); } : undefined } > {props.children} ); } function IndeterminateLoadingStrip() { const [containerWidth, setContainerWidth] = useState(0); const travelProgress = useSharedValue(0); const indicatorWidth = Math.max(MIN_INDICATOR_WIDTH, containerWidth * INDICATOR_WIDTH_FRACTION); useEffect(() => { travelProgress.value = 0; travelProgress.value = withRepeat( withTiming(1, { duration: 1100, easing: Easing.inOut(Easing.quad), }), -1, false, ); return () => { cancelAnimation(travelProgress); }; }, [travelProgress]); const indicatorStyle = useAnimatedStyle( () => ({ transform: [ { translateX: (containerWidth + indicatorWidth) * travelProgress.value - indicatorWidth, }, ], width: indicatorWidth, }), [containerWidth, indicatorWidth], ); return ( ); } export function LoadingStrip(props: { readonly progress?: number }) { if (props.progress === undefined) { return ; } const clampedProgress = Math.min(1, Math.max(0, props.progress)); return ( ); }