Skip to content

Commit 9be6789

Browse files
committed
feat: render custom svg/image
1 parent 77040ef commit 9be6789

File tree

14 files changed

+582
-86
lines changed

14 files changed

+582
-86
lines changed

eslint.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
const { defineConfig } = require('eslint/config');
1+
const { defineConfig, globalIgnores } = require('eslint/config');
22
const expoConfig = require('eslint-config-expo/flat');
33

44
module.exports = defineConfig([
5+
[globalIgnores(['.lib/*', 'dist/*', 'example/ios/*', 'example/android/*'])],
56
expoConfig,
67
{
7-
ignores: ['dist/*'],
88
plugins: {
99
'react-compiler': require('eslint-plugin-react-compiler'),
1010
},

example/assets/money-stack-2.png

285 KB
Loading

example/assets/money-stack.png

815 KB
Loading

example/assets/snow-flake.svg

Lines changed: 22 additions & 0 deletions
Loading

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"react": "19.0.0",
2020
"react-dom": "19.0.0",
2121
"react-native": "0.79.2",
22+
"react-native-element-dropdown": "^2.12.4",
2223
"react-native-reanimated": "^3.18.0",
2324
"react-native-web": "~0.20.0"
2425
},

example/src/App.tsx

Lines changed: 241 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useImage, useSVG } from '@shopify/react-native-skia';
12
import { useRef, useState } from 'react';
23
import {
34
StyleSheet,
@@ -7,23 +8,134 @@ import {
78
Switch,
89
Text,
910
} from 'react-native';
11+
import { Dropdown } from 'react-native-element-dropdown';
1012
import { Confetti, PIConfetti } from 'react-native-fast-confetti';
1113
import type {
1214
ConfettiMethods,
1315
ConfettiProps,
1416
} from 'react-native-fast-confetti';
1517

18+
type DropdownOption<T = any> = {
19+
label: string;
20+
value: T;
21+
};
22+
23+
const verticalSpacingOptions: DropdownOption<number>[] = [
24+
{ label: 'Dense (5)', value: 5 },
25+
{ label: 'Normal (20)', value: 20 },
26+
{ label: 'Loose (50)', value: 50 },
27+
{ label: 'Very Loose (100)', value: 100 },
28+
];
29+
30+
const radiusOptions: DropdownOption<'square' | 'circle'>[] = [
31+
{ label: 'No Radius (0)', value: 'square' },
32+
{ label: 'Circle', value: 'circle' },
33+
];
34+
35+
const textureOptions: DropdownOption<string>[] = [
36+
{ label: 'Default Confetti', value: 'default' },
37+
{ label: 'Money Stack 💰', value: 'money' },
38+
{ label: 'Snowflake ❄️', value: 'snowflake' },
39+
];
40+
1641
export default function App() {
1742
const confettiRef = useRef<ConfettiMethods>(null);
1843
const { height, width } = useWindowDimensions();
44+
1945
const [isPIConfetti, setIsPIConfetti] = useState(false);
20-
const [cannons, setCannons] = useState(false);
46+
47+
const [verticalSpacing, setVerticalSpacing] = useState(20);
48+
const [radiusRange, setRadiusRange] = useState<'square' | 'circle'>('square');
49+
const [textureType, setTextureType] = useState('default');
50+
const [cannonMode, setCannonMode] = useState(false);
51+
52+
const snowFlakeSVG = useSVG(require('../assets/snow-flake.svg'));
53+
const moneyStackImage = useImage(require('../assets/money-stack-2.png'));
2154

2255
const cannonPositions: ConfettiProps['cannonsPositions'] = [
2356
{ x: -30, y: height },
2457
{ x: width + 30, y: height },
2558
];
2659

60+
if (!snowFlakeSVG || !moneyStackImage) return null;
61+
62+
const effectiveVerticalSpacing = cannonMode ? 5 : verticalSpacing;
63+
64+
const confettiKey = `${isPIConfetti}-${effectiveVerticalSpacing}-${radiusRange}-${textureType}-${cannonMode}`;
65+
66+
const getTextureProps = (): ConfettiProps => {
67+
switch (textureType) {
68+
case 'money':
69+
return {
70+
type: 'image' as const,
71+
flakeImage: moneyStackImage,
72+
};
73+
case 'snowflake':
74+
return {
75+
type: 'svg' as const,
76+
flakeSvg: snowFlakeSVG,
77+
};
78+
default: {
79+
const range: [number, number] =
80+
radiusRange === 'square' ? [0, 0] : [0, 15];
81+
return {
82+
type: 'default' as const,
83+
radiusRange: range,
84+
};
85+
}
86+
}
87+
};
88+
89+
const getFlakeSize = () => {
90+
switch (textureType) {
91+
case 'money':
92+
return { width: 50, height: 50 };
93+
case 'snowflake':
94+
return { width: 10, height: 10 };
95+
default:
96+
return { width: 15, height: 8 };
97+
}
98+
};
99+
100+
const getRotation = () => {
101+
if (isPIConfetti) {
102+
switch (textureType) {
103+
case 'money':
104+
return {
105+
x: { min: 1 * Math.PI, max: 1.5 * Math.PI },
106+
z: { min: 1 * Math.PI, max: 3 * Math.PI },
107+
};
108+
case 'snowflake':
109+
return {
110+
x: { min: 0.5 * Math.PI, max: 2 * Math.PI },
111+
z: { min: 1 * Math.PI, max: 3 * Math.PI },
112+
};
113+
default:
114+
return {
115+
x: { min: 0.5 * Math.PI, max: 2 * Math.PI },
116+
z: { min: 1 * Math.PI, max: 5 * Math.PI },
117+
};
118+
}
119+
}
120+
switch (textureType) {
121+
case 'money':
122+
return {
123+
x: { min: 1 * Math.PI, max: 2 * Math.PI },
124+
z: { min: 1 * Math.PI, max: 2 * Math.PI },
125+
};
126+
case 'snowflake':
127+
return {
128+
x: { min: 0.5 * Math.PI, max: 20 * Math.PI },
129+
z: { min: 1 * Math.PI, max: 20 * Math.PI },
130+
};
131+
default:
132+
return {
133+
x: { min: 0.5 * Math.PI, max: 20 * Math.PI },
134+
z: { min: 1 * Math.PI, max: 20 * Math.PI },
135+
};
136+
}
137+
};
138+
27139
return (
28140
<View style={styles.container}>
29141
<View style={styles.switchContainer}>
@@ -35,44 +147,112 @@ export default function App() {
35147
/>
36148
<Text>PI Confetti</Text>
37149
</View>
38-
{!isPIConfetti && (
39-
<View style={styles.switchContainer}>
40-
<Text>Regular Confetti</Text>
41-
<Switch
42-
value={cannons}
43-
onValueChange={setCannons}
44-
trackColor={{ false: '#767577', true: '#81b0ff' }}
150+
151+
<View style={styles.controlsContainer}>
152+
{!isPIConfetti && (
153+
<View style={styles.switchContainer}>
154+
<Text>Normal Mode</Text>
155+
<Switch
156+
value={cannonMode}
157+
onValueChange={setCannonMode}
158+
trackColor={{ false: '#767577', true: '#ff6b6b' }}
159+
/>
160+
<Text>Cannons Mode 🎯</Text>
161+
</View>
162+
)}
163+
164+
{!isPIConfetti && !cannonMode && (
165+
<View style={styles.dropdownContainer}>
166+
<Text style={styles.dropdownLabel}>Vertical Spacing:</Text>
167+
<Dropdown
168+
style={styles.dropdown}
169+
placeholderStyle={styles.placeholderStyle}
170+
selectedTextStyle={styles.selectedTextStyle}
171+
data={verticalSpacingOptions}
172+
labelField="label"
173+
valueField="value"
174+
placeholder="Select spacing"
175+
value={verticalSpacing}
176+
onChange={(item: DropdownOption<number>) => {
177+
setVerticalSpacing(item.value);
178+
}}
179+
/>
180+
</View>
181+
)}
182+
183+
{textureType === 'default' && (
184+
<View style={styles.dropdownContainer}>
185+
<Text style={styles.dropdownLabel}>Corner Radius:</Text>
186+
<Dropdown
187+
style={styles.dropdown}
188+
placeholderStyle={styles.placeholderStyle}
189+
selectedTextStyle={styles.selectedTextStyle}
190+
data={radiusOptions}
191+
labelField="label"
192+
valueField="value"
193+
placeholder="Select radius"
194+
value={radiusRange}
195+
onChange={(item: DropdownOption<'square' | 'circle'>) => {
196+
setRadiusRange(item.value);
197+
}}
198+
/>
199+
</View>
200+
)}
201+
202+
<View style={styles.dropdownContainer}>
203+
<Text style={styles.dropdownLabel}>Texture:</Text>
204+
<Dropdown
205+
style={styles.dropdown}
206+
placeholderStyle={styles.placeholderStyle}
207+
selectedTextStyle={styles.selectedTextStyle}
208+
data={textureOptions}
209+
labelField="label"
210+
valueField="value"
211+
placeholder="Select texture"
212+
value={textureType}
213+
onChange={(item: DropdownOption<string>) => {
214+
setTextureType(item.value);
215+
}}
45216
/>
46-
<Text>Cannons Confetti</Text>
47217
</View>
48-
)}
218+
</View>
49219

50220
{isPIConfetti ? (
51221
<PIConfetti
222+
key={confettiKey}
52223
ref={confettiRef}
53224
fallDuration={2000}
54225
blastDuration={250}
55226
sizeVariation={0.3}
56-
radiusRange={[0, 15]}
57-
flakeSize={{ width: 18, height: 12 }}
227+
flakeSize={getFlakeSize()}
228+
count={500}
229+
{...getTextureProps()}
230+
rotation={getRotation()}
58231
/>
59232
) : (
60233
<Confetti
234+
key={confettiKey}
61235
ref={confettiRef}
62236
autoplay={true}
63-
verticalSpacing={cannons ? 0 : 20}
64-
cannonsPositions={cannons ? cannonPositions : undefined}
65-
radiusRange={[0, 15]}
66-
sizeVariation={0.5}
67-
flakeSize={{ width: 15, height: 10 }}
237+
verticalSpacing={effectiveVerticalSpacing}
238+
cannonsPositions={cannonMode ? cannonPositions : undefined}
239+
flakeSize={getFlakeSize()}
68240
count={500}
241+
fallDuration={4000}
242+
{...getTextureProps()}
243+
rotation={getRotation()}
69244
/>
70245
)}
71246

72-
<Button title="Resume" onPress={() => confettiRef.current?.resume()} />
73-
<Button title="Pause" onPress={() => confettiRef.current?.pause()} />
74-
<Button title="Restart" onPress={() => confettiRef.current?.restart()} />
75-
<Button title="Reset" onPress={() => confettiRef.current?.reset()} />
247+
<View style={styles.buttonContainer}>
248+
<Button title="Resume" onPress={() => confettiRef.current?.resume()} />
249+
<Button title="Pause" onPress={() => confettiRef.current?.pause()} />
250+
<Button
251+
title="Restart"
252+
onPress={() => confettiRef.current?.restart()}
253+
/>
254+
<Button title="Reset" onPress={() => confettiRef.current?.reset()} />
255+
</View>
76256
</View>
77257
);
78258
}
@@ -89,5 +269,45 @@ const styles = StyleSheet.create({
89269
alignItems: 'center',
90270
marginBottom: 20,
91271
gap: 8,
272+
paddingHorizontal: 20,
273+
},
274+
controlsContainer: {
275+
width: '100%',
276+
marginBottom: 30,
277+
gap: 15,
278+
paddingHorizontal: 20,
279+
},
280+
dropdownContainer: {
281+
width: '100%',
282+
},
283+
dropdownLabel: {
284+
fontSize: 16,
285+
fontWeight: '600',
286+
marginBottom: 5,
287+
color: '#333',
288+
},
289+
dropdown: {
290+
height: 50,
291+
borderColor: '#ddd',
292+
borderWidth: 1,
293+
borderRadius: 8,
294+
paddingHorizontal: 12,
295+
backgroundColor: '#f9f9f9',
296+
},
297+
placeholderStyle: {
298+
fontSize: 16,
299+
color: '#999',
300+
},
301+
selectedTextStyle: {
302+
fontSize: 16,
303+
color: '#333',
304+
},
305+
buttonContainer: {
306+
flexDirection: 'row',
307+
gap: 10,
308+
flexWrap: 'wrap',
309+
justifyContent: 'center',
310+
marginTop: 20,
311+
paddingHorizontal: 20,
92312
},
93313
});

0 commit comments

Comments
 (0)