react-achievements 3.6.0 → 3.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +817 -7
- package/dist/index.d.ts +1 -6
- package/dist/index.js.map +1 -1
- package/dist/types/core/types.d.ts +1 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A flexible and extensible achievement system for React applications. This package provides the foundation for implementing achievements in React applications with support for multiple state management solutions including Redux, Zustand, and Context API. Check the `stories/examples` directory for implementation examples with different state management solutions.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
<img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExbnMxdHVqanZvbGR6czJqOTdpejZqc3F3NXh6a2FiM3lmdnB0d3VoOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/LYXAZelQMeeYpzbgtT/giphy.gif" alt="React Achievements Demo" width="600" />
|
|
7
|
-
</p>
|
|
5
|
+
[](https://github.com/user-attachments/assets/a33fdae5-439b-4fc9-a388-ccb2f432a3a8)
|
|
8
6
|
|
|
9
7
|
## Installation
|
|
10
8
|
|
|
@@ -405,6 +403,125 @@ npm install react-achievements
|
|
|
405
403
|
|
|
406
404
|
Without `useBuiltInUI={true}`, you'll need to install the external UI dependencies (default behavior for v3.6.0).
|
|
407
405
|
|
|
406
|
+
### Built-in UI Component API Reference
|
|
407
|
+
|
|
408
|
+
The built-in UI system includes three core components that can be used standalone or customized via component injection.
|
|
409
|
+
|
|
410
|
+
#### BuiltInNotification
|
|
411
|
+
|
|
412
|
+
Displays achievement unlock notifications.
|
|
413
|
+
|
|
414
|
+
**Props:**
|
|
415
|
+
- `achievement` (object, required): Achievement object with `id`, `title`, `description`, and `icon`
|
|
416
|
+
- `onClose` (function, optional): Callback to dismiss the notification
|
|
417
|
+
- `duration` (number, optional): Auto-dismiss duration in ms (default: 5000)
|
|
418
|
+
- `position` (string, optional): Notification position - 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', or 'bottom-right' (default: 'top-center')
|
|
419
|
+
- `theme` (string | ThemeConfig, optional): Theme name or custom theme config
|
|
420
|
+
|
|
421
|
+
**Usage:**
|
|
422
|
+
```tsx
|
|
423
|
+
import { BuiltInNotification } from 'react-achievements';
|
|
424
|
+
|
|
425
|
+
<BuiltInNotification
|
|
426
|
+
achievement={{
|
|
427
|
+
id: 'score_100',
|
|
428
|
+
title: 'Century!',
|
|
429
|
+
description: 'Score 100 points',
|
|
430
|
+
icon: '🏆'
|
|
431
|
+
}}
|
|
432
|
+
onClose={() => console.log('Dismissed')}
|
|
433
|
+
duration={5000}
|
|
434
|
+
position="top-center"
|
|
435
|
+
theme="modern"
|
|
436
|
+
/>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### BuiltInModal
|
|
440
|
+
|
|
441
|
+
Modal dialog for displaying achievement history.
|
|
442
|
+
|
|
443
|
+
**Props:**
|
|
444
|
+
- `isOpen` (boolean, required): Modal open state
|
|
445
|
+
- `onClose` (function, required): Callback to close modal
|
|
446
|
+
- `achievements` (array, required): Array of achievement objects with `isUnlocked` status
|
|
447
|
+
- `icons` (object, optional): Custom icon mapping
|
|
448
|
+
- `theme` (string | ThemeConfig, optional): Theme name or custom theme config
|
|
449
|
+
|
|
450
|
+
**Usage:**
|
|
451
|
+
```tsx
|
|
452
|
+
import { BuiltInModal } from 'react-achievements';
|
|
453
|
+
|
|
454
|
+
<BuiltInModal
|
|
455
|
+
isOpen={isOpen}
|
|
456
|
+
onClose={() => setIsOpen(false)}
|
|
457
|
+
achievements={achievementsWithStatus}
|
|
458
|
+
icons={customIcons}
|
|
459
|
+
theme="minimal"
|
|
460
|
+
/>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Note:** This is the internal UI component. For the public API component with `showAllAchievements` support, use `BadgesModal` instead.
|
|
464
|
+
|
|
465
|
+
#### BuiltInConfetti
|
|
466
|
+
|
|
467
|
+
Confetti animation component.
|
|
468
|
+
|
|
469
|
+
**Props:**
|
|
470
|
+
- `show` (boolean, required): Whether confetti is active
|
|
471
|
+
- `duration` (number, optional): Animation duration in ms (default: 5000)
|
|
472
|
+
- `particleCount` (number, optional): Number of confetti particles (default: 50)
|
|
473
|
+
- `colors` (string[], optional): Array of color hex codes (default: ['#FFD700', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'])
|
|
474
|
+
|
|
475
|
+
**Usage:**
|
|
476
|
+
```tsx
|
|
477
|
+
import { BuiltInConfetti } from 'react-achievements';
|
|
478
|
+
|
|
479
|
+
<BuiltInConfetti
|
|
480
|
+
show={showConfetti}
|
|
481
|
+
duration={5000}
|
|
482
|
+
particleCount={150}
|
|
483
|
+
colors={['#ff0000', '#00ff00', '#0000ff']}
|
|
484
|
+
/>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**Customization via Component Injection:**
|
|
488
|
+
|
|
489
|
+
You can replace any built-in component with your own implementation:
|
|
490
|
+
|
|
491
|
+
```tsx
|
|
492
|
+
import { AchievementProvider, NotificationProps } from 'react-achievements';
|
|
493
|
+
|
|
494
|
+
const MyCustomNotification: React.FC<NotificationProps> = ({
|
|
495
|
+
achievement,
|
|
496
|
+
onClose,
|
|
497
|
+
duration
|
|
498
|
+
}) => {
|
|
499
|
+
useEffect(() => {
|
|
500
|
+
const timer = setTimeout(onClose, duration);
|
|
501
|
+
return () => clearTimeout(timer);
|
|
502
|
+
}, [duration, onClose]);
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<div className="my-notification">
|
|
506
|
+
<h3>{achievement.title}</h3>
|
|
507
|
+
<p>{achievement.description}</p>
|
|
508
|
+
<span>{achievement.icon}</span>
|
|
509
|
+
</div>
|
|
510
|
+
);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
<AchievementProvider
|
|
514
|
+
achievements={config}
|
|
515
|
+
ui={{
|
|
516
|
+
NotificationComponent: MyCustomNotification,
|
|
517
|
+
// ModalComponent: MyCustomModal,
|
|
518
|
+
// ConfettiComponent: MyCustomConfetti
|
|
519
|
+
}}
|
|
520
|
+
>
|
|
521
|
+
<App />
|
|
522
|
+
</AchievementProvider>
|
|
523
|
+
```
|
|
524
|
+
|
|
408
525
|
### Deprecation Timeline
|
|
409
526
|
|
|
410
527
|
- **v3.6.0 (current)**: Built-in UI available, external deps optional with deprecation warning
|
|
@@ -657,6 +774,15 @@ To allow users to view their achievement history, the package provides two essen
|
|
|
657
774
|
|
|
658
775
|
**Show All Achievements** (NEW in v3.5.0): Display both locked and unlocked achievements to motivate users and show them what's available:
|
|
659
776
|
|
|
777
|
+
**⚠️ IMPORTANT: Using getAllAchievements with BadgesModal**
|
|
778
|
+
|
|
779
|
+
When displaying all achievements (locked + unlocked) in the modal, you MUST use the `getAllAchievements()` method from the `useAchievements` hook:
|
|
780
|
+
|
|
781
|
+
- ✅ **Correct**: `allAchievements={getAllAchievements()}`
|
|
782
|
+
- ❌ **Incorrect**: `allAchievements={achievements.all}`
|
|
783
|
+
|
|
784
|
+
**Why?** `getAllAchievements()` returns an array of achievement objects with an `isUnlocked: boolean` property that the modal uses to display locked vs unlocked states. The `achievements.all` property is the raw configuration object and doesn't include unlock status information.
|
|
785
|
+
|
|
660
786
|
```tsx
|
|
661
787
|
import { useAchievements, BadgesModal } from 'react-achievements';
|
|
662
788
|
|
|
@@ -757,6 +883,25 @@ The library provides a small set of essential fallback icons for system use (err
|
|
|
757
883
|
|
|
758
884
|
React Achievements now supports async storage backends for modern applications that need large data capacity, server sync, or offline-first capabilities.
|
|
759
885
|
|
|
886
|
+
### Choosing the Right Storage
|
|
887
|
+
|
|
888
|
+
Select the storage option that best fits your application's needs:
|
|
889
|
+
|
|
890
|
+
| Storage Type | Capacity | Persistence | Network | Offline | Use Case |
|
|
891
|
+
|--------------|----------|-------------|---------|---------|----------|
|
|
892
|
+
| **MemoryStorage** | Unlimited | Session only | No | N/A | Testing, prototypes, temporary state |
|
|
893
|
+
| **LocalStorage** | ~5-10MB | Permanent | No | N/A | Simple apps, browser-only, small datasets |
|
|
894
|
+
| **IndexedDB** | ~50MB+ | Permanent | No | N/A | Large datasets, offline apps, PWAs |
|
|
895
|
+
| **RestAPI** | Unlimited | Server-side | Yes | No | Multi-device sync, cloud backup, user accounts |
|
|
896
|
+
| **OfflineQueue** | Unlimited | Hybrid | Yes | Yes | PWAs, unreliable connections, offline-first apps |
|
|
897
|
+
|
|
898
|
+
**Decision Tree:**
|
|
899
|
+
- **Need cloud sync or multi-device support?** → Use **RestAPI** or **OfflineQueue**
|
|
900
|
+
- **Large data storage (>10MB)?** → Use **IndexedDB**
|
|
901
|
+
- **Simple browser-only app?** → Use **LocalStorage** (default)
|
|
902
|
+
- **Testing or prototypes only?** → Use **MemoryStorage**
|
|
903
|
+
- **Offline-first with sync?** → Use **OfflineQueue** (wraps RestAPI)
|
|
904
|
+
|
|
760
905
|
### IndexedDB Storage
|
|
761
906
|
|
|
762
907
|
Browser-native storage with 50MB+ capacity (vs localStorage's 5-10MB limit):
|
|
@@ -1607,6 +1752,439 @@ The achievement components use default styling that works well out of the box. F
|
|
|
1607
1752
|
/>
|
|
1608
1753
|
```
|
|
1609
1754
|
|
|
1755
|
+
### Default Styles Reference
|
|
1756
|
+
|
|
1757
|
+
The `defaultStyles` export provides access to the default styling configuration for all components. Use this to extend or override specific style properties while keeping other defaults.
|
|
1758
|
+
|
|
1759
|
+
```tsx
|
|
1760
|
+
import { defaultStyles } from 'react-achievements';
|
|
1761
|
+
|
|
1762
|
+
// Access default styles
|
|
1763
|
+
console.log(defaultStyles.badgesButton);
|
|
1764
|
+
console.log(defaultStyles.badgesModal);
|
|
1765
|
+
|
|
1766
|
+
// Extend default styles
|
|
1767
|
+
<BadgesButton
|
|
1768
|
+
style={{
|
|
1769
|
+
...defaultStyles.badgesButton,
|
|
1770
|
+
backgroundColor: '#custom-color',
|
|
1771
|
+
borderRadius: '12px'
|
|
1772
|
+
}}
|
|
1773
|
+
unlockedAchievements={achievements}
|
|
1774
|
+
/>
|
|
1775
|
+
|
|
1776
|
+
// Override specific properties
|
|
1777
|
+
<BadgesModal
|
|
1778
|
+
style={{
|
|
1779
|
+
...defaultStyles.badgesModal,
|
|
1780
|
+
maxWidth: '800px',
|
|
1781
|
+
padding: '2rem'
|
|
1782
|
+
}}
|
|
1783
|
+
isOpen={isOpen}
|
|
1784
|
+
onClose={onClose}
|
|
1785
|
+
achievements={achievements}
|
|
1786
|
+
/>
|
|
1787
|
+
```
|
|
1788
|
+
|
|
1789
|
+
**Available style objects:**
|
|
1790
|
+
- `defaultStyles.badgesButton` - Default button styles
|
|
1791
|
+
- `defaultStyles.badgesModal` - Default modal styles
|
|
1792
|
+
- `defaultStyles.notification` - Default notification styles (built-in UI)
|
|
1793
|
+
- `defaultStyles.confetti` - Default confetti configuration
|
|
1794
|
+
|
|
1795
|
+
## Utility Functions & Hooks
|
|
1796
|
+
|
|
1797
|
+
### useWindowSize Hook
|
|
1798
|
+
|
|
1799
|
+
Returns current window dimensions for responsive UI components.
|
|
1800
|
+
|
|
1801
|
+
```tsx
|
|
1802
|
+
import { useWindowSize } from 'react-achievements';
|
|
1803
|
+
|
|
1804
|
+
const MyComponent = () => {
|
|
1805
|
+
const { width, height } = useWindowSize();
|
|
1806
|
+
|
|
1807
|
+
return (
|
|
1808
|
+
<div>
|
|
1809
|
+
Window size: {width} x {height}
|
|
1810
|
+
</div>
|
|
1811
|
+
);
|
|
1812
|
+
};
|
|
1813
|
+
```
|
|
1814
|
+
|
|
1815
|
+
**Returns:** `{ width: number; height: number }`
|
|
1816
|
+
|
|
1817
|
+
**Use cases:**
|
|
1818
|
+
- Responsive achievement UI layouts
|
|
1819
|
+
- Adaptive modal positioning
|
|
1820
|
+
- Mobile vs desktop rendering
|
|
1821
|
+
|
|
1822
|
+
### normalizeAchievements Function
|
|
1823
|
+
|
|
1824
|
+
Converts Simple API configuration to Complex API format internally. This function is used automatically by the `AchievementProvider`, so you typically don't need to call it directly.
|
|
1825
|
+
|
|
1826
|
+
```tsx
|
|
1827
|
+
import { normalizeAchievements } from 'react-achievements';
|
|
1828
|
+
|
|
1829
|
+
const simpleConfig = {
|
|
1830
|
+
score: {
|
|
1831
|
+
100: { title: 'Century!', icon: '🏆' }
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
|
|
1835
|
+
const normalized = normalizeAchievements(simpleConfig);
|
|
1836
|
+
// Returns complex format used internally
|
|
1837
|
+
```
|
|
1838
|
+
|
|
1839
|
+
**Note:** Usually not needed - the provider handles this automatically.
|
|
1840
|
+
|
|
1841
|
+
### isSimpleConfig Type Guard
|
|
1842
|
+
|
|
1843
|
+
Check if a configuration object uses the Simple API format.
|
|
1844
|
+
|
|
1845
|
+
```tsx
|
|
1846
|
+
import { isSimpleConfig } from 'react-achievements';
|
|
1847
|
+
|
|
1848
|
+
if (isSimpleConfig(myConfig)) {
|
|
1849
|
+
console.log('Using Simple API format');
|
|
1850
|
+
} else {
|
|
1851
|
+
console.log('Using Complex API format');
|
|
1852
|
+
}
|
|
1853
|
+
```
|
|
1854
|
+
|
|
1855
|
+
**Returns:** `boolean`
|
|
1856
|
+
|
|
1857
|
+
## TypeScript Type Reference
|
|
1858
|
+
|
|
1859
|
+
### Core Types
|
|
1860
|
+
|
|
1861
|
+
#### AchievementWithStatus
|
|
1862
|
+
|
|
1863
|
+
Achievement object with unlock status (returned by `getAllAchievements()`).
|
|
1864
|
+
|
|
1865
|
+
```tsx
|
|
1866
|
+
interface AchievementWithStatus {
|
|
1867
|
+
achievementId: string;
|
|
1868
|
+
achievementTitle: string;
|
|
1869
|
+
achievementDescription?: string;
|
|
1870
|
+
achievementIconKey?: string;
|
|
1871
|
+
isUnlocked: boolean;
|
|
1872
|
+
}
|
|
1873
|
+
```
|
|
1874
|
+
|
|
1875
|
+
#### AchievementMetrics
|
|
1876
|
+
|
|
1877
|
+
Metrics tracked for achievements.
|
|
1878
|
+
|
|
1879
|
+
```tsx
|
|
1880
|
+
type AchievementMetrics = Record<string, AchievementMetricValue>;
|
|
1881
|
+
type AchievementMetricValue = number | string | boolean | Date | null | undefined;
|
|
1882
|
+
```
|
|
1883
|
+
|
|
1884
|
+
**Example:**
|
|
1885
|
+
```tsx
|
|
1886
|
+
const metrics: AchievementMetrics = {
|
|
1887
|
+
score: 100,
|
|
1888
|
+
level: 5,
|
|
1889
|
+
completedTutorial: true,
|
|
1890
|
+
lastLoginDate: new Date()
|
|
1891
|
+
};
|
|
1892
|
+
```
|
|
1893
|
+
|
|
1894
|
+
#### UIConfig
|
|
1895
|
+
|
|
1896
|
+
UI configuration for built-in components.
|
|
1897
|
+
|
|
1898
|
+
```tsx
|
|
1899
|
+
interface UIConfig {
|
|
1900
|
+
theme?: 'modern' | 'minimal' | 'gamified' | string;
|
|
1901
|
+
customTheme?: ThemeConfig;
|
|
1902
|
+
NotificationComponent?: React.ComponentType<NotificationProps>;
|
|
1903
|
+
ModalComponent?: React.ComponentType<ModalProps>;
|
|
1904
|
+
ConfettiComponent?: React.ComponentType<ConfettiProps>;
|
|
1905
|
+
notificationPosition?: NotificationPosition;
|
|
1906
|
+
enableNotifications?: boolean;
|
|
1907
|
+
enableConfetti?: boolean;
|
|
1908
|
+
}
|
|
1909
|
+
```
|
|
1910
|
+
|
|
1911
|
+
#### StorageType Enum
|
|
1912
|
+
|
|
1913
|
+
```tsx
|
|
1914
|
+
enum StorageType {
|
|
1915
|
+
Local = 'local',
|
|
1916
|
+
Memory = 'memory',
|
|
1917
|
+
IndexedDB = 'indexeddb',
|
|
1918
|
+
RestAPI = 'restapi'
|
|
1919
|
+
}
|
|
1920
|
+
```
|
|
1921
|
+
|
|
1922
|
+
**Usage:**
|
|
1923
|
+
```tsx
|
|
1924
|
+
<AchievementProvider storage={StorageType.IndexedDB}>
|
|
1925
|
+
```
|
|
1926
|
+
|
|
1927
|
+
### Storage Interfaces
|
|
1928
|
+
|
|
1929
|
+
#### AchievementStorage (Synchronous)
|
|
1930
|
+
|
|
1931
|
+
```tsx
|
|
1932
|
+
interface AchievementStorage {
|
|
1933
|
+
getMetrics(): AchievementMetrics;
|
|
1934
|
+
setMetrics(metrics: AchievementMetrics): void;
|
|
1935
|
+
getUnlockedAchievements(): string[];
|
|
1936
|
+
setUnlockedAchievements(achievements: string[]): void;
|
|
1937
|
+
clear(): void;
|
|
1938
|
+
}
|
|
1939
|
+
```
|
|
1940
|
+
|
|
1941
|
+
#### AsyncAchievementStorage
|
|
1942
|
+
|
|
1943
|
+
```tsx
|
|
1944
|
+
interface AsyncAchievementStorage {
|
|
1945
|
+
getMetrics(): Promise<AchievementMetrics>;
|
|
1946
|
+
setMetrics(metrics: AchievementMetrics): Promise<void>;
|
|
1947
|
+
getUnlockedAchievements(): Promise<string[]>;
|
|
1948
|
+
setUnlockedAchievements(achievements: string[]): Promise<void>;
|
|
1949
|
+
clear(): Promise<void>;
|
|
1950
|
+
}
|
|
1951
|
+
```
|
|
1952
|
+
|
|
1953
|
+
#### RestApiStorageConfig
|
|
1954
|
+
|
|
1955
|
+
```tsx
|
|
1956
|
+
interface RestApiStorageConfig {
|
|
1957
|
+
baseUrl: string;
|
|
1958
|
+
userId: string;
|
|
1959
|
+
headers?: Record<string, string>;
|
|
1960
|
+
timeout?: number; // milliseconds, default: 10000
|
|
1961
|
+
}
|
|
1962
|
+
```
|
|
1963
|
+
|
|
1964
|
+
**Example:**
|
|
1965
|
+
```tsx
|
|
1966
|
+
const config: RestApiStorageConfig = {
|
|
1967
|
+
baseUrl: 'https://api.example.com',
|
|
1968
|
+
userId: 'user123',
|
|
1969
|
+
headers: {
|
|
1970
|
+
'Authorization': 'Bearer token'
|
|
1971
|
+
},
|
|
1972
|
+
timeout: 15000
|
|
1973
|
+
};
|
|
1974
|
+
```
|
|
1975
|
+
|
|
1976
|
+
### Import/Export Types
|
|
1977
|
+
|
|
1978
|
+
#### ImportOptions
|
|
1979
|
+
|
|
1980
|
+
```tsx
|
|
1981
|
+
interface ImportOptions {
|
|
1982
|
+
strategy?: 'replace' | 'merge' | 'preserve';
|
|
1983
|
+
validate?: boolean;
|
|
1984
|
+
expectedConfigHash?: string;
|
|
1985
|
+
}
|
|
1986
|
+
```
|
|
1987
|
+
|
|
1988
|
+
**Strategies:**
|
|
1989
|
+
- `replace`: Completely replace all existing data
|
|
1990
|
+
- `merge`: Combine imported and existing data (takes maximum values)
|
|
1991
|
+
- `preserve`: Only import new achievements, keep existing data
|
|
1992
|
+
|
|
1993
|
+
#### ImportResult
|
|
1994
|
+
|
|
1995
|
+
```tsx
|
|
1996
|
+
interface ImportResult {
|
|
1997
|
+
success: boolean;
|
|
1998
|
+
errors?: string[];
|
|
1999
|
+
warnings?: string[];
|
|
2000
|
+
imported?: {
|
|
2001
|
+
metrics: number;
|
|
2002
|
+
achievements: number;
|
|
2003
|
+
};
|
|
2004
|
+
mergedMetrics?: AchievementMetrics;
|
|
2005
|
+
mergedUnlocked?: string[];
|
|
2006
|
+
configMismatch?: boolean;
|
|
2007
|
+
}
|
|
2008
|
+
```
|
|
2009
|
+
|
|
2010
|
+
## Common Patterns & Recipes
|
|
2011
|
+
|
|
2012
|
+
Quick reference for common use cases and patterns.
|
|
2013
|
+
|
|
2014
|
+
### Pattern 1: Display Only Unlocked Achievements
|
|
2015
|
+
|
|
2016
|
+
Show users only the achievements they've unlocked:
|
|
2017
|
+
|
|
2018
|
+
```tsx
|
|
2019
|
+
import { useAchievements, BadgesModal } from 'react-achievements';
|
|
2020
|
+
|
|
2021
|
+
function MyComponent() {
|
|
2022
|
+
const { achievements } = useAchievements();
|
|
2023
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
2024
|
+
|
|
2025
|
+
return (
|
|
2026
|
+
<BadgesModal
|
|
2027
|
+
isOpen={isOpen}
|
|
2028
|
+
onClose={() => setIsOpen(false)}
|
|
2029
|
+
achievements={achievements.unlocked} // Only unlocked IDs
|
|
2030
|
+
/>
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
```
|
|
2034
|
+
|
|
2035
|
+
### Pattern 2: Display All Achievements (Locked + Unlocked)
|
|
2036
|
+
|
|
2037
|
+
Show both locked and unlocked achievements to motivate users:
|
|
2038
|
+
|
|
2039
|
+
```tsx
|
|
2040
|
+
import { useAchievements, BadgesModal } from 'react-achievements';
|
|
2041
|
+
|
|
2042
|
+
function MyComponent() {
|
|
2043
|
+
const { getAllAchievements } = useAchievements();
|
|
2044
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
2045
|
+
|
|
2046
|
+
return (
|
|
2047
|
+
<BadgesModal
|
|
2048
|
+
isOpen={isOpen}
|
|
2049
|
+
onClose={() => setIsOpen(false)}
|
|
2050
|
+
showAllAchievements={true}
|
|
2051
|
+
allAchievements={getAllAchievements()} // ⭐ Required!
|
|
2052
|
+
showUnlockConditions={true} // Show hints
|
|
2053
|
+
/>
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
2056
|
+
```
|
|
2057
|
+
|
|
2058
|
+
### Pattern 3: Export Achievement Data
|
|
2059
|
+
|
|
2060
|
+
Allow users to download their achievement progress:
|
|
2061
|
+
|
|
2062
|
+
```tsx
|
|
2063
|
+
import { useAchievements } from 'react-achievements';
|
|
2064
|
+
|
|
2065
|
+
function MyComponent() {
|
|
2066
|
+
const { exportData } = useAchievements();
|
|
2067
|
+
|
|
2068
|
+
const handleExport = () => {
|
|
2069
|
+
const jsonString = exportData();
|
|
2070
|
+
|
|
2071
|
+
// Create downloadable file
|
|
2072
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
2073
|
+
const url = URL.createObjectURL(blob);
|
|
2074
|
+
const link = document.createElement('a');
|
|
2075
|
+
link.href = url;
|
|
2076
|
+
link.download = `achievements-${Date.now()}.json`;
|
|
2077
|
+
link.click();
|
|
2078
|
+
URL.revokeObjectURL(url);
|
|
2079
|
+
};
|
|
2080
|
+
|
|
2081
|
+
return <button onClick={handleExport}>Export Progress</button>;
|
|
2082
|
+
}
|
|
2083
|
+
```
|
|
2084
|
+
|
|
2085
|
+
### Pattern 4: Import Achievement Data
|
|
2086
|
+
|
|
2087
|
+
Restore achievements from a backup:
|
|
2088
|
+
|
|
2089
|
+
```tsx
|
|
2090
|
+
import { useAchievements } from 'react-achievements';
|
|
2091
|
+
|
|
2092
|
+
function MyComponent() {
|
|
2093
|
+
const { importData, update } = useAchievements();
|
|
2094
|
+
|
|
2095
|
+
const handleImport = async (file: File) => {
|
|
2096
|
+
const text = await file.text();
|
|
2097
|
+
const result = importData(text, {
|
|
2098
|
+
strategy: 'merge', // Combine with existing data
|
|
2099
|
+
validate: true
|
|
2100
|
+
});
|
|
2101
|
+
|
|
2102
|
+
if (result.success && result.mergedMetrics) {
|
|
2103
|
+
update(result.mergedMetrics);
|
|
2104
|
+
alert(`Imported ${result.imported?.achievements} achievements!`);
|
|
2105
|
+
} else {
|
|
2106
|
+
alert('Import failed: ' + result.errors?.join(', '));
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
|
|
2110
|
+
return (
|
|
2111
|
+
<input
|
|
2112
|
+
type="file"
|
|
2113
|
+
accept=".json"
|
|
2114
|
+
onChange={(e) => e.target.files?.[0] && handleImport(e.target.files[0])}
|
|
2115
|
+
/>
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
```
|
|
2119
|
+
|
|
2120
|
+
### Pattern 5: Get Current Metrics
|
|
2121
|
+
|
|
2122
|
+
Check achievement progress programmatically:
|
|
2123
|
+
|
|
2124
|
+
```tsx
|
|
2125
|
+
import { useAchievements } from 'react-achievements';
|
|
2126
|
+
|
|
2127
|
+
function MyComponent() {
|
|
2128
|
+
const { getState } = useAchievements();
|
|
2129
|
+
|
|
2130
|
+
const handleCheckProgress = () => {
|
|
2131
|
+
const state = getState();
|
|
2132
|
+
console.log('Current metrics:', state.metrics);
|
|
2133
|
+
console.log('Unlocked achievements:', state.unlocked);
|
|
2134
|
+
console.log('Total unlocked:', state.unlocked.length);
|
|
2135
|
+
};
|
|
2136
|
+
|
|
2137
|
+
return <button onClick={handleCheckProgress}>Check Progress</button>;
|
|
2138
|
+
}
|
|
2139
|
+
```
|
|
2140
|
+
|
|
2141
|
+
### Pattern 6: Track Complex Events
|
|
2142
|
+
|
|
2143
|
+
Handle achievements based on multiple conditions:
|
|
2144
|
+
|
|
2145
|
+
```tsx
|
|
2146
|
+
import { useSimpleAchievements } from 'react-achievements';
|
|
2147
|
+
|
|
2148
|
+
function GameComponent() {
|
|
2149
|
+
const { track, trackMultiple } = useSimpleAchievements();
|
|
2150
|
+
|
|
2151
|
+
const handleLevelComplete = (score: number, time: number, accuracy: number) => {
|
|
2152
|
+
// Track multiple related metrics at once
|
|
2153
|
+
trackMultiple({
|
|
2154
|
+
score: score,
|
|
2155
|
+
completionTime: time,
|
|
2156
|
+
accuracy: accuracy,
|
|
2157
|
+
levelsCompleted: true
|
|
2158
|
+
});
|
|
2159
|
+
|
|
2160
|
+
// Achievements with custom conditions will evaluate all metrics
|
|
2161
|
+
// Example: "Perfect Level" achievement for score > 1000 AND accuracy === 100
|
|
2162
|
+
};
|
|
2163
|
+
|
|
2164
|
+
return <button onClick={() => handleLevelComplete(1200, 45, 100)}>Complete Level</button>;
|
|
2165
|
+
}
|
|
2166
|
+
```
|
|
2167
|
+
|
|
2168
|
+
### Pattern 7: Reset Progress
|
|
2169
|
+
|
|
2170
|
+
Clear all achievement data:
|
|
2171
|
+
|
|
2172
|
+
```tsx
|
|
2173
|
+
import { useAchievements } from 'react-achievements';
|
|
2174
|
+
|
|
2175
|
+
function SettingsComponent() {
|
|
2176
|
+
const { reset } = useAchievements();
|
|
2177
|
+
|
|
2178
|
+
const handleReset = () => {
|
|
2179
|
+
if (confirm('Are you sure? This will delete all achievement progress.')) {
|
|
2180
|
+
reset();
|
|
2181
|
+
alert('All achievements have been reset!');
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
|
|
2185
|
+
return <button onClick={handleReset}>Reset All Achievements</button>;
|
|
2186
|
+
}
|
|
2187
|
+
```
|
|
1610
2188
|
|
|
1611
2189
|
## API Reference
|
|
1612
2190
|
|
|
@@ -1622,11 +2200,243 @@ The achievement components use default styling that works well out of the box. F
|
|
|
1622
2200
|
|
|
1623
2201
|
### useAchievements Hook
|
|
1624
2202
|
|
|
1625
|
-
|
|
2203
|
+
The `useAchievements` hook provides access to all achievement functionality. It must be used within an `AchievementProvider`.
|
|
2204
|
+
|
|
2205
|
+
```tsx
|
|
2206
|
+
const {
|
|
2207
|
+
update,
|
|
2208
|
+
achievements,
|
|
2209
|
+
reset,
|
|
2210
|
+
getState,
|
|
2211
|
+
exportData,
|
|
2212
|
+
importData,
|
|
2213
|
+
getAllAchievements
|
|
2214
|
+
} = useAchievements();
|
|
2215
|
+
```
|
|
2216
|
+
|
|
2217
|
+
#### Methods
|
|
2218
|
+
|
|
2219
|
+
| Method | Signature | Description |
|
|
2220
|
+
|--------|-----------|-------------|
|
|
2221
|
+
| `update` | `(metrics: Record<string, any>) => void` | Update one or more achievement metrics. Triggers achievement evaluation. |
|
|
2222
|
+
| `achievements` | `{ unlocked: string[]; all: Record<string, any> }` | Object containing arrays of unlocked achievement IDs and the full configuration. |
|
|
2223
|
+
| `reset` | `() => void` | Clear all achievement data including metrics and unlock history. |
|
|
2224
|
+
| `getState` | `() => { metrics: AchievementMetrics; unlocked: string[] }` | Get current state with metrics (in array format) and unlocked IDs. |
|
|
2225
|
+
| `exportData` | `() => string` | Export all achievement data as JSON string for backup/transfer. |
|
|
2226
|
+
| `importData` | `(jsonString: string, options?: ImportOptions) => ImportResult` | Import previously exported data with merge strategies. |
|
|
2227
|
+
| `getAllAchievements` | `() => AchievementWithStatus[]` | **Get all achievements with unlock status. Required for `BadgesModal` when showing locked achievements.** |
|
|
2228
|
+
|
|
2229
|
+
#### Method Details
|
|
2230
|
+
|
|
2231
|
+
**`update(metrics: Record<string, any>): void`**
|
|
2232
|
+
|
|
2233
|
+
Update achievement metrics and trigger evaluation of achievement conditions.
|
|
2234
|
+
|
|
2235
|
+
```tsx
|
|
2236
|
+
// Single metric
|
|
2237
|
+
update({ score: 100 });
|
|
2238
|
+
|
|
2239
|
+
// Multiple metrics
|
|
2240
|
+
update({ score: 500, level: 10 });
|
|
2241
|
+
```
|
|
2242
|
+
|
|
2243
|
+
**`achievements: { unlocked: string[]; all: Record<string, any> }`**
|
|
2244
|
+
|
|
2245
|
+
Object containing current achievement state.
|
|
2246
|
+
|
|
2247
|
+
```tsx
|
|
2248
|
+
// Get unlocked achievement IDs
|
|
2249
|
+
console.log(achievements.unlocked); // ['score_100', 'level_5']
|
|
2250
|
+
|
|
2251
|
+
// Get count of unlocked achievements
|
|
2252
|
+
const count = achievements.unlocked.length;
|
|
2253
|
+
```
|
|
2254
|
+
|
|
2255
|
+
**`reset(): void`**
|
|
2256
|
+
|
|
2257
|
+
Clear all achievement data including metrics, unlocked achievements, and notification history.
|
|
2258
|
+
|
|
2259
|
+
```tsx
|
|
2260
|
+
// Reset all achievements
|
|
2261
|
+
reset();
|
|
2262
|
+
```
|
|
2263
|
+
|
|
2264
|
+
**`getState(): { metrics: AchievementMetrics; unlocked: string[] }`**
|
|
2265
|
+
|
|
2266
|
+
Get the current achievement state including metrics and unlocked achievement IDs.
|
|
2267
|
+
|
|
2268
|
+
```tsx
|
|
2269
|
+
const state = getState();
|
|
2270
|
+
console.log('Current metrics:', state.metrics);
|
|
2271
|
+
console.log('Unlocked achievements:', state.unlocked);
|
|
2272
|
+
```
|
|
2273
|
+
|
|
2274
|
+
**Note:** Metrics are returned in array format (e.g., `{ score: [100] }`) even if you passed scalar values.
|
|
2275
|
+
|
|
2276
|
+
**`exportData(): string`**
|
|
2277
|
+
|
|
2278
|
+
Export all achievement data as a JSON string for backup or transfer.
|
|
2279
|
+
|
|
2280
|
+
```tsx
|
|
2281
|
+
const jsonString = exportData();
|
|
2282
|
+
|
|
2283
|
+
// Save to file
|
|
2284
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
2285
|
+
const url = URL.createObjectURL(blob);
|
|
2286
|
+
const link = document.createElement('a');
|
|
2287
|
+
link.href = url;
|
|
2288
|
+
link.download = `achievements-${Date.now()}.json`;
|
|
2289
|
+
link.click();
|
|
2290
|
+
```
|
|
2291
|
+
|
|
2292
|
+
**`importData(jsonString: string, options?: ImportOptions): ImportResult`**
|
|
2293
|
+
|
|
2294
|
+
Import previously exported achievement data.
|
|
2295
|
+
|
|
2296
|
+
```tsx
|
|
2297
|
+
const result = importData(jsonString, {
|
|
2298
|
+
strategy: 'merge', // 'replace', 'merge', or 'preserve'
|
|
2299
|
+
validate: true
|
|
2300
|
+
});
|
|
2301
|
+
|
|
2302
|
+
if (result.success) {
|
|
2303
|
+
console.log(`Imported ${result.imported.achievements} achievements`);
|
|
2304
|
+
}
|
|
2305
|
+
```
|
|
2306
|
+
|
|
2307
|
+
**`getAllAchievements(): AchievementWithStatus[]`**
|
|
1626
2308
|
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
2309
|
+
Returns all achievements (locked and unlocked) with their status. **This is required when using `BadgesModal` with `showAllAchievements={true}`**.
|
|
2310
|
+
|
|
2311
|
+
```tsx
|
|
2312
|
+
const allAchievements = getAllAchievements();
|
|
2313
|
+
// Returns: [
|
|
2314
|
+
// { achievementId: 'score_100', achievementTitle: 'Century!', isUnlocked: true, ... },
|
|
2315
|
+
// { achievementId: 'score_500', achievementTitle: 'High Scorer!', isUnlocked: false, ... }
|
|
2316
|
+
// ]
|
|
2317
|
+
|
|
2318
|
+
// Use with BadgesModal to show all achievements
|
|
2319
|
+
<BadgesModal
|
|
2320
|
+
showAllAchievements={true}
|
|
2321
|
+
allAchievements={allAchievements}
|
|
2322
|
+
// ... other props
|
|
2323
|
+
/>
|
|
2324
|
+
```
|
|
2325
|
+
|
|
2326
|
+
**Note:** Use `achievements.unlocked` for simple cases where you only need IDs. Use `getAllAchievements()` when you need full achievement objects with unlock status.
|
|
2327
|
+
|
|
2328
|
+
### useSimpleAchievements Hook
|
|
2329
|
+
|
|
2330
|
+
Simplified wrapper around `useAchievements` with cleaner API for common use cases.
|
|
2331
|
+
|
|
2332
|
+
```tsx
|
|
2333
|
+
const {
|
|
2334
|
+
track,
|
|
2335
|
+
increment,
|
|
2336
|
+
trackMultiple,
|
|
2337
|
+
unlocked,
|
|
2338
|
+
all,
|
|
2339
|
+
unlockedCount,
|
|
2340
|
+
reset,
|
|
2341
|
+
getState,
|
|
2342
|
+
exportData,
|
|
2343
|
+
importData,
|
|
2344
|
+
getAllAchievements
|
|
2345
|
+
} = useSimpleAchievements();
|
|
2346
|
+
```
|
|
2347
|
+
|
|
2348
|
+
#### Methods
|
|
2349
|
+
|
|
2350
|
+
| Method | Signature | Description |
|
|
2351
|
+
|--------|-----------|-------------|
|
|
2352
|
+
| `track` | `(metric: string, value: any) => void` | Update a single metric value. |
|
|
2353
|
+
| `increment` | `(metric: string, amount?: number) => void` | Increment a metric by amount (default: 1). |
|
|
2354
|
+
| `trackMultiple` | `(metrics: Record<string, any>) => void` | Update multiple metrics at once. |
|
|
2355
|
+
| `unlocked` | `string[]` | Array of unlocked achievement IDs. |
|
|
2356
|
+
| `all` | `Record<string, any>` | All achievements configuration. |
|
|
2357
|
+
| `unlockedCount` | `number` | Number of unlocked achievements. |
|
|
2358
|
+
| `reset` | `() => void` | Clear all achievement data. |
|
|
2359
|
+
| `getState` | `() => { metrics; unlocked }` | Get current state. |
|
|
2360
|
+
| `exportData` | `() => string` | Export data as JSON. |
|
|
2361
|
+
| `importData` | `(json, options) => ImportResult` | Import data. |
|
|
2362
|
+
| `getAllAchievements` | `() => AchievementWithStatus[]` | Get all achievements with status. |
|
|
2363
|
+
|
|
2364
|
+
**Example:**
|
|
2365
|
+
|
|
2366
|
+
```tsx
|
|
2367
|
+
const { track, increment, unlocked, unlockedCount } = useSimpleAchievements();
|
|
2368
|
+
|
|
2369
|
+
// Track single metrics
|
|
2370
|
+
track('score', 100);
|
|
2371
|
+
track('completedTutorial', true);
|
|
2372
|
+
|
|
2373
|
+
// Increment values (great for clicks, actions, etc.)
|
|
2374
|
+
increment('buttonClicks'); // Adds 1
|
|
2375
|
+
increment('score', 50); // Adds 50
|
|
2376
|
+
|
|
2377
|
+
// Check progress
|
|
2378
|
+
console.log(`Unlocked ${unlockedCount} achievements`);
|
|
2379
|
+
console.log('Achievement IDs:', unlocked);
|
|
2380
|
+
```
|
|
2381
|
+
|
|
2382
|
+
### Component Props Reference
|
|
2383
|
+
|
|
2384
|
+
#### BadgesButton Props
|
|
2385
|
+
|
|
2386
|
+
| Prop | Type | Required | Description |
|
|
2387
|
+
|------|------|----------|-------------|
|
|
2388
|
+
| `onClick` | `() => void` | Yes | Handler for button click |
|
|
2389
|
+
| `unlockedAchievements` | `AchievementDetails[]` | Yes | Array of unlocked achievements |
|
|
2390
|
+
| `position` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | No | Fixed position (default: 'bottom-right') |
|
|
2391
|
+
| `placement` | `'fixed' \| 'inline'` | No | Positioning mode (default: 'fixed') |
|
|
2392
|
+
| `theme` | `string \| ThemeConfig` | No | Theme name or custom theme |
|
|
2393
|
+
| `style` | `React.CSSProperties` | No | Custom styles |
|
|
2394
|
+
| `icons` | `Record<string, string>` | No | Custom icon mapping |
|
|
2395
|
+
|
|
2396
|
+
**Example:**
|
|
2397
|
+
```tsx
|
|
2398
|
+
<BadgesButton
|
|
2399
|
+
onClick={() => setModalOpen(true)}
|
|
2400
|
+
unlockedAchievements={achievements.unlocked}
|
|
2401
|
+
position="bottom-right"
|
|
2402
|
+
placement="fixed"
|
|
2403
|
+
theme="modern"
|
|
2404
|
+
/>
|
|
2405
|
+
```
|
|
2406
|
+
|
|
2407
|
+
#### BadgesModal Props
|
|
2408
|
+
|
|
2409
|
+
| Prop | Type | Required | Description |
|
|
2410
|
+
|------|------|----------|-------------|
|
|
2411
|
+
| `isOpen` | `boolean` | Yes | Modal open state |
|
|
2412
|
+
| `onClose` | `() => void` | Yes | Close handler |
|
|
2413
|
+
| `achievements` | `AchievementDetails[]` | Yes* | Unlocked achievements (*not used if `showAllAchievements`) |
|
|
2414
|
+
| `showAllAchievements` | `boolean` | No | Show locked + unlocked (default: false) |
|
|
2415
|
+
| `showUnlockConditions` | `boolean` | No | Show unlock hints (default: false) |
|
|
2416
|
+
| `allAchievements` | `AchievementWithStatus[]` | No* | All achievements with status (*required if `showAllAchievements`) |
|
|
2417
|
+
| `icons` | `Record<string, string>` | No | Custom icon mapping |
|
|
2418
|
+
| `style` | `React.CSSProperties` | No | Custom styles |
|
|
2419
|
+
|
|
2420
|
+
**Example (unlocked only):**
|
|
2421
|
+
```tsx
|
|
2422
|
+
<BadgesModal
|
|
2423
|
+
isOpen={isOpen}
|
|
2424
|
+
onClose={() => setIsOpen(false)}
|
|
2425
|
+
achievements={achievements.unlocked}
|
|
2426
|
+
/>
|
|
2427
|
+
```
|
|
2428
|
+
|
|
2429
|
+
**Example (all achievements):**
|
|
2430
|
+
```tsx
|
|
2431
|
+
const { getAllAchievements } = useAchievements();
|
|
2432
|
+
|
|
2433
|
+
<BadgesModal
|
|
2434
|
+
isOpen={isOpen}
|
|
2435
|
+
onClose={() => setIsOpen(false)}
|
|
2436
|
+
showAllAchievements={true}
|
|
2437
|
+
allAchievements={getAllAchievements()} // Required!
|
|
2438
|
+
/>
|
|
2439
|
+
```
|
|
1630
2440
|
|
|
1631
2441
|
## Advanced: Complex API
|
|
1632
2442
|
|