Skip to content

Commit 3cdd302

Browse files
committed
feat: working library
1 parent de1ba45 commit 3cdd302

File tree

9 files changed

+15226
-15
lines changed

9 files changed

+15226
-15
lines changed

babel.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
module.exports = {
2+
plugins: ['babel-plugin-react-compiler'],
23
presets: ['module:react-native-builder-bob/babel-preset'],
34
};

example/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
"dependencies": {
1212
"@expo/metro-runtime": "~4.0.1",
1313
"expo": "~52.0.46",
14+
"expo-linear-gradient": "^14.1.4",
1415
"expo-status-bar": "~2.0.1",
1516
"react": "18.3.1",
1617
"react-dom": "18.3.1",
1718
"react-native": "0.76.9",
19+
"react-native-linear-gradient": "^2.8.3",
20+
"react-native-reanimated": "~3.16.1",
21+
"react-native-svg": "^15.12.0",
1822
"react-native-web": "~0.19.13"
1923
},
2024
"devDependencies": {

example/src/App.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,50 @@
1-
import { multiply } from 'react-native-shimmer-skeleton';
2-
import { Text, View, StyleSheet } from 'react-native';
3-
4-
const result = multiply(3, 7);
1+
import { Shimmer, ShimmerProvider } from 'react-native-shimmer-skeleton';
2+
import { View, StyleSheet, SafeAreaView } from 'react-native';
53

64
export default function App() {
75
return (
8-
<View style={styles.container}>
9-
<Text>Result: {result}</Text>
10-
</View>
6+
<SafeAreaView style={styles.container}>
7+
<ShimmerProvider duration={1000}>
8+
<View style={styles.shimmer1}>
9+
<Shimmer style={styles.shimmerMain} />
10+
</View>
11+
<View style={styles.shimmer2}>
12+
<Shimmer style={styles.shimmerMain} />
13+
</View>
14+
<View style={styles.shimmer3}>
15+
<Shimmer style={styles.shimmerMain} />
16+
</View>
17+
</ShimmerProvider>
18+
</SafeAreaView>
1119
);
1220
}
1321

1422
const styles = StyleSheet.create({
1523
container: {
1624
flex: 1,
1725
alignItems: 'center',
18-
justifyContent: 'center',
26+
justifyContent: 'flex-start',
27+
gap: 12,
28+
},
29+
shimmer1: {
30+
width: 200,
31+
height: 50,
32+
borderRadius: 10,
33+
backgroundColor: 'gray',
34+
},
35+
shimmer2: {
36+
width: 200,
37+
height: 50,
38+
borderRadius: 10,
39+
backgroundColor: 'gray',
40+
},
41+
shimmer3: {
42+
width: 200,
43+
height: 50,
44+
borderRadius: 10,
45+
backgroundColor: 'gray',
46+
},
47+
shimmerMain: {
48+
borderRadius: 10,
1949
},
2050
});

package.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,13 @@
6868
"@release-it/conventional-changelog": "^9.0.2",
6969
"@types/jest": "^29.5.5",
7070
"@types/react": "^19.0.12",
71+
"babel-plugin-react-compiler": "rc",
7172
"commitlint": "^19.6.1",
7273
"del-cli": "^5.1.0",
7374
"eslint": "^9.22.0",
7475
"eslint-config-prettier": "^10.1.1",
7576
"eslint-plugin-prettier": "^5.2.3",
77+
"eslint-plugin-react-hooks": "^6.0.0-rc.1",
7678
"jest": "^29.7.0",
7779
"prettier": "^3.0.3",
7880
"react": "18.3.1",
@@ -81,10 +83,6 @@
8183
"release-it": "^17.10.0",
8284
"typescript": "^5.2.2"
8385
},
84-
"peerDependencies": {
85-
"react": "*",
86-
"react-native": "*"
87-
},
8886
"workspaces": [
8987
"example"
9088
],
@@ -149,5 +147,15 @@
149147
"languages": "js",
150148
"type": "library",
151149
"version": "0.49.8"
150+
},
151+
"dependencies": {
152+
"react-native-reanimated": "^3.17.5",
153+
"react-native-svg": "^15.12.0"
154+
},
155+
"peerDependencies": {
156+
"react": "*",
157+
"react-native": "*",
158+
"react-native-reanimated": "^3.17.5",
159+
"react-native-svg": "^15.12.0"
152160
}
153161
}

src/LinearGradient.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
3+
import type { ViewStyle, StyleProp } from 'react-native';
4+
5+
interface Props {
6+
colors: string[];
7+
start?: { x: number; y: number };
8+
end?: { x: number; y: number };
9+
style?: StyleProp<ViewStyle>;
10+
children?: React.ReactNode;
11+
}
12+
13+
export const MyLinearGradient = ({
14+
colors,
15+
start = { x: 0, y: 0 },
16+
end = { x: 0, y: 1 },
17+
style,
18+
}: Props) => {
19+
const gradientId = 'grad';
20+
21+
return (
22+
<Svg style={style}>
23+
<Defs>
24+
<LinearGradient
25+
id={gradientId}
26+
x1={start.x}
27+
y1={start.y}
28+
x2={end.x}
29+
y2={end.y}
30+
>
31+
{colors.map((color, i) => (
32+
<Stop
33+
key={i}
34+
offset={`${(i / (colors.length - 1)) * 100}%`}
35+
stopColor={color === 'transparent' ? 'white' : color}
36+
stopOpacity={color === 'transparent' ? 0 : 1}
37+
/>
38+
))}
39+
</LinearGradient>
40+
</Defs>
41+
<Rect
42+
x="0"
43+
y="0"
44+
width="100%"
45+
height="100%"
46+
fill={`url(#${gradientId})`}
47+
/>
48+
</Svg>
49+
);
50+
};

src/Shimmer.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, { useContext, useEffect, useState } from 'react';
2+
import { View, StyleSheet, type ViewStyle } from 'react-native';
3+
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
4+
import { ShimmerContext } from './ShimmerContext';
5+
import { MyLinearGradient } from './LinearGradient';
6+
7+
interface Props {
8+
style?: ViewStyle | ViewStyle[];
9+
}
10+
11+
export const Shimmer: React.FC<Props> = ({ style }) => {
12+
const shimmer = useContext(ShimmerContext);
13+
const shimmerRef = React.useRef<View>(null);
14+
const [offset, setOffset] = useState(0);
15+
const progress = shimmer?.progress;
16+
17+
useEffect(() => {
18+
shimmer?.increaseActiveShimmers();
19+
return () => {
20+
shimmer?.decreaseActiveShimmers();
21+
};
22+
}, [shimmer]);
23+
24+
const measure = () => {
25+
if (shimmerRef.current) {
26+
shimmerRef.current.measureInWindow((x) => {
27+
setOffset(x);
28+
});
29+
}
30+
};
31+
32+
const gradientStyle = useAnimatedStyle(() => {
33+
return {
34+
transform: [{ translateX: (progress?.value ?? 0) - offset }],
35+
};
36+
});
37+
38+
return (
39+
<View ref={shimmerRef} onLayout={measure} style={[styles.container, style]}>
40+
<Animated.View style={[gradientStyle, styles.gradientWrapper]}>
41+
<MyLinearGradient
42+
colors={['transparent', '#FFFFFF30', 'transparent']}
43+
start={{ x: 0, y: 0.5 }}
44+
end={{ x: 1, y: 0.5 }}
45+
style={StyleSheet.absoluteFill}
46+
/>
47+
</Animated.View>
48+
</View>
49+
);
50+
};
51+
52+
const styles = StyleSheet.create({
53+
container: {
54+
overflow: 'hidden',
55+
position: 'relative',
56+
height: '100%',
57+
width: '100%',
58+
},
59+
gradientWrapper: {
60+
overflow: 'hidden',
61+
position: 'relative',
62+
width: 100,
63+
height: 100,
64+
},
65+
});

src/ShimmerContext.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {
2+
createContext,
3+
useEffect,
4+
useState,
5+
type FunctionComponent,
6+
} from 'react';
7+
import {
8+
cancelAnimation,
9+
Easing,
10+
useSharedValue,
11+
withRepeat,
12+
withTiming,
13+
type SharedValue,
14+
} from 'react-native-reanimated';
15+
16+
interface ShimmerContextType {
17+
progress: SharedValue<number>;
18+
increaseActiveShimmers: () => void;
19+
decreaseActiveShimmers: () => void;
20+
}
21+
22+
const ShimmerContext = createContext<ShimmerContextType | null>(null);
23+
24+
interface ShimmerProviderProps {
25+
children?: React.ReactNode;
26+
duration?: number;
27+
}
28+
29+
const ShimmerProvider: FunctionComponent<ShimmerProviderProps> = ({
30+
children,
31+
duration = 3000,
32+
}) => {
33+
const [activeShimmers, setActiveShimmers] = useState(0);
34+
const [isShimmerActive, setIsShimmerActive] = useState(false);
35+
const progress = useSharedValue(-300);
36+
37+
useEffect(() => {
38+
if (!isShimmerActive && activeShimmers > 0) {
39+
setIsShimmerActive(true);
40+
progress.value = -300;
41+
progress.value = withRepeat(
42+
withTiming(300, {
43+
duration: duration,
44+
easing: Easing.ease,
45+
}),
46+
-1,
47+
false
48+
);
49+
}
50+
51+
if (isShimmerActive && activeShimmers === 0) {
52+
cancelAnimation(progress);
53+
setIsShimmerActive(false);
54+
}
55+
}, [activeShimmers, isShimmerActive, progress, duration]);
56+
57+
const increaseActiveShimmers = () => {
58+
setActiveShimmers((prev) => prev + 1);
59+
};
60+
61+
const decreaseActiveShimmers = () => {
62+
setActiveShimmers((prev) => Math.max(prev - 1, 0));
63+
};
64+
65+
return (
66+
<ShimmerContext.Provider
67+
value={{ progress, increaseActiveShimmers, decreaseActiveShimmers }}
68+
>
69+
{children}
70+
</ShimmerContext.Provider>
71+
);
72+
};
73+
export { ShimmerContext, ShimmerProvider };

src/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export function multiply(a: number, b: number): number {
2-
return a * b;
3-
}
1+
export { Shimmer } from './Shimmer';
2+
export { ShimmerProvider } from './ShimmerContext';

0 commit comments

Comments
 (0)