import React, { useEffect, useRef, useState } from 'react'; import { Text as RNText, TextProps, StyleSheet, Animated, Easing, View } from 'react-native'; export interface AwesomeTextProps extends TextProps { variant?: 'h1' | 'h2' | 'h3' | 'body' | 'caption'; color?: string; weight?: 'normal' | 'bold' | 'light'; align?: 'left' | 'center' | 'right'; customStyle?: object; animation?: 'bounce' | 'zoom' | 'none'; duration?: number; delay?: number; containerStyle?: object; minZoom?: number; maxZoom?: number; } const AwesomeText: React.FC = ({ variant = 'body', color = '#000000', weight = 'normal', align = 'left', customStyle, children, animation = 'none', duration = 1000, delay = 0, containerStyle, minZoom = 0.8, maxZoom = 1.2, ...props }) => { const [isMounted, setIsMounted] = useState(false); // Animation values const bounceValue = useRef(new Animated.Value(0)).current; const zoomValue = useRef(new Animated.Value(1)).current; const animationRef = useRef(null); // Bounce Animation const startBounceAnimation = () => { if (animationRef.current) { animationRef.current.stop(); } animationRef.current = Animated.loop( Animated.sequence([ Animated.timing(bounceValue, { toValue: 1, duration: duration / 2, useNativeDriver: true, easing: Easing.out(Easing.bounce), isInteraction: false, }), Animated.timing(bounceValue, { toValue: 0, duration: duration / 2, useNativeDriver: true, easing: Easing.in(Easing.bounce), isInteraction: false, }), ]), { iterations: -1 } ); animationRef.current.start(); }; // Zoom Animation const startZoomAnimation = () => { if (animationRef.current) { animationRef.current.stop(); } animationRef.current = Animated.loop( Animated.sequence([ Animated.timing(zoomValue, { toValue: maxZoom, duration: duration, useNativeDriver: true, easing: Easing.bezier(0.4, 0, 0.2, 1), isInteraction: false, }), Animated.timing(zoomValue, { toValue: minZoom, duration: duration, useNativeDriver: true, easing: Easing.bezier(0.4, 0, 0.2, 1), isInteraction: false, }), ]), { iterations: -1, resetBeforeIteration: true } ); animationRef.current.start(); }; useEffect(() => { setIsMounted(true); return () => setIsMounted(false); }, []); useEffect(() => { if (!isMounted) return; // Reset animations bounceValue.setValue(0); zoomValue.setValue(minZoom); // Start new animations after a small delay const animationTimeout = setTimeout(() => { if (animation === 'bounce') { requestAnimationFrame(() => startBounceAnimation()); } else if (animation === 'zoom') { requestAnimationFrame(() => startZoomAnimation()); } }, delay); // Cleanup function to stop animations return () => { clearTimeout(animationTimeout); if (animationRef.current) { animationRef.current.stop(); } bounceValue.stopAnimation(); zoomValue.stopAnimation(); }; }, [animation, duration, delay, bounceValue, zoomValue, isMounted]); const getVariantStyle = () => { switch (variant) { case 'h1': return styles.h1; case 'h2': return styles.h2; case 'h3': return styles.h3; case 'caption': return styles.caption; default: return styles.body; } }; const getWeightStyle = () => { switch (weight) { case 'bold': return styles.bold; case 'light': return styles.light; default: return styles.normal; } }; const getAnimationStyle = () => { if (animation === 'bounce') { return { transform: [ { translateY: bounceValue.interpolate({ inputRange: [0, 1], outputRange: [0, -15], }), }, ], }; } if (animation === 'zoom') { return { transform: [ { scale: zoomValue } ], }; } return {}; }; const AnimatedText = Animated.createAnimatedComponent(RNText); return ( {children} ); }; const styles = StyleSheet.create({ container: { overflow: 'hidden', alignItems: 'center', justifyContent: 'center', }, h1: { fontSize: 32, lineHeight: 40, }, h2: { fontSize: 24, lineHeight: 32, }, h3: { fontSize: 20, lineHeight: 28, }, body: { fontSize: 16, lineHeight: 24, }, caption: { fontSize: 12, lineHeight: 16, }, bold: { fontWeight: 'bold', }, normal: { fontWeight: 'normal', }, light: { fontWeight: '300', }, }); export default AwesomeText;