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;