1+ import { useImage , useSVG } from '@shopify/react-native-skia' ;
12import { useRef , useState } from 'react' ;
23import {
34 StyleSheet ,
@@ -7,23 +8,134 @@ import {
78 Switch ,
89 Text ,
910} from 'react-native' ;
11+ import { Dropdown } from 'react-native-element-dropdown' ;
1012import { Confetti , PIConfetti } from 'react-native-fast-confetti' ;
1113import 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+
1641export 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