61 lines
1.8 KiB
TypeScript
61 lines
1.8 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { StyleSheet, ViewStyle } from 'react-native';
|
|
import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
|
|
import Svg, { Circle } from 'react-native-svg';
|
|
import { colors } from '../theme/colors';
|
|
|
|
type LoadingSpinnerProps = {
|
|
size?: number;
|
|
color?: string;
|
|
style?: ViewStyle;
|
|
};
|
|
|
|
export const LoadingSpinner = ({ size = 36, color = colors.accent, style }: LoadingSpinnerProps) => {
|
|
const rotation = useSharedValue(0);
|
|
const strokeWidth = Math.max(4, size * 0.14);
|
|
const center = size / 2;
|
|
const radius = center - strokeWidth;
|
|
const circumference = 2 * Math.PI * radius;
|
|
|
|
useEffect(() => {
|
|
rotation.value = withRepeat(withTiming(360, { duration: 900, easing: Easing.linear }), -1);
|
|
}, [rotation]);
|
|
|
|
const animatedStyle = useAnimatedStyle(() => ({
|
|
transform: [{ rotateZ: `${rotation.value}deg` }],
|
|
}));
|
|
|
|
return (
|
|
<Animated.View style={[styles.container, { width: size, height: size }, style, animatedStyle]} pointerEvents="none">
|
|
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
<Circle
|
|
cx={center}
|
|
cy={center}
|
|
r={radius}
|
|
stroke="rgba(255,255,255,0.2)"
|
|
strokeWidth={strokeWidth}
|
|
fill="none"
|
|
/>
|
|
<Circle
|
|
cx={center}
|
|
cy={center}
|
|
r={radius}
|
|
stroke={color}
|
|
strokeWidth={strokeWidth}
|
|
fill="none"
|
|
strokeLinecap="round"
|
|
strokeDasharray={`${circumference * 0.3} ${circumference * 0.7}`}
|
|
strokeDashoffset={circumference * 0.2}
|
|
/>
|
|
</Svg>
|
|
</Animated.View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
});
|