1- import React , { useEffect , useMemo } from 'react' ;
2- import { StyleSheet , useWindowDimensions , View } from 'react-native' ;
3- import Animated , {
4- Easing ,
5- useAnimatedStyle ,
6- useSharedValue ,
7- withRepeat ,
8- withTiming ,
9- } from 'react-native-reanimated' ;
10- import Svg , { Path , Rect , Defs , LinearGradient , Stop , ClipPath , G , Mask } from 'react-native-svg' ;
1+ import React , { useMemo } from 'react' ;
2+ import { StyleSheet , View } from 'react-native' ;
113
124import { useTheme } from '../../contexts/themeContext/ThemeContext' ;
5+ import { primitives } from '../../theme' ;
6+ import { NativeShimmerView } from '../UIComponents/NativeShimmerView' ;
137
14- export const Skeleton = ( ) => {
15- const width = useWindowDimensions ( ) . width ;
16- const startOffset = useSharedValue ( - width ) ;
17- const styles = useStyles ( ) ;
18-
8+ const SkeletonBlock = ( { style } : { style : React . ComponentProps < typeof View > [ 'style' ] } ) => {
199 const {
20- theme : {
21- channelListSkeleton : { animationTime = 1500 , container, height = 80 } ,
22- semantics,
23- } ,
10+ theme : { semantics } ,
2411 } = useTheme ( ) ;
2512
26- useEffect ( ( ) => {
27- startOffset . value = withRepeat (
28- withTiming ( width , {
29- duration : animationTime ,
30- easing : Easing . linear ,
31- } ) ,
32- - 1 ,
33- ) ;
34- // eslint-disable-next-line react-hooks/exhaustive-deps
35- } , [ ] ) ;
36-
37- const animatedStyle = useAnimatedStyle (
38- ( ) => ( {
39- transform : [ { translateX : startOffset . value } ] ,
40- } ) ,
41- [ ] ,
42- ) ;
43-
4413 return (
45- < View style = { [ styles . container , container ] } testID = 'channel-preview-skeleton' >
46- < Animated . View style = { [ animatedStyle ] } >
47- < Svg width = { width } height = { height } viewBox = '0 0 402 80' fill = 'none' >
48- { /* Mask */ }
49- < Defs >
50- < Mask id = 'path-1-inside-1_5596_231135' >
51- < Path d = 'M0 0H402V80H0V0Z' fill = 'white' />
52- </ Mask >
53-
54- { /* Gradients */ }
55- < LinearGradient
56- id = 'paint0_linear_5596_231135'
57- x1 = '48'
58- y1 = '24'
59- x2 = '0'
60- y2 = '24'
61- gradientUnits = 'userSpaceOnUse'
62- >
63- < Stop stopColor = 'white' />
64- < Stop offset = '1' stopColor = 'white' stopOpacity = '0' />
65- </ LinearGradient >
66-
67- < LinearGradient
68- id = 'paint1_linear_5596_231135'
69- x1 = '242'
70- y1 = '8'
71- x2 = '0'
72- y2 = '8'
73- gradientUnits = 'userSpaceOnUse'
74- >
75- < Stop stopColor = 'white' />
76- < Stop offset = '1' stopColor = 'white' stopOpacity = '0' />
77- </ LinearGradient >
78-
79- < LinearGradient
80- id = 'paint2_linear_5596_231135'
81- x1 = '48'
82- y1 = '8'
83- x2 = '0'
84- y2 = '8'
85- gradientUnits = 'userSpaceOnUse'
86- >
87- < Stop stopColor = 'white' />
88- < Stop offset = '1' stopColor = 'white' stopOpacity = '0' />
89- </ LinearGradient >
90-
91- < LinearGradient
92- id = 'paint3_linear_5596_231135'
93- x1 = '199'
94- y1 = '8'
95- x2 = '0'
96- y2 = '8'
97- gradientUnits = 'userSpaceOnUse'
98- >
99- < Stop stopColor = 'white' />
100- < Stop offset = '1' stopColor = 'white' stopOpacity = '0' />
101- </ LinearGradient >
14+ < View style = { style } >
15+ < NativeShimmerView
16+ baseColor = { semantics . backgroundCoreSurface }
17+ gradientColor = { semantics . skeletonLoadingHighlight }
18+ style = { StyleSheet . absoluteFillObject }
19+ />
20+ </ View >
21+ ) ;
22+ } ;
10223
103- { /* ClipPaths */ }
104- < ClipPath id = 'clip0_5596_231135' >
105- < Path d = 'M16 40C16 26.7452 26.7452 16 40 16C53.2548 16 64 26.7452 64 40C64 53.2548 53.2548 64 40 64C26.7452 64 16 53.2548 16 40Z' />
106- </ ClipPath >
24+ const SkeletonAvatar = ( ) => {
25+ const styles = useStyles ( ) ;
26+ return < SkeletonBlock style = { styles . avatar } /> ;
27+ } ;
10728
108- < ClipPath id = 'clip1_5596_231135' >
109- < Path d = 'M80 28C80 23.5817 83.5817 20 88 20H314C318.418 20 322 23.5817 322 28C322 32.4183 318.418 36 314 36H88C83.5817 60 80 56.4183 80 52Z' />
110- </ ClipPath >
29+ const SkeletonTimestamp = ( ) => {
30+ const styles = useStyles ( ) ;
31+ return < SkeletonBlock style = { styles . badge } /> ;
32+ } ;
11133
112- < ClipPath id = 'clip2_5596_231135' >
113- < Path d = 'M338 28C338 23.5817 341.582 20 346 20H378C382.418 20 386 23.5817 386 28C386 32.4183 382.418 36 378 36H346C341.582 36 338 32.4183 338 28Z' />
114- </ ClipPath >
115- </ Defs >
34+ const SkeletonContent = ( ) => {
35+ const styles = useStyles ( ) ;
36+ return (
37+ < View style = { styles . textContainer } >
38+ < View style = { styles . headerRow } >
39+ < SkeletonBlock style = { styles . title } />
40+ < SkeletonTimestamp />
41+ </ View >
11642
117- { /* Avatar */ }
118- < G clipPath = 'url(#clip0_5596_231135)' >
119- < Path
120- d = 'M16 40C16 26.7452 26.7452 16 40 16C53.2548 16 64 26.7452 64 40C64 53.2548 53.2548 64 40 64C26.7452 64 16 53.2548 16 40Z'
121- fill = { semantics . backgroundCoreSurface }
122- />
123- < Rect width = '48' height = '48' x = '16' y = '16' fill = 'url(#paint0_linear_5596_231135)' />
124- </ G >
43+ < SkeletonBlock style = { styles . subtitle } />
44+ </ View >
45+ ) ;
46+ } ;
12547
126- { /* Title */ }
127- < G clipPath = 'url(#clip1_5596_231135)' >
128- < Path
129- d = 'M80 28C80 23.5817 83.5817 20 88 20H314C318.418 20 322 23.5817 322 28C322 32.4183 318.418 36 314 36H88C83.5817 36 80 32.4183 80 28Z'
130- fill = { semantics . backgroundCoreSurface }
131- />
132- < Rect width = '242' height = '16' x = '80' y = '20' fill = 'url(#paint1_linear_5596_231135)' />
133- </ G >
48+ export const Skeleton = ( ) => {
49+ const styles = useStyles ( ) ;
13450
135- { /* Badge */ }
136- < G clipPath = 'url(#clip2_5596_231135)' >
137- < Path
138- d = 'M338 28C338 23.5817 341.582 20 346 20H378C382.418 20 386 23.5817 386 28C386 32.4183 382.418 36 378 36H346C341.582 36 338 32.4183 338 28Z'
139- fill = { semantics . backgroundCoreSurface }
140- />
141- < Rect width = '48' height = '16' x = '338' y = '20' fill = 'url(#paint2_linear_5596_231135)' />
142- </ G >
51+ const {
52+ theme : {
53+ channelListSkeleton : { container, height = 80 } ,
54+ } ,
55+ } = useTheme ( ) ;
14356
144- { /* Subtitle */ }
145- < Path
146- d = 'M80 52C80 47.5817 83.5817 44 88 44H272C276.418 44 280 47.5817 280 52C280 56.4183 276.418 60 272 60H88C83.5817 60 80 56.4183 80 52Z'
147- fill = { semantics . backgroundCoreSurface }
148- />
149- < Rect width = '199' height = '16' x = '80' y = '44' fill = 'url(#paint3_linear_5596_231135)' />
150- </ Svg >
151- </ Animated . View >
57+ return (
58+ < View style = { [ styles . container , container ] } testID = 'channel-preview-skeleton' >
59+ < View style = { [ styles . content , { height } ] } >
60+ < SkeletonAvatar />
61+ < SkeletonContent />
62+ </ View >
15263 </ View >
15364 ) ;
15465} ;
@@ -162,11 +73,53 @@ const useStyles = () => {
16273
16374 return useMemo ( ( ) => {
16475 return StyleSheet . create ( {
76+ avatar : {
77+ borderRadius : primitives . radiusMax ,
78+ height : 48 ,
79+ overflow : 'hidden' ,
80+ width : 48 ,
81+ } ,
82+ badge : {
83+ borderRadius : primitives . radiusMax ,
84+ width : 48 ,
85+ height : 16 ,
86+ minWidth : 0 ,
87+ overflow : 'hidden' ,
88+ } ,
16589 container : {
90+ borderBottomColor : semantics . borderCoreSubtle ,
16691 borderBottomWidth : 1 ,
16792 flexDirection : 'row' ,
168- borderBottomColor : semantics . borderCoreDefault ,
93+ } ,
94+ content : {
95+ alignItems : 'center' ,
96+ flexDirection : 'row' ,
97+ gap : primitives . spacingMd ,
98+ padding : primitives . spacingMd ,
99+ width : '100%' ,
100+ } ,
101+ headerRow : {
102+ alignItems : 'center' ,
103+ flexDirection : 'row' ,
104+ gap : primitives . spacingMd ,
105+ width : '100%' ,
106+ } ,
107+ subtitle : {
108+ borderRadius : primitives . radiusMax ,
109+ height : primitives . spacingMd ,
110+ overflow : 'hidden' ,
111+ width : '65%' ,
112+ } ,
113+ textContainer : {
114+ flex : 1 ,
115+ gap : primitives . spacingXs ,
116+ } ,
117+ title : {
118+ borderRadius : primitives . radiusMax ,
119+ flex : 1 ,
120+ height : 16 ,
121+ overflow : 'hidden' ,
169122 } ,
170123 } ) ;
171- } , [ semantics ] ) ;
124+ } , [ semantics . borderCoreDefault ] ) ;
172125} ;
0 commit comments