react-achievements 3.1.0 → 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 +305 -235
- package/dist/index.d.ts +146 -74
- package/dist/index.js +316 -110
- package/dist/index.js.map +1 -1
- package/dist/types/core/icons/defaultIcons.d.ts +0 -73
- 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,19 +27,14 @@ import {
|
|
|
27
27
|
BadgesModal
|
|
28
28
|
} from 'react-achievements';
|
|
29
29
|
|
|
30
|
-
// Define achievements with the new
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
completedTutorial: {
|
|
40
|
-
true: { title: 'Tutorial Master', description: 'Complete the tutorial', icon: '📚' }
|
|
41
|
-
}
|
|
42
|
-
};
|
|
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
|
+
]);
|
|
43
38
|
|
|
44
39
|
// Demo component with all essential features
|
|
45
40
|
const DemoComponent = () => {
|
|
@@ -93,7 +88,7 @@ const DemoComponent = () => {
|
|
|
93
88
|
const App = () => {
|
|
94
89
|
return (
|
|
95
90
|
<AchievementProvider
|
|
96
|
-
achievements={
|
|
91
|
+
achievements={gameAchievements}
|
|
97
92
|
storage="local"
|
|
98
93
|
>
|
|
99
94
|
<DemoComponent />
|
|
@@ -110,44 +105,178 @@ When you click "Score 100 points":
|
|
|
110
105
|
3. The achievement is stored and visible in the badges modal
|
|
111
106
|
4. The badges button updates to show the new count
|
|
112
107
|
|
|
113
|
-
##
|
|
108
|
+
## Simple API (Recommended)
|
|
114
109
|
|
|
115
|
-
|
|
110
|
+
Perfect for 90% of use cases - threshold-based achievements with minimal configuration:
|
|
116
111
|
|
|
117
|
-
### Before (Complex API)
|
|
118
112
|
```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
|
-
```
|
|
113
|
+
import { AchievementProvider, useSimpleAchievements } from 'react-achievements';
|
|
134
114
|
|
|
135
|
-
### After (Simple API)
|
|
136
|
-
```tsx
|
|
137
115
|
const achievements = {
|
|
116
|
+
// Numeric thresholds
|
|
138
117
|
score: {
|
|
139
|
-
100: { title: 'Century!', description: 'Score 100 points', icon: '🏆' }
|
|
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
|
+
}
|
|
140
141
|
}
|
|
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
|
+
]);
|
|
142
271
|
```
|
|
143
272
|
|
|
144
273
|
### Key Benefits
|
|
145
|
-
- **
|
|
146
|
-
- **
|
|
147
|
-
- **
|
|
148
|
-
- **
|
|
149
|
-
- **
|
|
150
|
-
- **
|
|
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
|
|
151
280
|
|
|
152
281
|
## State Management Options
|
|
153
282
|
|
|
@@ -204,88 +333,23 @@ To allow users to view their achievement history, the package provides two essen
|
|
|
204
333
|
|
|
205
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.
|
|
206
335
|
|
|
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
336
|
|
|
251
|
-
|
|
252
|
-
track('score', 1000);
|
|
253
|
-
track('accuracy', 100); // Unlocks "Perfect Combo" if both conditions met
|
|
254
|
-
```
|
|
337
|
+
## Default Icons
|
|
255
338
|
|
|
256
|
-
|
|
257
|
-
For complex scenarios requiring full control:
|
|
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:
|
|
258
340
|
|
|
259
341
|
```tsx
|
|
260
|
-
import { AchievementProvider
|
|
342
|
+
import { AchievementProvider } from 'react-achievements';
|
|
261
343
|
|
|
262
|
-
//
|
|
344
|
+
// Example achievement configuration using direct emoji icons
|
|
263
345
|
const achievements = {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
achievementDetails: {
|
|
270
|
-
achievementId: 'score_100',
|
|
271
|
-
achievementTitle: 'Century!',
|
|
272
|
-
achievementDescription: 'Score 100 points',
|
|
273
|
-
achievementIconKey: 'trophy'
|
|
346
|
+
pageViews: {
|
|
347
|
+
5: {
|
|
348
|
+
title: 'Getting Started',
|
|
349
|
+
description: 'Viewed 5 pages',
|
|
350
|
+
icon: '👣'
|
|
274
351
|
}
|
|
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
|
-
}]
|
|
352
|
+
}
|
|
289
353
|
};
|
|
290
354
|
|
|
291
355
|
// Create your app component
|
|
@@ -293,61 +357,7 @@ const App = () => {
|
|
|
293
357
|
return (
|
|
294
358
|
<AchievementProvider
|
|
295
359
|
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
|
|
360
|
+
storage="local"
|
|
351
361
|
>
|
|
352
362
|
<Game />
|
|
353
363
|
</AchievementProvider>
|
|
@@ -355,111 +365,101 @@ const App = () => {
|
|
|
355
365
|
};
|
|
356
366
|
```
|
|
357
367
|
|
|
358
|
-
###
|
|
368
|
+
### Using Icons
|
|
359
369
|
|
|
360
|
-
|
|
370
|
+
The Simple API makes icon usage straightforward - just include emojis directly in your achievement definitions:
|
|
361
371
|
|
|
362
372
|
```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
|
-
);
|
|
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
|
+
}
|
|
381
384
|
};
|
|
382
385
|
```
|
|
383
386
|
|
|
384
|
-
###
|
|
387
|
+
### Fallback Icons
|
|
385
388
|
|
|
386
|
-
The
|
|
387
|
-
|
|
388
|
-
- General Progress & Milestones (levelUp, questComplete, etc.)
|
|
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.)
|
|
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.
|
|
397
390
|
|
|
398
391
|
## Custom Storage
|
|
399
392
|
|
|
400
393
|
You can implement your own storage solution by implementing the `AchievementStorage` interface:
|
|
401
394
|
|
|
402
|
-
```
|
|
403
|
-
import { AchievementStorage } from 'react-achievements
|
|
395
|
+
```tsx
|
|
396
|
+
import { AchievementStorage, AchievementMetrics, AchievementProvider } from 'react-achievements';
|
|
404
397
|
|
|
405
398
|
class CustomStorage implements AchievementStorage {
|
|
406
|
-
getMetrics() {
|
|
399
|
+
getMetrics(): AchievementMetrics {
|
|
407
400
|
// Your implementation
|
|
401
|
+
return {};
|
|
408
402
|
}
|
|
409
403
|
|
|
410
|
-
setMetrics(metrics) {
|
|
404
|
+
setMetrics(metrics: AchievementMetrics): void {
|
|
411
405
|
// Your implementation
|
|
412
406
|
}
|
|
413
407
|
|
|
414
|
-
getUnlockedAchievements() {
|
|
408
|
+
getUnlockedAchievements(): string[] {
|
|
415
409
|
// Your implementation
|
|
410
|
+
return [];
|
|
416
411
|
}
|
|
417
412
|
|
|
418
|
-
setUnlockedAchievements(achievements) {
|
|
413
|
+
setUnlockedAchievements(achievements: string[]): void {
|
|
419
414
|
// Your implementation
|
|
420
415
|
}
|
|
421
416
|
|
|
422
|
-
clear() {
|
|
417
|
+
clear(): void {
|
|
423
418
|
// Your implementation
|
|
424
419
|
}
|
|
425
420
|
}
|
|
426
421
|
|
|
427
422
|
// Use your custom storage
|
|
423
|
+
const gameAchievements = {
|
|
424
|
+
score: {
|
|
425
|
+
100: { title: 'Century!', icon: '🏆' }
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
428
429
|
const App = () => {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
);
|
|
430
|
+
return (
|
|
431
|
+
<AchievementProvider
|
|
432
|
+
achievements={gameAchievements}
|
|
433
|
+
storage={new CustomStorage()} // Use your custom storage implementation
|
|
434
|
+
>
|
|
435
|
+
</AchievementProvider>
|
|
436
|
+
);
|
|
437
437
|
};
|
|
438
|
+
|
|
439
|
+
export default App;
|
|
438
440
|
```
|
|
439
441
|
|
|
440
442
|
## Styling
|
|
441
443
|
|
|
442
|
-
|
|
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:
|
|
443
445
|
|
|
444
446
|
```tsx
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
</AchievementProvider>
|
|
459
|
-
);
|
|
460
|
-
};
|
|
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
|
+
/>
|
|
461
460
|
```
|
|
462
461
|
|
|
462
|
+
|
|
463
463
|
## API Reference
|
|
464
464
|
|
|
465
465
|
### AchievementProvider Props
|
|
@@ -479,6 +479,76 @@ Returns an object with:
|
|
|
479
479
|
- `achievements`: Object containing unlocked and locked achievements
|
|
480
480
|
- `reset`: Function to reset achievement storage
|
|
481
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
|
+
|
|
482
552
|
## License
|
|
483
553
|
|
|
484
554
|
MIT
|