/**
* Skeleton (native). Same contract as web; the gradient sweep would need
* a gradient dependency, so native pulses opacity on a containerFill block
* instead — same "shape of what's coming" effect, zero new deps.
*
*
*
*
* Reduced motion (AccessibilityInfo) renders a static fill.
*/
import { useEffect, useRef, type ReactElement } from "react";
import { Animated, View, type DimensionValue, type ViewStyle } from "react-native";
import { useTheme } from "@plyxui/styles";
import { radius } from "@plyxui/core";
import { useReducedMotion } from "@plyxui/hooks";
export type SkeletonVariant = "text" | "rect" | "circle";
export interface SkeletonProps {
variant?: SkeletonVariant;
width?: number | string;
height?: number | string;
/** For variant="text": number of stacked lines. Last line is shorter. Default 1. */
lines?: number;
style?: ViewStyle;
}
export function Skeleton({ variant = "rect", width, height, lines = 1, style }: SkeletonProps) {
const { colors } = useTheme();
const reduced = useReducedMotion();
const opacity = useRef(new Animated.Value(1)).current;
useEffect(() => {
if (reduced) {
opacity.setValue(1);
return;
}
const loop = Animated.loop(
Animated.sequence([
Animated.timing(opacity, { toValue: 0.45, duration: 800, useNativeDriver: true }),
Animated.timing(opacity, { toValue: 1, duration: 800, useNativeDriver: true }),
]),
);
loop.start();
return () => loop.stop();
}, [opacity, reduced]);
const block = (dims: ViewStyle, key?: number): ReactElement => (
);
if (variant === "text" && lines > 1) {
return (
{Array.from({ length: lines }, (_, i) =>
block(
{
height: (height as DimensionValue) ?? 14,
borderRadius: radius.sm,
width: i === lines - 1 ? "60%" : "100%",
},
i,
),
)}
);
}
const dims: ViewStyle =
variant === "circle"
? {
width: (width as DimensionValue) ?? 40,
height: (height as DimensionValue) ?? (width as DimensionValue) ?? 40,
borderRadius: radius.pill,
}
: variant === "text"
? { width: (width as DimensionValue) ?? "100%", height: (height as DimensionValue) ?? 14, borderRadius: radius.sm }
: { width: (width as DimensionValue) ?? "100%", height: (height as DimensionValue) ?? 16, borderRadius: radius.md };
return (
);
}