Matiks-UI / src /components /CountdownOverlay.tsx
Abhisingh-18's picture
Mirror of github.com/Abhisingh18/Matiks-UI
0bedb61 verified
Raw
History Blame Contribute Delete
3.89 kB
import React, { useEffect, useState } from 'react';
import { StyleSheet, View, Dimensions } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withSequence,
withDelay,
withSpring,
interpolate,
Easing,
runOnJS,
} from 'react-native-reanimated';
import { COLORS } from '../constants/colors';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
interface CountdownOverlayProps {
onComplete: () => void;
}
export const CountdownOverlay: React.FC<CountdownOverlayProps> = ({ onComplete }) => {
const [currentNumber, setCurrentNumber] = useState(3);
const [showGo, setShowGo] = useState(false);
const [visible, setVisible] = useState(true);
const numberScale = useSharedValue(0);
const numberOpacity = useSharedValue(0);
const ringScale = useSharedValue(0);
const bgOpacity = useSharedValue(1);
const animateNumber = (num: number) => {
numberScale.value = 0.3;
numberOpacity.value = 0;
ringScale.value = 0.5;
numberScale.value = withSequence(
withSpring(1, { damping: 8, stiffness: 150 }),
withDelay(500, withTiming(1.5, { duration: 200 }))
);
numberOpacity.value = withSequence(
withTiming(1, { duration: 150 }),
withDelay(500, withTiming(0, { duration: 200 }))
);
ringScale.value = withSequence(
withSpring(1, { damping: 8, stiffness: 120 }),
withDelay(400, withTiming(1.8, { duration: 300 }))
);
};
useEffect(() => {
animateNumber(3);
const timers = [
setTimeout(() => { setCurrentNumber(2); animateNumber(2); }, 900),
setTimeout(() => { setCurrentNumber(1); animateNumber(1); }, 1800),
setTimeout(() => {
setShowGo(true);
numberScale.value = 0.3;
numberOpacity.value = 0;
numberScale.value = withSpring(1.2, { damping: 6, stiffness: 100 });
numberOpacity.value = withTiming(1, { duration: 150 });
}, 2700),
setTimeout(() => {
bgOpacity.value = withTiming(0, { duration: 300 });
setTimeout(() => {
setVisible(false);
onComplete();
}, 300);
}, 3300),
];
return () => timers.forEach(clearTimeout);
}, []);
const numberStyle = useAnimatedStyle(() => ({
transform: [{ scale: numberScale.value }],
opacity: numberOpacity.value,
}));
const ringStyle = useAnimatedStyle(() => ({
transform: [{ scale: ringScale.value }],
opacity: interpolate(ringScale.value, [0.5, 1, 1.8], [0.3, 0.5, 0]),
}));
const containerStyle = useAnimatedStyle(() => ({
opacity: bgOpacity.value,
}));
if (!visible) return null;
return (
<Animated.View style={[styles.overlay, containerStyle]}>
{/* Pulse Ring */}
<Animated.View style={[styles.ring, ringStyle]} />
{/* Number / GO */}
<Animated.Text
style={[
styles.number,
showGo && styles.goText,
numberStyle,
]}
>
{showGo ? 'GO!' : currentNumber}
</Animated.Text>
</Animated.View>
);
};
const styles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(10, 10, 11, 0.95)',
alignItems: 'center',
justifyContent: 'center',
zIndex: 999,
},
ring: {
position: 'absolute',
width: 150,
height: 150,
borderRadius: 75,
borderWidth: 3,
borderColor: COLORS.primary,
},
number: {
fontSize: 80,
fontWeight: '900',
color: COLORS.text,
textShadowColor: COLORS.primary + '60',
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 30,
fontVariant: ['tabular-nums'],
},
goText: {
color: COLORS.primary,
fontSize: 64,
letterSpacing: 8,
},
});