react-achievements 4.1.1 → 4.3.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 +6 -5
- package/dist/headless.cjs +37 -50
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.ts +11 -10
- package/dist/headless.esm.js +37 -50
- package/dist/headless.esm.js.map +1 -1
- package/dist/index.cjs +223 -128
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +36 -16
- package/dist/index.esm.js +224 -129
- package/dist/index.esm.js.map +1 -1
- package/dist/web.cjs +223 -128
- package/dist/web.cjs.map +1 -1
- package/dist/web.d.ts +36 -16
- package/dist/web.esm.js +224 -129
- package/dist/web.esm.js.map +1 -1
- package/package.json +4 -4
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AchievementEngine } from 'achievements-engine';
|
|
2
2
|
export { AchievementBuilder, AchievementEngine, AchievementError, AsyncStorageAdapter, ConfigurationError, ImportValidationError, IndexedDBStorage, LocalStorage, MemoryStorage, OfflineQueueStorage, RestApiStorage, StorageError, StorageQuotaError, StorageType, SyncError, createConfigHash, exportAchievementData, importAchievementData, isAchievementError, isRecoverableError, isSimpleConfig, normalizeAchievements } from 'achievements-engine';
|
|
3
|
-
import React, { createContext, useState, useCallback, useEffect, useContext,
|
|
3
|
+
import React, { createContext, useState, useCallback, useEffect, useContext, useRef, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
// Type guard to detect async storage
|
|
6
6
|
function isAsyncStorage(storage) {
|
|
@@ -63,15 +63,12 @@ function warnDeprecation(message) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const AchievementContext = createContext(undefined);
|
|
66
|
-
const getAllAchievementRecord = (
|
|
67
|
-
return Object.fromEntries(
|
|
68
|
-
achievement.achievementId,
|
|
69
|
-
achievement,
|
|
70
|
-
]));
|
|
66
|
+
const getAllAchievementRecord = (achievements) => {
|
|
67
|
+
return Object.fromEntries(achievements.map((achievement) => [achievement.achievementId, achievement]));
|
|
71
68
|
};
|
|
72
69
|
const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'local', children, onError, useBuiltInUI, restApiConfig, engine: externalEngine, eventMapping, icons = {}, }) => {
|
|
73
70
|
if (useBuiltInUI !== undefined) {
|
|
74
|
-
warnDeprecation('`useBuiltInUI` is deprecated and is now a no-op because built-in UI is the default. It will be removed in
|
|
71
|
+
warnDeprecation('`useBuiltInUI` is deprecated and is now a no-op because built-in UI is the default. It will be removed in 5.0.');
|
|
75
72
|
}
|
|
76
73
|
if (achievementsConfig && externalEngine) {
|
|
77
74
|
throw new Error('Cannot provide both "achievements" and "engine" props to AchievementProvider.\n\n' +
|
|
@@ -97,15 +94,9 @@ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'lo
|
|
|
97
94
|
eventMapping,
|
|
98
95
|
});
|
|
99
96
|
});
|
|
100
|
-
const [
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}));
|
|
104
|
-
const syncAchievementState = useCallback(() => {
|
|
105
|
-
setAchievementState({
|
|
106
|
-
unlocked: [...engine.getUnlocked()],
|
|
107
|
-
all: getAllAchievementRecord(engine),
|
|
108
|
-
});
|
|
97
|
+
const [achievementSnapshot, setAchievementSnapshot] = useState(() => engine.getSnapshot());
|
|
98
|
+
const syncAchievementState = useCallback((snapshot) => {
|
|
99
|
+
setAchievementSnapshot(snapshot || engine.getSnapshot());
|
|
109
100
|
}, [engine]);
|
|
110
101
|
useEffect(() => {
|
|
111
102
|
return () => {
|
|
@@ -115,10 +106,17 @@ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'lo
|
|
|
115
106
|
};
|
|
116
107
|
}, [engine, externalEngine]);
|
|
117
108
|
useEffect(() => {
|
|
118
|
-
|
|
119
|
-
const unsubscribeStateChanged = engine.on('state:changed',
|
|
109
|
+
let isMounted = true;
|
|
110
|
+
const unsubscribeStateChanged = engine.on('state:changed', (event) => {
|
|
111
|
+
syncAchievementState(event);
|
|
112
|
+
});
|
|
113
|
+
engine.ready().then(() => {
|
|
114
|
+
if (isMounted) {
|
|
115
|
+
syncAchievementState();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
120
118
|
return () => {
|
|
121
|
-
|
|
119
|
+
isMounted = false;
|
|
122
120
|
unsubscribeStateChanged();
|
|
123
121
|
};
|
|
124
122
|
}, [engine, syncAchievementState]);
|
|
@@ -127,18 +125,12 @@ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'lo
|
|
|
127
125
|
};
|
|
128
126
|
const reset = () => {
|
|
129
127
|
engine.reset();
|
|
130
|
-
syncAchievementState();
|
|
131
128
|
};
|
|
132
129
|
const getState = () => {
|
|
133
|
-
const
|
|
134
|
-
const unlocked = engine.getUnlocked();
|
|
135
|
-
const metricsInArrayFormat = {};
|
|
136
|
-
Object.entries(metrics).forEach(([key, value]) => {
|
|
137
|
-
metricsInArrayFormat[key] = Array.isArray(value) ? value : [value];
|
|
138
|
-
});
|
|
130
|
+
const snapshot = engine.getSnapshot();
|
|
139
131
|
return {
|
|
140
|
-
metrics:
|
|
141
|
-
unlocked:
|
|
132
|
+
metrics: snapshot.metrics,
|
|
133
|
+
unlocked: snapshot.unlockedIds,
|
|
142
134
|
};
|
|
143
135
|
};
|
|
144
136
|
const exportData = () => {
|
|
@@ -150,11 +142,16 @@ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'lo
|
|
|
150
142
|
return result;
|
|
151
143
|
};
|
|
152
144
|
const getAllAchievements = () => {
|
|
153
|
-
return engine.
|
|
145
|
+
return engine.getSnapshot().allAchievements;
|
|
146
|
+
};
|
|
147
|
+
const achievements = {
|
|
148
|
+
unlocked: achievementSnapshot.unlockedIds,
|
|
149
|
+
all: getAllAchievementRecord(achievementSnapshot.allAchievements),
|
|
154
150
|
};
|
|
155
151
|
return (React.createElement(AchievementContext.Provider, { value: {
|
|
156
152
|
update,
|
|
157
|
-
achievements
|
|
153
|
+
achievements,
|
|
154
|
+
snapshot: achievementSnapshot,
|
|
158
155
|
reset,
|
|
159
156
|
getState,
|
|
160
157
|
exportData,
|
|
@@ -319,24 +316,36 @@ const BuiltInNotification = ({ achievement, onClose, duration = 5000, position =
|
|
|
319
316
|
var _a, _b, _c;
|
|
320
317
|
const [isVisible, setIsVisible] = useState(false);
|
|
321
318
|
const [isExiting, setIsExiting] = useState(false);
|
|
319
|
+
const onCloseRef = useRef(onClose);
|
|
320
|
+
const exitTimerRef = useRef(null);
|
|
322
321
|
// Merge custom icons with defaults
|
|
323
322
|
const mergedIcons = Object.assign(Object.assign({}, defaultAchievementIcons), icons);
|
|
324
323
|
// Get theme configuration
|
|
325
324
|
const themeConfig = getTheme(theme) || builtInThemes.modern;
|
|
326
325
|
const { notification: themeStyles } = themeConfig;
|
|
326
|
+
useEffect(() => {
|
|
327
|
+
onCloseRef.current = onClose;
|
|
328
|
+
}, [onClose]);
|
|
329
|
+
const closeAfterExit = useCallback(() => {
|
|
330
|
+
setIsExiting(true);
|
|
331
|
+
if (exitTimerRef.current) {
|
|
332
|
+
clearTimeout(exitTimerRef.current);
|
|
333
|
+
}
|
|
334
|
+
exitTimerRef.current = setTimeout(() => { var _a; return (_a = onCloseRef.current) === null || _a === void 0 ? void 0 : _a.call(onCloseRef); }, 300);
|
|
335
|
+
}, []);
|
|
327
336
|
useEffect(() => {
|
|
328
337
|
// Slide in animation
|
|
329
338
|
const showTimer = setTimeout(() => setIsVisible(true), 10);
|
|
330
339
|
// Auto-dismiss
|
|
331
|
-
const dismissTimer = setTimeout(
|
|
332
|
-
setIsExiting(true);
|
|
333
|
-
setTimeout(() => onClose === null || onClose === void 0 ? void 0 : onClose(), 300);
|
|
334
|
-
}, duration);
|
|
340
|
+
const dismissTimer = setTimeout(closeAfterExit, duration);
|
|
335
341
|
return () => {
|
|
336
342
|
clearTimeout(showTimer);
|
|
337
343
|
clearTimeout(dismissTimer);
|
|
344
|
+
if (exitTimerRef.current) {
|
|
345
|
+
clearTimeout(exitTimerRef.current);
|
|
346
|
+
}
|
|
338
347
|
};
|
|
339
|
-
}, [duration,
|
|
348
|
+
}, [duration, closeAfterExit]);
|
|
340
349
|
const getPositionStyles = () => {
|
|
341
350
|
const stackedOffset = 20 + stackIndex * 104;
|
|
342
351
|
const base = {
|
|
@@ -424,10 +433,7 @@ const BuiltInNotification = ({ achievement, onClose, duration = 5000, position =
|
|
|
424
433
|
React.createElement("div", { style: headerStyles }, "Achievement Unlocked!"),
|
|
425
434
|
React.createElement("div", { style: titleStyles }, achievement.achievementTitle),
|
|
426
435
|
achievement.achievementDescription && (React.createElement("div", { style: descriptionStyles }, achievement.achievementDescription))),
|
|
427
|
-
React.createElement("button", { onClick: () =>
|
|
428
|
-
setIsExiting(true);
|
|
429
|
-
setTimeout(() => onClose === null || onClose === void 0 ? void 0 : onClose(), 300);
|
|
430
|
-
}, style: closeButtonStyles, onMouseEnter: (e) => (e.currentTarget.style.opacity = '1'), onMouseLeave: (e) => (e.currentTarget.style.opacity = '0.6'), "aria-label": "Close notification" }, "\u00D7")));
|
|
436
|
+
React.createElement("button", { onClick: closeAfterExit, style: closeButtonStyles, onMouseEnter: (e) => (e.currentTarget.style.opacity = '1'), onMouseLeave: (e) => (e.currentTarget.style.opacity = '0.6'), "aria-label": "Close notification" }, "\u00D7")));
|
|
431
437
|
};
|
|
432
438
|
|
|
433
439
|
/**
|
|
@@ -537,17 +543,20 @@ const BuiltInConfetti = ({ show, duration = 5000, particleCount = 50, colors = [
|
|
|
537
543
|
React.createElement("div", { style: containerStyles, "data-testid": "built-in-confetti" }, particles)));
|
|
538
544
|
};
|
|
539
545
|
|
|
540
|
-
const
|
|
546
|
+
const DEFAULT_NOTIFICATION_DURATION_MS = 5000;
|
|
547
|
+
const CONFETTI_DURATION_MS = 5000;
|
|
541
548
|
const AchievementUIContext = createContext({
|
|
542
549
|
icons: {},
|
|
543
550
|
ui: {},
|
|
544
551
|
});
|
|
545
552
|
const AchievementEffects = ({ icons, ui }) => {
|
|
553
|
+
var _a;
|
|
546
554
|
const engine = useAchievementEngine();
|
|
547
555
|
const seenAchievementsRef = useRef(new Set(engine.getUnlocked()));
|
|
548
556
|
const confettiTimerRef = useRef(null);
|
|
549
557
|
const [showConfetti, setShowConfetti] = useState(false);
|
|
550
558
|
const [notifications, setNotifications] = useState([]);
|
|
559
|
+
const notificationDuration = (_a = ui.notificationDuration) !== null && _a !== void 0 ? _a : DEFAULT_NOTIFICATION_DURATION_MS;
|
|
551
560
|
useEffect(() => {
|
|
552
561
|
const unsubscribeUnlocked = engine.on('achievement:unlocked', (event) => {
|
|
553
562
|
if (seenAchievementsRef.current.has(event.achievementId)) {
|
|
@@ -577,7 +586,7 @@ const AchievementEffects = ({ icons, ui }) => {
|
|
|
577
586
|
confettiTimerRef.current = setTimeout(() => {
|
|
578
587
|
setShowConfetti(false);
|
|
579
588
|
confettiTimerRef.current = null;
|
|
580
|
-
},
|
|
589
|
+
}, CONFETTI_DURATION_MS);
|
|
581
590
|
}
|
|
582
591
|
});
|
|
583
592
|
const unsubscribeStateChanged = engine.on('state:changed', () => {
|
|
@@ -600,13 +609,13 @@ const AchievementEffects = ({ icons, ui }) => {
|
|
|
600
609
|
const ConfettiComponentResolved = ui.ConfettiComponent || BuiltInConfetti;
|
|
601
610
|
return (React.createElement(React.Fragment, null,
|
|
602
611
|
ui.enableNotifications !== false &&
|
|
603
|
-
notifications.map((notification, index) => (React.createElement(NotificationComponent, { key: notification.achievementId, achievement: notification, onClose: () => setNotifications((currentNotifications) => currentNotifications.filter((currentNotification) => currentNotification.achievementId !== notification.achievementId)), duration:
|
|
604
|
-
ui.enableConfetti !== false && (React.createElement(ConfettiComponentResolved, { show: showConfetti, duration:
|
|
612
|
+
notifications.map((notification, index) => (React.createElement(NotificationComponent, { key: notification.achievementId, achievement: notification, onClose: () => setNotifications((currentNotifications) => currentNotifications.filter((currentNotification) => currentNotification.achievementId !== notification.achievementId)), duration: notificationDuration, position: ui.notificationPosition || 'top-center', theme: ui.theme || 'modern', icons: icons, stackIndex: index }))),
|
|
613
|
+
ui.enableConfetti !== false && (React.createElement(ConfettiComponentResolved, { show: showConfetti, duration: CONFETTI_DURATION_MS }))));
|
|
605
614
|
};
|
|
606
615
|
const AchievementProvider = (_a) => {
|
|
607
616
|
var { children, icons = {}, ui = {}, useBuiltInUI } = _a, providerProps = __rest(_a, ["children", "icons", "ui", "useBuiltInUI"]);
|
|
608
617
|
if (useBuiltInUI !== undefined) {
|
|
609
|
-
warnDeprecation('`useBuiltInUI` is deprecated and is now a no-op because built-in UI is the default. It will be removed in
|
|
618
|
+
warnDeprecation('`useBuiltInUI` is deprecated and is now a no-op because built-in UI is the default. It will be removed in 5.0.');
|
|
610
619
|
}
|
|
611
620
|
const uiContextValue = useMemo(() => ({ icons, ui }), [icons, ui]);
|
|
612
621
|
return (React.createElement(AchievementUIContext.Provider, { value: uiContextValue },
|
|
@@ -624,18 +633,14 @@ const useAchievements = () => {
|
|
|
624
633
|
};
|
|
625
634
|
|
|
626
635
|
const useAchievementState = () => {
|
|
627
|
-
const {
|
|
628
|
-
const allAchievements = getAllAchievements();
|
|
629
|
-
const unlockedIds = achievements.unlocked;
|
|
630
|
-
const unlockedAchievementSet = new Set(unlockedIds);
|
|
631
|
-
const unlockedAchievements = allAchievements.filter((achievement) => unlockedAchievementSet.has(achievement.achievementId));
|
|
636
|
+
const { snapshot } = useAchievements();
|
|
632
637
|
return {
|
|
633
|
-
unlockedIds,
|
|
634
|
-
unlockedAchievements,
|
|
635
|
-
allAchievements,
|
|
636
|
-
unlockedCount:
|
|
637
|
-
totalCount:
|
|
638
|
-
metrics:
|
|
638
|
+
unlockedIds: snapshot.unlockedIds,
|
|
639
|
+
unlockedAchievements: snapshot.unlockedAchievements,
|
|
640
|
+
allAchievements: snapshot.allAchievements,
|
|
641
|
+
unlockedCount: snapshot.unlockedCount,
|
|
642
|
+
totalCount: snapshot.totalCount,
|
|
643
|
+
metrics: snapshot.metrics,
|
|
639
644
|
};
|
|
640
645
|
};
|
|
641
646
|
|
|
@@ -776,13 +781,98 @@ const defaultStyles = {
|
|
|
776
781
|
},
|
|
777
782
|
};
|
|
778
783
|
|
|
784
|
+
const isNumericString = (value) => /^-?\d+(\.\d+)?$/.test(value);
|
|
785
|
+
const getBackdropBlurFilter = (backdropBlur) => {
|
|
786
|
+
if (backdropBlur === undefined) {
|
|
787
|
+
return undefined;
|
|
788
|
+
}
|
|
789
|
+
if (typeof backdropBlur === 'number') {
|
|
790
|
+
if (backdropBlur <= 0) {
|
|
791
|
+
return undefined;
|
|
792
|
+
}
|
|
793
|
+
return `blur(${backdropBlur}px)`;
|
|
794
|
+
}
|
|
795
|
+
const trimmedBlur = backdropBlur.trim();
|
|
796
|
+
if (!trimmedBlur ||
|
|
797
|
+
trimmedBlur === '0' ||
|
|
798
|
+
trimmedBlur === '0px' ||
|
|
799
|
+
trimmedBlur === 'none') {
|
|
800
|
+
return undefined;
|
|
801
|
+
}
|
|
802
|
+
const blurValue = isNumericString(trimmedBlur) ? `${trimmedBlur}px` : trimmedBlur;
|
|
803
|
+
return blurValue.startsWith('blur(') ? blurValue : `blur(${blurValue})`;
|
|
804
|
+
};
|
|
805
|
+
const getBackdropBlurStyles = (backdropBlur) => {
|
|
806
|
+
const backdropFilter = getBackdropBlurFilter(backdropBlur);
|
|
807
|
+
if (!backdropFilter) {
|
|
808
|
+
return {};
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
backdropFilter,
|
|
812
|
+
WebkitBackdropFilter: backdropFilter,
|
|
813
|
+
};
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const compactAchievementStyles = {
|
|
817
|
+
achievementList: {
|
|
818
|
+
display: 'grid',
|
|
819
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
|
|
820
|
+
gap: '10px',
|
|
821
|
+
},
|
|
822
|
+
achievementItem: {
|
|
823
|
+
display: 'flex',
|
|
824
|
+
flexDirection: 'column',
|
|
825
|
+
alignItems: 'center',
|
|
826
|
+
justifyContent: 'center',
|
|
827
|
+
gap: '6px',
|
|
828
|
+
padding: '12px 10px',
|
|
829
|
+
borderRadius: '8px',
|
|
830
|
+
aspectRatio: '1 / 1',
|
|
831
|
+
minHeight: '120px',
|
|
832
|
+
textAlign: 'center',
|
|
833
|
+
},
|
|
834
|
+
lockedAchievementItem: {
|
|
835
|
+
display: 'flex',
|
|
836
|
+
flexDirection: 'column',
|
|
837
|
+
alignItems: 'center',
|
|
838
|
+
justifyContent: 'center',
|
|
839
|
+
gap: '6px',
|
|
840
|
+
padding: '12px 10px',
|
|
841
|
+
borderRadius: '8px',
|
|
842
|
+
aspectRatio: '1 / 1',
|
|
843
|
+
minHeight: '120px',
|
|
844
|
+
textAlign: 'center',
|
|
845
|
+
},
|
|
846
|
+
achievementIcon: {
|
|
847
|
+
fontSize: '34px',
|
|
848
|
+
lineHeight: 1,
|
|
849
|
+
flexShrink: 0,
|
|
850
|
+
},
|
|
851
|
+
achievementTitle: {
|
|
852
|
+
margin: '0',
|
|
853
|
+
fontSize: '13px',
|
|
854
|
+
lineHeight: 1.2,
|
|
855
|
+
},
|
|
856
|
+
achievementDescription: {
|
|
857
|
+
margin: '0',
|
|
858
|
+
fontSize: '11px',
|
|
859
|
+
lineHeight: 1.25,
|
|
860
|
+
},
|
|
861
|
+
lockIcon: {
|
|
862
|
+
fontSize: '15px',
|
|
863
|
+
top: '8px',
|
|
864
|
+
right: '8px',
|
|
865
|
+
transform: 'none',
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
const getDensityStyles = (density) => (density === 'compact' ? compactAchievementStyles : {});
|
|
779
869
|
const resolveIcon = (achievement, icons) => {
|
|
780
870
|
return ((achievement.achievementIconKey && icons[achievement.achievementIconKey]) ||
|
|
781
871
|
achievement.achievementIconKey ||
|
|
782
872
|
icons.default ||
|
|
783
873
|
'⭐');
|
|
784
874
|
};
|
|
785
|
-
const AchievementsList = ({ achievements, showLocked = true, showUnlockConditions = false, icons = {}, styles = {}, emptyState, className, renderAchievement, }) => {
|
|
875
|
+
const AchievementsList = ({ achievements, showLocked = true, showUnlockConditions = false, icons = {}, styles = {}, emptyState, className, density = 'comfortable', renderAchievement, }) => {
|
|
786
876
|
const context = useContext(AchievementContext);
|
|
787
877
|
const uiContext = useContext(AchievementUIContext);
|
|
788
878
|
const mergedIcons = Object.assign(Object.assign(Object.assign(Object.assign({}, defaultAchievementIcons), context === null || context === void 0 ? void 0 : context.icons), uiContext.icons), icons);
|
|
@@ -793,36 +883,37 @@ const AchievementsList = ({ achievements, showLocked = true, showUnlockCondition
|
|
|
793
883
|
const achievementsToDisplay = showLocked
|
|
794
884
|
? sourceAchievements
|
|
795
885
|
: sourceAchievements.filter((achievement) => achievement.isUnlocked);
|
|
886
|
+
const densityStyles = getDensityStyles(density);
|
|
796
887
|
if (achievementsToDisplay.length === 0) {
|
|
797
888
|
return (React.createElement("p", { style: { textAlign: 'center', color: '#666' } }, emptyState || 'No achievements configured.'));
|
|
798
889
|
}
|
|
799
|
-
return (React.createElement("div", { className: className, style: Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementList), styles === null || styles === void 0 ? void 0 : styles.achievementList), "data-testid": "achievements-list" }, achievementsToDisplay.map((achievement, index) => {
|
|
890
|
+
return (React.createElement("div", { className: className, style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementList), densityStyles === null || densityStyles === void 0 ? void 0 : densityStyles.achievementList), styles === null || styles === void 0 ? void 0 : styles.achievementList), "data-density": density, "data-testid": "achievements-list" }, achievementsToDisplay.map((achievement, index) => {
|
|
800
891
|
const isLocked = !achievement.isUnlocked;
|
|
801
892
|
const icon = resolveIcon(achievement, mergedIcons);
|
|
802
893
|
if (renderAchievement) {
|
|
803
|
-
return (React.createElement(React.Fragment, { key: achievement.achievementId }, renderAchievement({ achievement, isLocked, icon, index })));
|
|
894
|
+
return (React.createElement(React.Fragment, { key: achievement.achievementId }, renderAchievement({ achievement, isLocked, icon, index, density })));
|
|
804
895
|
}
|
|
805
896
|
return (React.createElement("div", { key: achievement.achievementId, style: Object.assign(Object.assign(Object.assign({}, (isLocked
|
|
806
|
-
? Object.assign(Object.assign({}, defaultStyles.badgesModal.lockedAchievementItem), styles === null || styles === void 0 ? void 0 : styles.lockedAchievementItem) : defaultStyles.badgesModal.achievementItem)), styles === null || styles === void 0 ? void 0 : styles.achievementItem), { position: 'relative' }), "data-testid": "achievement-list-item", "data-unlocked": achievement.isUnlocked ? 'true' : 'false' },
|
|
807
|
-
React.createElement("div", { style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementIcon), styles === null || styles === void 0 ? void 0 : styles.achievementIcon), { opacity: isLocked ? 0.4 : 1 }) }, icon),
|
|
808
|
-
React.createElement("div", { style: { flex: 1 } },
|
|
809
|
-
React.createElement("h3", { style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementTitle), styles === null || styles === void 0 ? void 0 : styles.achievementTitle), { color: isLocked ? '#999' : undefined }) }, achievement.achievementTitle),
|
|
810
|
-
achievement.achievementDescription && (React.createElement("p", { style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementDescription), styles === null || styles === void 0 ? void 0 : styles.achievementDescription), { color: isLocked ? '#aaa' : '#666' }) },
|
|
897
|
+
? Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.lockedAchievementItem), densityStyles === null || densityStyles === void 0 ? void 0 : densityStyles.lockedAchievementItem), styles === null || styles === void 0 ? void 0 : styles.lockedAchievementItem) : Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementItem), densityStyles === null || densityStyles === void 0 ? void 0 : densityStyles.achievementItem))), styles === null || styles === void 0 ? void 0 : styles.achievementItem), { position: 'relative' }), "data-testid": "achievement-list-item", "data-unlocked": achievement.isUnlocked ? 'true' : 'false' },
|
|
898
|
+
React.createElement("div", { style: Object.assign(Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementIcon), densityStyles === null || densityStyles === void 0 ? void 0 : densityStyles.achievementIcon), styles === null || styles === void 0 ? void 0 : styles.achievementIcon), { opacity: isLocked ? 0.4 : 1 }) }, icon),
|
|
899
|
+
React.createElement("div", { style: density === 'compact' ? { width: '100%', minWidth: 0 } : { flex: 1 } },
|
|
900
|
+
React.createElement("h3", { style: Object.assign(Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementTitle), densityStyles === null || densityStyles === void 0 ? void 0 : densityStyles.achievementTitle), styles === null || styles === void 0 ? void 0 : styles.achievementTitle), { color: isLocked ? '#999' : undefined }) }, achievement.achievementTitle),
|
|
901
|
+
achievement.achievementDescription && (React.createElement("p", { style: Object.assign(Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementDescription), densityStyles === null || densityStyles === void 0 ? void 0 : densityStyles.achievementDescription), styles === null || styles === void 0 ? void 0 : styles.achievementDescription), { color: isLocked ? '#aaa' : '#666' }) },
|
|
811
902
|
achievement.achievementDescription,
|
|
812
903
|
showUnlockConditions && isLocked && (React.createElement("span", { style: {
|
|
813
904
|
display: 'block',
|
|
814
|
-
fontSize: '12px',
|
|
815
|
-
marginTop: '4px',
|
|
905
|
+
fontSize: density === 'compact' ? '11px' : '12px',
|
|
906
|
+
marginTop: density === 'compact' ? '2px' : '4px',
|
|
816
907
|
fontStyle: 'italic',
|
|
817
908
|
color: '#888',
|
|
818
909
|
} },
|
|
819
910
|
"\uD83D\uDD13 ",
|
|
820
911
|
achievement.achievementDescription))))),
|
|
821
|
-
isLocked && (React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.lockIcon), styles === null || styles === void 0 ? void 0 : styles.lockIcon) }, "\uD83D\uDD12"))));
|
|
912
|
+
isLocked && (React.createElement("div", { style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.lockIcon), densityStyles === null || densityStyles === void 0 ? void 0 : densityStyles.lockIcon), styles === null || styles === void 0 ? void 0 : styles.lockIcon) }, "\uD83D\uDD12"))));
|
|
822
913
|
})));
|
|
823
914
|
};
|
|
824
915
|
|
|
825
|
-
const AchievementsModal = ({ isOpen, onClose, achievements, title = '🏆 Achievements', styles = {}, icons = {}, showLocked = true, showUnlockConditions = false, emptyState, renderAchievement, theme, }) => {
|
|
916
|
+
const AchievementsModal = ({ isOpen, onClose, achievements, title = '🏆 Achievements', styles = {}, icons = {}, showLocked = true, showUnlockConditions = false, emptyState, renderAchievement, theme, hideScrollbar = false, density = 'comfortable', backdropBlur, }) => {
|
|
826
917
|
const context = useContext(AchievementContext);
|
|
827
918
|
const uiContext = useContext(AchievementUIContext);
|
|
828
919
|
useEffect(() => {
|
|
@@ -850,19 +941,30 @@ const AchievementsModal = ({ isOpen, onClose, achievements, title = '🏆 Achiev
|
|
|
850
941
|
? sourceAchievements
|
|
851
942
|
: sourceAchievements === null || sourceAchievements === void 0 ? void 0 : sourceAchievements.filter((achievement) => achievement.isUnlocked);
|
|
852
943
|
const resolvedTheme = theme || uiContext.ui.theme || 'modern';
|
|
944
|
+
const backdropBlurFilter = getBackdropBlurFilter(backdropBlur);
|
|
853
945
|
const mergedIcons = Object.assign(Object.assign(Object.assign(Object.assign({}, defaultAchievementIcons), context === null || context === void 0 ? void 0 : context.icons), uiContext.icons), icons);
|
|
854
946
|
if (CustomModal) {
|
|
855
947
|
if (!modalAchievements) {
|
|
856
948
|
throw new Error('AchievementsModal requires either an achievements prop or an AchievementProvider parent.');
|
|
857
949
|
}
|
|
858
|
-
return (React.createElement(CustomModal, { isOpen: isOpen, onClose: onClose, achievements: modalAchievements, icons: mergedIcons, theme: resolvedTheme }));
|
|
950
|
+
return (React.createElement(CustomModal, { isOpen: isOpen, onClose: onClose, achievements: modalAchievements, icons: mergedIcons, theme: resolvedTheme, hideScrollbar: hideScrollbar, density: density, backdropBlur: backdropBlur }));
|
|
859
951
|
}
|
|
860
|
-
return (React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.overlay), styles === null || styles === void 0 ? void 0 : styles.overlay), role: "presentation", onClick: onClose, "data-testid": "achievements-modal-overlay" },
|
|
861
|
-
React.createElement("
|
|
952
|
+
return (React.createElement("div", { style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.overlay), styles === null || styles === void 0 ? void 0 : styles.overlay), getBackdropBlurStyles(backdropBlur)), role: "presentation", onClick: onClose, "data-backdrop-blur": backdropBlurFilter, "data-testid": "achievements-modal-overlay" },
|
|
953
|
+
hideScrollbar && (React.createElement("style", null, `
|
|
954
|
+
[data-react-achievements-modal-content][data-hide-scrollbar="true"] {
|
|
955
|
+
scrollbar-width: none;
|
|
956
|
+
-ms-overflow-style: none;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
[data-react-achievements-modal-content][data-hide-scrollbar="true"]::-webkit-scrollbar {
|
|
960
|
+
display: none;
|
|
961
|
+
}
|
|
962
|
+
`)),
|
|
963
|
+
React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.content), styles === null || styles === void 0 ? void 0 : styles.content), role: "dialog", "aria-modal": "true", "aria-labelledby": "achievements-modal-title", onClick: (event) => event.stopPropagation(), "data-hide-scrollbar": hideScrollbar ? 'true' : undefined, "data-density": density, "data-react-achievements-modal-content": true, "data-testid": "achievements-modal" },
|
|
862
964
|
React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.header), styles === null || styles === void 0 ? void 0 : styles.header) },
|
|
863
965
|
React.createElement("h2", { id: "achievements-modal-title", style: { margin: 0 } }, title),
|
|
864
966
|
React.createElement("button", { onClick: onClose, style: Object.assign(Object.assign({}, defaultStyles.badgesModal.closeButton), styles === null || styles === void 0 ? void 0 : styles.closeButton), "aria-label": "Close" }, "\u00D7")),
|
|
865
|
-
React.createElement(AchievementsList, { achievements: modalAchievements, showLocked: showLocked, showUnlockConditions: showUnlockConditions, icons: icons, styles: styles, emptyState: emptyState, renderAchievement: renderAchievement }))));
|
|
967
|
+
React.createElement(AchievementsList, { achievements: modalAchievements, showLocked: showLocked, showUnlockConditions: showUnlockConditions, icons: icons, styles: styles, emptyState: emptyState, renderAchievement: renderAchievement, density: density }))));
|
|
866
968
|
};
|
|
867
969
|
|
|
868
970
|
const getPositionStyles$1 = (position) => {
|
|
@@ -882,7 +984,7 @@ const getPositionStyles$1 = (position) => {
|
|
|
882
984
|
return Object.assign(Object.assign({}, base), { bottom: 0, right: 0 });
|
|
883
985
|
}
|
|
884
986
|
};
|
|
885
|
-
const AchievementsWidget = ({ position = 'bottom-right', placement = 'fixed', showAllAchievements = true, showUnlockConditions = false, showCount = true, icons, theme, label = 'Achievements', icon = '🏆', triggerClassName, renderTrigger, buttonStyles, modalStyles, modalTitle, emptyState, renderAchievement, }) => {
|
|
987
|
+
const AchievementsWidget = ({ position = 'bottom-right', placement = 'fixed', showAllAchievements = true, showUnlockConditions = false, showCount = true, icons, theme, density = 'comfortable', label = 'Achievements', icon = '🏆', triggerClassName, renderTrigger, buttonStyles, modalStyles, modalTitle, emptyState, renderAchievement, hideModalScrollbar = false, modalBackdropBlur, }) => {
|
|
886
988
|
const uiContext = useContext(AchievementUIContext);
|
|
887
989
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
888
990
|
const { unlockedAchievements, allAchievements, unlockedCount, totalCount } = useAchievementState();
|
|
@@ -922,7 +1024,7 @@ const AchievementsWidget = ({ position = 'bottom-right', placement = 'fixed', sh
|
|
|
922
1024
|
React.createElement("span", null, icon),
|
|
923
1025
|
React.createElement("span", { style: { flex: 1 } }, label),
|
|
924
1026
|
showCount && React.createElement("span", null, unlockedCount))),
|
|
925
|
-
React.createElement(AchievementsModal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), achievements: modalAchievements, showUnlockConditions: showUnlockConditions, icons: icons, styles: modalStyles, title: modalTitle, emptyState: emptyState, renderAchievement: renderAchievement, theme: resolvedTheme })));
|
|
1027
|
+
React.createElement(AchievementsModal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), achievements: modalAchievements, showUnlockConditions: showUnlockConditions, icons: icons, styles: modalStyles, title: modalTitle, emptyState: emptyState, renderAchievement: renderAchievement, theme: resolvedTheme, hideScrollbar: hideModalScrollbar, density: density, backdropBlur: modalBackdropBlur })));
|
|
926
1028
|
};
|
|
927
1029
|
|
|
928
1030
|
const getPositionStyles = (position) => {
|
|
@@ -944,11 +1046,11 @@ const getPositionStyles = (position) => {
|
|
|
944
1046
|
};
|
|
945
1047
|
/**
|
|
946
1048
|
* @deprecated Use `AchievementsWidget` for new integrations. This v3
|
|
947
|
-
* compatibility wrapper will be removed in
|
|
1049
|
+
* compatibility wrapper will be removed in 5.0.
|
|
948
1050
|
*/
|
|
949
1051
|
const BadgesButton = ({ onClick, position = 'bottom-right', placement = 'fixed', styles = {}, unlockedAchievements, theme = 'modern', }) => {
|
|
950
1052
|
React.useEffect(() => {
|
|
951
|
-
warnDeprecation('`BadgesButton` is deprecated. Use `AchievementsWidget` instead. `BadgesButton` will be removed in
|
|
1053
|
+
warnDeprecation('`BadgesButton` is deprecated. Use `AchievementsWidget` instead. `BadgesButton` will be removed in 5.0.');
|
|
952
1054
|
}, []);
|
|
953
1055
|
// Get theme configuration for consistent styling
|
|
954
1056
|
const themeConfig = getTheme(theme) || builtInThemes.modern;
|
|
@@ -985,11 +1087,11 @@ const BadgesButton = ({ onClick, position = 'bottom-right', placement = 'fixed',
|
|
|
985
1087
|
/**
|
|
986
1088
|
* @deprecated Use `AchievementsModal`, `AchievementsWidget`, or
|
|
987
1089
|
* `AchievementsList` for new integrations. This v3 compatibility wrapper will
|
|
988
|
-
* be removed in
|
|
1090
|
+
* be removed in 5.0.
|
|
989
1091
|
*/
|
|
990
1092
|
const BadgesModal = ({ isOpen, onClose, achievements, styles = {}, icons = {}, showAllAchievements = false, showUnlockConditions = false, allAchievements, }) => {
|
|
991
1093
|
useEffect(() => {
|
|
992
|
-
warnDeprecation('`BadgesModal` is deprecated. Use `AchievementsWidget` or `AchievementsList` instead. `BadgesModal` will be removed in
|
|
1094
|
+
warnDeprecation('`BadgesModal` is deprecated. Use `AchievementsWidget` or `AchievementsList` instead. `BadgesModal` will be removed in 5.0.');
|
|
993
1095
|
}, []);
|
|
994
1096
|
const achievementsToDisplay = showAllAchievements && allAchievements
|
|
995
1097
|
? allAchievements
|
|
@@ -999,7 +1101,7 @@ const BadgesModal = ({ isOpen, onClose, achievements, styles = {}, icons = {}, s
|
|
|
999
1101
|
|
|
1000
1102
|
/**
|
|
1001
1103
|
* @deprecated Use `AchievementsWidget` for new integrations. This v3
|
|
1002
|
-
* compatibility wrapper will be removed in
|
|
1104
|
+
* compatibility wrapper will be removed in 5.0.
|
|
1003
1105
|
*/
|
|
1004
1106
|
const BadgesButtonWithModal = ({ unlockedAchievements, position = 'bottom-right', placement = 'fixed', showAllAchievements = false, allAchievements, showUnlockConditions = false, icons, theme = 'modern', buttonStyles, modalStyles, }) => {
|
|
1005
1107
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
@@ -1010,11 +1112,11 @@ const BadgesButtonWithModal = ({ unlockedAchievements, position = 'bottom-right'
|
|
|
1010
1112
|
|
|
1011
1113
|
/**
|
|
1012
1114
|
* @deprecated Use the provider `ui.ConfettiComponent` option or the built-in
|
|
1013
|
-
* confetti default. This v3 compatibility wrapper will be removed in
|
|
1115
|
+
* confetti default. This v3 compatibility wrapper will be removed in 5.0.
|
|
1014
1116
|
*/
|
|
1015
1117
|
const ConfettiWrapper = ({ show }) => {
|
|
1016
1118
|
useEffect(() => {
|
|
1017
|
-
warnDeprecation('`ConfettiWrapper` is deprecated. Use the provider `ui.ConfettiComponent` option or built-in confetti defaults instead. `ConfettiWrapper` will be removed in
|
|
1119
|
+
warnDeprecation('`ConfettiWrapper` is deprecated. Use the provider `ui.ConfettiComponent` option or built-in confetti defaults instead. `ConfettiWrapper` will be removed in 5.0.');
|
|
1018
1120
|
}, []);
|
|
1019
1121
|
return React.createElement(BuiltInConfetti, { show: show, particleCount: 200 });
|
|
1020
1122
|
};
|
|
@@ -1049,17 +1151,11 @@ const LevelProgress = ({ level, currentXP, nextLevelXP, label = 'Level', valueLa
|
|
|
1049
1151
|
* Provides the v4 happy path for direct metric updates plus explicit state names.
|
|
1050
1152
|
*/
|
|
1051
1153
|
const useSimpleAchievements = () => {
|
|
1052
|
-
const { update, reset, getState, exportData, importData } = useAchievements();
|
|
1154
|
+
const { update, reset, getState, exportData, importData, engine } = useAchievements();
|
|
1053
1155
|
const achievementState = useAchievementState();
|
|
1054
1156
|
const track = (metric, value) => update({ [metric]: value });
|
|
1055
1157
|
const increment = (metric, amount = 1) => {
|
|
1056
|
-
|
|
1057
|
-
const currentMetricArray = currentState.metrics[metric] || [0];
|
|
1058
|
-
const currentValue = Array.isArray(currentMetricArray)
|
|
1059
|
-
? currentMetricArray[0]
|
|
1060
|
-
: currentMetricArray;
|
|
1061
|
-
const newValue = (typeof currentValue === 'number' ? currentValue : 0) + amount;
|
|
1062
|
-
update({ [metric]: newValue });
|
|
1158
|
+
return engine.increment(metric, amount);
|
|
1063
1159
|
};
|
|
1064
1160
|
const trackMultiple = (metrics) => update(metrics);
|
|
1065
1161
|
return {
|
|
@@ -1078,11 +1174,11 @@ const useSimpleAchievements = () => {
|
|
|
1078
1174
|
importData,
|
|
1079
1175
|
getAllAchievements: () => achievementState.allAchievements,
|
|
1080
1176
|
/**
|
|
1081
|
-
* @deprecated Use `unlockedIds` instead. This alias will be removed in
|
|
1177
|
+
* @deprecated Use `unlockedIds` instead. This alias will be removed in 5.0.
|
|
1082
1178
|
*/
|
|
1083
1179
|
unlocked: achievementState.unlockedIds,
|
|
1084
1180
|
/**
|
|
1085
|
-
* @deprecated Use `allAchievements` instead. This alias will be removed in
|
|
1181
|
+
* @deprecated Use `allAchievements` instead. This alias will be removed in 5.0.
|
|
1086
1182
|
*/
|
|
1087
1183
|
all: achievementState.allAchievements,
|
|
1088
1184
|
};
|
|
@@ -1092,12 +1188,14 @@ const useSimpleAchievements = () => {
|
|
|
1092
1188
|
* Built-in modal component
|
|
1093
1189
|
* Modern, theme-aware achievement modal with smooth animations
|
|
1094
1190
|
*/
|
|
1095
|
-
const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'modern', }) => {
|
|
1191
|
+
const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'modern', hideScrollbar = false, density = 'comfortable', backdropBlur, }) => {
|
|
1096
1192
|
// Merge custom icons with defaults
|
|
1097
1193
|
const mergedIcons = Object.assign(Object.assign({}, defaultAchievementIcons), icons);
|
|
1098
1194
|
// Get theme configuration
|
|
1099
1195
|
const themeConfig = getTheme(theme) || builtInThemes.modern;
|
|
1100
1196
|
const { modal: themeStyles } = themeConfig;
|
|
1197
|
+
const isCompact = density === 'compact';
|
|
1198
|
+
const backdropBlurFilter = getBackdropBlurFilter(backdropBlur);
|
|
1101
1199
|
useEffect(() => {
|
|
1102
1200
|
if (isOpen) {
|
|
1103
1201
|
// Lock body scroll when modal is open
|
|
@@ -1113,24 +1211,12 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1113
1211
|
}, [isOpen]);
|
|
1114
1212
|
if (!isOpen)
|
|
1115
1213
|
return null;
|
|
1116
|
-
const overlayStyles = {
|
|
1117
|
-
position: 'fixed',
|
|
1118
|
-
top: 0,
|
|
1119
|
-
left: 0,
|
|
1120
|
-
right: 0,
|
|
1121
|
-
bottom: 0,
|
|
1122
|
-
backgroundColor: themeStyles.overlayColor,
|
|
1123
|
-
display: 'flex',
|
|
1124
|
-
alignItems: 'center',
|
|
1125
|
-
justifyContent: 'center',
|
|
1126
|
-
zIndex: 10000,
|
|
1127
|
-
animation: 'fadeIn 0.3s ease-in-out',
|
|
1128
|
-
};
|
|
1214
|
+
const overlayStyles = Object.assign({ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: themeStyles.overlayColor, display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 10000, animation: 'fadeIn 0.3s ease-in-out' }, getBackdropBlurStyles(backdropBlur));
|
|
1129
1215
|
const modalStyles = {
|
|
1130
1216
|
background: themeStyles.background,
|
|
1131
1217
|
borderRadius: themeStyles.borderRadius,
|
|
1132
|
-
padding: '32px',
|
|
1133
|
-
maxWidth: '600px',
|
|
1218
|
+
padding: isCompact ? '18px' : '32px',
|
|
1219
|
+
maxWidth: isCompact ? '520px' : '600px',
|
|
1134
1220
|
width: '90%',
|
|
1135
1221
|
maxHeight: '80vh',
|
|
1136
1222
|
overflow: 'auto',
|
|
@@ -1142,18 +1228,18 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1142
1228
|
display: 'flex',
|
|
1143
1229
|
justifyContent: 'space-between',
|
|
1144
1230
|
alignItems: 'center',
|
|
1145
|
-
marginBottom: '24px',
|
|
1231
|
+
marginBottom: isCompact ? '16px' : '24px',
|
|
1146
1232
|
};
|
|
1147
1233
|
const titleStyles = {
|
|
1148
1234
|
margin: 0,
|
|
1149
1235
|
color: themeStyles.textColor,
|
|
1150
|
-
fontSize: themeStyles.headerFontSize || '28px',
|
|
1236
|
+
fontSize: isCompact ? '22px' : themeStyles.headerFontSize || '28px',
|
|
1151
1237
|
fontWeight: 'bold',
|
|
1152
1238
|
};
|
|
1153
1239
|
const closeButtonStyles = {
|
|
1154
1240
|
background: 'none',
|
|
1155
1241
|
border: 'none',
|
|
1156
|
-
fontSize: '32px',
|
|
1242
|
+
fontSize: isCompact ? '26px' : '32px',
|
|
1157
1243
|
cursor: 'pointer',
|
|
1158
1244
|
color: themeStyles.textColor,
|
|
1159
1245
|
opacity: 0.6,
|
|
@@ -1161,17 +1247,17 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1161
1247
|
padding: 0,
|
|
1162
1248
|
lineHeight: 1,
|
|
1163
1249
|
};
|
|
1164
|
-
const isBadgeLayout = themeStyles.achievementLayout === 'badge';
|
|
1250
|
+
const isBadgeLayout = isCompact || themeStyles.achievementLayout === 'badge';
|
|
1165
1251
|
const listStyles = isBadgeLayout
|
|
1166
1252
|
? {
|
|
1167
1253
|
display: 'grid',
|
|
1168
|
-
gridTemplateColumns:
|
|
1169
|
-
gap: '16px',
|
|
1254
|
+
gridTemplateColumns: `repeat(auto-fill, minmax(${isCompact ? '112px' : '140px'}, 1fr))`,
|
|
1255
|
+
gap: isCompact ? '10px' : '16px',
|
|
1170
1256
|
}
|
|
1171
1257
|
: {
|
|
1172
1258
|
display: 'flex',
|
|
1173
1259
|
flexDirection: 'column',
|
|
1174
|
-
gap: '12px',
|
|
1260
|
+
gap: isCompact ? '8px' : '12px',
|
|
1175
1261
|
};
|
|
1176
1262
|
const getAchievementItemStyles = (isUnlocked) => {
|
|
1177
1263
|
const baseStyles = {
|
|
@@ -1185,24 +1271,24 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1185
1271
|
};
|
|
1186
1272
|
if (isBadgeLayout) {
|
|
1187
1273
|
// Badge layout: vertical, centered, square-ish
|
|
1188
|
-
return Object.assign(Object.assign({}, baseStyles), { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: '20px 12px', aspectRatio: '1 / 1.1', minHeight: '160px' });
|
|
1274
|
+
return Object.assign(Object.assign({}, baseStyles), { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: isCompact ? '12px 8px' : '20px 12px', aspectRatio: '1 / 1.1', minHeight: isCompact ? '118px' : '160px' });
|
|
1189
1275
|
}
|
|
1190
1276
|
else {
|
|
1191
1277
|
// Horizontal layout (default)
|
|
1192
|
-
return Object.assign(Object.assign({}, baseStyles), { display: 'flex', gap: '16px', padding: '16px' });
|
|
1278
|
+
return Object.assign(Object.assign({}, baseStyles), { display: 'flex', gap: isCompact ? '10px' : '16px', padding: isCompact ? '10px 12px' : '16px' });
|
|
1193
1279
|
}
|
|
1194
1280
|
};
|
|
1195
1281
|
const getIconContainerStyles = (isUnlocked) => {
|
|
1196
1282
|
if (isBadgeLayout) {
|
|
1197
1283
|
return {
|
|
1198
|
-
fontSize: '48px',
|
|
1284
|
+
fontSize: isCompact ? '32px' : '48px',
|
|
1199
1285
|
lineHeight: 1,
|
|
1200
|
-
marginBottom: '8px',
|
|
1286
|
+
marginBottom: isCompact ? '6px' : '8px',
|
|
1201
1287
|
opacity: isUnlocked ? 1 : 0.3,
|
|
1202
1288
|
};
|
|
1203
1289
|
}
|
|
1204
1290
|
return {
|
|
1205
|
-
fontSize: '40px',
|
|
1291
|
+
fontSize: isCompact ? '28px' : '40px',
|
|
1206
1292
|
flexShrink: 0,
|
|
1207
1293
|
lineHeight: 1,
|
|
1208
1294
|
opacity: isUnlocked ? 1 : 0.3,
|
|
@@ -1220,14 +1306,14 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1220
1306
|
? {
|
|
1221
1307
|
margin: '0 0 4px 0',
|
|
1222
1308
|
color: themeStyles.textColor,
|
|
1223
|
-
fontSize: '14px',
|
|
1309
|
+
fontSize: isCompact ? '12px' : '14px',
|
|
1224
1310
|
fontWeight: 'bold',
|
|
1225
1311
|
lineHeight: '1.3',
|
|
1226
1312
|
}
|
|
1227
1313
|
: {
|
|
1228
|
-
margin: '0 0 8px 0',
|
|
1314
|
+
margin: isCompact ? '0 0 4px 0' : '0 0 8px 0',
|
|
1229
1315
|
color: themeStyles.textColor,
|
|
1230
|
-
fontSize: '18px',
|
|
1316
|
+
fontSize: isCompact ? '14px' : '18px',
|
|
1231
1317
|
fontWeight: 'bold',
|
|
1232
1318
|
overflow: 'hidden',
|
|
1233
1319
|
textOverflow: 'ellipsis',
|
|
@@ -1238,27 +1324,27 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1238
1324
|
margin: 0,
|
|
1239
1325
|
color: themeStyles.textColor,
|
|
1240
1326
|
opacity: 0.7,
|
|
1241
|
-
fontSize: '11px',
|
|
1327
|
+
fontSize: isCompact ? '10px' : '11px',
|
|
1242
1328
|
lineHeight: '1.3',
|
|
1243
1329
|
}
|
|
1244
1330
|
: {
|
|
1245
1331
|
margin: 0,
|
|
1246
1332
|
color: themeStyles.textColor,
|
|
1247
1333
|
opacity: 0.8,
|
|
1248
|
-
fontSize: '14px',
|
|
1334
|
+
fontSize: isCompact ? '12px' : '14px',
|
|
1249
1335
|
};
|
|
1250
1336
|
const getLockIconStyles = () => {
|
|
1251
1337
|
if (isBadgeLayout) {
|
|
1252
1338
|
return {
|
|
1253
1339
|
position: 'absolute',
|
|
1254
|
-
top: '8px',
|
|
1255
|
-
right: '8px',
|
|
1256
|
-
fontSize: '18px',
|
|
1340
|
+
top: isCompact ? '6px' : '8px',
|
|
1341
|
+
right: isCompact ? '6px' : '8px',
|
|
1342
|
+
fontSize: isCompact ? '14px' : '18px',
|
|
1257
1343
|
opacity: 0.6,
|
|
1258
1344
|
};
|
|
1259
1345
|
}
|
|
1260
1346
|
return {
|
|
1261
|
-
fontSize: '24px',
|
|
1347
|
+
fontSize: isCompact ? '18px' : '24px',
|
|
1262
1348
|
flexShrink: 0,
|
|
1263
1349
|
opacity: 0.5,
|
|
1264
1350
|
};
|
|
@@ -1279,9 +1365,18 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1279
1365
|
opacity: 1;
|
|
1280
1366
|
}
|
|
1281
1367
|
}
|
|
1368
|
+
|
|
1369
|
+
[data-react-achievements-built-in-modal][data-hide-scrollbar="true"] {
|
|
1370
|
+
scrollbar-width: none;
|
|
1371
|
+
-ms-overflow-style: none;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
[data-react-achievements-built-in-modal][data-hide-scrollbar="true"]::-webkit-scrollbar {
|
|
1375
|
+
display: none;
|
|
1376
|
+
}
|
|
1282
1377
|
`),
|
|
1283
|
-
React.createElement("div", { style: overlayStyles, onClick: onClose, "data-testid": "built-in-modal-overlay" },
|
|
1284
|
-
React.createElement("div", { style: modalStyles, onClick: (e) => e.stopPropagation(), "data-testid": "built-in-modal" },
|
|
1378
|
+
React.createElement("div", { style: overlayStyles, onClick: onClose, "data-backdrop-blur": backdropBlurFilter, "data-testid": "built-in-modal-overlay" },
|
|
1379
|
+
React.createElement("div", { style: modalStyles, onClick: (e) => e.stopPropagation(), "data-hide-scrollbar": hideScrollbar ? 'true' : undefined, "data-density": density, "data-react-achievements-built-in-modal": true, "data-testid": "built-in-modal" },
|
|
1285
1380
|
React.createElement("div", { style: headerStyles },
|
|
1286
1381
|
React.createElement("h2", { style: titleStyles }, "\uD83C\uDFC6 Achievements"),
|
|
1287
1382
|
React.createElement("button", { onClick: onClose, style: closeButtonStyles, onMouseEnter: (e) => (e.currentTarget.style.opacity = '1'), onMouseLeave: (e) => (e.currentTarget.style.opacity = '0.6'), "aria-label": "Close modal" }, "\u00D7")),
|