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