Rework skeleton to use reanimated instead of moti

This commit is contained in:
Zoe Roux 2024-05-27 15:02:28 +02:00
parent 6458f419aa
commit 26a3c2c984
No known key found for this signature in database

View File

@ -19,12 +19,20 @@
*/
import { LinearGradient as LG } from "expo-linear-gradient";
import { MotiView, motify } from "moti";
import { useState } from "react";
import { Platform, View, type ViewProps } from "react-native";
import { memo, useEffect } from "react";
import { Platform, StyleSheet, View, type ViewProps } from "react-native";
import Animated, {
SharedValue,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withDelay,
withRepeat,
withTiming,
} from "react-native-reanimated";
import { em, percent, px, rem, useYoshiki } from "yoshiki/native";
const LinearGradient = motify(LG)();
const LinearGradient = Animated.createAnimatedComponent(LG);
export const SkeletonCss = () => (
<style jsx global>{`
@ -42,6 +50,39 @@ export const SkeletonCss = () => (
`}</style>
);
const AnimatedGradient = memo(
function Gradient({ color, width }: { color: string; width: SharedValue<number> }) {
const mult = useSharedValue(-1);
const animated = useAnimatedStyle(() => ({
transform: [
{
translateX: width.value * mult.value,
},
],
}));
useEffect(() => {
mult.value = withRepeat(withDelay(800, withTiming(1, { duration: 800 })), 0);
});
return (
<LinearGradient
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
colors={["transparent", color, "transparent"]}
style={[
StyleSheet.absoluteFillObject,
{ transform: [{ translateX: -width.value }] },
animated,
]}
/>
);
},
function propsAreEqual(prev, next) {
return prev.color === next.color;
},
);
export const Skeleton = ({
children,
show: forcedShow,
@ -55,8 +96,7 @@ export const Skeleton = ({
variant?: "text" | "header" | "round" | "custom" | "fill" | "filltext";
}) => {
const { css, theme } = useYoshiki();
const [width, setWidth] = useState<number | undefined>(undefined);
const perc = (v: number) => (v / 100) * width!;
const width = useSharedValue(0);
if (forcedShow === undefined && children && children !== true) return <>{children}</>;
@ -100,13 +140,11 @@ export const Skeleton = ({
>
{(forcedShow || !children || children === true) &&
[...Array(lines)].map((_, i) => (
<MotiView
<View
key={`skeleton_${i}`}
// No clue why it is a number on mobile and a string on web but /shrug
animate={{ opacity: Platform.OS === "web" ? ("1" as any) : 1 }}
exit={{ opacity: 0 }}
transition={{ type: "timing" }}
onLayout={(e) => setWidth(e.nativeEvent.layout.width)}
onLayout={(e) => {
width.value = e.nativeEvent.layout.width;
}}
{...css([
{
bg: (theme) => theme.overlay0,
@ -127,28 +165,8 @@ export const Skeleton = ({
},
])}
>
<LinearGradient
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
colors={["transparent", theme.overlay1, "transparent"]}
transition={{
loop: true,
repeatReverse: false,
}}
animate={{
translateX: width
? [perc(-100), { value: perc(100), type: "timing", duration: 800, delay: 800 }]
: undefined,
}}
{...css({
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
})}
/>
</MotiView>
<AnimatedGradient color={theme.overlay1} width={width} />
</View>
))}
{children}
</View>