import useTheme from "../hooks/useTheme";
import { themeColors, ThemeName } from "../constants/Colors";
import React, { useEffect, useMemo } from "react";
import { View, ViewProps, ViewStyle, Dimensions } from "react-native";
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withRepeat,
interpolate,
cancelAnimation,
Extrapolate,
Easing,
} from "react-native-reanimated";
const {
width: FULL_ITEM_WIDTH,
} = Dimensions.get('window');
export type Props = ViewProps & {
themeScheme?: "light" | "dark";
children?: string | React.ReactNode;
variant?: "default" | "secondary" | ThemeName;
width?: ViewStyle['width'];
height?: ViewStyle['height'];
borderRadius?: ViewStyle["borderRadius"]
animationDirection?: "leftToRight" | "rightToLeft" | "topToBottom" | "bottomToTop";
animationType?: "shiver" | "pulse",
center?: boolean;
backgroundColor?: ViewStyle["backgroundColor"],
highlightColor?: ViewStyle["backgroundColor"],
square?: boolean
};
const Skeleton = ({
width = 100,
height = 100,
borderRadius = 8,
themeScheme,
animationType = "pulse",
animationDirection = "leftToRight",
variant = "default",
square,
...otherProps
}: Props) => {
const { currentTheme, themeScheme: defaultThemeScheme } = useTheme();
const ptpWidth = (typeof width === 'string' && width.includes('%'))
? (parseFloat(width) / 100) * FULL_ITEM_WIDTH
: (typeof width === 'number' ? width : FULL_ITEM_WIDTH);
// const ptpHeight = (typeof height === 'string' && height.includes('%'))
// ? (parseFloat(height) / 100) * FULL_ITEM_HEIGHT
// : (typeof height === 'number' ? height : FULL_ITEM_HEIGHT);
const progress = useSharedValue(0);
const opacity = useSharedValue(1);
const colorStyle = useMemo(() => {
if (variant === 'default') {
return {
backgroundColor: currentTheme.muted_foreground,
highlightColor: currentTheme.border,
};
}
if (variant === 'secondary') {
return {
backgroundColor: currentTheme.border,
highlightColor: currentTheme.muted_foreground,
};
}
const theme = themeColors.find((t) => t.name === variant)?.[themeScheme ?? defaultThemeScheme];
return {
backgroundColor: theme?.muted_foreground || currentTheme.muted_foreground,
highlightColor: theme?.border || currentTheme.border,
};
}, [currentTheme, themeScheme, defaultThemeScheme, variant]);
useEffect(() => {
if (animationType === "pulse") {
opacity.value = withRepeat(
withTiming(0.5, { duration: 800, easing: Easing.inOut(Easing.ease) }),
-1,
true
);
} else {
progress.value = withRepeat(withTiming(1, { duration: 1500 }), -1, false);
}
return () => {
cancelAnimation(progress);
cancelAnimation(opacity);
};
}, [animationType, progress, opacity]);
const animatedStyle = useAnimatedStyle(() => {
if (animationType === "pulse") {
return {
opacity: opacity.value,
};
}
let translateX = 0;
let translateY = 0;
let scale = 1;
if (animationType === "shiver") {
const offset = interpolate(
progress.value,
[0, 1],
[-ptpWidth, ptpWidth],
Extrapolate.CLAMP
);
if (animationDirection === "leftToRight") {
translateX = offset;
} else if (animationDirection === "rightToLeft") {
translateX = -offset;
} else if (animationDirection === "topToBottom") {
translateY = offset;
} else if (animationDirection === "bottomToTop") {
translateY = -offset;
}
}
return {
transform: [{ translateX }, { translateY }, { scale }],
};
});
return (
);
};
export default Skeleton;
export const SkeletonWithoutAnimation = ({
width = 100,
height = 100,
borderRadius = 8,
themeScheme,
variant = "default",
...otherProps
}: {
themeScheme?: "light" | "dark";
children?: string | React.ReactNode;
variant?: "default" | "secondary" | ThemeName;
width?: ViewStyle['width'];
height?: ViewStyle['height'];
borderRadius?: ViewStyle["borderRadius"]
}) => {
const { currentTheme, themeScheme: defaultThemeScheme } = useTheme();
const colorStyle = useMemo(() => {
if (variant === 'default') {
return {
backgroundColor: currentTheme.input,
highlightColor: currentTheme.border,
};
}
if (variant === 'secondary') {
return {
backgroundColor: currentTheme.muted,
highlightColor: currentTheme.muted_foreground,
};
}
const theme = themeColors.find((t) => t.name === variant)?.[themeScheme ?? defaultThemeScheme];
return {
backgroundColor: theme?.muted_foreground || currentTheme.muted_foreground,
highlightColor: theme?.border || currentTheme.border,
};
}, [currentTheme, themeScheme, defaultThemeScheme, variant]);
return (
);
};