import React, { useCallback, useMemo, useState, type PropsWithChildren, } from 'react'; import { StyleSheet, useWindowDimensions, type LayoutChangeEvent, } from 'react-native'; import Animated, { useSharedValue } from 'react-native-reanimated'; import { AnimationSettingsProvider } from '../../helpers/contexts/animation-settings-context'; import LinearGradientComponent from './linear-gradient'; import { SkeletonAnimationProvider, useSkeletonAnimation, useSkeletonPulseAnimation, useSkeletonRootAnimation, useSkeletonShimmerAnimation, } from './skeleton.animation'; import { DISPLAY_NAME } from './skeleton.constants'; import styleSheet, { nativeStyles } from './skeleton.styles'; import type { SkeletonProps } from './skeleton.types'; // -------------------------------------------------- const ShimmerAnimation: React.FC<{ animation: SkeletonProps['animation']; isAnimatedStyleActive?: boolean; }> = ({ animation, isAnimatedStyleActive = true }) => { const { rContainerStyle, gradientColors } = useSkeletonShimmerAnimation({ animation, }); const shimmerStyle = isAnimatedStyleActive ? [StyleSheet.absoluteFill, nativeStyles.borderCurve, rContainerStyle] : [StyleSheet.absoluteFill, nativeStyles.borderCurve]; return ( ); }; // -------------------------------------------------- const PulseAnimation: React.FC< PropsWithChildren<{ animation: SkeletonProps['animation']; isAnimatedStyleActive?: boolean; }> > = ({ children, animation, isAnimatedStyleActive = true }) => { const { variant } = useSkeletonAnimation(); const { rContainerStyle } = useSkeletonPulseAnimation({ animation, }); if (variant === 'pulse') { const pulseStyle = isAnimatedStyleActive ? rContainerStyle : undefined; return {children}; } return children; }; // -------------------------------------------------- const Skeleton: React.FC = (props) => { const { children, isLoading = true, variant = 'shimmer', animation, isAnimatedStyleActive = true, className, style, ...restProps } = props; const [componentWidth, setComponentWidth] = useState(0); const [offset, setOffset] = useState(0); const progress = useSharedValue(0); const { width: screenWidth } = useWindowDimensions(); const { isAllAnimationsDisabled, entering, exiting } = useSkeletonRootAnimation({ animation, isLoading, variant, progress, }); const tvStyles = styleSheet.skeleton({ className }); const handleLayout = useCallback( (event: LayoutChangeEvent) => { if (componentWidth === 0) { const { width, x } = event.nativeEvent.layout; setComponentWidth(width); setOffset(x); } }, [componentWidth] ); const animationContextValue = useMemo( () => ({ isLoading, variant, progress, componentWidth, offset, screenWidth, }), [isLoading, variant, progress, componentWidth, offset, screenWidth] ); const animationSettingsContextValue = useMemo( () => ({ isAllAnimationsDisabled, }), [isAllAnimationsDisabled] ); if (!isLoading) { return ( {children} ); } return ( {variant === 'shimmer' && componentWidth > 0 && ( )} ); }; // -------------------------------------------------- Skeleton.displayName = DISPLAY_NAME.SKELETON; /** * Skeleton component for displaying loading placeholders * * @component Skeleton - Animated loading placeholder that can display shimmer or pulse effects. * Shows skeleton state when isLoading is true, otherwise displays children content. * Supports customizable animations through the animation prop with shimmer and pulse configurations. * Shape and size are controlled via className for maximum flexibility. * * @see Full documentation: https://heroui.com/components/skeleton */ export default Skeleton;