Skip to content

Instantly share code, notes, and snippets.

@maheshmuttintidev
Forked from rvibit/ProPlanCard.tsx
Created January 7, 2025 06:52
Show Gist options
  • Save maheshmuttintidev/3d6ddcee57c1a2c69ffaa603c59f885d to your computer and use it in GitHub Desktop.
Save maheshmuttintidev/3d6ddcee57c1a2c69ffaa603c59f885d to your computer and use it in GitHub Desktop.
ProPlanCard with animated sparkles
import React from 'react';
import { View, Text, StyleSheet, Pressable, Dimensions } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import Animated, {
useAnimatedStyle,
withSpring,
withRepeat,
withSequence,
useSharedValue
} from 'react-native-reanimated';
import { SparkleAnimation } from './SparkleAnimation';
const { width } = Dimensions.get('window');
export function ProPlanCard() {
const scale = useSharedValue(1);
const sparklePositions = [
{ top: '10%', left: '10%' },
{ top: '20%', right: '15%' },
{ bottom: '20%', left: '20%' },
{ bottom: '30%', right: '25%' },
];
React.useEffect(() => {
scale.value = withRepeat(
withSequence(
withSpring(1.02),
withSpring(1)
),
-1,
true
);
}, []);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<Pressable>
<Animated.View style={[styles.container, animatedStyle]}>
<LinearGradient
colors={['#4F46E5', '#7C3AED', '#9333EA']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.gradient}
>
{sparklePositions.map((position, index) => (
<View key={index} style={[styles.sparkleWrapper, position]}>
<SparkleAnimation />
</View>
))}
<View style={styles.content}>
<Text style={styles.title}>Upgrade to Pro</Text>
<Text style={styles.price}>$9.99<Text style={styles.period}>/month</Text></Text>
</View>
</LinearGradient>
</Animated.View>
</Pressable>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 16,
marginBottom: 20,
borderRadius: 16,
elevation: 5,
shadowColor: '#4F46E5',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
},
gradient: {
borderRadius: 16,
overflow: 'hidden',
},
content: {
padding: 16,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
sparkleWrapper: {
position: 'absolute',
zIndex: 1,
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#fff',
},
price: {
fontSize: 18,
fontWeight: 'bold',
color: '#fff',
},
period: {
fontSize: 14,
fontWeight: 'normal',
},
});
import React from 'react';
import { StyleSheet } from 'react-native';
import Animated, {
useAnimatedStyle,
withRepeat,
withTiming,
withDelay,
withSequence,
useSharedValue,
useAnimatedProps
} from 'react-native-reanimated';
import { Path, Svg } from 'react-native-svg';
const AnimatedPath = Animated.createAnimatedComponent(Path);
export function SparkleAnimation() {
const opacity = useSharedValue(0);
const scale = useSharedValue(0.3);
React.useEffect(() => {
opacity.value = withRepeat(
withSequence(
withDelay(Math.random() * 1000,
withTiming(1, { duration: 400 })
),
withTiming(0, { duration: 400 })
),
-1,
true
);
scale.value = withRepeat(
withSequence(
withTiming(1, { duration: 400 }),
withTiming(0.3, { duration: 400 })
),
-1,
true
);
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [{ scale: scale.value }],
}));
const animatedProps = useAnimatedProps(() => ({
opacity: opacity.value,
}));
return (
<Animated.View style={[styles.sparkle, animatedStyle]}>
<Svg width="20" height="20" viewBox="0 0 20 20">
<AnimatedPath
animatedProps={animatedProps}
d="M10 0L12.5 7.5L20 10L12.5 12.5L10 20L7.5 12.5L0 10L7.5 7.5L10 0Z"
fill="#FFD700"
/>
</Svg>
</Animated.View>
);
}
const styles = StyleSheet.create({
sparkle: {
position: 'absolute',
width: 20,
height: 20,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment