diff --git a/apps/mobile/src/components/Skeleton.tsx b/apps/mobile/src/components/Skeleton.tsx new file mode 100644 index 0000000..23f52d2 --- /dev/null +++ b/apps/mobile/src/components/Skeleton.tsx @@ -0,0 +1,57 @@ +import React, { useEffect, useRef } from 'react'; +import { View, Animated, StyleSheet, ViewStyle } from 'react-native'; +import { COLORS } from '../theme/tokens'; + +interface SkeletonProps { + width?: number | string; + height?: number | string; + borderRadius?: number; + style?: ViewStyle; +} + +export const Skeleton: React.FC = ({ + width, + height, + borderRadius = 4, + style, +}) => { + const opacity = useRef(new Animated.Value(0.3)).current; + + useEffect(() => { + Animated.loop( + Animated.sequence([ + Animated.timing(opacity, { + toValue: 0.7, + duration: 800, + useNativeDriver: true, + }), + Animated.timing(opacity, { + toValue: 0.3, + duration: 800, + useNativeDriver: true, + }), + ]) + ).start(); + }, [opacity]); + + return ( + + ); +}; + +const styles = StyleSheet.create({ + skeleton: { + backgroundColor: COLORS.bgElevated, + }, +}); diff --git a/apps/mobile/src/screens/DevCardViewScreen.tsx b/apps/mobile/src/screens/DevCardViewScreen.tsx index c228df4..46cf951 100644 --- a/apps/mobile/src/screens/DevCardViewScreen.tsx +++ b/apps/mobile/src/screens/DevCardViewScreen.tsx @@ -14,6 +14,7 @@ import { } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; +import { Skeleton } from '../components/Skeleton'; import { PLATFORMS, getProfileUrl, getWebViewUrl } from '@devcard/shared'; import { API_BASE_URL } from '../config'; import { useAuth } from '../context/AuthContext'; @@ -175,7 +176,41 @@ export default function DevCardViewScreen({ navigation, route }: Props) { if (loading) { return ( - + + {/* Header Skeleton */} + + + + + + + + + + + + + + + + + + + {/* Tiles Skeleton */} + + + {[1, 2, 3].map(i => ( + + + + + + + + + ))} + + ); }