You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.2 KiB
134 lines
3.2 KiB
import React, { forwardRef } from 'react';
|
|
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
|
|
import { Animated, Easing, Platform, View } from 'react-native';
|
|
import { skeletonStyle, skeletonTextStyle } from './styles';
|
|
|
|
type ISkeletonProps = React.ComponentProps<typeof View> &
|
|
VariantProps<typeof skeletonStyle> & {
|
|
isLoaded?: boolean;
|
|
startColor?: string;
|
|
};
|
|
|
|
type ISkeletonTextProps = React.ComponentProps<typeof View> &
|
|
VariantProps<typeof skeletonTextStyle> & {
|
|
_lines?: number;
|
|
isLoaded?: boolean;
|
|
startColor?: string;
|
|
};
|
|
|
|
const Skeleton = forwardRef<
|
|
React.ComponentRef<typeof Animated.View>,
|
|
ISkeletonProps
|
|
>(function Skeleton(
|
|
{
|
|
className,
|
|
variant,
|
|
children,
|
|
startColor = 'bg-background-200',
|
|
isLoaded = false,
|
|
speed = 2,
|
|
...props
|
|
},
|
|
ref
|
|
) {
|
|
const pulseAnim = new Animated.Value(1);
|
|
const customTimingFunction = Easing.bezier(0.4, 0, 0.6, 1);
|
|
const fadeDuration = 0.6;
|
|
const animationDuration = (fadeDuration * 10000) / speed; // Convert seconds to milliseconds
|
|
|
|
const pulse = Animated.sequence([
|
|
Animated.timing(pulseAnim, {
|
|
toValue: 1, // Start with opacity 1
|
|
duration: animationDuration / 2, // Third of the animation duration
|
|
easing: customTimingFunction,
|
|
useNativeDriver: Platform.OS !== 'web',
|
|
}),
|
|
Animated.timing(pulseAnim, {
|
|
toValue: 0.75,
|
|
duration: animationDuration / 2, // Third of the animation duration
|
|
easing: customTimingFunction,
|
|
useNativeDriver: Platform.OS !== 'web',
|
|
}),
|
|
Animated.timing(pulseAnim, {
|
|
toValue: 1,
|
|
duration: animationDuration / 2, // Third of the animation duration
|
|
easing: customTimingFunction,
|
|
useNativeDriver: Platform.OS !== 'web',
|
|
}),
|
|
]);
|
|
|
|
if (!isLoaded) {
|
|
Animated.loop(pulse).start();
|
|
return (
|
|
<Animated.View
|
|
style={{ opacity: pulseAnim }}
|
|
className={`${startColor} ${skeletonStyle({
|
|
variant,
|
|
class: className,
|
|
})}`}
|
|
{...props}
|
|
ref={ref}
|
|
/>
|
|
);
|
|
} else {
|
|
Animated.loop(pulse).stop();
|
|
|
|
return children;
|
|
}
|
|
});
|
|
|
|
const SkeletonText = forwardRef<
|
|
React.ComponentRef<typeof View>,
|
|
ISkeletonTextProps
|
|
>(function SkeletonText(
|
|
{
|
|
className,
|
|
_lines,
|
|
isLoaded = false,
|
|
startColor = 'bg-background-200',
|
|
gap = 2,
|
|
children,
|
|
...props
|
|
},
|
|
ref
|
|
) {
|
|
if (!isLoaded) {
|
|
if (_lines) {
|
|
return (
|
|
<View
|
|
className={`${skeletonTextStyle({
|
|
gap,
|
|
})}`}
|
|
ref={ref}
|
|
>
|
|
{Array.from({ length: _lines }).map((_, index) => (
|
|
<Skeleton
|
|
key={index}
|
|
className={`${startColor} ${skeletonTextStyle({
|
|
class: className,
|
|
})}`}
|
|
{...props}
|
|
/>
|
|
))}
|
|
</View>
|
|
);
|
|
} else {
|
|
return (
|
|
<Skeleton
|
|
className={`${startColor} ${skeletonTextStyle({
|
|
class: className,
|
|
})}`}
|
|
{...props}
|
|
ref={ref}
|
|
/>
|
|
);
|
|
}
|
|
} else {
|
|
return children;
|
|
}
|
|
});
|
|
|
|
Skeleton.displayName = 'Skeleton';
|
|
SkeletonText.displayName = 'SkeletonText';
|
|
|
|
export { Skeleton, SkeletonText };
|