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/README.md +91 -94
- package/dist/components/AchievementModal.d.ts +3 -1
- package/dist/components/BadgesButton.d.ts +2 -0
- package/dist/components/BadgesModal.d.ts +3 -1
- package/dist/components/ConfettiWrapper.d.ts +0 -2
- package/dist/context/AchievementContext.d.ts +2 -0
- package/dist/defaultStyles.d.ts +28 -0
- package/dist/index.cjs.js +173 -110
- package/dist/index.esm.js +173 -110
- package/dist/types.d.ts +6 -7
- package/package.json +1 -1
- package/src/components/AchievementModal.tsx +13 -34
- package/src/components/BadgesButton.tsx +5 -11
- package/src/components/BadgesModal.tsx +15 -39
- package/src/components/ConfettiWrapper.tsx +2 -10
- package/src/context/AchievementContext.tsx +50 -37
- package/src/defaultStyles.ts +138 -0
- package/src/types.ts +8 -10
package/dist/index.cjs.js
CHANGED
|
@@ -2,91 +2,149 @@
|
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 = ({
|
|
40
|
-
if (!
|
|
129
|
+
const BadgesModal = ({ isOpen, achievements, onClose, styles }) => {
|
|
130
|
+
if (!isOpen)
|
|
41
131
|
return null;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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", {
|
|
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
|
|
237
|
+
const ConfettiWrapper = ({ show }) => {
|
|
180
238
|
const { width, height } = useWindowSize();
|
|
181
239
|
if (!show)
|
|
182
240
|
return null;
|
|
183
|
-
return
|
|
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
|
|
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
|
-
|
|
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 =>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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,
|
|
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, {
|
|
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, {
|
|
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;
|