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