react-achievements 3.6.0 → 3.6.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 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
- <p align="center">
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
- Returns an object with:
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
- - `update`: Function to update achievement metrics
1628
- - `achievements`: Object containing unlocked and locked achievements
1629
- - `reset`: Function to reset achievement storage
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