/** * @file Skeleton.tsx * @description * Shimmer / skeleton loading component for the **Call Logs** list in CometChat UIKit. * It renders a column of 20 placeholder rows that mimic an avatar, name, subtitle and a * trailing action icon. A diagonal animated gradient (“shimmer”) sweeps across the rows * to hint that data is loading. * * The component is completely theme-aware and can be customised via the `style` prop * or by tweaking the UIKit theme’s `callLogsStyles.skeletonStyle` object. * */ import React, { useEffect, useRef } from "react"; import { Animated, Dimensions, Easing, ScrollView, StyleSheet, View, } from "react-native"; import Svg, { Defs, G, LinearGradient, Path, Rect, Stop, } from "react-native-svg"; import { useTheme } from "../../theme"; import { CometChatTheme } from "../../theme/type"; const { width: screenWidth } = Dimensions.get("window"); /** * Shape of the style object accepted by this component. * It is taken straight from the UIKit theme definition so that consumers * may override only the bits they need. */ type SkeletonStyle = CometChatTheme["callLogsStyles"]["skeletonStyle"]; interface SkeletonProps { /** * Per-instance style overrides. **All keys are optional**; any missing keys * fall back to the values provided by the current CometChat theme. */ style?: Partial; } /* -------------------------------------------------------------------------- */ /* Row layouts */ /* -------------------------------------------------------------------------- */ /** * Bottom SVG layer – a set of simple `Rect`s that will receive the animated * gradient fill. Placed below the solid colour mask from `SkeletonItemTop`. */ const SkeletonItemBottom: React.FC = ({ style = {} }) => { const theme = useTheme(); const defaults = theme.callLogsStyles.skeletonStyle; /* always-defined fallback */ const gradientColors = style.linearGradientColors ?? defaults.linearGradientColors ?? ["#E8E8E8", "#F5F5F5"]; return ( {/* Avatar circle replacement */} {/* Primary text placeholder */} {/* Secondary text placeholder */} {/* Trailing icon placeholder */} ); }; /** * Top SVG layer – a solid colour mask with transparent “holes” cut out * where the animated gradient from `SkeletonItemBottom` should be visible. */ const SkeletonItemTop: React.FC = ({ style = {} }) => { const theme = useTheme(); const defaults = theme.callLogsStyles.skeletonStyle; return ( ); }; /* -------------------------------------------------------------------------- */ /* Main Skeleton component */ /* -------------------------------------------------------------------------- */ /** * Animated shimmer skeleton for the Call Logs list. * * @param props.style – Partial style overrides. * * @example * ```tsx * * ``` */ export const Skeleton: React.FC = ({ style = {} }) => { const theme = useTheme(); const defaults = theme.callLogsStyles.skeletonStyle; /* guaranteed speed */ const speed = style.speed ?? defaults.speed ?? 1; const shimmerAnim = useRef(new Animated.Value(0)).current; /* ------------------------------------------------------------------ */ /* Lifecycle */ /* ------------------------------------------------------------------ */ useEffect(() => { shimmerAnim.setValue(0); Animated.loop( Animated.timing(shimmerAnim, { toValue: 1, duration: (1 / speed) * 1000, // safe easing: Easing.linear, useNativeDriver: false, }) ).start(); }, [shimmerAnim, speed]); /* Animated translation for the first and second diagonal bars */ const translateX = shimmerAnim.interpolate({ inputRange: [0, 1], outputRange: [-screenWidth * 2, screenWidth], }); /* ------------------------------------------------------------------ */ /* Render */ /* ------------------------------------------------------------------ */ return ( {/* Bottom layer – gradient-filled rows */} {Array.from({ length: 20 }).map((_, i) => ( ))} {/* Diagonal shimmer bars (two for seamless wrap-around) */} {[0, screenWidth / 2].map((offset, idx) => ( ))} {Array.from({ length: 20 }).map((_, i) => ( ))} ); }; /* ----------------------------- local styles ------------------------------ */ const localStyles = StyleSheet.create({ /** Base style for each diagonal shimmer bar */ animatedBar: { position: "absolute", top: 0, bottom: 0, width: "25%", }, });