import LinearGradient from 'react-native-linear-gradient'; import React, { useEffect, useMemo, useState, useCallback, useRef, } from 'react'; import type { LayoutChangeEvent, ViewProps } from 'react-native'; import { Animated, Easing, Platform, StyleSheet } from 'react-native'; import type { Theme } from '../../theme'; import { useTheme } from '../../theme'; import { StyledContainer, StyledGradientContainer } from './StyledSkeleton'; interface SkeletonProps extends ViewProps { /** * Intent of the component. */ intent?: 'light' | 'dark'; /** * Variant of the component. */ variant?: 'circular' | 'rectangular' | 'rounded'; } const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient); const gradientPositions = { start: { x: 0, y: 0 }, end: { x: 1, y: 0 }, }; const getGradientColors = (theme: Theme, intent: 'light' | 'dark') => { switch (intent) { case 'light': { return [ theme.__hd__.skeleton.colors.lightGradientStart, theme.__hd__.skeleton.colors.lightGradientEnd, theme.__hd__.skeleton.colors.lightGradientStart, ]; } case 'dark': { return [ theme.__hd__.skeleton.colors.darkGradientStart, theme.__hd__.skeleton.colors.darkGradientEnd, theme.__hd__.skeleton.colors.darkGradientStart, ]; } } }; const Skeleton = ({ intent = 'light', variant = 'rounded', style, onLayout, ...props }: SkeletonProps) => { const theme = useTheme(); const colors = useMemo( () => getGradientColors(theme, intent), [theme, intent] ); const [containerWidth, setContainerWidth] = useState( Number(StyleSheet.flatten(style)?.width) || 0 ); const [shouldStartAnimation, setShouldStartAnimation] = useState(false); const animatedValue = useRef(new Animated.Value(0)).current; useEffect(() => { if (shouldStartAnimation) { Animated.loop( Animated.timing(animatedValue, { toValue: 1, duration: 1000, easing: Easing.linear, useNativeDriver: Platform.OS === 'ios' || Platform.OS === 'android', }) ).start(); } }, [shouldStartAnimation]); const translateX = animatedValue.interpolate({ inputRange: [0, 1], outputRange: [-containerWidth, containerWidth], }); const onContainerLayout = useCallback((e: LayoutChangeEvent) => { const { width } = e.nativeEvent.layout; setContainerWidth(width); if (!shouldStartAnimation) { setShouldStartAnimation(true); } onLayout?.(e); }, []); return ( ); }; export default Skeleton;