react-achievements 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -1,90 +1,148 @@
1
1
  import React, { useEffect, useRef, useState, useCallback, createContext, useContext } from 'react';
2
2
 
3
- const AchievementModal = ({ show, achievement, onClose }) => {
4
- if (!show || !achievement)
5
- return null;
6
- const modalStyle = {
3
+ const defaultStyles = {
4
+ achievementModal: {
5
+ overlay: {
6
+ position: 'fixed',
7
+ top: 0,
8
+ left: 0,
9
+ right: 0,
10
+ bottom: 0,
11
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ },
16
+ content: {
17
+ backgroundColor: '#ffffff',
18
+ borderRadius: '8px',
19
+ padding: '20px',
20
+ maxWidth: '400px',
21
+ width: '100%',
22
+ },
23
+ title: {
24
+ fontSize: '24px',
25
+ fontWeight: 'bold',
26
+ marginBottom: '10px',
27
+ },
28
+ icon: {
29
+ width: '50px',
30
+ height: '50px',
31
+ marginBottom: '10px',
32
+ },
33
+ description: {
34
+ fontSize: '16px',
35
+ marginBottom: '20px',
36
+ },
37
+ button: {
38
+ backgroundColor: '#007bff',
39
+ color: '#ffffff',
40
+ padding: '10px 20px',
41
+ borderRadius: '4px',
42
+ border: 'none',
43
+ cursor: 'pointer',
44
+ },
45
+ },
46
+ badgesModal: {
47
+ overlay: {
48
+ position: 'fixed',
49
+ top: 0,
50
+ left: 0,
51
+ right: 0,
52
+ bottom: 0,
53
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
54
+ display: 'flex',
55
+ alignItems: 'center',
56
+ justifyContent: 'center',
57
+ },
58
+ content: {
59
+ backgroundColor: '#ffffff',
60
+ borderRadius: '8px',
61
+ padding: '20px',
62
+ maxWidth: '600px',
63
+ width: '100%',
64
+ maxHeight: '80vh',
65
+ overflowY: 'auto',
66
+ },
67
+ title: {
68
+ fontSize: '24px',
69
+ fontWeight: 'bold',
70
+ marginBottom: '20px',
71
+ },
72
+ badgeContainer: {
73
+ display: 'flex',
74
+ flexWrap: 'wrap',
75
+ justifyContent: 'center',
76
+ },
77
+ badge: {
78
+ display: 'flex',
79
+ flexDirection: 'column',
80
+ alignItems: 'center',
81
+ margin: '10px',
82
+ },
83
+ badgeIcon: {
84
+ width: '50px',
85
+ height: '50px',
86
+ marginBottom: '5px',
87
+ },
88
+ badgeTitle: {
89
+ fontSize: '14px',
90
+ textAlign: 'center',
91
+ },
92
+ button: {
93
+ backgroundColor: '#007bff',
94
+ color: '#ffffff',
95
+ padding: '10px 20px',
96
+ borderRadius: '4px',
97
+ border: 'none',
98
+ cursor: 'pointer',
99
+ marginTop: '20px',
100
+ },
101
+ },
102
+ badgesButton: {
7
103
  position: 'fixed',
8
- top: '50%',
9
- left: '50%',
10
- transform: 'translate(-50%, -50%)',
11
- backgroundColor: '#fff',
12
- padding: '20px',
13
- borderRadius: '8px',
14
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
104
+ padding: '10px 20px',
105
+ backgroundColor: '#007bff',
106
+ color: '#ffffff',
107
+ border: 'none',
108
+ borderRadius: '4px',
109
+ cursor: 'pointer',
15
110
  zIndex: 1000,
16
- };
17
- const overlayStyle = {
18
- position: 'fixed',
19
- top: 0,
20
- left: 0,
21
- right: 0,
22
- bottom: 0,
23
- backgroundColor: 'rgba(0,0,0,0.5)',
24
- zIndex: 999,
25
- };
26
- return (React.createElement(React.Fragment, null,
27
- React.createElement("div", { style: overlayStyle, onClick: onClose }),
28
- React.createElement("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "achievement-title" },
29
- React.createElement("h2", { id: "achievement-title" }, "Achievement Unlocked!"),
30
- React.createElement("img", { src: achievement.icon, alt: achievement.title, style: { width: '50px', height: '50px' } }),
31
- React.createElement("h3", null, achievement.title),
32
- React.createElement("p", null, achievement.description),
33
- React.createElement("button", { onClick: onClose }, "Okay"))));
111
+ },
112
+ };
113
+
114
+ const AchievementModal = ({ isOpen, achievement, onClose, styles }) => {
115
+ if (!isOpen || !achievement)
116
+ return null;
117
+ return (React.createElement("div", { style: styles.overlay },
118
+ React.createElement("div", { style: styles.content },
119
+ React.createElement("h2", { style: styles.title }, "Achievement Unlocked!"),
120
+ React.createElement("img", { src: achievement.icon, alt: achievement.title, style: styles.icon }),
121
+ React.createElement("h3", { style: styles.title }, achievement.title),
122
+ React.createElement("p", { style: styles.description }, achievement.description),
123
+ React.createElement("button", { onClick: onClose, style: styles.button }, "Okay"))));
34
124
  };
35
125
  var AchievementModal$1 = React.memo(AchievementModal);
36
126
 
37
- const BadgesModal = ({ show, achievements, onClose }) => {
38
- if (!show)
127
+ const BadgesModal = ({ isOpen, achievements, onClose, styles }) => {
128
+ if (!isOpen)
39
129
  return null;
40
- const modalStyle = {
41
- position: 'fixed',
42
- top: '50%',
43
- left: '50%',
44
- transform: 'translate(-50%, -50%)',
45
- backgroundColor: '#fff',
46
- padding: '20px',
47
- borderRadius: '8px',
48
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
49
- zIndex: 1000,
50
- maxWidth: '80%',
51
- maxHeight: '80%',
52
- overflow: 'auto',
53
- };
54
- const overlayStyle = {
55
- position: 'fixed',
56
- top: 0,
57
- left: 0,
58
- right: 0,
59
- bottom: 0,
60
- backgroundColor: 'rgba(0,0,0,0.5)',
61
- zIndex: 999,
62
- };
63
- return (React.createElement(React.Fragment, null,
64
- React.createElement("div", { style: overlayStyle, onClick: onClose }),
65
- React.createElement("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "badges-title" },
66
- React.createElement("h2", { id: "badges-title" }, "Your Achievements"),
67
- React.createElement("div", { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'center' } }, achievements.map(achievement => (React.createElement("div", { key: achievement.id, style: { margin: '10px', textAlign: 'center' } },
68
- React.createElement("img", { src: achievement.icon, alt: achievement.title, style: { width: '50px', height: '50px' } }),
69
- React.createElement("h4", null, achievement.title))))),
70
- React.createElement("button", { onClick: onClose, style: { marginTop: '20px' } }, "Close"))));
130
+ return (React.createElement("div", { style: styles.overlay },
131
+ React.createElement("div", { style: styles.content },
132
+ React.createElement("h2", { style: styles.title }, "Your Achievements"),
133
+ React.createElement("div", { style: styles.badgeContainer }, achievements.map((achievement) => (React.createElement("div", { key: achievement.id, style: styles.badge },
134
+ React.createElement("img", { src: achievement.icon, alt: achievement.title, style: styles.badgeIcon }),
135
+ React.createElement("span", { style: styles.badgeTitle }, achievement.title))))),
136
+ React.createElement("button", { onClick: onClose, style: styles.button }, "Close"))));
71
137
  };
72
138
  var BadgesModal$1 = React.memo(BadgesModal);
73
139
 
74
- const BadgesButton = ({ onClick, position }) => {
75
- const buttonStyle = {
76
- position: 'fixed',
140
+ const BadgesButton = ({ onClick, position, styles }) => {
141
+ const positionStyle = {
77
142
  [position.split('-')[0]]: '20px',
78
143
  [position.split('-')[1]]: '20px',
79
- padding: '10px 20px',
80
- backgroundColor: '#007bff',
81
- color: '#fff',
82
- border: 'none',
83
- borderRadius: '5px',
84
- cursor: 'pointer',
85
- zIndex: 998,
86
144
  };
87
- return (React.createElement("button", { style: buttonStyle, onClick: onClick }, "View Achievements"));
145
+ return (React.createElement("button", { onClick: onClick, style: Object.assign(Object.assign({}, styles), positionStyle) }, "View Achievements"));
88
146
  };
89
147
  var BadgesButton$1 = React.memo(BadgesButton);
90
148
 
@@ -174,32 +232,19 @@ var useWindowSize = function (initialWidth, initialHeight) {
174
232
  return state;
175
233
  };
176
234
 
177
- const ConfettiWrapper = ({ show, confettiProps }) => {
235
+ const ConfettiWrapper = ({ show }) => {
178
236
  const { width, height } = useWindowSize();
179
237
  if (!show)
180
238
  return null;
181
- return (React.createElement(Confetti, Object.assign({ width: width, height: height }, confettiProps)));
239
+ return React.createElement(Confetti, { width: width, height: height, recycle: false });
182
240
  };
183
241
 
184
242
  const AchievementContext = createContext(undefined);
185
- const AchievementProvider = ({ children, config, initialState = {}, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
186
- const extractMetrics = (state) => {
187
- return Object.keys(config).reduce((acc, key) => {
188
- if (key in state) {
189
- acc[key] = state[key];
190
- }
191
- else {
192
- acc[key] = [];
193
- }
194
- return acc;
195
- }, {});
196
- };
243
+ const AchievementProvider = ({ children, config, initialState = {}, storageKey = 'react-achievements', badgesButtonPosition = 'top-right', styles = {}, }) => {
244
+ const mergedStyles = React.useMemo(() => mergeDeep(defaultStyles, styles), [styles]);
197
245
  const [metrics, setMetrics] = useState(() => {
198
246
  const savedMetrics = localStorage.getItem(`${storageKey}-metrics`);
199
- if (savedMetrics) {
200
- return JSON.parse(savedMetrics);
201
- }
202
- return extractMetrics(initialState);
247
+ return savedMetrics ? JSON.parse(savedMetrics) : initialState;
203
248
  });
204
249
  const [unlockedAchievements, setUnlockedAchievements] = useState(() => {
205
250
  const saved = localStorage.getItem(`${storageKey}-unlocked-achievements`);
@@ -225,14 +270,20 @@ const AchievementProvider = ({ children, config, initialState = {}, storageKey =
225
270
  });
226
271
  if (newAchievements.length > 0) {
227
272
  const newlyUnlockedIds = newAchievements.map(a => a.id);
228
- setUnlockedAchievements(prev => [...prev, ...newlyUnlockedIds]);
229
- setNewlyUnlockedAchievements(prev => [...prev, ...newlyUnlockedIds]);
230
- localStorage.setItem(`${storageKey}-unlocked-achievements`, JSON.stringify([...unlockedAchievements, ...newlyUnlockedIds]));
231
- localStorage.setItem(`${storageKey}-newly-unlocked-achievements`, JSON.stringify([...newlyUnlockedAchievements, ...newlyUnlockedIds]));
273
+ setUnlockedAchievements(prev => {
274
+ const updated = [...prev, ...newlyUnlockedIds];
275
+ localStorage.setItem(`${storageKey}-unlocked-achievements`, JSON.stringify(updated));
276
+ return updated;
277
+ });
278
+ setNewlyUnlockedAchievements(prev => {
279
+ const updated = [...prev, ...newlyUnlockedIds];
280
+ localStorage.setItem(`${storageKey}-newly-unlocked-achievements`, JSON.stringify(updated));
281
+ return updated;
282
+ });
232
283
  setAchievementQueue(prevQueue => [...prevQueue, ...newAchievements]);
233
284
  setShowConfetti(true);
234
285
  }
235
- }, [config, metrics, unlockedAchievements, newlyUnlockedAchievements, storageKey]);
286
+ }, [config, metrics, unlockedAchievements, storageKey]);
236
287
  useEffect(() => {
237
288
  checkAchievements();
238
289
  }, [metrics, checkAchievements]);
@@ -248,9 +299,7 @@ const AchievementProvider = ({ children, config, initialState = {}, storageKey =
248
299
  });
249
300
  }
250
301
  }, [achievementQueue, currentAchievement, storageKey]);
251
- const showBadgesModal = () => {
252
- setShowBadges(true);
253
- };
302
+ const showBadgesModal = () => setShowBadges(true);
254
303
  const getAchievements = (achievedIds) => {
255
304
  return Object.values(config).flatMap(conditions => conditions.filter(c => achievedIds.includes(c.data.id)).map(c => c.data));
256
305
  };
@@ -267,23 +316,16 @@ const AchievementProvider = ({ children, config, initialState = {}, storageKey =
267
316
  checkAchievements,
268
317
  showBadgesModal
269
318
  };
270
- useEffect(() => {
271
- if (newlyUnlockedAchievements.length > 0) {
272
- const achievementsToShow = getAchievements(newlyUnlockedAchievements);
273
- setAchievementQueue(achievementsToShow);
274
- setShowConfetti(true);
275
- }
276
- }, []); // Run this effect only on component mount
277
319
  return (React.createElement(AchievementContext.Provider, { value: contextValue },
278
320
  children,
279
- React.createElement(AchievementModal$1, { show: !!currentAchievement, achievement: currentAchievement, onClose: () => {
321
+ React.createElement(AchievementModal$1, { isOpen: !!currentAchievement, achievement: currentAchievement, onClose: () => {
280
322
  setCurrentAchievement(null);
281
323
  if (achievementQueue.length === 0 && newlyUnlockedAchievements.length === 0) {
282
324
  setShowConfetti(false);
283
325
  }
284
- } }),
285
- React.createElement(BadgesModal$1, { show: showBadges, achievements: getAchievements(unlockedAchievements), onClose: () => setShowBadges(false) }),
286
- React.createElement(BadgesButton$1, { onClick: showBadgesModal, position: badgesButtonPosition }),
326
+ }, styles: mergedStyles.achievementModal }),
327
+ React.createElement(BadgesModal$1, { isOpen: showBadges, achievements: getAchievements(unlockedAchievements), onClose: () => setShowBadges(false), styles: mergedStyles.badgesModal }),
328
+ React.createElement(BadgesButton$1, { onClick: showBadgesModal, position: badgesButtonPosition, styles: mergedStyles.badgesButton }),
287
329
  React.createElement(ConfettiWrapper, { show: showConfetti || achievementQueue.length > 0 })));
288
330
  };
289
331
  const useAchievement = () => {
@@ -293,5 +335,26 @@ const useAchievement = () => {
293
335
  }
294
336
  return context;
295
337
  };
338
+ // Helper function to deep merge objects
339
+ function mergeDeep(target, source) {
340
+ const output = Object.assign({}, target);
341
+ if (isObject(target) && isObject(source)) {
342
+ Object.keys(source).forEach(key => {
343
+ if (isObject(source[key])) {
344
+ if (!(key in target))
345
+ Object.assign(output, { [key]: source[key] });
346
+ else
347
+ output[key] = mergeDeep(target[key], source[key]);
348
+ }
349
+ else {
350
+ Object.assign(output, { [key]: source[key] });
351
+ }
352
+ });
353
+ }
354
+ return output;
355
+ }
356
+ function isObject(item) {
357
+ return (item && typeof item === 'object' && !Array.isArray(item));
358
+ }
296
359
 
297
360
  export { AchievementProvider, ConfettiWrapper, useAchievement };
package/dist/types.d.ts CHANGED
@@ -1,18 +1,17 @@
1
- export type MetricValueItem = number | boolean | string | any;
2
- export type MetricValue = MetricValueItem[];
3
- export interface Metrics {
4
- [key: string]: MetricValue;
5
- }
6
1
  export interface AchievementData {
7
2
  id: string;
8
3
  title: string;
9
4
  description: string;
10
5
  icon: string;
11
6
  }
7
+ export type MetricValue = number | string | boolean | Date;
8
+ export interface Metrics {
9
+ [key: string]: MetricValue[];
10
+ }
12
11
  export interface AchievementCondition {
13
- check: (metricValue: MetricValue) => boolean;
12
+ check: (value: MetricValue[]) => boolean;
14
13
  data: AchievementData;
15
14
  }
16
15
  export interface AchievementConfig {
17
- [metricKey: string]: AchievementCondition[];
16
+ [key: string]: AchievementCondition[];
18
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-achievements",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "This package allows users to transpose a React achievements engine over their React apps",
5
5
  "keywords": [
6
6
  "react",
@@ -1,48 +1,27 @@
1
1
  import React from 'react';
2
2
  import { AchievementData } from '../types';
3
+ import { Styles } from '../defaultStyles';
3
4
 
4
5
  interface AchievementModalProps {
5
- show: boolean;
6
+ isOpen: boolean;
6
7
  achievement: AchievementData | null;
7
8
  onClose: () => void;
9
+ styles: Styles['achievementModal'];
8
10
  }
9
11
 
10
- const AchievementModal: React.FC<AchievementModalProps> = ({ show, achievement, onClose }) => {
11
- if (!show || !achievement) return null;
12
-
13
- const modalStyle: React.CSSProperties = {
14
- position: 'fixed',
15
- top: '50%',
16
- left: '50%',
17
- transform: 'translate(-50%, -50%)',
18
- backgroundColor: '#fff',
19
- padding: '20px',
20
- borderRadius: '8px',
21
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
22
- zIndex: 1000,
23
- };
24
-
25
- const overlayStyle: React.CSSProperties = {
26
- position: 'fixed',
27
- top: 0,
28
- left: 0,
29
- right: 0,
30
- bottom: 0,
31
- backgroundColor: 'rgba(0,0,0,0.5)',
32
- zIndex: 999,
33
- };
12
+ const AchievementModal: React.FC<AchievementModalProps> = ({ isOpen, achievement, onClose, styles }) => {
13
+ if (!isOpen || !achievement) return null;
34
14
 
35
15
  return (
36
- <>
37
- <div style={overlayStyle} onClick={onClose} />
38
- <div style={modalStyle} role="dialog" aria-modal="true" aria-labelledby="achievement-title">
39
- <h2 id="achievement-title">Achievement Unlocked!</h2>
40
- <img src={achievement.icon} alt={achievement.title} style={{ width: '50px', height: '50px' }} />
41
- <h3>{achievement.title}</h3>
42
- <p>{achievement.description}</p>
43
- <button onClick={onClose}>Okay</button>
16
+ <div style={styles.overlay as React.CSSProperties}>
17
+ <div style={styles.content}>
18
+ <h2 style={styles.title}>Achievement Unlocked!</h2>
19
+ <img src={achievement.icon} alt={achievement.title} style={styles.icon} />
20
+ <h3 style={styles.title}>{achievement.title}</h3>
21
+ <p style={styles.description}>{achievement.description}</p>
22
+ <button onClick={onClose} style={styles.button}>Okay</button>
44
23
  </div>
45
- </>
24
+ </div>
46
25
  );
47
26
  };
48
27
 
@@ -1,26 +1,20 @@
1
1
  import React from 'react';
2
+ import { Styles } from '../defaultStyles';
2
3
 
3
4
  interface BadgesButtonProps {
4
5
  onClick: () => void;
5
6
  position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
+ styles: Styles['badgesButton'];
6
8
  }
7
9
 
8
- const BadgesButton: React.FC<BadgesButtonProps> = ({ onClick, position }) => {
9
- const buttonStyle: React.CSSProperties = {
10
- position: 'fixed',
10
+ const BadgesButton: React.FC<BadgesButtonProps> = ({ onClick, position, styles }) => {
11
+ const positionStyle = {
11
12
  [position.split('-')[0]]: '20px',
12
13
  [position.split('-')[1]]: '20px',
13
- padding: '10px 20px',
14
- backgroundColor: '#007bff',
15
- color: '#fff',
16
- border: 'none',
17
- borderRadius: '5px',
18
- cursor: 'pointer',
19
- zIndex: 998,
20
14
  };
21
15
 
22
16
  return (
23
- <button style={buttonStyle} onClick={onClick}>
17
+ <button onClick={onClick} style={{ ...styles, ...positionStyle }}>
24
18
  View Achievements
25
19
  </button>
26
20
  );
@@ -1,56 +1,32 @@
1
1
  import React from 'react';
2
2
  import { AchievementData } from '../types';
3
+ import { Styles } from '../defaultStyles';
3
4
 
4
5
  interface BadgesModalProps {
5
- show: boolean;
6
+ isOpen: boolean;
6
7
  achievements: AchievementData[];
7
8
  onClose: () => void;
9
+ styles: Styles['badgesModal'];
8
10
  }
9
11
 
10
- const BadgesModal: React.FC<BadgesModalProps> = ({ show, achievements, onClose }) => {
11
- if (!show) return null;
12
-
13
- const modalStyle: React.CSSProperties = {
14
- position: 'fixed',
15
- top: '50%',
16
- left: '50%',
17
- transform: 'translate(-50%, -50%)',
18
- backgroundColor: '#fff',
19
- padding: '20px',
20
- borderRadius: '8px',
21
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
22
- zIndex: 1000,
23
- maxWidth: '80%',
24
- maxHeight: '80%',
25
- overflow: 'auto',
26
- };
27
-
28
- const overlayStyle: React.CSSProperties = {
29
- position: 'fixed',
30
- top: 0,
31
- left: 0,
32
- right: 0,
33
- bottom: 0,
34
- backgroundColor: 'rgba(0,0,0,0.5)',
35
- zIndex: 999,
36
- };
12
+ const BadgesModal: React.FC<BadgesModalProps> = ({ isOpen, achievements, onClose, styles }) => {
13
+ if (!isOpen) return null;
37
14
 
38
15
  return (
39
- <>
40
- <div style={overlayStyle} onClick={onClose} />
41
- <div style={modalStyle} role="dialog" aria-modal="true" aria-labelledby="badges-title">
42
- <h2 id="badges-title">Your Achievements</h2>
43
- <div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
44
- {achievements.map(achievement => (
45
- <div key={achievement.id} style={{ margin: '10px', textAlign: 'center' }}>
46
- <img src={achievement.icon} alt={achievement.title} style={{ width: '50px', height: '50px' }} />
47
- <h4>{achievement.title}</h4>
16
+ <div style={styles.overlay as React.CSSProperties}>
17
+ <div style={styles.content}>
18
+ <h2 style={styles.title}>Your Achievements</h2>
19
+ <div style={styles.badgeContainer}>
20
+ {achievements.map((achievement) => (
21
+ <div key={achievement.id} style={styles.badge}>
22
+ <img src={achievement.icon} alt={achievement.title} style={styles.badgeIcon} />
23
+ <span style={styles.badgeTitle}>{achievement.title}</span>
48
24
  </div>
49
25
  ))}
50
26
  </div>
51
- <button onClick={onClose} style={{ marginTop: '20px' }}>Close</button>
27
+ <button onClick={onClose} style={styles.button}>Close</button>
52
28
  </div>
53
- </>
29
+ </div>
54
30
  );
55
31
  };
56
32
 
@@ -1,25 +1,17 @@
1
1
  import React from 'react';
2
2
  import Confetti from 'react-confetti';
3
3
  import { useWindowSize } from 'react-use';
4
- import { ConfettiProps } from 'react-confetti';
5
4
 
6
5
  interface ConfettiWrapperProps {
7
6
  show: boolean;
8
- confettiProps?: Partial<ConfettiProps>;
9
7
  }
10
8
 
11
- const ConfettiWrapper: React.FC<ConfettiWrapperProps> = ({ show, confettiProps }) => {
9
+ const ConfettiWrapper: React.FC<ConfettiWrapperProps> = ({ show }) => {
12
10
  const { width, height } = useWindowSize();
13
11
 
14
12
  if (!show) return null;
15
13
 
16
- return (
17
- <Confetti
18
- width={width}
19
- height={height}
20
- {...confettiProps}
21
- />
22
- );
14
+ return <Confetti width={width} height={height} recycle={false} />;
23
15
  };
24
16
 
25
17
  export default ConfettiWrapper;