import React, { useEffect } from 'react' import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' import Animated, { useSharedValue, useAnimatedStyle, withRepeat, withTiming, Easing, } from 'react-native-reanimated' import { useFontScale, useTheme } from '../../hooks' // ================================= // ====== Constants ================ // ================================= const PREVENT_SCALING_DEFAULT = 1 // ================================= // ====== Component ================ // ================================= interface SpinnerProps { /** * Size of the spinner in px * */ size?: number /** * Specifies the maximum size spinner can scale to if the device's font-size is increased. */ maxFontSizeMultiplier?: number /** * Specifies the minimum size spinner can scale to if the device's font-size is decreased. */ minFontSizeMultiplier?: number /** * Style to apply to the spinner */ style?: StyleProp } export function Spinner({ size = 20, maxFontSizeMultiplier = PREVENT_SCALING_DEFAULT, minFontSizeMultiplier = PREVENT_SCALING_DEFAULT, style, }: SpinnerProps) { const styles = useStyles({ maxFontSizeMultiplier, minFontSizeMultiplier, size }) const rotation = useSharedValue(0) const animatedStyle = useAnimatedStyle(() => { return { transform: [{ rotate: `${rotation.value}deg` }], } }) useEffect(() => { rotation.value = withRepeat( withTiming(360, { duration: 1000, easing: Easing.linear, }), -1 // loops animation infinitely ) return () => { rotation.value = 0 // Reset rotation when Spinner unmounts } }, [rotation]) return ( ) } // ================================= // ====== Styles =================== // ================================= interface Styles { maxFontSizeMultiplier: number | undefined minFontSizeMultiplier: number | undefined size: number } const useStyles = ({ maxFontSizeMultiplier, minFontSizeMultiplier, size }: Styles) => { const { colors } = useTheme() const fontScale = useFontScale({ maxFontSizeMultiplier, minFontSizeMultiplier }) const scalableSize = size * fontScale return StyleSheet.create({ animatedContainer: { width: scalableSize, height: scalableSize, borderRadius: scalableSize / 2, }, circle: { width: scalableSize, height: scalableSize, borderRadius: scalableSize / 2, borderStyle: 'solid', borderWidth: 3, }, spinner: { borderColor: colors.borderColorDefaultDarkest, }, track: { borderColor: colors.fillColorNeutral050Base, }, clipping: { position: 'absolute', top: 0, left: 0, width: scalableSize / 2, height: scalableSize / 2, overflow: 'hidden', zIndex: 200, }, }) }