import React, { createContext, useContext } from 'react' import { View, StyleSheet } from 'react-native' import { useAnimatedStyle, useDerivedValue, useSharedValue, } from 'react-native-reanimated' import type Animated from 'react-native-reanimated' import { View as MotiView } from '../components' import { MotiTransitionProp } from '../core' import { DEFAULT_SKELETON_SIZE as DEFAULT_SIZE, defaultDarkColors, defaultLightColors, baseColors, } from './shared' import { MotiSkeletonProps } from './types' export default function Skeleton(props: MotiSkeletonProps) { const skeletonGroupContext = useContext(SkeletonGroupContext) const { radius = 8, children, show = skeletonGroupContext ?? !children, width, height = children ? undefined : DEFAULT_SIZE, boxHeight, colorMode = 'dark', colors = colorMode === 'dark' ? defaultDarkColors : defaultLightColors, backgroundColor = colors[0] ?? colors[1] ?? baseColors[colorMode]?.secondary, backgroundSize = 6, disableExitAnimation, transition, } = props const measuredWidthSv = useSharedValue(0) const borderRadius = (() => { if (radius === 'square') { return 0 } if (radius === 'round') { return 99999 } return radius })() const outerHeight = (() => { if (boxHeight != null) return boxHeight if (show && !children) { return height } return undefined })() return ( {children} { if (measuredWidthSv.value !== nativeEvent.layout.width) { measuredWidthSv.value = nativeEvent.layout.width } }} pointerEvents="none" > {disableExitAnimation && !show ? null : ( )} ) } const AnimatedGradient = React.memo( function AnimatedGradient({ colors, backgroundSize, transition, show, measuredWidthSv, Gradient, }: { colors: string[] backgroundSize: number transition?: MotiTransitionProp show: boolean measuredWidthSv: Animated.SharedValue } & Pick) { return ( ({ width: measuredWidthSv.value * backgroundSize, }), [backgroundSize, measuredWidthSv] ), ]} from={{ opacity: 0, translateX: 0, }} animate={useDerivedValue(() => { return { opacity: show ? 1 : 0, translateX: -measuredWidthSv.value * (backgroundSize - 1), } }, [measuredWidthSv, show])} transition={{ translateX: { type: 'timing', loop: show, delay: 200, duration: 3000, }, opacity: { type: 'timing', delay: 0, duration: 200, }, ...(transition as any), }} > ) }, function propsAreEqual(prev, next) { if (prev.backgroundSize !== next.backgroundSize) return false if (prev.show !== next.show) return false const didColorsChange = prev.colors.some((color, index) => { return color !== next.colors[index] }) if (didColorsChange) return false // transition changes will not be respected return true } ) const SkeletonGroupContext = createContext(undefined) function SkeletonGroup({ children, show, }: { children: React.ReactNode /** * If `true`, all `Skeleton` children components will be shown. * * If `false`, the `Skeleton` children will be hidden. */ show: boolean }) { return ( {children} ) } Skeleton.Group = SkeletonGroup