Skip to content

Commit d39db7c

Browse files
committed
feat: reimplement channel list component with new shimmer
1 parent 01b7394 commit d39db7c

File tree

1 file changed

+92
-139
lines changed

1 file changed

+92
-139
lines changed
Lines changed: 92 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,65 @@
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

124
import { 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

Comments
 (0)