react-achievements 1.3.0 → 1.3.2

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