import React, { useEffect } from 'react'; import { View, type DimensionValue, type StyleProp, type ViewStyle } from 'react-native'; import Animated, { Easing, ReduceMotion, useAnimatedStyle, useSharedValue, withRepeat, withTiming, } from 'react-native-reanimated'; import { m3Colors, m3Shape, spacing } from '../theme/proulr-theme'; const PULSE_DURATION_MS = 900; const PULSE_MIN_OPACITY = 0.45; /** * Pulso de opacidade compartilhado por cada bloco de skeleton. Respeita o * "reduzir movimento" do sistema (fica estatico). Como `react-native-reanimated` * ja e usado nos primitivos do pacote (ex.: RangeSlider), importamos direto. */ function usePulseStyle() { const progress = useSharedValue(PULSE_MIN_OPACITY); useEffect(() => { progress.value = withRepeat( withTiming(1, { duration: PULSE_DURATION_MS, easing: Easing.inOut(Easing.ease), reduceMotion: ReduceMotion.System, }), -1, true, ); }, [progress]); return useAnimatedStyle(() => ({ opacity: progress.value })); } export type SkeletonProps = { width?: DimensionValue; height?: DimensionValue; radius?: number; style?: StyleProp; }; /** Bloco tonal com shimmer — base de qualquer skeleton. */ export function Skeleton({ width = '100%', height = 16, radius = m3Shape.cornerSmall, style, }: SkeletonProps) { const pulse = usePulseStyle(); return ( ); } export type SkeletonCircleProps = { size?: number; style?: StyleProp; }; /** Placeholder circular (avatar). */ export function SkeletonCircle({ size = 48, style }: SkeletonCircleProps) { return ; } export type SkeletonTextProps = { lines?: number; lineHeight?: number; gap?: number; /** Largura da ultima linha (efeito paragrafo). */ lastLineWidth?: DimensionValue; style?: StyleProp; }; /** Bloco de linhas de texto (ultima linha mais curta). */ export function SkeletonText({ lines = 3, lineHeight = 12, gap = spacing.xs, lastLineWidth = '60%', style, }: SkeletonTextProps) { return ( {Array.from({ length: Math.max(1, lines) }).map((_, index) => { const isLast = index === lines - 1; return ( 1 ? lastLineWidth : '100%'} radius={m3Shape.cornerFull} /> ); })} ); }