react-achievements 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- 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 +198 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +30 -16
- package/dist/index.esm.js +198 -115
- package/dist/index.esm.js.map +1 -1
- package/dist/web.cjs +198 -115
- package/dist/web.cjs.map +1 -1
- package/dist/web.d.ts +30 -16
- package/dist/web.esm.js +198 -115
- package/dist/web.esm.js.map +1 -1
- package/package.json +4 -4
package/dist/web.esm.js
CHANGED
|
@@ -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,
|
|
@@ -606,7 +603,7 @@ const AchievementEffects = ({ icons, ui }) => {
|
|
|
606
603
|
const AchievementProvider = (_a) => {
|
|
607
604
|
var { children, icons = {}, ui = {}, useBuiltInUI } = _a, providerProps = __rest(_a, ["children", "icons", "ui", "useBuiltInUI"]);
|
|
608
605
|
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
|
|
606
|
+
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
607
|
}
|
|
611
608
|
const uiContextValue = useMemo(() => ({ icons, ui }), [icons, ui]);
|
|
612
609
|
return (React.createElement(AchievementUIContext.Provider, { value: uiContextValue },
|
|
@@ -624,18 +621,14 @@ const useAchievements = () => {
|
|
|
624
621
|
};
|
|
625
622
|
|
|
626
623
|
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));
|
|
624
|
+
const { snapshot } = useAchievements();
|
|
632
625
|
return {
|
|
633
|
-
unlockedIds,
|
|
634
|
-
unlockedAchievements,
|
|
635
|
-
allAchievements,
|
|
636
|
-
unlockedCount:
|
|
637
|
-
totalCount:
|
|
638
|
-
metrics:
|
|
626
|
+
unlockedIds: snapshot.unlockedIds,
|
|
627
|
+
unlockedAchievements: snapshot.unlockedAchievements,
|
|
628
|
+
allAchievements: snapshot.allAchievements,
|
|
629
|
+
unlockedCount: snapshot.unlockedCount,
|
|
630
|
+
totalCount: snapshot.totalCount,
|
|
631
|
+
metrics: snapshot.metrics,
|
|
639
632
|
};
|
|
640
633
|
};
|
|
641
634
|
|
|
@@ -776,13 +769,98 @@ const defaultStyles = {
|
|
|
776
769
|
},
|
|
777
770
|
};
|
|
778
771
|
|
|
772
|
+
const isNumericString = (value) => /^-?\d+(\.\d+)?$/.test(value);
|
|
773
|
+
const getBackdropBlurFilter = (backdropBlur) => {
|
|
774
|
+
if (backdropBlur === undefined) {
|
|
775
|
+
return undefined;
|
|
776
|
+
}
|
|
777
|
+
if (typeof backdropBlur === 'number') {
|
|
778
|
+
if (backdropBlur <= 0) {
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
return `blur(${backdropBlur}px)`;
|
|
782
|
+
}
|
|
783
|
+
const trimmedBlur = backdropBlur.trim();
|
|
784
|
+
if (!trimmedBlur ||
|
|
785
|
+
trimmedBlur === '0' ||
|
|
786
|
+
trimmedBlur === '0px' ||
|
|
787
|
+
trimmedBlur === 'none') {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
const blurValue = isNumericString(trimmedBlur) ? `${trimmedBlur}px` : trimmedBlur;
|
|
791
|
+
return blurValue.startsWith('blur(') ? blurValue : `blur(${blurValue})`;
|
|
792
|
+
};
|
|
793
|
+
const getBackdropBlurStyles = (backdropBlur) => {
|
|
794
|
+
const backdropFilter = getBackdropBlurFilter(backdropBlur);
|
|
795
|
+
if (!backdropFilter) {
|
|
796
|
+
return {};
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
backdropFilter,
|
|
800
|
+
WebkitBackdropFilter: backdropFilter,
|
|
801
|
+
};
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
const compactAchievementStyles = {
|
|
805
|
+
achievementList: {
|
|
806
|
+
display: 'grid',
|
|
807
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
|
|
808
|
+
gap: '10px',
|
|
809
|
+
},
|
|
810
|
+
achievementItem: {
|
|
811
|
+
display: 'flex',
|
|
812
|
+
flexDirection: 'column',
|
|
813
|
+
alignItems: 'center',
|
|
814
|
+
justifyContent: 'center',
|
|
815
|
+
gap: '6px',
|
|
816
|
+
padding: '12px 10px',
|
|
817
|
+
borderRadius: '8px',
|
|
818
|
+
aspectRatio: '1 / 1',
|
|
819
|
+
minHeight: '120px',
|
|
820
|
+
textAlign: 'center',
|
|
821
|
+
},
|
|
822
|
+
lockedAchievementItem: {
|
|
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
|
+
achievementIcon: {
|
|
835
|
+
fontSize: '34px',
|
|
836
|
+
lineHeight: 1,
|
|
837
|
+
flexShrink: 0,
|
|
838
|
+
},
|
|
839
|
+
achievementTitle: {
|
|
840
|
+
margin: '0',
|
|
841
|
+
fontSize: '13px',
|
|
842
|
+
lineHeight: 1.2,
|
|
843
|
+
},
|
|
844
|
+
achievementDescription: {
|
|
845
|
+
margin: '0',
|
|
846
|
+
fontSize: '11px',
|
|
847
|
+
lineHeight: 1.25,
|
|
848
|
+
},
|
|
849
|
+
lockIcon: {
|
|
850
|
+
fontSize: '15px',
|
|
851
|
+
top: '8px',
|
|
852
|
+
right: '8px',
|
|
853
|
+
transform: 'none',
|
|
854
|
+
},
|
|
855
|
+
};
|
|
856
|
+
const getDensityStyles = (density) => (density === 'compact' ? compactAchievementStyles : {});
|
|
779
857
|
const resolveIcon = (achievement, icons) => {
|
|
780
858
|
return ((achievement.achievementIconKey && icons[achievement.achievementIconKey]) ||
|
|
781
859
|
achievement.achievementIconKey ||
|
|
782
860
|
icons.default ||
|
|
783
861
|
'⭐');
|
|
784
862
|
};
|
|
785
|
-
const AchievementsList = ({ achievements, showLocked = true, showUnlockConditions = false, icons = {}, styles = {}, emptyState, className, renderAchievement, }) => {
|
|
863
|
+
const AchievementsList = ({ achievements, showLocked = true, showUnlockConditions = false, icons = {}, styles = {}, emptyState, className, density = 'comfortable', renderAchievement, }) => {
|
|
786
864
|
const context = useContext(AchievementContext);
|
|
787
865
|
const uiContext = useContext(AchievementUIContext);
|
|
788
866
|
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 +871,37 @@ const AchievementsList = ({ achievements, showLocked = true, showUnlockCondition
|
|
|
793
871
|
const achievementsToDisplay = showLocked
|
|
794
872
|
? sourceAchievements
|
|
795
873
|
: sourceAchievements.filter((achievement) => achievement.isUnlocked);
|
|
874
|
+
const densityStyles = getDensityStyles(density);
|
|
796
875
|
if (achievementsToDisplay.length === 0) {
|
|
797
876
|
return (React.createElement("p", { style: { textAlign: 'center', color: '#666' } }, emptyState || 'No achievements configured.'));
|
|
798
877
|
}
|
|
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) => {
|
|
878
|
+
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
879
|
const isLocked = !achievement.isUnlocked;
|
|
801
880
|
const icon = resolveIcon(achievement, mergedIcons);
|
|
802
881
|
if (renderAchievement) {
|
|
803
|
-
return (React.createElement(React.Fragment, { key: achievement.achievementId }, renderAchievement({ achievement, isLocked, icon, index })));
|
|
882
|
+
return (React.createElement(React.Fragment, { key: achievement.achievementId }, renderAchievement({ achievement, isLocked, icon, index, density })));
|
|
804
883
|
}
|
|
805
884
|
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' }) },
|
|
885
|
+
? 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' },
|
|
886
|
+
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),
|
|
887
|
+
React.createElement("div", { style: density === 'compact' ? { width: '100%', minWidth: 0 } : { flex: 1 } },
|
|
888
|
+
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),
|
|
889
|
+
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
890
|
achievement.achievementDescription,
|
|
812
891
|
showUnlockConditions && isLocked && (React.createElement("span", { style: {
|
|
813
892
|
display: 'block',
|
|
814
|
-
fontSize: '12px',
|
|
815
|
-
marginTop: '4px',
|
|
893
|
+
fontSize: density === 'compact' ? '11px' : '12px',
|
|
894
|
+
marginTop: density === 'compact' ? '2px' : '4px',
|
|
816
895
|
fontStyle: 'italic',
|
|
817
896
|
color: '#888',
|
|
818
897
|
} },
|
|
819
898
|
"\uD83D\uDD13 ",
|
|
820
899
|
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"))));
|
|
900
|
+
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
901
|
})));
|
|
823
902
|
};
|
|
824
903
|
|
|
825
|
-
const AchievementsModal = ({ isOpen, onClose, achievements, title = '🏆 Achievements', styles = {}, icons = {}, showLocked = true, showUnlockConditions = false, emptyState, renderAchievement, theme, }) => {
|
|
904
|
+
const AchievementsModal = ({ isOpen, onClose, achievements, title = '🏆 Achievements', styles = {}, icons = {}, showLocked = true, showUnlockConditions = false, emptyState, renderAchievement, theme, hideScrollbar = false, density = 'comfortable', backdropBlur, }) => {
|
|
826
905
|
const context = useContext(AchievementContext);
|
|
827
906
|
const uiContext = useContext(AchievementUIContext);
|
|
828
907
|
useEffect(() => {
|
|
@@ -850,19 +929,30 @@ const AchievementsModal = ({ isOpen, onClose, achievements, title = '🏆 Achiev
|
|
|
850
929
|
? sourceAchievements
|
|
851
930
|
: sourceAchievements === null || sourceAchievements === void 0 ? void 0 : sourceAchievements.filter((achievement) => achievement.isUnlocked);
|
|
852
931
|
const resolvedTheme = theme || uiContext.ui.theme || 'modern';
|
|
932
|
+
const backdropBlurFilter = getBackdropBlurFilter(backdropBlur);
|
|
853
933
|
const mergedIcons = Object.assign(Object.assign(Object.assign(Object.assign({}, defaultAchievementIcons), context === null || context === void 0 ? void 0 : context.icons), uiContext.icons), icons);
|
|
854
934
|
if (CustomModal) {
|
|
855
935
|
if (!modalAchievements) {
|
|
856
936
|
throw new Error('AchievementsModal requires either an achievements prop or an AchievementProvider parent.');
|
|
857
937
|
}
|
|
858
|
-
return (React.createElement(CustomModal, { isOpen: isOpen, onClose: onClose, achievements: modalAchievements, icons: mergedIcons, theme: resolvedTheme }));
|
|
938
|
+
return (React.createElement(CustomModal, { isOpen: isOpen, onClose: onClose, achievements: modalAchievements, icons: mergedIcons, theme: resolvedTheme, hideScrollbar: hideScrollbar, density: density, backdropBlur: backdropBlur }));
|
|
859
939
|
}
|
|
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("
|
|
940
|
+
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" },
|
|
941
|
+
hideScrollbar && (React.createElement("style", null, `
|
|
942
|
+
[data-react-achievements-modal-content][data-hide-scrollbar="true"] {
|
|
943
|
+
scrollbar-width: none;
|
|
944
|
+
-ms-overflow-style: none;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
[data-react-achievements-modal-content][data-hide-scrollbar="true"]::-webkit-scrollbar {
|
|
948
|
+
display: none;
|
|
949
|
+
}
|
|
950
|
+
`)),
|
|
951
|
+
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
952
|
React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.header), styles === null || styles === void 0 ? void 0 : styles.header) },
|
|
863
953
|
React.createElement("h2", { id: "achievements-modal-title", style: { margin: 0 } }, title),
|
|
864
954
|
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 }))));
|
|
955
|
+
React.createElement(AchievementsList, { achievements: modalAchievements, showLocked: showLocked, showUnlockConditions: showUnlockConditions, icons: icons, styles: styles, emptyState: emptyState, renderAchievement: renderAchievement, density: density }))));
|
|
866
956
|
};
|
|
867
957
|
|
|
868
958
|
const getPositionStyles$1 = (position) => {
|
|
@@ -882,7 +972,7 @@ const getPositionStyles$1 = (position) => {
|
|
|
882
972
|
return Object.assign(Object.assign({}, base), { bottom: 0, right: 0 });
|
|
883
973
|
}
|
|
884
974
|
};
|
|
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, }) => {
|
|
975
|
+
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
976
|
const uiContext = useContext(AchievementUIContext);
|
|
887
977
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
888
978
|
const { unlockedAchievements, allAchievements, unlockedCount, totalCount } = useAchievementState();
|
|
@@ -922,7 +1012,7 @@ const AchievementsWidget = ({ position = 'bottom-right', placement = 'fixed', sh
|
|
|
922
1012
|
React.createElement("span", null, icon),
|
|
923
1013
|
React.createElement("span", { style: { flex: 1 } }, label),
|
|
924
1014
|
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 })));
|
|
1015
|
+
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
1016
|
};
|
|
927
1017
|
|
|
928
1018
|
const getPositionStyles = (position) => {
|
|
@@ -944,11 +1034,11 @@ const getPositionStyles = (position) => {
|
|
|
944
1034
|
};
|
|
945
1035
|
/**
|
|
946
1036
|
* @deprecated Use `AchievementsWidget` for new integrations. This v3
|
|
947
|
-
* compatibility wrapper will be removed in
|
|
1037
|
+
* compatibility wrapper will be removed in 5.0.
|
|
948
1038
|
*/
|
|
949
1039
|
const BadgesButton = ({ onClick, position = 'bottom-right', placement = 'fixed', styles = {}, unlockedAchievements, theme = 'modern', }) => {
|
|
950
1040
|
React.useEffect(() => {
|
|
951
|
-
warnDeprecation('`BadgesButton` is deprecated. Use `AchievementsWidget` instead. `BadgesButton` will be removed in
|
|
1041
|
+
warnDeprecation('`BadgesButton` is deprecated. Use `AchievementsWidget` instead. `BadgesButton` will be removed in 5.0.');
|
|
952
1042
|
}, []);
|
|
953
1043
|
// Get theme configuration for consistent styling
|
|
954
1044
|
const themeConfig = getTheme(theme) || builtInThemes.modern;
|
|
@@ -985,11 +1075,11 @@ const BadgesButton = ({ onClick, position = 'bottom-right', placement = 'fixed',
|
|
|
985
1075
|
/**
|
|
986
1076
|
* @deprecated Use `AchievementsModal`, `AchievementsWidget`, or
|
|
987
1077
|
* `AchievementsList` for new integrations. This v3 compatibility wrapper will
|
|
988
|
-
* be removed in
|
|
1078
|
+
* be removed in 5.0.
|
|
989
1079
|
*/
|
|
990
1080
|
const BadgesModal = ({ isOpen, onClose, achievements, styles = {}, icons = {}, showAllAchievements = false, showUnlockConditions = false, allAchievements, }) => {
|
|
991
1081
|
useEffect(() => {
|
|
992
|
-
warnDeprecation('`BadgesModal` is deprecated. Use `AchievementsWidget` or `AchievementsList` instead. `BadgesModal` will be removed in
|
|
1082
|
+
warnDeprecation('`BadgesModal` is deprecated. Use `AchievementsWidget` or `AchievementsList` instead. `BadgesModal` will be removed in 5.0.');
|
|
993
1083
|
}, []);
|
|
994
1084
|
const achievementsToDisplay = showAllAchievements && allAchievements
|
|
995
1085
|
? allAchievements
|
|
@@ -999,7 +1089,7 @@ const BadgesModal = ({ isOpen, onClose, achievements, styles = {}, icons = {}, s
|
|
|
999
1089
|
|
|
1000
1090
|
/**
|
|
1001
1091
|
* @deprecated Use `AchievementsWidget` for new integrations. This v3
|
|
1002
|
-
* compatibility wrapper will be removed in
|
|
1092
|
+
* compatibility wrapper will be removed in 5.0.
|
|
1003
1093
|
*/
|
|
1004
1094
|
const BadgesButtonWithModal = ({ unlockedAchievements, position = 'bottom-right', placement = 'fixed', showAllAchievements = false, allAchievements, showUnlockConditions = false, icons, theme = 'modern', buttonStyles, modalStyles, }) => {
|
|
1005
1095
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
@@ -1010,11 +1100,11 @@ const BadgesButtonWithModal = ({ unlockedAchievements, position = 'bottom-right'
|
|
|
1010
1100
|
|
|
1011
1101
|
/**
|
|
1012
1102
|
* @deprecated Use the provider `ui.ConfettiComponent` option or the built-in
|
|
1013
|
-
* confetti default. This v3 compatibility wrapper will be removed in
|
|
1103
|
+
* confetti default. This v3 compatibility wrapper will be removed in 5.0.
|
|
1014
1104
|
*/
|
|
1015
1105
|
const ConfettiWrapper = ({ show }) => {
|
|
1016
1106
|
useEffect(() => {
|
|
1017
|
-
warnDeprecation('`ConfettiWrapper` is deprecated. Use the provider `ui.ConfettiComponent` option or built-in confetti defaults instead. `ConfettiWrapper` will be removed in
|
|
1107
|
+
warnDeprecation('`ConfettiWrapper` is deprecated. Use the provider `ui.ConfettiComponent` option or built-in confetti defaults instead. `ConfettiWrapper` will be removed in 5.0.');
|
|
1018
1108
|
}, []);
|
|
1019
1109
|
return React.createElement(BuiltInConfetti, { show: show, particleCount: 200 });
|
|
1020
1110
|
};
|
|
@@ -1049,17 +1139,11 @@ const LevelProgress = ({ level, currentXP, nextLevelXP, label = 'Level', valueLa
|
|
|
1049
1139
|
* Provides the v4 happy path for direct metric updates plus explicit state names.
|
|
1050
1140
|
*/
|
|
1051
1141
|
const useSimpleAchievements = () => {
|
|
1052
|
-
const { update, reset, getState, exportData, importData } = useAchievements();
|
|
1142
|
+
const { update, reset, getState, exportData, importData, engine } = useAchievements();
|
|
1053
1143
|
const achievementState = useAchievementState();
|
|
1054
1144
|
const track = (metric, value) => update({ [metric]: value });
|
|
1055
1145
|
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 });
|
|
1146
|
+
return engine.increment(metric, amount);
|
|
1063
1147
|
};
|
|
1064
1148
|
const trackMultiple = (metrics) => update(metrics);
|
|
1065
1149
|
return {
|
|
@@ -1078,11 +1162,11 @@ const useSimpleAchievements = () => {
|
|
|
1078
1162
|
importData,
|
|
1079
1163
|
getAllAchievements: () => achievementState.allAchievements,
|
|
1080
1164
|
/**
|
|
1081
|
-
* @deprecated Use `unlockedIds` instead. This alias will be removed in
|
|
1165
|
+
* @deprecated Use `unlockedIds` instead. This alias will be removed in 5.0.
|
|
1082
1166
|
*/
|
|
1083
1167
|
unlocked: achievementState.unlockedIds,
|
|
1084
1168
|
/**
|
|
1085
|
-
* @deprecated Use `allAchievements` instead. This alias will be removed in
|
|
1169
|
+
* @deprecated Use `allAchievements` instead. This alias will be removed in 5.0.
|
|
1086
1170
|
*/
|
|
1087
1171
|
all: achievementState.allAchievements,
|
|
1088
1172
|
};
|
|
@@ -1092,12 +1176,14 @@ const useSimpleAchievements = () => {
|
|
|
1092
1176
|
* Built-in modal component
|
|
1093
1177
|
* Modern, theme-aware achievement modal with smooth animations
|
|
1094
1178
|
*/
|
|
1095
|
-
const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'modern', }) => {
|
|
1179
|
+
const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'modern', hideScrollbar = false, density = 'comfortable', backdropBlur, }) => {
|
|
1096
1180
|
// Merge custom icons with defaults
|
|
1097
1181
|
const mergedIcons = Object.assign(Object.assign({}, defaultAchievementIcons), icons);
|
|
1098
1182
|
// Get theme configuration
|
|
1099
1183
|
const themeConfig = getTheme(theme) || builtInThemes.modern;
|
|
1100
1184
|
const { modal: themeStyles } = themeConfig;
|
|
1185
|
+
const isCompact = density === 'compact';
|
|
1186
|
+
const backdropBlurFilter = getBackdropBlurFilter(backdropBlur);
|
|
1101
1187
|
useEffect(() => {
|
|
1102
1188
|
if (isOpen) {
|
|
1103
1189
|
// Lock body scroll when modal is open
|
|
@@ -1113,24 +1199,12 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1113
1199
|
}, [isOpen]);
|
|
1114
1200
|
if (!isOpen)
|
|
1115
1201
|
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
|
-
};
|
|
1202
|
+
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
1203
|
const modalStyles = {
|
|
1130
1204
|
background: themeStyles.background,
|
|
1131
1205
|
borderRadius: themeStyles.borderRadius,
|
|
1132
|
-
padding: '32px',
|
|
1133
|
-
maxWidth: '600px',
|
|
1206
|
+
padding: isCompact ? '18px' : '32px',
|
|
1207
|
+
maxWidth: isCompact ? '520px' : '600px',
|
|
1134
1208
|
width: '90%',
|
|
1135
1209
|
maxHeight: '80vh',
|
|
1136
1210
|
overflow: 'auto',
|
|
@@ -1142,18 +1216,18 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1142
1216
|
display: 'flex',
|
|
1143
1217
|
justifyContent: 'space-between',
|
|
1144
1218
|
alignItems: 'center',
|
|
1145
|
-
marginBottom: '24px',
|
|
1219
|
+
marginBottom: isCompact ? '16px' : '24px',
|
|
1146
1220
|
};
|
|
1147
1221
|
const titleStyles = {
|
|
1148
1222
|
margin: 0,
|
|
1149
1223
|
color: themeStyles.textColor,
|
|
1150
|
-
fontSize: themeStyles.headerFontSize || '28px',
|
|
1224
|
+
fontSize: isCompact ? '22px' : themeStyles.headerFontSize || '28px',
|
|
1151
1225
|
fontWeight: 'bold',
|
|
1152
1226
|
};
|
|
1153
1227
|
const closeButtonStyles = {
|
|
1154
1228
|
background: 'none',
|
|
1155
1229
|
border: 'none',
|
|
1156
|
-
fontSize: '32px',
|
|
1230
|
+
fontSize: isCompact ? '26px' : '32px',
|
|
1157
1231
|
cursor: 'pointer',
|
|
1158
1232
|
color: themeStyles.textColor,
|
|
1159
1233
|
opacity: 0.6,
|
|
@@ -1161,17 +1235,17 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1161
1235
|
padding: 0,
|
|
1162
1236
|
lineHeight: 1,
|
|
1163
1237
|
};
|
|
1164
|
-
const isBadgeLayout = themeStyles.achievementLayout === 'badge';
|
|
1238
|
+
const isBadgeLayout = isCompact || themeStyles.achievementLayout === 'badge';
|
|
1165
1239
|
const listStyles = isBadgeLayout
|
|
1166
1240
|
? {
|
|
1167
1241
|
display: 'grid',
|
|
1168
|
-
gridTemplateColumns:
|
|
1169
|
-
gap: '16px',
|
|
1242
|
+
gridTemplateColumns: `repeat(auto-fill, minmax(${isCompact ? '112px' : '140px'}, 1fr))`,
|
|
1243
|
+
gap: isCompact ? '10px' : '16px',
|
|
1170
1244
|
}
|
|
1171
1245
|
: {
|
|
1172
1246
|
display: 'flex',
|
|
1173
1247
|
flexDirection: 'column',
|
|
1174
|
-
gap: '12px',
|
|
1248
|
+
gap: isCompact ? '8px' : '12px',
|
|
1175
1249
|
};
|
|
1176
1250
|
const getAchievementItemStyles = (isUnlocked) => {
|
|
1177
1251
|
const baseStyles = {
|
|
@@ -1185,24 +1259,24 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1185
1259
|
};
|
|
1186
1260
|
if (isBadgeLayout) {
|
|
1187
1261
|
// 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' });
|
|
1262
|
+
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
1263
|
}
|
|
1190
1264
|
else {
|
|
1191
1265
|
// Horizontal layout (default)
|
|
1192
|
-
return Object.assign(Object.assign({}, baseStyles), { display: 'flex', gap: '16px', padding: '16px' });
|
|
1266
|
+
return Object.assign(Object.assign({}, baseStyles), { display: 'flex', gap: isCompact ? '10px' : '16px', padding: isCompact ? '10px 12px' : '16px' });
|
|
1193
1267
|
}
|
|
1194
1268
|
};
|
|
1195
1269
|
const getIconContainerStyles = (isUnlocked) => {
|
|
1196
1270
|
if (isBadgeLayout) {
|
|
1197
1271
|
return {
|
|
1198
|
-
fontSize: '48px',
|
|
1272
|
+
fontSize: isCompact ? '32px' : '48px',
|
|
1199
1273
|
lineHeight: 1,
|
|
1200
|
-
marginBottom: '8px',
|
|
1274
|
+
marginBottom: isCompact ? '6px' : '8px',
|
|
1201
1275
|
opacity: isUnlocked ? 1 : 0.3,
|
|
1202
1276
|
};
|
|
1203
1277
|
}
|
|
1204
1278
|
return {
|
|
1205
|
-
fontSize: '40px',
|
|
1279
|
+
fontSize: isCompact ? '28px' : '40px',
|
|
1206
1280
|
flexShrink: 0,
|
|
1207
1281
|
lineHeight: 1,
|
|
1208
1282
|
opacity: isUnlocked ? 1 : 0.3,
|
|
@@ -1220,14 +1294,14 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1220
1294
|
? {
|
|
1221
1295
|
margin: '0 0 4px 0',
|
|
1222
1296
|
color: themeStyles.textColor,
|
|
1223
|
-
fontSize: '14px',
|
|
1297
|
+
fontSize: isCompact ? '12px' : '14px',
|
|
1224
1298
|
fontWeight: 'bold',
|
|
1225
1299
|
lineHeight: '1.3',
|
|
1226
1300
|
}
|
|
1227
1301
|
: {
|
|
1228
|
-
margin: '0 0 8px 0',
|
|
1302
|
+
margin: isCompact ? '0 0 4px 0' : '0 0 8px 0',
|
|
1229
1303
|
color: themeStyles.textColor,
|
|
1230
|
-
fontSize: '18px',
|
|
1304
|
+
fontSize: isCompact ? '14px' : '18px',
|
|
1231
1305
|
fontWeight: 'bold',
|
|
1232
1306
|
overflow: 'hidden',
|
|
1233
1307
|
textOverflow: 'ellipsis',
|
|
@@ -1238,27 +1312,27 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1238
1312
|
margin: 0,
|
|
1239
1313
|
color: themeStyles.textColor,
|
|
1240
1314
|
opacity: 0.7,
|
|
1241
|
-
fontSize: '11px',
|
|
1315
|
+
fontSize: isCompact ? '10px' : '11px',
|
|
1242
1316
|
lineHeight: '1.3',
|
|
1243
1317
|
}
|
|
1244
1318
|
: {
|
|
1245
1319
|
margin: 0,
|
|
1246
1320
|
color: themeStyles.textColor,
|
|
1247
1321
|
opacity: 0.8,
|
|
1248
|
-
fontSize: '14px',
|
|
1322
|
+
fontSize: isCompact ? '12px' : '14px',
|
|
1249
1323
|
};
|
|
1250
1324
|
const getLockIconStyles = () => {
|
|
1251
1325
|
if (isBadgeLayout) {
|
|
1252
1326
|
return {
|
|
1253
1327
|
position: 'absolute',
|
|
1254
|
-
top: '8px',
|
|
1255
|
-
right: '8px',
|
|
1256
|
-
fontSize: '18px',
|
|
1328
|
+
top: isCompact ? '6px' : '8px',
|
|
1329
|
+
right: isCompact ? '6px' : '8px',
|
|
1330
|
+
fontSize: isCompact ? '14px' : '18px',
|
|
1257
1331
|
opacity: 0.6,
|
|
1258
1332
|
};
|
|
1259
1333
|
}
|
|
1260
1334
|
return {
|
|
1261
|
-
fontSize: '24px',
|
|
1335
|
+
fontSize: isCompact ? '18px' : '24px',
|
|
1262
1336
|
flexShrink: 0,
|
|
1263
1337
|
opacity: 0.5,
|
|
1264
1338
|
};
|
|
@@ -1279,9 +1353,18 @@ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'mode
|
|
|
1279
1353
|
opacity: 1;
|
|
1280
1354
|
}
|
|
1281
1355
|
}
|
|
1356
|
+
|
|
1357
|
+
[data-react-achievements-built-in-modal][data-hide-scrollbar="true"] {
|
|
1358
|
+
scrollbar-width: none;
|
|
1359
|
+
-ms-overflow-style: none;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
[data-react-achievements-built-in-modal][data-hide-scrollbar="true"]::-webkit-scrollbar {
|
|
1363
|
+
display: none;
|
|
1364
|
+
}
|
|
1282
1365
|
`),
|
|
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" },
|
|
1366
|
+
React.createElement("div", { style: overlayStyles, onClick: onClose, "data-backdrop-blur": backdropBlurFilter, "data-testid": "built-in-modal-overlay" },
|
|
1367
|
+
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
1368
|
React.createElement("div", { style: headerStyles },
|
|
1286
1369
|
React.createElement("h2", { style: titleStyles }, "\uD83C\uDFC6 Achievements"),
|
|
1287
1370
|
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")),
|