react-achievements 3.0.2 → 3.2.0
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 +338 -225
- package/dist/index.d.ts +205 -75
- package/dist/index.js +458 -115
- package/dist/index.js.map +1 -1
- package/dist/types/core/icons/defaultIcons.d.ts +0 -73
- package/dist/types/core/types.d.ts +14 -0
- package/dist/types/core/utils/configNormalizer.d.ts +3 -0
- package/dist/types/hooks/useSimpleAchievements.d.ts +40 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/providers/AchievementProvider.d.ts +2 -2
- package/dist/types/utils/achievementHelpers.d.ts +135 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -16,43 +16,48 @@ Note: React and React DOM should be version 16.8.0 or higher. If you already hav
|
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
|
-
Here's a complete working example that shows automatic notifications and achievement tracking:
|
|
19
|
+
Here's a complete working example using the **new Simple API** that shows automatic notifications and achievement tracking:
|
|
20
20
|
|
|
21
21
|
```tsx
|
|
22
22
|
import React, { useState } from 'react';
|
|
23
23
|
import {
|
|
24
24
|
AchievementProvider,
|
|
25
|
-
|
|
25
|
+
useSimpleAchievements,
|
|
26
26
|
BadgesButton,
|
|
27
27
|
BadgesModal
|
|
28
28
|
} from 'react-achievements';
|
|
29
29
|
|
|
30
|
-
// Define
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
achievementIconKey: 'trophy'
|
|
39
|
-
}
|
|
40
|
-
}]
|
|
41
|
-
};
|
|
30
|
+
// Define achievements with the new three-tier Builder API - 95% less code!
|
|
31
|
+
import { AchievementBuilder } from 'react-achievements';
|
|
32
|
+
|
|
33
|
+
const gameAchievements = AchievementBuilder.combine([
|
|
34
|
+
AchievementBuilder.createScoreAchievements([100, 500]), // Smart defaults
|
|
35
|
+
AchievementBuilder.createLevelAchievement(5).withAward({ title: 'Leveling Up', icon: '📈' }), // Chainable customization
|
|
36
|
+
AchievementBuilder.createBooleanAchievement('completedTutorial').withAward({ title: 'Tutorial Master', icon: '📚' })
|
|
37
|
+
]);
|
|
42
38
|
|
|
43
|
-
// Demo component with all essential features
|
|
39
|
+
// Demo component with all essential features
|
|
44
40
|
const DemoComponent = () => {
|
|
45
41
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
46
|
-
const {
|
|
42
|
+
const { track, unlocked, unlockedCount, reset } = useSimpleAchievements();
|
|
47
43
|
|
|
48
44
|
return (
|
|
49
45
|
<div>
|
|
50
46
|
<h1>Achievement Demo</h1>
|
|
51
47
|
|
|
52
|
-
{/*
|
|
53
|
-
<button onClick={() =>
|
|
48
|
+
{/* Simple tracking - much easier! */}
|
|
49
|
+
<button onClick={() => track('score', 100)}>
|
|
54
50
|
Score 100 points
|
|
55
51
|
</button>
|
|
52
|
+
<button onClick={() => track('score', 500)}>
|
|
53
|
+
Score 500 points
|
|
54
|
+
</button>
|
|
55
|
+
<button onClick={() => track('level', 5)}>
|
|
56
|
+
Reach level 5
|
|
57
|
+
</button>
|
|
58
|
+
<button onClick={() => track('completedTutorial', true)}>
|
|
59
|
+
Complete tutorial
|
|
60
|
+
</button>
|
|
56
61
|
|
|
57
62
|
{/* Reset button */}
|
|
58
63
|
<button onClick={reset}>
|
|
@@ -60,20 +65,20 @@ const DemoComponent = () => {
|
|
|
60
65
|
</button>
|
|
61
66
|
|
|
62
67
|
{/* Shows unlocked achievements count */}
|
|
63
|
-
<p>Unlocked: {
|
|
68
|
+
<p>Unlocked: {unlockedCount}</p>
|
|
64
69
|
|
|
65
70
|
{/* Floating badges button */}
|
|
66
71
|
<BadgesButton
|
|
67
72
|
position="bottom-right"
|
|
68
73
|
onClick={() => setIsModalOpen(true)}
|
|
69
|
-
unlockedAchievements={
|
|
74
|
+
unlockedAchievements={[]} // Simplified for demo
|
|
70
75
|
/>
|
|
71
76
|
|
|
72
77
|
{/* Achievement history modal */}
|
|
73
78
|
<BadgesModal
|
|
74
79
|
isOpen={isModalOpen}
|
|
75
80
|
onClose={() => setIsModalOpen(false)}
|
|
76
|
-
achievements={
|
|
81
|
+
achievements={[]} // Simplified for demo
|
|
77
82
|
/>
|
|
78
83
|
</div>
|
|
79
84
|
);
|
|
@@ -83,7 +88,7 @@ const DemoComponent = () => {
|
|
|
83
88
|
const App = () => {
|
|
84
89
|
return (
|
|
85
90
|
<AchievementProvider
|
|
86
|
-
achievements={
|
|
91
|
+
achievements={gameAchievements}
|
|
87
92
|
storage="local"
|
|
88
93
|
>
|
|
89
94
|
<DemoComponent />
|
|
@@ -95,11 +100,184 @@ export default App;
|
|
|
95
100
|
```
|
|
96
101
|
|
|
97
102
|
When you click "Score 100 points":
|
|
98
|
-
1. A toast notification appears
|
|
99
|
-
2. Confetti animation plays
|
|
103
|
+
1. A toast notification appears automatically
|
|
104
|
+
2. Confetti animation plays
|
|
100
105
|
3. The achievement is stored and visible in the badges modal
|
|
101
106
|
4. The badges button updates to show the new count
|
|
102
107
|
|
|
108
|
+
## Simple API (Recommended)
|
|
109
|
+
|
|
110
|
+
Perfect for 90% of use cases - threshold-based achievements with minimal configuration:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { AchievementProvider, useSimpleAchievements } from 'react-achievements';
|
|
114
|
+
|
|
115
|
+
const achievements = {
|
|
116
|
+
// Numeric thresholds
|
|
117
|
+
score: {
|
|
118
|
+
100: { title: 'Century!', description: 'Score 100 points', icon: '🏆' },
|
|
119
|
+
500: { title: 'High Scorer!', icon: '⭐' }
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// Boolean achievements
|
|
123
|
+
completedTutorial: {
|
|
124
|
+
true: { title: 'Tutorial Master', description: 'Complete the tutorial', icon: '📚' }
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// String-based achievements
|
|
128
|
+
characterClass: {
|
|
129
|
+
wizard: { title: 'Arcane Scholar', description: 'Choose the wizard class', icon: '🧙♂️' },
|
|
130
|
+
warrior: { title: 'Battle Hardened', description: 'Choose the warrior class', icon: '⚔️' }
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Custom condition functions for complex logic
|
|
134
|
+
combo: {
|
|
135
|
+
custom: {
|
|
136
|
+
title: 'Perfect Combo',
|
|
137
|
+
description: 'Score 1000+ with 100% accuracy',
|
|
138
|
+
icon: '💎',
|
|
139
|
+
condition: (metrics) => metrics.score >= 1000 && metrics.accuracy === 100
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const { track, unlocked, unlockedCount, reset } = useSimpleAchievements();
|
|
145
|
+
|
|
146
|
+
// Track achievements easily
|
|
147
|
+
track('score', 100); // Unlocks "Century!" achievement
|
|
148
|
+
track('completedTutorial', true); // Unlocks "Tutorial Master"
|
|
149
|
+
track('characterClass', 'wizard'); // Unlocks "Arcane Scholar"
|
|
150
|
+
|
|
151
|
+
// Track multiple metrics for custom conditions
|
|
152
|
+
track('score', 1000);
|
|
153
|
+
track('accuracy', 100); // Unlocks "Perfect Combo" if both conditions met
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Simple API Comparison Logic
|
|
157
|
+
|
|
158
|
+
When using the Simple API, achievement conditions use different comparison operators depending on the value type:
|
|
159
|
+
|
|
160
|
+
| Value Type | Comparison | Example | When Achievement Unlocks |
|
|
161
|
+
|------------|------------|---------|-------------------------|
|
|
162
|
+
| **Numeric** | `>=` (greater than or equal) | `score: { 100: {...} }` | When `track('score', 100)` or higher |
|
|
163
|
+
| **Boolean** | `===` (strict equality) | `completedTutorial: { true: {...} }` | When `track('completedTutorial', true)` |
|
|
164
|
+
| **String** | `===` (strict equality) | `characterClass: { wizard: {...} }` | When `track('characterClass', 'wizard')` |
|
|
165
|
+
|
|
166
|
+
**Important Notes:**
|
|
167
|
+
- **Numeric achievements** use `>=` comparison, so they unlock when you reach **or exceed** the threshold
|
|
168
|
+
- **Boolean and string achievements** use exact equality matching
|
|
169
|
+
- Custom condition functions have full control over comparison logic
|
|
170
|
+
|
|
171
|
+
**Examples:**
|
|
172
|
+
```tsx
|
|
173
|
+
// Numeric: Achievement unlocks at 100 or higher
|
|
174
|
+
track('score', 150); // ✅ Unlocks "Century!" (threshold: 100)
|
|
175
|
+
track('score', 99); // ❌ Does not unlock
|
|
176
|
+
|
|
177
|
+
// Boolean: Must match exactly
|
|
178
|
+
track('completedTutorial', true); // ✅ Unlocks achievement
|
|
179
|
+
track('completedTutorial', false); // ❌ Does not unlock
|
|
180
|
+
|
|
181
|
+
// String: Must match exactly
|
|
182
|
+
track('characterClass', 'wizard'); // ✅ Unlocks "Arcane Scholar"
|
|
183
|
+
track('characterClass', 'Wizard'); // ❌ Does not unlock (case sensitive)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Three-Tier Builder API
|
|
187
|
+
|
|
188
|
+
The AchievementBuilder provides three levels of complexity to match your needs - from zero-config defaults to full custom logic:
|
|
189
|
+
|
|
190
|
+
### Tier 1: Smart Defaults (90% of use cases)
|
|
191
|
+
|
|
192
|
+
Zero configuration needed - just specify what you want to track:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import { AchievementBuilder } from 'react-achievements';
|
|
196
|
+
|
|
197
|
+
// Individual achievements with smart defaults
|
|
198
|
+
AchievementBuilder.createScoreAchievement(100); // "Score 100!" + 🏆
|
|
199
|
+
AchievementBuilder.createLevelAchievement(5); // "Level 5!" + 📈
|
|
200
|
+
AchievementBuilder.createBooleanAchievement('completedTutorial'); // "Completed Tutorial!" + ✅
|
|
201
|
+
|
|
202
|
+
// Bulk creation with smart defaults
|
|
203
|
+
AchievementBuilder.createScoreAchievements([100, 500, 1000]);
|
|
204
|
+
AchievementBuilder.createLevelAchievements([5, 10, 25]);
|
|
205
|
+
|
|
206
|
+
// Mixed: some defaults, some custom awards
|
|
207
|
+
const achievements = AchievementBuilder.createScoreAchievements([
|
|
208
|
+
100, // Uses default "Score 100!" + 🏆
|
|
209
|
+
[500, { title: 'High Scorer!', icon: '⭐' }], // Custom award
|
|
210
|
+
1000 // Uses default "Score 1000!" + 🏆
|
|
211
|
+
]);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Tier 2: Chainable Customization
|
|
215
|
+
|
|
216
|
+
Start with defaults, then customize awards as needed:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// Individual achievements with custom awards
|
|
220
|
+
const achievements = AchievementBuilder.combine([
|
|
221
|
+
AchievementBuilder.createScoreAchievement(100)
|
|
222
|
+
.withAward({ title: 'Century!', description: 'Amazing score!', icon: '🏆' }),
|
|
223
|
+
|
|
224
|
+
AchievementBuilder.createLevelAchievement(5)
|
|
225
|
+
.withAward({ title: 'Getting Started', icon: '🌱' }),
|
|
226
|
+
|
|
227
|
+
AchievementBuilder.createBooleanAchievement('completedTutorial')
|
|
228
|
+
.withAward({ title: 'Tutorial Master', description: 'You did it!', icon: '📚' }),
|
|
229
|
+
|
|
230
|
+
AchievementBuilder.createValueAchievement('characterClass', 'wizard')
|
|
231
|
+
.withAward({ title: 'Arcane Scholar', icon: '🧙♂️' })
|
|
232
|
+
]);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Tier 3: Full Control for Complex Logic
|
|
236
|
+
|
|
237
|
+
Complete control over achievement conditions for power users:
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
// Handle complex scenarios like Date, null, undefined values
|
|
241
|
+
const complexAchievement = AchievementBuilder.create()
|
|
242
|
+
.withId('weekly_login')
|
|
243
|
+
.withMetric('lastLoginDate')
|
|
244
|
+
.withCondition((value, state) => {
|
|
245
|
+
// Handle all possible value types
|
|
246
|
+
if (value === null || value === undefined) return false;
|
|
247
|
+
if (value instanceof Date) {
|
|
248
|
+
return value.getTime() > Date.now() - (7 * 24 * 60 * 60 * 1000);
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
})
|
|
252
|
+
.withAward({
|
|
253
|
+
title: 'Weekly Warrior',
|
|
254
|
+
description: 'Logged in within the last week',
|
|
255
|
+
icon: '📅'
|
|
256
|
+
})
|
|
257
|
+
.build();
|
|
258
|
+
|
|
259
|
+
// Multiple complex achievements
|
|
260
|
+
const advancedAchievements = AchievementBuilder.combine([
|
|
261
|
+
complexAchievement,
|
|
262
|
+
AchievementBuilder.create()
|
|
263
|
+
.withId('perfect_combo')
|
|
264
|
+
.withMetric('gameState')
|
|
265
|
+
.withCondition((value, state) => {
|
|
266
|
+
return state.score >= 1000 && state.accuracy === 100;
|
|
267
|
+
})
|
|
268
|
+
.withAward({ title: 'Perfect!', icon: '💎' })
|
|
269
|
+
.build()
|
|
270
|
+
]);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Key Benefits
|
|
274
|
+
- **Progressive complexity**: Start simple, add complexity only when needed
|
|
275
|
+
- **Zero configuration**: Works out of the box with smart defaults
|
|
276
|
+
- **Chainable customization**: Fine-tune awards without changing logic
|
|
277
|
+
- **Type-safe**: Full TypeScript support for complex conditions
|
|
278
|
+
- **Handles edge cases**: Date, null, undefined values in Tier 3
|
|
279
|
+
- **Combinable**: Mix and match different tiers in one configuration
|
|
280
|
+
|
|
103
281
|
## State Management Options
|
|
104
282
|
|
|
105
283
|
This package includes example implementations for different state management solutions in the `stories/examples` directory:
|
|
@@ -155,156 +333,31 @@ To allow users to view their achievement history, the package provides two essen
|
|
|
155
333
|
|
|
156
334
|
These components are the recommended way to give users access to their achievement history. While you could build custom UI using the `useAchievements` hook data, these components provide a polished, ready-to-use interface for achievement history.
|
|
157
335
|
|
|
158
|
-
## Basic Usage
|
|
159
|
-
|
|
160
|
-
```tsx
|
|
161
|
-
import { AchievementProvider, useAchievements } from 'react-achievements';
|
|
162
|
-
// For specific state management implementations:
|
|
163
|
-
// import { AchievementProvider, useAchievements } from 'react-achievements/redux';
|
|
164
|
-
// import { AchievementProvider, useAchievements } from 'react-achievements/zustand';
|
|
165
|
-
// import { AchievementProvider, useAchievements } from 'react-achievements/context';
|
|
166
|
-
|
|
167
|
-
// Define your achievements with various data types and conditions
|
|
168
|
-
const achievements = {
|
|
169
|
-
// Numeric achievements with thresholds
|
|
170
|
-
score: {
|
|
171
|
-
100: {
|
|
172
|
-
title: 'Century!',
|
|
173
|
-
description: 'Score 100 points',
|
|
174
|
-
icon: 'trophy'
|
|
175
|
-
},
|
|
176
|
-
500: {
|
|
177
|
-
title: 'Half a Thousand!',
|
|
178
|
-
description: 'Score 500 points',
|
|
179
|
-
icon: 'gold',
|
|
180
|
-
condition: (value) => value >= 500
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
|
|
184
|
-
// Boolean achievements
|
|
185
|
-
completedTutorial: {
|
|
186
|
-
true: {
|
|
187
|
-
title: 'Tutorial Master',
|
|
188
|
-
description: 'Complete the tutorial',
|
|
189
|
-
icon: 'book'
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
// String-based achievements
|
|
194
|
-
characterClass: {
|
|
195
|
-
'wizard': {
|
|
196
|
-
title: 'Arcane Scholar',
|
|
197
|
-
description: 'Choose the wizard class',
|
|
198
|
-
icon: 'wand'
|
|
199
|
-
},
|
|
200
|
-
'warrior': {
|
|
201
|
-
title: 'Battle Hardened',
|
|
202
|
-
description: 'Choose the warrior class',
|
|
203
|
-
icon: 'sword'
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
|
|
207
|
-
// Array-based achievements
|
|
208
|
-
collectedItems: {
|
|
209
|
-
['sword', 'shield', 'potion']: {
|
|
210
|
-
title: 'Fully Equipped',
|
|
211
|
-
description: 'Collect all essential items',
|
|
212
|
-
icon: 'backpack',
|
|
213
|
-
condition: (items) => ['sword', 'shield', 'potion'].every(item => items.includes(item))
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
|
|
217
|
-
// Object-based achievements
|
|
218
|
-
playerStats: {
|
|
219
|
-
{ strength: 10, intelligence: 10 }: {
|
|
220
|
-
title: 'Balanced Warrior',
|
|
221
|
-
description: 'Achieve balanced stats',
|
|
222
|
-
icon: 'scale',
|
|
223
|
-
condition: (stats) => stats.strength === 10 && stats.intelligence === 10
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
// Time-based achievements
|
|
228
|
-
playTime: {
|
|
229
|
-
3600: {
|
|
230
|
-
title: 'Dedicated Player',
|
|
231
|
-
description: 'Play for 1 hour',
|
|
232
|
-
icon: 'clock',
|
|
233
|
-
condition: (seconds) => seconds >= 3600
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
// Combination achievements
|
|
238
|
-
combo: {
|
|
239
|
-
{ score: 1000, level: 5 }: {
|
|
240
|
-
title: 'Rising Star',
|
|
241
|
-
description: 'Reach level 5 with 1000 points',
|
|
242
|
-
icon: 'star',
|
|
243
|
-
condition: (metrics) => metrics.score >= 1000 && metrics.level >= 5
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
// Create your app component
|
|
249
|
-
const App = () => {
|
|
250
|
-
return (
|
|
251
|
-
<AchievementProvider
|
|
252
|
-
achievements={achievements}
|
|
253
|
-
storage="local" // or "memory" or custom storage
|
|
254
|
-
>
|
|
255
|
-
<Game />
|
|
256
|
-
</AchievementProvider>
|
|
257
|
-
);
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// Use achievements in your components
|
|
261
|
-
const Game = () => {
|
|
262
|
-
const { update, achievements } = useAchievements();
|
|
263
|
-
|
|
264
|
-
const handleScoreUpdate = (newScore: number) => {
|
|
265
|
-
update({ score: newScore });
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
return (
|
|
269
|
-
<div>
|
|
270
|
-
<h1>Game</h1>
|
|
271
|
-
<p>Unlocked Achievements: {achievements.unlocked.length}</p>
|
|
272
|
-
<button onClick={() => handleScoreUpdate(100)}>
|
|
273
|
-
Score 100 points
|
|
274
|
-
</button>
|
|
275
|
-
</div>
|
|
276
|
-
);
|
|
277
|
-
};
|
|
278
|
-
```
|
|
279
336
|
|
|
280
337
|
## Default Icons
|
|
281
338
|
|
|
282
339
|
The package comes with a comprehensive set of default icons that you can use in your achievements. These are available through the `defaultAchievementIcons` export:
|
|
283
340
|
|
|
284
341
|
```tsx
|
|
285
|
-
import { AchievementProvider
|
|
286
|
-
|
|
287
|
-
// Example achievement configuration using
|
|
288
|
-
const
|
|
289
|
-
pageViews:
|
|
290
|
-
{
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
achievementTitle: 'Getting Started',
|
|
295
|
-
achievementDescription: 'Viewed 5 pages',
|
|
296
|
-
achievementIconKey: 'firstStep' // This will use the 👣 emoji from defaultAchievementIcons
|
|
297
|
-
}
|
|
342
|
+
import { AchievementProvider } from 'react-achievements';
|
|
343
|
+
|
|
344
|
+
// Example achievement configuration using direct emoji icons
|
|
345
|
+
const achievements = {
|
|
346
|
+
pageViews: {
|
|
347
|
+
5: {
|
|
348
|
+
title: 'Getting Started',
|
|
349
|
+
description: 'Viewed 5 pages',
|
|
350
|
+
icon: '👣'
|
|
298
351
|
}
|
|
299
|
-
|
|
352
|
+
}
|
|
300
353
|
};
|
|
301
354
|
|
|
302
355
|
// Create your app component
|
|
303
356
|
const App = () => {
|
|
304
357
|
return (
|
|
305
358
|
<AchievementProvider
|
|
306
|
-
achievements={
|
|
307
|
-
|
|
359
|
+
achievements={achievements}
|
|
360
|
+
storage="local"
|
|
308
361
|
>
|
|
309
362
|
<Game />
|
|
310
363
|
</AchievementProvider>
|
|
@@ -312,111 +365,101 @@ const App = () => {
|
|
|
312
365
|
};
|
|
313
366
|
```
|
|
314
367
|
|
|
315
|
-
###
|
|
368
|
+
### Using Icons
|
|
316
369
|
|
|
317
|
-
|
|
370
|
+
The Simple API makes icon usage straightforward - just include emojis directly in your achievement definitions:
|
|
318
371
|
|
|
319
372
|
```tsx
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
<AchievementProvider
|
|
332
|
-
achievements={achievementConfig}
|
|
333
|
-
icons={customIcons} // Pass your custom icons to the provider
|
|
334
|
-
>
|
|
335
|
-
<Game />
|
|
336
|
-
</AchievementProvider>
|
|
337
|
-
);
|
|
373
|
+
const achievements = {
|
|
374
|
+
score: {
|
|
375
|
+
100: { title: 'Century!', icon: '🏆' },
|
|
376
|
+
500: { title: 'High Scorer!', icon: '⭐' },
|
|
377
|
+
1000: { title: 'Elite Player!', icon: '💎' }
|
|
378
|
+
},
|
|
379
|
+
level: {
|
|
380
|
+
5: { title: 'Getting Started', icon: '🌱' },
|
|
381
|
+
10: { title: 'Rising Star', icon: '🚀' },
|
|
382
|
+
25: { title: 'Expert', icon: '👑' }
|
|
383
|
+
}
|
|
338
384
|
};
|
|
339
385
|
```
|
|
340
386
|
|
|
341
|
-
###
|
|
387
|
+
### Fallback Icons
|
|
342
388
|
|
|
343
|
-
The
|
|
344
|
-
|
|
345
|
-
- General Progress & Milestones (levelUp, questComplete, etc.)
|
|
346
|
-
- Social & Engagement (shared, liked, etc.)
|
|
347
|
-
- Time & Activity (activeDay, streak, etc.)
|
|
348
|
-
- Creativity & Skill (artist, expert, etc.)
|
|
349
|
-
- Achievement Types (bronze, silver, gold, etc.)
|
|
350
|
-
- Numbers & Counters (one, ten, hundred, etc.)
|
|
351
|
-
- Actions & Interactions (clicked, discovered, etc.)
|
|
352
|
-
- Placeholders (default, loading, error, etc.)
|
|
353
|
-
- Miscellaneous (trophy, star, gem, etc.)
|
|
389
|
+
The library provides a small set of essential fallback icons for system use (error states, loading, etc.). These are automatically used when needed and don't require any configuration.
|
|
354
390
|
|
|
355
391
|
## Custom Storage
|
|
356
392
|
|
|
357
393
|
You can implement your own storage solution by implementing the `AchievementStorage` interface:
|
|
358
394
|
|
|
359
|
-
```
|
|
360
|
-
import { AchievementStorage } from 'react-achievements
|
|
395
|
+
```tsx
|
|
396
|
+
import { AchievementStorage, AchievementMetrics, AchievementProvider } from 'react-achievements';
|
|
361
397
|
|
|
362
398
|
class CustomStorage implements AchievementStorage {
|
|
363
|
-
getMetrics() {
|
|
399
|
+
getMetrics(): AchievementMetrics {
|
|
364
400
|
// Your implementation
|
|
401
|
+
return {};
|
|
365
402
|
}
|
|
366
403
|
|
|
367
|
-
setMetrics(metrics) {
|
|
404
|
+
setMetrics(metrics: AchievementMetrics): void {
|
|
368
405
|
// Your implementation
|
|
369
406
|
}
|
|
370
407
|
|
|
371
|
-
getUnlockedAchievements() {
|
|
408
|
+
getUnlockedAchievements(): string[] {
|
|
372
409
|
// Your implementation
|
|
410
|
+
return [];
|
|
373
411
|
}
|
|
374
412
|
|
|
375
|
-
setUnlockedAchievements(achievements) {
|
|
413
|
+
setUnlockedAchievements(achievements: string[]): void {
|
|
376
414
|
// Your implementation
|
|
377
415
|
}
|
|
378
416
|
|
|
379
|
-
clear() {
|
|
417
|
+
clear(): void {
|
|
380
418
|
// Your implementation
|
|
381
419
|
}
|
|
382
420
|
}
|
|
383
421
|
|
|
384
422
|
// Use your custom storage
|
|
423
|
+
const gameAchievements = {
|
|
424
|
+
score: {
|
|
425
|
+
100: { title: 'Century!', icon: '🏆' }
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
385
429
|
const App = () => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
);
|
|
430
|
+
return (
|
|
431
|
+
<AchievementProvider
|
|
432
|
+
achievements={gameAchievements}
|
|
433
|
+
storage={new CustomStorage()} // Use your custom storage implementation
|
|
434
|
+
>
|
|
435
|
+
</AchievementProvider>
|
|
436
|
+
);
|
|
394
437
|
};
|
|
438
|
+
|
|
439
|
+
export default App;
|
|
395
440
|
```
|
|
396
441
|
|
|
397
442
|
## Styling
|
|
398
443
|
|
|
399
|
-
|
|
444
|
+
The achievement components use default styling that works well out of the box. For custom styling, you can override the CSS classes or customize individual component props:
|
|
400
445
|
|
|
401
446
|
```tsx
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
</AchievementProvider>
|
|
416
|
-
);
|
|
417
|
-
};
|
|
447
|
+
// Individual component styling
|
|
448
|
+
<BadgesButton
|
|
449
|
+
position="bottom-right"
|
|
450
|
+
style={{ backgroundColor: '#ff0000' }}
|
|
451
|
+
unlockedAchievements={achievements.unlocked}
|
|
452
|
+
/>
|
|
453
|
+
|
|
454
|
+
<BadgesModal
|
|
455
|
+
isOpen={isModalOpen}
|
|
456
|
+
onClose={() => setIsModalOpen(false)}
|
|
457
|
+
achievements={achievements.unlocked}
|
|
458
|
+
style={{ backgroundColor: '#f0f0f0' }}
|
|
459
|
+
/>
|
|
418
460
|
```
|
|
419
461
|
|
|
462
|
+
|
|
420
463
|
## API Reference
|
|
421
464
|
|
|
422
465
|
### AchievementProvider Props
|
|
@@ -436,6 +479,76 @@ Returns an object with:
|
|
|
436
479
|
- `achievements`: Object containing unlocked and locked achievements
|
|
437
480
|
- `reset`: Function to reset achievement storage
|
|
438
481
|
|
|
482
|
+
## Advanced: Complex API
|
|
483
|
+
|
|
484
|
+
For complex scenarios requiring full control over achievement logic, you can use the traditional Complex API with POJO (Plain Old JavaScript Object) configurations:
|
|
485
|
+
|
|
486
|
+
```tsx
|
|
487
|
+
import { AchievementProvider, useAchievements } from 'react-achievements';
|
|
488
|
+
|
|
489
|
+
// Define your achievements using the traditional complex format
|
|
490
|
+
const achievements = {
|
|
491
|
+
score: [{
|
|
492
|
+
isConditionMet: (value: AchievementMetricArrayValue, state: AchievementState) => {
|
|
493
|
+
const numValue = Array.isArray(value) ? value[0] : value;
|
|
494
|
+
return typeof numValue === 'number' && numValue >= 100;
|
|
495
|
+
},
|
|
496
|
+
achievementDetails: {
|
|
497
|
+
achievementId: 'score_100',
|
|
498
|
+
achievementTitle: 'Century!',
|
|
499
|
+
achievementDescription: 'Score 100 points',
|
|
500
|
+
achievementIconKey: 'trophy'
|
|
501
|
+
}
|
|
502
|
+
}],
|
|
503
|
+
|
|
504
|
+
completedTutorial: [{
|
|
505
|
+
isConditionMet: (value: AchievementMetricArrayValue, state: AchievementState) => {
|
|
506
|
+
const boolValue = Array.isArray(value) ? value[0] : value;
|
|
507
|
+
return typeof boolValue === 'boolean' && boolValue === true;
|
|
508
|
+
},
|
|
509
|
+
achievementDetails: {
|
|
510
|
+
achievementId: 'tutorial_complete',
|
|
511
|
+
achievementTitle: 'Tutorial Master',
|
|
512
|
+
achievementDescription: 'Complete the tutorial',
|
|
513
|
+
achievementIconKey: 'book'
|
|
514
|
+
}
|
|
515
|
+
}]
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// Create your app component
|
|
519
|
+
const App = () => {
|
|
520
|
+
return (
|
|
521
|
+
<AchievementProvider
|
|
522
|
+
achievements={achievements}
|
|
523
|
+
storage="local" // or "memory" or custom storage
|
|
524
|
+
>
|
|
525
|
+
<Game />
|
|
526
|
+
</AchievementProvider>
|
|
527
|
+
);
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// Use achievements in your components
|
|
531
|
+
const Game = () => {
|
|
532
|
+
const { update, achievements } = useAchievements();
|
|
533
|
+
|
|
534
|
+
const handleScoreUpdate = (newScore: number) => {
|
|
535
|
+
update({ score: newScore });
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
return (
|
|
539
|
+
<div>
|
|
540
|
+
<h1>Game</h1>
|
|
541
|
+
<p>Unlocked Achievements: {achievements.unlocked.length}</p>
|
|
542
|
+
<button onClick={() => handleScoreUpdate(100)}>
|
|
543
|
+
Score 100 points
|
|
544
|
+
</button>
|
|
545
|
+
</div>
|
|
546
|
+
);
|
|
547
|
+
};
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
This API provides maximum flexibility for complex achievement logic but requires more verbose configuration. Most users should use the Simple API or Builder API instead.
|
|
551
|
+
|
|
439
552
|
## License
|
|
440
553
|
|
|
441
554
|
MIT
|