import React, { useEffect, useRef } from 'react'; import { View, Animated, StyleSheet, ViewStyle } from 'react-native'; interface SkeletonItemProps { width?: number | `${number}%`; height?: number; borderRadius?: number; style?: ViewStyle; } /** * Bloc de squelette animé avec effet de pulsation (shimmer). * Composant interne utilisé par SkeletonLoader pour construire les cartes placeholder. */ const SkeletonItem: React.FC = ({ width = '100%', height = 16, borderRadius = 4, style, }) => { const opacity = useRef(new Animated.Value(0.3)).current; useEffect(() => { /** Boucle d'animation : alterne entre 30% et 70% d'opacité */ const animation = Animated.loop( Animated.sequence([ Animated.timing(opacity, { toValue: 0.7, duration: 800, useNativeDriver: true, }), Animated.timing(opacity, { toValue: 0.3, duration: 800, useNativeDriver: true, }), ]) ); animation.start(); return () => animation.stop(); }, [opacity]); return ( ); }; interface SkeletonLoaderProps { /** Nombre de cartes placeholder à afficher pendant le chargement initial */ count?: number; } /** * Affiche des cartes placeholder animées pendant le chargement des données. * Simule la structure d'une WorkflowCard/ExecutionCard pour éviter le layout shift * et indiquer à l'utilisateur que du contenu arrive. */ const SkeletonLoader: React.FC = ({ count = 5 }) => { return ( <> {Array.from({ length: count }).map((_, index) => ( ))} ); }; const styles = StyleSheet.create({ card: { backgroundColor: '#1E1E1E', marginHorizontal: 16, marginVertical: 6, padding: 16, borderRadius: 12, }, skeleton: { backgroundColor: '#2C2C2C', }, }); export default SkeletonLoader;