/ components / SkeletonLoading.tsx
SkeletonLoading.tsx
1 import React, { useEffect, useRef } from 'react'; 2 import { 3 View, 4 Animated, 5 StyleSheet, 6 Dimensions, 7 ViewStyle, 8 } from 'react-native'; 9 import { useTheme } from '@/constants/ThemeContext'; 10 import { Colors } from '@/constants/Colors'; 11 import { ShimmerEffect, ShimmerCard } from './ShimmerEffect'; 12 13 const { width: SCREEN_WIDTH } = Dimensions.get('window'); 14 15 interface SkeletonLoadingProps { 16 width?: number | string; 17 height?: number; 18 borderRadius?: number; 19 style?: ViewStyle; 20 } 21 22 export const SkeletonItem: React.FC<SkeletonLoadingProps> = ({ 23 width = '100%', 24 height = 20, 25 borderRadius = 4, 26 style, 27 }) => { 28 const { actualTheme } = useTheme(); 29 const colors = Colors[actualTheme]; 30 const animatedValue = useRef(new Animated.Value(0)).current; 31 32 useEffect(() => { 33 const startAnimation = () => { 34 Animated.loop( 35 Animated.sequence([ 36 Animated.timing(animatedValue, { 37 toValue: 1, 38 duration: 1000, 39 useNativeDriver: false, 40 }), 41 Animated.timing(animatedValue, { 42 toValue: 0, 43 duration: 1000, 44 useNativeDriver: false, 45 }), 46 ]) 47 ).start(); 48 }; 49 50 startAnimation(); 51 }, [animatedValue]); 52 53 const backgroundColor = animatedValue.interpolate({ 54 inputRange: [0, 1], 55 outputRange: [colors.card, colors.border], 56 }); 57 58 return ( 59 <Animated.View 60 style={[ 61 { 62 width: Number(width) || 100, 63 height, 64 borderRadius, 65 backgroundColor, 66 }, 67 style, 68 ]} 69 /> 70 ); 71 }; 72 73 export const MangaCardSkeleton: React.FC = () => { 74 const { actualTheme } = useTheme(); 75 const colors = Colors[actualTheme]; 76 const styles = getStyles(colors); 77 78 return <ShimmerCard style={styles.cardContainer} />; 79 }; 80 81 export const RecentlyReadSkeleton: React.FC = () => { 82 const cardWidth = Math.min(160, (SCREEN_WIDTH - 64) / 2); 83 84 return ( 85 <View style={{ flexDirection: 'row', paddingHorizontal: 16 }}> 86 {Array.from({ length: 3 }).map((_, index) => ( 87 <View key={index} style={{ width: cardWidth, marginRight: 12 }}> 88 <MangaCardSkeleton /> 89 </View> 90 ))} 91 </View> 92 ); 93 }; 94 95 export const TrendingSkeleton: React.FC = () => { 96 return ( 97 <View style={{ flexDirection: 'row', paddingHorizontal: 16 }}> 98 {Array.from({ length: 4 }).map((_, index) => ( 99 <View key={index} style={{ width: 200, height: 260, marginRight: 12 }}> 100 <ShimmerEffect width={200} height={260} borderRadius={16} /> 101 </View> 102 ))} 103 </View> 104 ); 105 }; 106 107 export const NewReleasesSkeleton: React.FC = () => { 108 const { actualTheme } = useTheme(); 109 const colors = Colors[actualTheme]; 110 const styles = getStyles(colors); 111 112 return ( 113 <View style={styles.gridContainer}> 114 {Array.from({ length: 6 }).map((_, index) => ( 115 <View key={index} style={styles.gridItem}> 116 <MangaCardSkeleton /> 117 </View> 118 ))} 119 </View> 120 ); 121 }; 122 123 export const FeaturedMangaSkeleton: React.FC = () => { 124 return ( 125 <View style={{ height: 280, marginHorizontal: 16, marginBottom: 24 }}> 126 <ShimmerEffect width={300} height={280} borderRadius={16} /> 127 </View> 128 ); 129 }; 130 131 const getStyles = (colors: typeof Colors.light) => 132 StyleSheet.create({ 133 cardContainer: { 134 borderRadius: 12, 135 overflow: 'hidden', 136 backgroundColor: colors.card, 137 }, 138 cardInfo: { 139 padding: 8, 140 }, 141 gridContainer: { 142 flexDirection: 'row', 143 flexWrap: 'wrap', 144 paddingHorizontal: 16, 145 }, 146 gridItem: { 147 width: '50%', 148 padding: 8, 149 }, 150 }); 151 152 export default SkeletonItem;