/ components / PageTransition.tsx
PageTransition.tsx
1 import React, { useRef, useEffect } from 'react'; 2 import { Animated, ViewStyle } from 'react-native'; 3 4 interface PageTransitionProps { 5 children: React.ReactNode; 6 style?: ViewStyle; 7 transitionType?: 'fade' | 'slide' | 'scale'; 8 duration?: number; 9 delay?: number; 10 } 11 12 export const PageTransition: React.FC<PageTransitionProps> = ({ 13 children, 14 style, 15 transitionType = 'fade', 16 duration = 300, 17 delay = 0, 18 }) => { 19 const opacity = useRef(new Animated.Value(0)).current; 20 const translateY = useRef(new Animated.Value(50)).current; 21 const scale = useRef(new Animated.Value(0.9)).current; 22 23 useEffect(() => { 24 const animations: Animated.CompositeAnimation[] = []; 25 26 if ( 27 transitionType === 'fade' || 28 transitionType === 'slide' || 29 transitionType === 'scale' 30 ) { 31 animations.push( 32 Animated.timing(opacity, { 33 toValue: 1, 34 duration, 35 useNativeDriver: true, 36 }) 37 ); 38 } 39 40 if (transitionType === 'slide') { 41 animations.push( 42 Animated.timing(translateY, { 43 toValue: 0, 44 duration, 45 useNativeDriver: true, 46 }) 47 ); 48 } 49 50 if (transitionType === 'scale') { 51 animations.push( 52 Animated.timing(scale, { 53 toValue: 1, 54 duration, 55 useNativeDriver: true, 56 }) 57 ); 58 } 59 60 const animation = Animated.parallel(animations); 61 62 const timer = setTimeout(() => { 63 animation.start(); 64 }, delay); 65 66 return () => { 67 clearTimeout(timer); 68 animation.stop(); 69 }; 70 }, [transitionType, duration, delay, opacity, translateY, scale]); 71 72 const getAnimatedStyle = () => { 73 const baseStyle = { opacity }; 74 75 switch (transitionType) { 76 case 'slide': 77 return { 78 ...baseStyle, 79 transform: [{ translateY }], 80 }; 81 case 'scale': 82 return { 83 ...baseStyle, 84 transform: [{ scale }], 85 }; 86 default: 87 return baseStyle; 88 } 89 }; 90 91 return ( 92 <Animated.View style={[getAnimatedStyle(), style]}> 93 {children} 94 </Animated.View> 95 ); 96 }; 97 98 // Hook for page transitions 99 export const usePageTransition = ( 100 transitionType: 'fade' | 'slide' | 'scale' = 'fade', 101 duration: number = 300 102 ) => { 103 const opacity = useRef(new Animated.Value(0)).current; 104 const translateY = useRef(new Animated.Value(50)).current; 105 const scale = useRef(new Animated.Value(0.9)).current; 106 107 const animateIn = () => { 108 const animations: Animated.CompositeAnimation[] = [ 109 Animated.timing(opacity, { 110 toValue: 1, 111 duration, 112 useNativeDriver: true, 113 }), 114 ]; 115 116 if (transitionType === 'slide') { 117 animations.push( 118 Animated.timing(translateY, { 119 toValue: 0, 120 duration, 121 useNativeDriver: true, 122 }) 123 ); 124 } 125 126 if (transitionType === 'scale') { 127 animations.push( 128 Animated.timing(scale, { 129 toValue: 1, 130 duration, 131 useNativeDriver: true, 132 }) 133 ); 134 } 135 136 Animated.parallel(animations).start(); 137 }; 138 139 const animateOut = () => { 140 const animations: Animated.CompositeAnimation[] = [ 141 Animated.timing(opacity, { 142 toValue: 0, 143 duration: duration / 2, 144 useNativeDriver: true, 145 }), 146 ]; 147 148 if (transitionType === 'slide') { 149 animations.push( 150 Animated.timing(translateY, { 151 toValue: -50, 152 duration: duration / 2, 153 useNativeDriver: true, 154 }) 155 ); 156 } 157 158 if (transitionType === 'scale') { 159 animations.push( 160 Animated.timing(scale, { 161 toValue: 0.8, 162 duration: duration / 2, 163 useNativeDriver: true, 164 }) 165 ); 166 } 167 168 return Animated.parallel(animations); 169 }; 170 171 const getAnimatedStyle = () => { 172 const baseStyle = { opacity }; 173 174 switch (transitionType) { 175 case 'slide': 176 return { 177 ...baseStyle, 178 transform: [{ translateY }], 179 }; 180 case 'scale': 181 return { 182 ...baseStyle, 183 transform: [{ scale }], 184 }; 185 default: 186 return baseStyle; 187 } 188 }; 189 190 return { 191 animateIn, 192 animateOut, 193 animatedStyle: getAnimatedStyle(), 194 }; 195 };