UNPKG

@cometchat/chat-uikit-react-native

Version:

Ready-to-use Chat UI Components for React Native

115 lines (113 loc) 6.92 kB
import React, { useEffect, useMemo, useRef } from "react"; import { Animated, Dimensions, Easing, ScrollView, StyleSheet, View } from "react-native"; import Svg, { Circle, Defs, LinearGradient, Rect, Stop } from "react-native-svg"; import { useTheme } from "../theme"; // ────────────────────────────────────────────────────────────────────────────── // Utility helpers // ────────────────────────────────────────────────────────────────────────────── function getStyleValue(key, overrides, theme) { // Guaranteed fallback to theme defaults; cast is safe as UI‑Kit defines them. return (overrides?.[key] ?? theme.userStyles.skeletonStyle[key]); } // ────────────────────────────────────────────────────────────────────────────── // Layout constants – tweak here if the design changes // ────────────────────────────────────────────────────────────────────────────── const { width: SCREEN_WIDTH } = Dimensions.get("window"); const PADDING = 20; const AVATAR_RADIUS = 25; const LIST_ITEM_HEIGHT = 25; const LIST_ITEM_SPACING = 54; // gap between items (includes avatar & text) const LIST_ITEM_COUNT = 14; /** Total height required for the SVG canvas */ const TOTAL_HEIGHT = PADDING + LIST_ITEM_COUNT * (LIST_ITEM_HEIGHT + LIST_ITEM_SPACING); // ────────────────────────────────────────────────────────────────────────────── // SVG rows factory (memoized for perf) // ────────────────────────────────────────────────────────────────────────────── const useRows = (fill) => useMemo(() => Array.from({ length: LIST_ITEM_COUNT }).map((_, index) => { const y = PADDING + index * (LIST_ITEM_HEIGHT + LIST_ITEM_SPACING) - 20; return ( // eslint-disable-next-line react/no-array-index-key <React.Fragment key={index}> <Circle cx={PADDING + AVATAR_RADIUS} cy={y + AVATAR_RADIUS} r={AVATAR_RADIUS} fill={fill}/> <Rect x={PADDING + AVATAR_RADIUS * 2 + 12} y={y + 12} width={SCREEN_WIDTH - (PADDING + AVATAR_RADIUS * 2 + 12 + PADDING)} height={LIST_ITEM_HEIGHT} rx={LIST_ITEM_HEIGHT / 2} fill={fill}/> </React.Fragment>); }), [fill]); // ────────────────────────────────────────────────────────────────────────────── // Component Implementation // ────────────────────────────────────────────────────────────────────────────── export const Skeleton = ({ style }) => { const theme = useTheme(); const get = (key) => getStyleValue(key, style, theme); // Shimmer animation ------------------------------------------ const translate = useRef(new Animated.Value(0)).current; useEffect(() => { const speed = get("speed"); const duration = 1000 / speed; const loop = Animated.loop(Animated.timing(translate, { toValue: 1, duration, easing: Easing.linear, useNativeDriver: false, // SVG not yet compatible with native driver })); loop.start(); return () => loop.stop(); }, [get("speed"), translate]); const translateX = translate.interpolate({ inputRange: [0, 1], outputRange: [-SCREEN_WIDTH * 2, SCREEN_WIDTH], }); // Pre‑build row shapes (bottom gradient + top mask) const rowsGradient = useRows("url(#gradient)"); const rowsSolid = useRows(String(get("backgroundColor"))); return (<ScrollView scrollEnabled={false} showsVerticalScrollIndicator={false} style={{ backgroundColor: get("containerBackgroundColor") }}> {/* Bottom layer (gradient fill) */} <Svg height={TOTAL_HEIGHT} width={SCREEN_WIDTH} fill='none' preserveAspectRatio='xMidYMid meet'> <Defs> <LinearGradient id='gradient' x1='0' y1='0' x2={SCREEN_WIDTH} y2='0' gradientUnits='userSpaceOnUse'> {(() => { const colors = get("linearGradientColors"); return [ <Stop key={0} stopColor={colors[0]}/>, <Stop key={1} offset='1' stopColor={colors[1]}/>, ]; })()} </LinearGradient> </Defs> {rowsGradient} </Svg> {/* Moving shimmer highlight (rendered twice for coverage) */} {[0, SCREEN_WIDTH / 2].map((offset) => ( // eslint-disable-next-line react/no-array-index-key <Animated.View key={offset} style={[ styles.shimmer, { transform: [ { translateX: Animated.add(translateX, offset) }, { translateY: -20 }, { rotate: "15deg" }, ], backgroundColor: get("shimmerBackgroundColor"), opacity: get("shimmerOpacity"), }, ]}/>))} {/* Top mask – solid shapes clip the shimmer to list items */} <View style={StyleSheet.absoluteFill} pointerEvents='none'> <Svg height={TOTAL_HEIGHT} width={SCREEN_WIDTH} fill='none' preserveAspectRatio='xMidYMid meet'> {rowsSolid} </Svg> </View> </ScrollView>); }; // ────────────────────────────────────────────────────────────────────────────── // Styles // ────────────────────────────────────────────────────────────────────────────── const styles = StyleSheet.create({ shimmer: { position: "absolute", width: "25%", top: 0, bottom: 0, }, }); //# sourceMappingURL=Skeleton.js.map