react-achievements 3.4.2 → 3.6.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 +354 -21
- package/dist/index.d.ts +210 -2
- package/dist/index.js +915 -185
- package/dist/index.js.map +1 -1
- package/dist/types/core/components/BadgesButton.d.ts +18 -3
- package/dist/types/core/components/BadgesModal.d.ts +4 -1
- package/dist/types/core/hooks/useWindowSize.d.ts +16 -0
- package/dist/types/core/types.d.ts +6 -0
- package/dist/types/core/ui/BuiltInConfetti.d.ts +7 -0
- package/dist/types/core/ui/BuiltInModal.d.ts +7 -0
- package/dist/types/core/ui/BuiltInNotification.d.ts +7 -0
- package/dist/types/core/ui/LegacyWrappers.d.ts +21 -0
- package/dist/types/core/ui/interfaces.d.ts +131 -0
- package/dist/types/core/ui/legacyDetector.d.ts +40 -0
- package/dist/types/core/ui/themes.d.ts +14 -0
- package/dist/types/hooks/useSimpleAchievements.d.ts +5 -0
- package/dist/types/index.d.ts +6 -1
- package/dist/types/providers/AchievementProvider.d.ts +15 -2
- package/package.json +15 -1
- package/dist/assets/defaultIcons.d.ts +0 -81
- package/dist/badges.d.ts +0 -8
- package/dist/components/Achievement.d.ts +0 -10
- package/dist/components/AchievementModal.d.ts +0 -12
- package/dist/components/Badge.d.ts +0 -9
- package/dist/components/BadgesButton.d.ts +0 -14
- package/dist/components/BadgesModal.d.ts +0 -12
- package/dist/components/ConfettiWrapper.d.ts +0 -6
- package/dist/components/Progress.d.ts +0 -6
- package/dist/context/AchievementContext.d.ts +0 -21
- package/dist/defaultStyles.d.ts +0 -19
- package/dist/hooks/useAchievement.d.ts +0 -8
- package/dist/hooks/useAchievementState.d.ts +0 -4
- package/dist/index.cjs.js +0 -2428
- package/dist/index.esm.js +0 -2403
- package/dist/levels.d.ts +0 -7
- package/dist/providers/AchievementProvider.d.ts +0 -12
- package/dist/redux/achievementSlice.d.ts +0 -30
- package/dist/redux/notificationSlice.d.ts +0 -7
- package/dist/redux/store.d.ts +0 -15
- package/dist/stories/Button.d.ts +0 -28
- package/dist/stories/Button.stories.d.ts +0 -23
- package/dist/stories/Header.d.ts +0 -13
- package/dist/stories/Header.stories.d.ts +0 -18
- package/dist/stories/Page.d.ts +0 -3
- package/dist/stories/Page.stories.d.ts +0 -12
- package/dist/types/core/context/AchievementContext.d.ts +0 -5
- package/dist/types/stories/Button.d.ts +0 -16
- package/dist/types/stories/Button.stories.d.ts +0 -23
- package/dist/types/stories/Header.d.ts +0 -13
- package/dist/types/stories/Header.stories.d.ts +0 -18
- package/dist/types/stories/Page.d.ts +0 -3
- package/dist/types/stories/Page.stories.d.ts +0 -12
- package/dist/types.d.ts +0 -37
- package/dist/utils/EventEmitter.d.ts +0 -6
package/dist/index.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState, useEffect, createContext, useRef, useContext } from 'react';
|
|
2
2
|
import Modal from 'react-modal';
|
|
3
3
|
import Confetti from 'react-confetti';
|
|
4
|
-
import { useWindowSize } from 'react-use';
|
|
5
|
-
import { toast, ToastContainer } from 'react-toastify';
|
|
6
|
-
import 'react-toastify/dist/ReactToastify.css';
|
|
7
4
|
|
|
8
5
|
const isDate = (value) => {
|
|
9
6
|
return value instanceof Date;
|
|
@@ -861,6 +858,122 @@ class OfflineQueueStorage {
|
|
|
861
858
|
}
|
|
862
859
|
}
|
|
863
860
|
|
|
861
|
+
/**
|
|
862
|
+
* Built-in theme presets
|
|
863
|
+
*/
|
|
864
|
+
const builtInThemes = {
|
|
865
|
+
/**
|
|
866
|
+
* Modern theme - Dark gradients with vibrant accents
|
|
867
|
+
* Inspired by contemporary achievement systems (Discord, Steam, Xbox)
|
|
868
|
+
*/
|
|
869
|
+
modern: {
|
|
870
|
+
name: 'modern',
|
|
871
|
+
notification: {
|
|
872
|
+
background: 'linear-gradient(135deg, rgba(30, 30, 50, 0.98) 0%, rgba(50, 50, 70, 0.98) 100%)',
|
|
873
|
+
textColor: '#ffffff',
|
|
874
|
+
accentColor: '#4CAF50',
|
|
875
|
+
borderRadius: '12px',
|
|
876
|
+
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
|
|
877
|
+
fontSize: {
|
|
878
|
+
header: '12px',
|
|
879
|
+
title: '18px',
|
|
880
|
+
description: '14px',
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
modal: {
|
|
884
|
+
overlayColor: 'rgba(0, 0, 0, 0.85)',
|
|
885
|
+
background: 'linear-gradient(135deg, #1e1e32 0%, #323246 100%)',
|
|
886
|
+
textColor: '#ffffff',
|
|
887
|
+
accentColor: '#4CAF50',
|
|
888
|
+
borderRadius: '16px',
|
|
889
|
+
headerFontSize: '28px',
|
|
890
|
+
},
|
|
891
|
+
confetti: {
|
|
892
|
+
colors: ['#FFD700', '#4CAF50', '#2196F3', '#FF6B6B'],
|
|
893
|
+
particleCount: 50,
|
|
894
|
+
shapes: ['circle', 'square'],
|
|
895
|
+
},
|
|
896
|
+
},
|
|
897
|
+
/**
|
|
898
|
+
* Minimal theme - Clean, light design with subtle accents
|
|
899
|
+
* Perfect for professional or minimalist applications
|
|
900
|
+
*/
|
|
901
|
+
minimal: {
|
|
902
|
+
name: 'minimal',
|
|
903
|
+
notification: {
|
|
904
|
+
background: 'rgba(255, 255, 255, 0.98)',
|
|
905
|
+
textColor: '#333333',
|
|
906
|
+
accentColor: '#4CAF50',
|
|
907
|
+
borderRadius: '8px',
|
|
908
|
+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
909
|
+
fontSize: {
|
|
910
|
+
header: '11px',
|
|
911
|
+
title: '16px',
|
|
912
|
+
description: '13px',
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
modal: {
|
|
916
|
+
overlayColor: 'rgba(0, 0, 0, 0.5)',
|
|
917
|
+
background: '#ffffff',
|
|
918
|
+
textColor: '#333333',
|
|
919
|
+
accentColor: '#4CAF50',
|
|
920
|
+
borderRadius: '12px',
|
|
921
|
+
headerFontSize: '24px',
|
|
922
|
+
},
|
|
923
|
+
confetti: {
|
|
924
|
+
colors: ['#4CAF50', '#2196F3'],
|
|
925
|
+
particleCount: 30,
|
|
926
|
+
shapes: ['circle'],
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
/**
|
|
930
|
+
* Gamified theme - Modern gaming aesthetic with sci-fi colors
|
|
931
|
+
* Dark navy backgrounds with cyan and orange accents (2024 gaming trend)
|
|
932
|
+
* Features square/badge-shaped achievement cards
|
|
933
|
+
*/
|
|
934
|
+
gamified: {
|
|
935
|
+
name: 'gamified',
|
|
936
|
+
notification: {
|
|
937
|
+
background: 'linear-gradient(135deg, rgba(5, 8, 22, 0.98) 0%, rgba(15, 23, 42, 0.98) 100%)',
|
|
938
|
+
textColor: '#22d3ee', // Bright cyan
|
|
939
|
+
accentColor: '#f97316', // Bright orange
|
|
940
|
+
borderRadius: '6px',
|
|
941
|
+
boxShadow: '0 8px 32px rgba(34, 211, 238, 0.4), 0 0 20px rgba(249, 115, 22, 0.3)',
|
|
942
|
+
fontSize: {
|
|
943
|
+
header: '13px',
|
|
944
|
+
title: '20px',
|
|
945
|
+
description: '15px',
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
modal: {
|
|
949
|
+
overlayColor: 'rgba(5, 8, 22, 0.85)',
|
|
950
|
+
background: 'linear-gradient(135deg, #0f172a 0%, #050816 100%)',
|
|
951
|
+
textColor: '#22d3ee', // Bright cyan
|
|
952
|
+
accentColor: '#f97316', // Bright orange
|
|
953
|
+
borderRadius: '8px',
|
|
954
|
+
headerFontSize: '32px',
|
|
955
|
+
achievementCardBorderRadius: '8px', // Square badge-like cards
|
|
956
|
+
achievementLayout: 'badge', // Use badge/grid layout instead of horizontal list
|
|
957
|
+
},
|
|
958
|
+
confetti: {
|
|
959
|
+
colors: ['#22d3ee', '#f97316', '#a855f7', '#eab308'], // Cyan, orange, purple, yellow
|
|
960
|
+
particleCount: 100,
|
|
961
|
+
shapes: ['circle', 'square'],
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
};
|
|
965
|
+
/**
|
|
966
|
+
* Retrieve a theme by name (internal use only)
|
|
967
|
+
* Only checks built-in themes
|
|
968
|
+
*
|
|
969
|
+
* @param name - Theme name (built-in only)
|
|
970
|
+
* @returns Theme configuration or undefined if not found
|
|
971
|
+
* @internal
|
|
972
|
+
*/
|
|
973
|
+
function getTheme(name) {
|
|
974
|
+
return builtInThemes[name];
|
|
975
|
+
}
|
|
976
|
+
|
|
864
977
|
const getPositionStyles = (position) => {
|
|
865
978
|
const base = {
|
|
866
979
|
position: 'fixed',
|
|
@@ -878,13 +991,34 @@ const getPositionStyles = (position) => {
|
|
|
878
991
|
return Object.assign(Object.assign({}, base), { bottom: 0, right: 0 });
|
|
879
992
|
}
|
|
880
993
|
};
|
|
881
|
-
const BadgesButton = ({ onClick, position, styles = {}, unlockedAchievements }) => {
|
|
882
|
-
|
|
994
|
+
const BadgesButton = ({ onClick, position = 'bottom-right', placement = 'fixed', styles = {}, unlockedAchievements, theme = 'modern', }) => {
|
|
995
|
+
// Get theme configuration for consistent styling
|
|
996
|
+
const themeConfig = getTheme(theme) || builtInThemes.modern;
|
|
997
|
+
const accentColor = themeConfig.notification.accentColor;
|
|
998
|
+
// Different styling for fixed vs inline placement
|
|
999
|
+
const baseStyles = placement === 'inline'
|
|
1000
|
+
? Object.assign({
|
|
1001
|
+
// Inline mode: looks like a navigation item
|
|
1002
|
+
backgroundColor: 'transparent', color: themeConfig.notification.textColor, padding: '12px 16px', border: 'none', borderRadius: '6px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px', fontSize: '15px', width: '100%', textAlign: 'left', transition: 'background-color 0.2s ease-in-out' }, styles) : Object.assign(Object.assign({
|
|
1003
|
+
// Fixed mode: floating button
|
|
1004
|
+
backgroundColor: accentColor, color: 'white', padding: '10px 20px', border: 'none', borderRadius: '20px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '16px', boxShadow: '0 2px 5px rgba(0,0,0,0.2)', transition: 'transform 0.2s ease-in-out' }, getPositionStyles(position)), styles);
|
|
883
1005
|
return (React.createElement("button", { onClick: onClick, style: baseStyles, onMouseEnter: (e) => {
|
|
884
|
-
|
|
1006
|
+
if (placement === 'inline') {
|
|
1007
|
+
// Inline mode: subtle background color change
|
|
1008
|
+
e.target.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
// Fixed mode: scale transformation
|
|
1012
|
+
e.target.style.transform = 'scale(1.05)';
|
|
1013
|
+
}
|
|
885
1014
|
}, onMouseLeave: (e) => {
|
|
886
|
-
|
|
887
|
-
|
|
1015
|
+
if (placement === 'inline') {
|
|
1016
|
+
e.target.style.backgroundColor = 'transparent';
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
e.target.style.transform = 'scale(1)';
|
|
1020
|
+
}
|
|
1021
|
+
}, "data-placement": placement, "data-testid": "badges-button" },
|
|
888
1022
|
"\uD83C\uDFC6 Achievements (",
|
|
889
1023
|
unlockedAchievements.length,
|
|
890
1024
|
")"));
|
|
@@ -901,90 +1035,181 @@ const defaultAchievementIcons = {
|
|
|
901
1035
|
star: '⭐',
|
|
902
1036
|
};
|
|
903
1037
|
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
top: 0,
|
|
910
|
-
left: 0,
|
|
911
|
-
right: 0,
|
|
912
|
-
bottom: 0,
|
|
913
|
-
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
|
914
|
-
display: 'flex',
|
|
915
|
-
alignItems: 'center',
|
|
916
|
-
justifyContent: 'center',
|
|
917
|
-
zIndex: 1000,
|
|
918
|
-
};
|
|
919
|
-
const defaultContentStyle = {
|
|
920
|
-
position: 'relative',
|
|
921
|
-
background: '#fff',
|
|
922
|
-
borderRadius: '8px',
|
|
923
|
-
padding: '20px',
|
|
924
|
-
maxWidth: '500px',
|
|
925
|
-
width: '90%',
|
|
926
|
-
maxHeight: '80vh',
|
|
927
|
-
overflow: 'auto',
|
|
928
|
-
};
|
|
929
|
-
const defaultHeaderStyle = {
|
|
930
|
-
display: 'flex',
|
|
931
|
-
justifyContent: 'space-between',
|
|
932
|
-
alignItems: 'center',
|
|
933
|
-
marginBottom: '20px',
|
|
934
|
-
};
|
|
935
|
-
const defaultCloseButtonStyle = {
|
|
936
|
-
background: 'none',
|
|
1038
|
+
const defaultStyles = {
|
|
1039
|
+
badgesButton: {
|
|
1040
|
+
backgroundColor: '#4CAF50',
|
|
1041
|
+
color: 'white',
|
|
1042
|
+
padding: '10px 20px',
|
|
937
1043
|
border: 'none',
|
|
938
|
-
|
|
1044
|
+
borderRadius: '20px',
|
|
939
1045
|
cursor: 'pointer',
|
|
940
|
-
padding: '0',
|
|
941
|
-
};
|
|
942
|
-
const defaultAchievementListStyle = {
|
|
943
1046
|
display: 'flex',
|
|
944
|
-
flexDirection: 'column',
|
|
945
|
-
gap: '16px',
|
|
946
|
-
};
|
|
947
|
-
const defaultAchievementItemStyle = {
|
|
948
|
-
display: 'flex',
|
|
949
|
-
gap: '16px',
|
|
950
|
-
padding: '16px',
|
|
951
|
-
borderRadius: '8px',
|
|
952
|
-
backgroundColor: '#f5f5f5',
|
|
953
1047
|
alignItems: 'center',
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1048
|
+
gap: '8px',
|
|
1049
|
+
fontSize: '16px',
|
|
1050
|
+
boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
|
|
1051
|
+
transition: 'transform 0.2s ease-in-out',
|
|
1052
|
+
},
|
|
1053
|
+
badgesModal: {
|
|
1054
|
+
overlay: {
|
|
1055
|
+
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
|
1056
|
+
display: 'flex',
|
|
1057
|
+
alignItems: 'center',
|
|
1058
|
+
justifyContent: 'center',
|
|
1059
|
+
zIndex: 1000,
|
|
1060
|
+
},
|
|
1061
|
+
content: {
|
|
1062
|
+
position: 'relative',
|
|
1063
|
+
background: '#fff',
|
|
1064
|
+
borderRadius: '8px',
|
|
1065
|
+
padding: '20px',
|
|
1066
|
+
maxWidth: '500px',
|
|
1067
|
+
width: '90%',
|
|
1068
|
+
maxHeight: '80vh',
|
|
1069
|
+
overflow: 'auto',
|
|
1070
|
+
},
|
|
1071
|
+
header: {
|
|
1072
|
+
display: 'flex',
|
|
1073
|
+
justifyContent: 'space-between',
|
|
1074
|
+
alignItems: 'center',
|
|
1075
|
+
marginBottom: '20px',
|
|
1076
|
+
},
|
|
1077
|
+
closeButton: {
|
|
1078
|
+
background: 'none',
|
|
1079
|
+
border: 'none',
|
|
1080
|
+
fontSize: '24px',
|
|
1081
|
+
cursor: 'pointer',
|
|
1082
|
+
padding: '0',
|
|
1083
|
+
},
|
|
1084
|
+
achievementList: {
|
|
1085
|
+
display: 'flex',
|
|
1086
|
+
flexDirection: 'column',
|
|
1087
|
+
gap: '16px',
|
|
1088
|
+
},
|
|
1089
|
+
achievementItem: {
|
|
1090
|
+
display: 'flex',
|
|
1091
|
+
gap: '16px',
|
|
1092
|
+
padding: '16px',
|
|
1093
|
+
borderRadius: '8px',
|
|
1094
|
+
backgroundColor: '#f5f5f5',
|
|
1095
|
+
alignItems: 'center',
|
|
1096
|
+
},
|
|
1097
|
+
achievementTitle: {
|
|
1098
|
+
margin: '0',
|
|
1099
|
+
fontSize: '18px',
|
|
1100
|
+
fontWeight: 'bold',
|
|
1101
|
+
},
|
|
1102
|
+
achievementDescription: {
|
|
1103
|
+
margin: '4px 0 0 0',
|
|
1104
|
+
color: '#666',
|
|
1105
|
+
},
|
|
1106
|
+
achievementIcon: {
|
|
1107
|
+
fontSize: '32px',
|
|
1108
|
+
display: 'flex',
|
|
1109
|
+
alignItems: 'center',
|
|
1110
|
+
justifyContent: 'center',
|
|
1111
|
+
},
|
|
1112
|
+
lockIcon: {
|
|
1113
|
+
fontSize: '24px',
|
|
1114
|
+
position: 'absolute',
|
|
1115
|
+
top: '50%',
|
|
1116
|
+
right: '16px',
|
|
1117
|
+
transform: 'translateY(-50%)',
|
|
1118
|
+
},
|
|
1119
|
+
lockedAchievementItem: {
|
|
1120
|
+
display: 'flex',
|
|
1121
|
+
gap: '16px',
|
|
1122
|
+
padding: '16px',
|
|
1123
|
+
borderRadius: '8px',
|
|
1124
|
+
backgroundColor: '#e0e0e0',
|
|
1125
|
+
alignItems: 'center',
|
|
1126
|
+
opacity: 0.5,
|
|
1127
|
+
},
|
|
1128
|
+
},
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
const BadgesModal = ({ isOpen, onClose, achievements, styles = {}, icons = {}, showAllAchievements = false, showUnlockConditions = false, allAchievements, }) => {
|
|
1132
|
+
// Merge custom icons with default icons, with custom icons taking precedence
|
|
1133
|
+
const mergedIcons = Object.assign(Object.assign({}, defaultAchievementIcons), icons);
|
|
970
1134
|
return (React.createElement(Modal, { isOpen: isOpen, onRequestClose: onClose, style: {
|
|
971
|
-
overlay: Object.assign(Object.assign({},
|
|
972
|
-
content: Object.assign(Object.assign({},
|
|
1135
|
+
overlay: Object.assign(Object.assign({}, defaultStyles.badgesModal.overlay), styles === null || styles === void 0 ? void 0 : styles.overlay),
|
|
1136
|
+
content: Object.assign(Object.assign({}, defaultStyles.badgesModal.content), styles === null || styles === void 0 ? void 0 : styles.content)
|
|
973
1137
|
}, contentLabel: "Achievements" },
|
|
974
|
-
React.createElement("div", { style: Object.assign(Object.assign({},
|
|
1138
|
+
React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.header), styles === null || styles === void 0 ? void 0 : styles.header) },
|
|
975
1139
|
React.createElement("h2", { style: { margin: 0 } }, "\uD83C\uDFC6 Achievements"),
|
|
976
|
-
React.createElement("button", { onClick: onClose, style: Object.assign(Object.assign({},
|
|
977
|
-
React.createElement("div", { style: Object.assign(Object.assign({},
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1140
|
+
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")),
|
|
1141
|
+
React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementList), styles === null || styles === void 0 ? void 0 : styles.achievementList) }, (() => {
|
|
1142
|
+
// Determine which achievements to display
|
|
1143
|
+
const achievementsToDisplay = showAllAchievements && allAchievements
|
|
1144
|
+
? allAchievements
|
|
1145
|
+
: achievements.map(a => (Object.assign(Object.assign({}, a), { isUnlocked: true })));
|
|
1146
|
+
return (React.createElement(React.Fragment, null,
|
|
1147
|
+
achievementsToDisplay.map((achievement) => {
|
|
1148
|
+
const isLocked = !achievement.isUnlocked;
|
|
1149
|
+
return (React.createElement("div", { key: achievement.achievementId, style: Object.assign(Object.assign(Object.assign({}, (isLocked
|
|
1150
|
+
? 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' }) },
|
|
1151
|
+
achievement.achievementIconKey && (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 }) }, achievement.achievementIconKey in mergedIcons
|
|
1152
|
+
? mergedIcons[achievement.achievementIconKey]
|
|
1153
|
+
: mergedIcons.default || '⭐')),
|
|
1154
|
+
React.createElement("div", { style: { flex: 1 } },
|
|
1155
|
+
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),
|
|
1156
|
+
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' }) },
|
|
1157
|
+
achievement.achievementDescription,
|
|
1158
|
+
showUnlockConditions && isLocked && achievement.achievementDescription && (React.createElement("span", { style: {
|
|
1159
|
+
display: 'block',
|
|
1160
|
+
fontSize: '12px',
|
|
1161
|
+
marginTop: '4px',
|
|
1162
|
+
fontStyle: 'italic',
|
|
1163
|
+
color: '#888'
|
|
1164
|
+
} },
|
|
1165
|
+
"\uD83D\uDD13 ",
|
|
1166
|
+
achievement.achievementDescription)))),
|
|
1167
|
+
isLocked && (React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.lockIcon), styles === null || styles === void 0 ? void 0 : styles.lockIcon) }, "\uD83D\uDD12"))));
|
|
1168
|
+
}),
|
|
1169
|
+
achievementsToDisplay.length === 0 && (React.createElement("p", { style: { textAlign: 'center', color: '#666' } }, "No achievements configured."))));
|
|
1170
|
+
})())));
|
|
986
1171
|
};
|
|
987
1172
|
|
|
1173
|
+
/**
|
|
1174
|
+
* Hook to track window dimensions
|
|
1175
|
+
* Replacement for react-use's useWindowSize
|
|
1176
|
+
*
|
|
1177
|
+
* @returns Object with width and height properties
|
|
1178
|
+
*
|
|
1179
|
+
* @example
|
|
1180
|
+
* ```tsx
|
|
1181
|
+
* const { width, height } = useWindowSize();
|
|
1182
|
+
* console.log(`Window size: ${width}x${height}`);
|
|
1183
|
+
* ```
|
|
1184
|
+
*/
|
|
1185
|
+
function useWindowSize() {
|
|
1186
|
+
const [size, setSize] = useState({
|
|
1187
|
+
width: typeof window !== 'undefined' ? window.innerWidth : 0,
|
|
1188
|
+
height: typeof window !== 'undefined' ? window.innerHeight : 0,
|
|
1189
|
+
});
|
|
1190
|
+
useEffect(() => {
|
|
1191
|
+
// Handle SSR - window may not be defined
|
|
1192
|
+
if (typeof window === 'undefined') {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const handleResize = () => {
|
|
1196
|
+
setSize({
|
|
1197
|
+
width: window.innerWidth,
|
|
1198
|
+
height: window.innerHeight,
|
|
1199
|
+
});
|
|
1200
|
+
};
|
|
1201
|
+
// Set initial size
|
|
1202
|
+
handleResize();
|
|
1203
|
+
// Add event listener
|
|
1204
|
+
window.addEventListener('resize', handleResize);
|
|
1205
|
+
// Cleanup
|
|
1206
|
+
return () => {
|
|
1207
|
+
window.removeEventListener('resize', handleResize);
|
|
1208
|
+
};
|
|
1209
|
+
}, []);
|
|
1210
|
+
return size;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
988
1213
|
const ConfettiWrapper = ({ show }) => {
|
|
989
1214
|
const { width, height } = useWindowSize();
|
|
990
1215
|
if (!show)
|
|
@@ -1299,8 +1524,547 @@ function preserveMetrics(current, imported) {
|
|
|
1299
1524
|
return preserved;
|
|
1300
1525
|
}
|
|
1301
1526
|
|
|
1527
|
+
/**
|
|
1528
|
+
* Built-in notification component
|
|
1529
|
+
* Modern, theme-aware achievement notification with smooth animations
|
|
1530
|
+
*/
|
|
1531
|
+
const BuiltInNotification = ({ achievement, onClose, duration = 5000, position = 'top-center', theme = 'modern', }) => {
|
|
1532
|
+
var _a, _b, _c;
|
|
1533
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
1534
|
+
const [isExiting, setIsExiting] = useState(false);
|
|
1535
|
+
// Get theme configuration
|
|
1536
|
+
const themeConfig = getTheme(theme) || builtInThemes.modern;
|
|
1537
|
+
const { notification: themeStyles } = themeConfig;
|
|
1538
|
+
useEffect(() => {
|
|
1539
|
+
// Slide in animation
|
|
1540
|
+
const showTimer = setTimeout(() => setIsVisible(true), 10);
|
|
1541
|
+
// Auto-dismiss
|
|
1542
|
+
const dismissTimer = setTimeout(() => {
|
|
1543
|
+
setIsExiting(true);
|
|
1544
|
+
setTimeout(() => onClose === null || onClose === void 0 ? void 0 : onClose(), 300);
|
|
1545
|
+
}, duration);
|
|
1546
|
+
return () => {
|
|
1547
|
+
clearTimeout(showTimer);
|
|
1548
|
+
clearTimeout(dismissTimer);
|
|
1549
|
+
};
|
|
1550
|
+
}, [duration, onClose]);
|
|
1551
|
+
const getPositionStyles = () => {
|
|
1552
|
+
const base = {
|
|
1553
|
+
position: 'fixed',
|
|
1554
|
+
zIndex: 9999,
|
|
1555
|
+
};
|
|
1556
|
+
switch (position) {
|
|
1557
|
+
case 'top-center':
|
|
1558
|
+
return Object.assign(Object.assign({}, base), { top: 20, left: '50%', transform: 'translateX(-50%)' });
|
|
1559
|
+
case 'top-left':
|
|
1560
|
+
return Object.assign(Object.assign({}, base), { top: 20, left: 20 });
|
|
1561
|
+
case 'top-right':
|
|
1562
|
+
return Object.assign(Object.assign({}, base), { top: 20, right: 20 });
|
|
1563
|
+
case 'bottom-center':
|
|
1564
|
+
return Object.assign(Object.assign({}, base), { bottom: 20, left: '50%', transform: 'translateX(-50%)' });
|
|
1565
|
+
case 'bottom-left':
|
|
1566
|
+
return Object.assign(Object.assign({}, base), { bottom: 20, left: 20 });
|
|
1567
|
+
case 'bottom-right':
|
|
1568
|
+
return Object.assign(Object.assign({}, base), { bottom: 20, right: 20 });
|
|
1569
|
+
default:
|
|
1570
|
+
return Object.assign(Object.assign({}, base), { top: 20, left: '50%', transform: 'translateX(-50%)' });
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
const containerStyles = Object.assign(Object.assign({}, getPositionStyles()), { background: themeStyles.background, borderRadius: themeStyles.borderRadius, boxShadow: themeStyles.boxShadow, padding: '16px 24px', minWidth: '320px', maxWidth: '500px', display: 'flex', alignItems: 'center', gap: '16px', opacity: isVisible && !isExiting ? 1 : 0, transform: position.startsWith('top')
|
|
1574
|
+
? `translateY(${isVisible && !isExiting ? '0' : '-20px'}) ${position.includes('center') ? 'translateX(-50%)' : ''}`
|
|
1575
|
+
: `translateY(${isVisible && !isExiting ? '0' : '20px'}) ${position.includes('center') ? 'translateX(-50%)' : ''}`, transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', pointerEvents: isVisible ? 'auto' : 'none' });
|
|
1576
|
+
const iconStyles = {
|
|
1577
|
+
fontSize: '48px',
|
|
1578
|
+
lineHeight: 1,
|
|
1579
|
+
flexShrink: 0,
|
|
1580
|
+
};
|
|
1581
|
+
const contentStyles = {
|
|
1582
|
+
flex: 1,
|
|
1583
|
+
color: themeStyles.textColor,
|
|
1584
|
+
minWidth: 0,
|
|
1585
|
+
};
|
|
1586
|
+
const headerStyles = {
|
|
1587
|
+
fontSize: ((_a = themeStyles.fontSize) === null || _a === void 0 ? void 0 : _a.header) || '12px',
|
|
1588
|
+
textTransform: 'uppercase',
|
|
1589
|
+
letterSpacing: '1px',
|
|
1590
|
+
opacity: 0.8,
|
|
1591
|
+
marginBottom: '4px',
|
|
1592
|
+
color: themeStyles.accentColor,
|
|
1593
|
+
fontWeight: 600,
|
|
1594
|
+
};
|
|
1595
|
+
const titleStyles = {
|
|
1596
|
+
fontSize: ((_b = themeStyles.fontSize) === null || _b === void 0 ? void 0 : _b.title) || '18px',
|
|
1597
|
+
fontWeight: 'bold',
|
|
1598
|
+
marginBottom: '4px',
|
|
1599
|
+
overflow: 'hidden',
|
|
1600
|
+
textOverflow: 'ellipsis',
|
|
1601
|
+
whiteSpace: 'nowrap',
|
|
1602
|
+
};
|
|
1603
|
+
const descriptionStyles = {
|
|
1604
|
+
fontSize: ((_c = themeStyles.fontSize) === null || _c === void 0 ? void 0 : _c.description) || '14px',
|
|
1605
|
+
opacity: 0.9,
|
|
1606
|
+
overflow: 'hidden',
|
|
1607
|
+
textOverflow: 'ellipsis',
|
|
1608
|
+
display: '-webkit-box',
|
|
1609
|
+
WebkitLineClamp: 2,
|
|
1610
|
+
WebkitBoxOrient: 'vertical',
|
|
1611
|
+
};
|
|
1612
|
+
const closeButtonStyles = {
|
|
1613
|
+
background: 'none',
|
|
1614
|
+
border: 'none',
|
|
1615
|
+
color: themeStyles.textColor,
|
|
1616
|
+
fontSize: '24px',
|
|
1617
|
+
cursor: 'pointer',
|
|
1618
|
+
opacity: 0.6,
|
|
1619
|
+
transition: 'opacity 0.2s',
|
|
1620
|
+
padding: '4px',
|
|
1621
|
+
flexShrink: 0,
|
|
1622
|
+
lineHeight: 1,
|
|
1623
|
+
};
|
|
1624
|
+
return (React.createElement("div", { style: containerStyles, "data-testid": "built-in-notification" },
|
|
1625
|
+
React.createElement("div", { style: iconStyles }, achievement.icon),
|
|
1626
|
+
React.createElement("div", { style: contentStyles },
|
|
1627
|
+
React.createElement("div", { style: headerStyles }, "Achievement Unlocked!"),
|
|
1628
|
+
React.createElement("div", { style: titleStyles }, achievement.title),
|
|
1629
|
+
achievement.description && (React.createElement("div", { style: descriptionStyles }, achievement.description))),
|
|
1630
|
+
React.createElement("button", { onClick: () => {
|
|
1631
|
+
setIsExiting(true);
|
|
1632
|
+
setTimeout(() => onClose === null || onClose === void 0 ? void 0 : onClose(), 300);
|
|
1633
|
+
}, style: closeButtonStyles, onMouseEnter: (e) => (e.currentTarget.style.opacity = '1'), onMouseLeave: (e) => (e.currentTarget.style.opacity = '0.6'), "aria-label": "Close notification" }, "\u00D7")));
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
/**
|
|
1637
|
+
* Built-in confetti component
|
|
1638
|
+
* Lightweight CSS-based confetti animation
|
|
1639
|
+
*/
|
|
1640
|
+
const BuiltInConfetti = ({ show, duration = 5000, particleCount = 50, colors = ['#FFD700', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'], }) => {
|
|
1641
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
1642
|
+
const { width, height } = useWindowSize();
|
|
1643
|
+
useEffect(() => {
|
|
1644
|
+
if (show) {
|
|
1645
|
+
setIsVisible(true);
|
|
1646
|
+
const timer = setTimeout(() => setIsVisible(false), duration);
|
|
1647
|
+
return () => clearTimeout(timer);
|
|
1648
|
+
}
|
|
1649
|
+
else {
|
|
1650
|
+
setIsVisible(false);
|
|
1651
|
+
}
|
|
1652
|
+
}, [show, duration]);
|
|
1653
|
+
if (!isVisible)
|
|
1654
|
+
return null;
|
|
1655
|
+
const containerStyles = {
|
|
1656
|
+
position: 'fixed',
|
|
1657
|
+
top: 0,
|
|
1658
|
+
left: 0,
|
|
1659
|
+
width: '100%',
|
|
1660
|
+
height: '100%',
|
|
1661
|
+
pointerEvents: 'none',
|
|
1662
|
+
zIndex: 10001,
|
|
1663
|
+
overflow: 'hidden',
|
|
1664
|
+
};
|
|
1665
|
+
// Generate particles
|
|
1666
|
+
const particles = Array.from({ length: particleCount }, (_, i) => {
|
|
1667
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
1668
|
+
const startX = Math.random() * width;
|
|
1669
|
+
const rotation = Math.random() * 360;
|
|
1670
|
+
const fallDuration = 3 + Math.random() * 2; // 3-5 seconds
|
|
1671
|
+
const delay = Math.random() * 0.5; // 0-0.5s delay
|
|
1672
|
+
const shape = Math.random() > 0.5 ? 'circle' : 'square';
|
|
1673
|
+
const particleStyles = {
|
|
1674
|
+
position: 'absolute',
|
|
1675
|
+
top: '-20px',
|
|
1676
|
+
left: `${startX}px`,
|
|
1677
|
+
width: '10px',
|
|
1678
|
+
height: '10px',
|
|
1679
|
+
backgroundColor: color,
|
|
1680
|
+
borderRadius: shape === 'circle' ? '50%' : '0',
|
|
1681
|
+
transform: `rotate(${rotation}deg)`,
|
|
1682
|
+
animation: `confettiFall ${fallDuration}s linear ${delay}s forwards`,
|
|
1683
|
+
opacity: 0.9,
|
|
1684
|
+
};
|
|
1685
|
+
return React.createElement("div", { key: i, style: particleStyles, "data-testid": "confetti-particle" });
|
|
1686
|
+
});
|
|
1687
|
+
return (React.createElement(React.Fragment, null,
|
|
1688
|
+
React.createElement("style", null, `
|
|
1689
|
+
@keyframes confettiFall {
|
|
1690
|
+
0% {
|
|
1691
|
+
transform: translateY(0) rotate(0deg);
|
|
1692
|
+
opacity: 1;
|
|
1693
|
+
}
|
|
1694
|
+
100% {
|
|
1695
|
+
transform: translateY(${height + 50}px) rotate(720deg);
|
|
1696
|
+
opacity: 0;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
`),
|
|
1700
|
+
React.createElement("div", { style: containerStyles, "data-testid": "built-in-confetti" }, particles)));
|
|
1701
|
+
};
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* Legacy UI library detection system
|
|
1705
|
+
* Attempts to dynamically import external UI libraries
|
|
1706
|
+
* Shows deprecation warnings when detected
|
|
1707
|
+
*/
|
|
1708
|
+
let cachedLibraries = null;
|
|
1709
|
+
let detectionAttempted = false;
|
|
1710
|
+
let deprecationWarningShown = false;
|
|
1711
|
+
/**
|
|
1712
|
+
* Attempts to dynamically import legacy UI libraries
|
|
1713
|
+
* Uses try/catch to gracefully handle missing dependencies
|
|
1714
|
+
* Caches result to avoid multiple import attempts
|
|
1715
|
+
*
|
|
1716
|
+
* @returns Promise resolving to LegacyLibraries object
|
|
1717
|
+
*/
|
|
1718
|
+
function detectLegacyLibraries() {
|
|
1719
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1720
|
+
if (detectionAttempted && cachedLibraries !== null) {
|
|
1721
|
+
return cachedLibraries;
|
|
1722
|
+
}
|
|
1723
|
+
detectionAttempted = true;
|
|
1724
|
+
const libraries = {};
|
|
1725
|
+
// Try to import react-toastify
|
|
1726
|
+
try {
|
|
1727
|
+
const toastifyModule = yield import('react-toastify');
|
|
1728
|
+
libraries.toast = toastifyModule.toast;
|
|
1729
|
+
libraries.ToastContainer = toastifyModule.ToastContainer;
|
|
1730
|
+
}
|
|
1731
|
+
catch (_a) {
|
|
1732
|
+
// Not installed, will use built-in notification
|
|
1733
|
+
}
|
|
1734
|
+
// Try to import react-modal
|
|
1735
|
+
try {
|
|
1736
|
+
const modalModule = yield import('react-modal');
|
|
1737
|
+
libraries.Modal = modalModule.default;
|
|
1738
|
+
}
|
|
1739
|
+
catch (_b) {
|
|
1740
|
+
// Not installed, will use built-in modal
|
|
1741
|
+
}
|
|
1742
|
+
// Try to import react-confetti
|
|
1743
|
+
try {
|
|
1744
|
+
const confettiModule = yield import('react-confetti');
|
|
1745
|
+
libraries.Confetti = confettiModule.default;
|
|
1746
|
+
}
|
|
1747
|
+
catch (_c) {
|
|
1748
|
+
// Not installed, will use built-in confetti
|
|
1749
|
+
}
|
|
1750
|
+
// Try to import react-use (only for useWindowSize)
|
|
1751
|
+
try {
|
|
1752
|
+
const reactUseModule = yield import('react-use');
|
|
1753
|
+
libraries.useWindowSize = reactUseModule.useWindowSize;
|
|
1754
|
+
}
|
|
1755
|
+
catch (_d) {
|
|
1756
|
+
// Not installed, will use built-in useWindowSize
|
|
1757
|
+
}
|
|
1758
|
+
cachedLibraries = libraries;
|
|
1759
|
+
// Show deprecation warning if ANY legacy library is found
|
|
1760
|
+
if (!deprecationWarningShown && Object.keys(libraries).length > 0) {
|
|
1761
|
+
const foundLibraries = [];
|
|
1762
|
+
if (libraries.toast)
|
|
1763
|
+
foundLibraries.push('react-toastify');
|
|
1764
|
+
if (libraries.Modal)
|
|
1765
|
+
foundLibraries.push('react-modal');
|
|
1766
|
+
if (libraries.Confetti)
|
|
1767
|
+
foundLibraries.push('react-confetti');
|
|
1768
|
+
if (libraries.useWindowSize)
|
|
1769
|
+
foundLibraries.push('react-use');
|
|
1770
|
+
console.warn(`[react-achievements] DEPRECATION WARNING: External UI dependencies (${foundLibraries.join(', ')}) are deprecated and will become fully optional in v4.0.0.\n\n` +
|
|
1771
|
+
`The library now includes built-in UI components with modern design and theme support.\n\n` +
|
|
1772
|
+
`To migrate:\n` +
|
|
1773
|
+
`1. Add "useBuiltInUI={true}" to your AchievementProvider\n` +
|
|
1774
|
+
`2. Test your application (UI will change to modern theme)\n` +
|
|
1775
|
+
`3. Optionally customize with theme="minimal" or theme="gamified"\n` +
|
|
1776
|
+
`4. Remove external dependencies from package.json\n\n` +
|
|
1777
|
+
`To silence this warning, set useBuiltInUI={true} in AchievementProvider.\n\n` +
|
|
1778
|
+
`Learn more: https://github.com/dave-b-b/react-achievements#migration-guide`);
|
|
1779
|
+
deprecationWarningShown = true;
|
|
1780
|
+
}
|
|
1781
|
+
return libraries;
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* Built-in modal component
|
|
1787
|
+
* Modern, theme-aware achievement modal with smooth animations
|
|
1788
|
+
*/
|
|
1789
|
+
const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'modern', }) => {
|
|
1790
|
+
// Merge custom icons with defaults
|
|
1791
|
+
const mergedIcons = Object.assign(Object.assign({}, defaultAchievementIcons), icons);
|
|
1792
|
+
// Get theme configuration
|
|
1793
|
+
const themeConfig = getTheme(theme) || builtInThemes.modern;
|
|
1794
|
+
const { modal: themeStyles } = themeConfig;
|
|
1795
|
+
useEffect(() => {
|
|
1796
|
+
if (isOpen) {
|
|
1797
|
+
// Lock body scroll when modal is open
|
|
1798
|
+
document.body.style.overflow = 'hidden';
|
|
1799
|
+
}
|
|
1800
|
+
else {
|
|
1801
|
+
// Restore body scroll
|
|
1802
|
+
document.body.style.overflow = '';
|
|
1803
|
+
}
|
|
1804
|
+
return () => {
|
|
1805
|
+
document.body.style.overflow = '';
|
|
1806
|
+
};
|
|
1807
|
+
}, [isOpen]);
|
|
1808
|
+
if (!isOpen)
|
|
1809
|
+
return null;
|
|
1810
|
+
const overlayStyles = {
|
|
1811
|
+
position: 'fixed',
|
|
1812
|
+
top: 0,
|
|
1813
|
+
left: 0,
|
|
1814
|
+
right: 0,
|
|
1815
|
+
bottom: 0,
|
|
1816
|
+
backgroundColor: themeStyles.overlayColor,
|
|
1817
|
+
display: 'flex',
|
|
1818
|
+
alignItems: 'center',
|
|
1819
|
+
justifyContent: 'center',
|
|
1820
|
+
zIndex: 10000,
|
|
1821
|
+
animation: 'fadeIn 0.3s ease-in-out',
|
|
1822
|
+
};
|
|
1823
|
+
const modalStyles = {
|
|
1824
|
+
background: themeStyles.background,
|
|
1825
|
+
borderRadius: themeStyles.borderRadius,
|
|
1826
|
+
padding: '32px',
|
|
1827
|
+
maxWidth: '600px',
|
|
1828
|
+
width: '90%',
|
|
1829
|
+
maxHeight: '80vh',
|
|
1830
|
+
overflow: 'auto',
|
|
1831
|
+
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.5)',
|
|
1832
|
+
animation: 'scaleIn 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
1833
|
+
position: 'relative',
|
|
1834
|
+
};
|
|
1835
|
+
const headerStyles = {
|
|
1836
|
+
display: 'flex',
|
|
1837
|
+
justifyContent: 'space-between',
|
|
1838
|
+
alignItems: 'center',
|
|
1839
|
+
marginBottom: '24px',
|
|
1840
|
+
};
|
|
1841
|
+
const titleStyles = {
|
|
1842
|
+
margin: 0,
|
|
1843
|
+
color: themeStyles.textColor,
|
|
1844
|
+
fontSize: themeStyles.headerFontSize || '28px',
|
|
1845
|
+
fontWeight: 'bold',
|
|
1846
|
+
};
|
|
1847
|
+
const closeButtonStyles = {
|
|
1848
|
+
background: 'none',
|
|
1849
|
+
border: 'none',
|
|
1850
|
+
fontSize: '32px',
|
|
1851
|
+
cursor: 'pointer',
|
|
1852
|
+
color: themeStyles.textColor,
|
|
1853
|
+
opacity: 0.6,
|
|
1854
|
+
transition: 'opacity 0.2s',
|
|
1855
|
+
padding: 0,
|
|
1856
|
+
lineHeight: 1,
|
|
1857
|
+
};
|
|
1858
|
+
const isBadgeLayout = themeStyles.achievementLayout === 'badge';
|
|
1859
|
+
const listStyles = isBadgeLayout
|
|
1860
|
+
? {
|
|
1861
|
+
display: 'grid',
|
|
1862
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
|
|
1863
|
+
gap: '16px',
|
|
1864
|
+
}
|
|
1865
|
+
: {
|
|
1866
|
+
display: 'flex',
|
|
1867
|
+
flexDirection: 'column',
|
|
1868
|
+
gap: '12px',
|
|
1869
|
+
};
|
|
1870
|
+
const getAchievementItemStyles = (isUnlocked) => {
|
|
1871
|
+
const baseStyles = {
|
|
1872
|
+
borderRadius: themeStyles.achievementCardBorderRadius || '12px',
|
|
1873
|
+
backgroundColor: isUnlocked
|
|
1874
|
+
? `${themeStyles.accentColor}1A` // 10% opacity
|
|
1875
|
+
: 'rgba(255, 255, 255, 0.05)',
|
|
1876
|
+
border: `2px solid ${isUnlocked ? themeStyles.accentColor : 'rgba(255, 255, 255, 0.1)'}`,
|
|
1877
|
+
opacity: isUnlocked ? 1 : 0.5,
|
|
1878
|
+
transition: 'all 0.2s',
|
|
1879
|
+
};
|
|
1880
|
+
if (isBadgeLayout) {
|
|
1881
|
+
// Badge layout: vertical, centered, square-ish
|
|
1882
|
+
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' });
|
|
1883
|
+
}
|
|
1884
|
+
else {
|
|
1885
|
+
// Horizontal layout (default)
|
|
1886
|
+
return Object.assign(Object.assign({}, baseStyles), { display: 'flex', gap: '16px', padding: '16px' });
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
const getIconContainerStyles = (isUnlocked) => {
|
|
1890
|
+
if (isBadgeLayout) {
|
|
1891
|
+
return {
|
|
1892
|
+
fontSize: '48px',
|
|
1893
|
+
lineHeight: 1,
|
|
1894
|
+
marginBottom: '8px',
|
|
1895
|
+
opacity: isUnlocked ? 1 : 0.3,
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
return {
|
|
1899
|
+
fontSize: '40px',
|
|
1900
|
+
flexShrink: 0,
|
|
1901
|
+
lineHeight: 1,
|
|
1902
|
+
opacity: isUnlocked ? 1 : 0.3,
|
|
1903
|
+
};
|
|
1904
|
+
};
|
|
1905
|
+
const contentStyles = isBadgeLayout
|
|
1906
|
+
? {
|
|
1907
|
+
width: '100%',
|
|
1908
|
+
}
|
|
1909
|
+
: {
|
|
1910
|
+
flex: 1,
|
|
1911
|
+
minWidth: 0,
|
|
1912
|
+
};
|
|
1913
|
+
const achievementTitleStyles = isBadgeLayout
|
|
1914
|
+
? {
|
|
1915
|
+
margin: '0 0 4px 0',
|
|
1916
|
+
color: themeStyles.textColor,
|
|
1917
|
+
fontSize: '14px',
|
|
1918
|
+
fontWeight: 'bold',
|
|
1919
|
+
lineHeight: '1.3',
|
|
1920
|
+
}
|
|
1921
|
+
: {
|
|
1922
|
+
margin: '0 0 8px 0',
|
|
1923
|
+
color: themeStyles.textColor,
|
|
1924
|
+
fontSize: '18px',
|
|
1925
|
+
fontWeight: 'bold',
|
|
1926
|
+
overflow: 'hidden',
|
|
1927
|
+
textOverflow: 'ellipsis',
|
|
1928
|
+
whiteSpace: 'nowrap',
|
|
1929
|
+
};
|
|
1930
|
+
const achievementDescriptionStyles = isBadgeLayout
|
|
1931
|
+
? {
|
|
1932
|
+
margin: 0,
|
|
1933
|
+
color: themeStyles.textColor,
|
|
1934
|
+
opacity: 0.7,
|
|
1935
|
+
fontSize: '11px',
|
|
1936
|
+
lineHeight: '1.3',
|
|
1937
|
+
}
|
|
1938
|
+
: {
|
|
1939
|
+
margin: 0,
|
|
1940
|
+
color: themeStyles.textColor,
|
|
1941
|
+
opacity: 0.8,
|
|
1942
|
+
fontSize: '14px',
|
|
1943
|
+
};
|
|
1944
|
+
const getLockIconStyles = () => {
|
|
1945
|
+
if (isBadgeLayout) {
|
|
1946
|
+
return {
|
|
1947
|
+
position: 'absolute',
|
|
1948
|
+
top: '8px',
|
|
1949
|
+
right: '8px',
|
|
1950
|
+
fontSize: '18px',
|
|
1951
|
+
opacity: 0.6,
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
return {
|
|
1955
|
+
fontSize: '24px',
|
|
1956
|
+
flexShrink: 0,
|
|
1957
|
+
opacity: 0.5,
|
|
1958
|
+
};
|
|
1959
|
+
};
|
|
1960
|
+
return (React.createElement(React.Fragment, null,
|
|
1961
|
+
React.createElement("style", null, `
|
|
1962
|
+
@keyframes fadeIn {
|
|
1963
|
+
from { opacity: 0; }
|
|
1964
|
+
to { opacity: 1; }
|
|
1965
|
+
}
|
|
1966
|
+
@keyframes scaleIn {
|
|
1967
|
+
from {
|
|
1968
|
+
transform: scale(0.9);
|
|
1969
|
+
opacity: 0;
|
|
1970
|
+
}
|
|
1971
|
+
to {
|
|
1972
|
+
transform: scale(1);
|
|
1973
|
+
opacity: 1;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
`),
|
|
1977
|
+
React.createElement("div", { style: overlayStyles, onClick: onClose, "data-testid": "built-in-modal-overlay" },
|
|
1978
|
+
React.createElement("div", { style: modalStyles, onClick: (e) => e.stopPropagation(), "data-testid": "built-in-modal" },
|
|
1979
|
+
React.createElement("div", { style: headerStyles },
|
|
1980
|
+
React.createElement("h2", { style: titleStyles }, "\uD83C\uDFC6 Achievements"),
|
|
1981
|
+
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")),
|
|
1982
|
+
React.createElement("div", { style: listStyles }, achievements.length === 0 ? (React.createElement("div", { style: { textAlign: 'center', padding: '40px 20px', color: themeStyles.textColor, opacity: 0.6 } }, "No achievements yet. Start exploring to unlock them!")) : (achievements.map((achievement) => {
|
|
1983
|
+
const icon = (achievement.achievementIconKey &&
|
|
1984
|
+
mergedIcons[achievement.achievementIconKey]) ||
|
|
1985
|
+
mergedIcons.default ||
|
|
1986
|
+
'⭐';
|
|
1987
|
+
return (React.createElement("div", { key: achievement.achievementId, style: Object.assign(Object.assign({}, getAchievementItemStyles(achievement.isUnlocked)), { position: isBadgeLayout ? 'relative' : 'static' }) },
|
|
1988
|
+
React.createElement("div", { style: getIconContainerStyles(achievement.isUnlocked) }, icon),
|
|
1989
|
+
React.createElement("div", { style: contentStyles },
|
|
1990
|
+
React.createElement("h3", { style: achievementTitleStyles }, achievement.achievementTitle),
|
|
1991
|
+
React.createElement("p", { style: achievementDescriptionStyles }, achievement.achievementDescription)),
|
|
1992
|
+
!achievement.isUnlocked && (React.createElement("div", { style: getLockIconStyles() }, "\uD83D\uDD12"))));
|
|
1993
|
+
})))))));
|
|
1994
|
+
};
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* Legacy library wrappers for backwards compatibility
|
|
1998
|
+
* Wraps external UI libraries to match our component interfaces
|
|
1999
|
+
*/
|
|
2000
|
+
/**
|
|
2001
|
+
* Wrapper for react-toastify toast notifications
|
|
2002
|
+
* Falls back to built-in notification if not available
|
|
2003
|
+
*/
|
|
2004
|
+
const createLegacyToastNotification = (libraries) => {
|
|
2005
|
+
return ({ achievement, onClose }) => {
|
|
2006
|
+
const { toast } = libraries;
|
|
2007
|
+
useEffect(() => {
|
|
2008
|
+
if (!toast)
|
|
2009
|
+
return;
|
|
2010
|
+
// Call toast.success with achievement content
|
|
2011
|
+
toast.success(React.createElement("div", { style: { display: 'flex', alignItems: 'center' } },
|
|
2012
|
+
React.createElement("span", { style: { fontSize: '2em', marginRight: '10px' } }, achievement.icon),
|
|
2013
|
+
React.createElement("div", null,
|
|
2014
|
+
React.createElement("div", { style: { fontSize: '12px', opacity: 0.8, marginBottom: '4px' } }, "Achievement Unlocked!"),
|
|
2015
|
+
React.createElement("div", { style: { fontWeight: 'bold', marginBottom: '4px' } }, achievement.title),
|
|
2016
|
+
achievement.description && (React.createElement("div", { style: { fontSize: '13px', opacity: 0.9 } }, achievement.description)))), {
|
|
2017
|
+
position: 'top-right',
|
|
2018
|
+
autoClose: 5000,
|
|
2019
|
+
hideProgressBar: false,
|
|
2020
|
+
closeOnClick: true,
|
|
2021
|
+
pauseOnHover: true,
|
|
2022
|
+
draggable: true,
|
|
2023
|
+
toastId: achievement.id,
|
|
2024
|
+
onClose,
|
|
2025
|
+
});
|
|
2026
|
+
}, [achievement, toast, onClose]);
|
|
2027
|
+
return null; // Toast handles its own rendering
|
|
2028
|
+
};
|
|
2029
|
+
};
|
|
2030
|
+
/**
|
|
2031
|
+
* Wrapper for react-confetti Confetti component
|
|
2032
|
+
* Falls back to built-in confetti if not available
|
|
2033
|
+
*/
|
|
2034
|
+
const createLegacyConfettiWrapper = (libraries) => {
|
|
2035
|
+
return ({ show, duration = 5000, particleCount = 200, colors }) => {
|
|
2036
|
+
const { Confetti, useWindowSize: legacyUseWindowSize } = libraries;
|
|
2037
|
+
// If Confetti not available, use built-in
|
|
2038
|
+
if (!Confetti) {
|
|
2039
|
+
return (React.createElement(BuiltInConfetti, { show: show, duration: duration, particleCount: particleCount, colors: colors }));
|
|
2040
|
+
}
|
|
2041
|
+
// Use react-confetti with react-use's useWindowSize if available
|
|
2042
|
+
// Otherwise fall back to default dimensions
|
|
2043
|
+
let width = 0;
|
|
2044
|
+
let height = 0;
|
|
2045
|
+
if (legacyUseWindowSize) {
|
|
2046
|
+
const size = legacyUseWindowSize();
|
|
2047
|
+
width = size.width;
|
|
2048
|
+
height = size.height;
|
|
2049
|
+
}
|
|
2050
|
+
else if (typeof window !== 'undefined') {
|
|
2051
|
+
width = window.innerWidth;
|
|
2052
|
+
height = window.innerHeight;
|
|
2053
|
+
}
|
|
2054
|
+
if (!show)
|
|
2055
|
+
return null;
|
|
2056
|
+
return (React.createElement(Confetti, { width: width, height: height, numberOfPieces: particleCount, recycle: false, colors: colors, style: {
|
|
2057
|
+
position: 'fixed',
|
|
2058
|
+
top: 0,
|
|
2059
|
+
left: 0,
|
|
2060
|
+
zIndex: 10001,
|
|
2061
|
+
pointerEvents: 'none',
|
|
2062
|
+
} }));
|
|
2063
|
+
};
|
|
2064
|
+
};
|
|
2065
|
+
|
|
1302
2066
|
const AchievementContext = createContext(undefined);
|
|
1303
|
-
const AchievementProvider = ({ achievements: achievementsConfig, storage = StorageType.Local, children, icons = {}, onError, restApiConfig, }) => {
|
|
2067
|
+
const AchievementProvider = ({ achievements: achievementsConfig, storage = StorageType.Local, children, icons = {}, onError, restApiConfig, ui = {}, useBuiltInUI = false, }) => {
|
|
1304
2068
|
// Normalize the configuration to the complex format
|
|
1305
2069
|
const achievements = normalizeAchievements(achievementsConfig);
|
|
1306
2070
|
const [achievementState, setAchievementState] = useState({
|
|
@@ -1314,6 +2078,10 @@ const AchievementProvider = ({ achievements: achievementsConfig, storage = Stora
|
|
|
1314
2078
|
const metricsUpdatedRef = useRef(false);
|
|
1315
2079
|
const [showConfetti, setShowConfetti] = useState(false);
|
|
1316
2080
|
const [_currentAchievement, setCurrentAchievement] = useState(null);
|
|
2081
|
+
// NEW: UI component resolution state (v3.6.0)
|
|
2082
|
+
const [legacyLibraries, setLegacyLibraries] = useState(null);
|
|
2083
|
+
const [uiReady, setUiReady] = useState(useBuiltInUI); // Ready immediately if forcing built-in
|
|
2084
|
+
const [currentNotification, setCurrentNotification] = useState(null);
|
|
1317
2085
|
if (!storageRef.current) {
|
|
1318
2086
|
if (typeof storage === 'string') {
|
|
1319
2087
|
// StorageType enum
|
|
@@ -1395,6 +2163,30 @@ const AchievementProvider = ({ achievements: achievementsConfig, storage = Stora
|
|
|
1395
2163
|
console.error('Error saving notified achievements', e);
|
|
1396
2164
|
}
|
|
1397
2165
|
};
|
|
2166
|
+
// NEW: Detect legacy UI libraries on mount (v3.6.0)
|
|
2167
|
+
useEffect(() => {
|
|
2168
|
+
if (useBuiltInUI) {
|
|
2169
|
+
// User explicitly wants built-in UI, skip detection
|
|
2170
|
+
setUiReady(true);
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
// Attempt to detect legacy libraries
|
|
2174
|
+
detectLegacyLibraries().then((libs) => {
|
|
2175
|
+
setLegacyLibraries(libs);
|
|
2176
|
+
setUiReady(true);
|
|
2177
|
+
});
|
|
2178
|
+
}, [useBuiltInUI]);
|
|
2179
|
+
// NEW: Resolve UI components based on detection and config (v3.6.0)
|
|
2180
|
+
const NotificationComponent = ui.NotificationComponent ||
|
|
2181
|
+
(useBuiltInUI ? BuiltInNotification :
|
|
2182
|
+
legacyLibraries && Object.keys(legacyLibraries).length > 0 && legacyLibraries.toast
|
|
2183
|
+
? createLegacyToastNotification(legacyLibraries)
|
|
2184
|
+
: BuiltInNotification);
|
|
2185
|
+
const ConfettiComponentResolved = ui.ConfettiComponent ||
|
|
2186
|
+
(useBuiltInUI ? BuiltInConfetti :
|
|
2187
|
+
legacyLibraries && Object.keys(legacyLibraries).length > 0 && legacyLibraries.Confetti
|
|
2188
|
+
? createLegacyConfettiWrapper(legacyLibraries)
|
|
2189
|
+
: BuiltInConfetti);
|
|
1398
2190
|
useEffect(() => {
|
|
1399
2191
|
if (!initialLoadRef.current) {
|
|
1400
2192
|
const savedUnlocked = storageImpl.getUnlockedAchievements() || [];
|
|
@@ -1444,30 +2236,21 @@ const AchievementProvider = ({ achievements: achievementsConfig, storage = Stora
|
|
|
1444
2236
|
const allUnlocked = [...achievementState.unlocked, ...newlyUnlockedAchievements];
|
|
1445
2237
|
setAchievementState(prev => (Object.assign(Object.assign({}, prev), { unlocked: allUnlocked })));
|
|
1446
2238
|
storageImpl.setUnlockedAchievements(allUnlocked);
|
|
1447
|
-
if (achievementToShow) {
|
|
2239
|
+
if (achievementToShow && (ui.enableNotifications !== false)) {
|
|
1448
2240
|
const achievement = achievementToShow;
|
|
1449
|
-
//
|
|
2241
|
+
// Get icon to display
|
|
1450
2242
|
let iconToDisplay = '🏆';
|
|
1451
2243
|
if (achievement.achievementIconKey && achievement.achievementIconKey in icons) {
|
|
1452
2244
|
iconToDisplay = icons[achievement.achievementIconKey];
|
|
1453
2245
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
:
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
React.createElement("div", { style: { color: '#666' } }, achievement.achievementDescription))), {
|
|
1463
|
-
position: "top-right",
|
|
1464
|
-
autoClose: 5000,
|
|
1465
|
-
hideProgressBar: false,
|
|
1466
|
-
closeOnClick: true,
|
|
1467
|
-
pauseOnHover: true,
|
|
1468
|
-
draggable: true,
|
|
1469
|
-
progress: undefined,
|
|
1470
|
-
toastId
|
|
2246
|
+
// NEW: Use resolved notification component (v3.6.0)
|
|
2247
|
+
setCurrentNotification({
|
|
2248
|
+
achievement: {
|
|
2249
|
+
id: achievement.achievementId || `achievement-${Date.now()}`,
|
|
2250
|
+
title: achievement.achievementTitle || 'Achievement Unlocked!',
|
|
2251
|
+
description: achievement.achievementDescription || '',
|
|
2252
|
+
icon: iconToDisplay,
|
|
2253
|
+
},
|
|
1471
2254
|
});
|
|
1472
2255
|
// Update seen achievements
|
|
1473
2256
|
if (achievement.achievementId) {
|
|
@@ -1486,6 +2269,24 @@ const AchievementProvider = ({ achievements: achievementsConfig, storage = Stora
|
|
|
1486
2269
|
}
|
|
1487
2270
|
}
|
|
1488
2271
|
}, [metrics, achievementState.unlocked, achievements, icons]);
|
|
2272
|
+
const getAllAchievements = () => {
|
|
2273
|
+
const result = [];
|
|
2274
|
+
// Iterate through all normalized achievements
|
|
2275
|
+
Object.entries(achievements).forEach(([_metricName, metricAchievements]) => {
|
|
2276
|
+
metricAchievements.forEach((achievement) => {
|
|
2277
|
+
const { achievementDetails } = achievement;
|
|
2278
|
+
const isUnlocked = achievementState.unlocked.includes(achievementDetails.achievementId);
|
|
2279
|
+
result.push({
|
|
2280
|
+
achievementId: achievementDetails.achievementId,
|
|
2281
|
+
achievementTitle: achievementDetails.achievementTitle,
|
|
2282
|
+
achievementDescription: achievementDetails.achievementDescription,
|
|
2283
|
+
achievementIconKey: achievementDetails.achievementIconKey,
|
|
2284
|
+
isUnlocked,
|
|
2285
|
+
});
|
|
2286
|
+
});
|
|
2287
|
+
});
|
|
2288
|
+
return result;
|
|
2289
|
+
};
|
|
1489
2290
|
const update = (newMetrics) => {
|
|
1490
2291
|
metricsUpdatedRef.current = true;
|
|
1491
2292
|
const updatedMetrics = Object.assign({}, metrics);
|
|
@@ -1571,10 +2372,11 @@ const AchievementProvider = ({ achievements: achievementsConfig, storage = Stora
|
|
|
1571
2372
|
getState,
|
|
1572
2373
|
exportData,
|
|
1573
2374
|
importData,
|
|
2375
|
+
getAllAchievements,
|
|
1574
2376
|
} },
|
|
1575
2377
|
children,
|
|
1576
|
-
React.createElement(
|
|
1577
|
-
React.createElement(
|
|
2378
|
+
uiReady && currentNotification && ui.enableNotifications !== false && (React.createElement(NotificationComponent, { achievement: currentNotification.achievement, onClose: () => setCurrentNotification(null), duration: 5000, position: ui.notificationPosition || 'top-center', theme: ui.theme || 'modern' })),
|
|
2379
|
+
uiReady && ui.enableConfetti !== false && (React.createElement(ConfettiComponentResolved, { show: showConfetti, duration: 5000 }))));
|
|
1578
2380
|
};
|
|
1579
2381
|
|
|
1580
2382
|
const useAchievements = () => {
|
|
@@ -1590,7 +2392,7 @@ const useAchievements = () => {
|
|
|
1590
2392
|
* Provides an easier API for common use cases while maintaining access to advanced features.
|
|
1591
2393
|
*/
|
|
1592
2394
|
const useSimpleAchievements = () => {
|
|
1593
|
-
const { update, achievements, reset, getState, exportData, importData } = useAchievements();
|
|
2395
|
+
const { update, achievements, reset, getState, exportData, importData, getAllAchievements } = useAchievements();
|
|
1594
2396
|
return {
|
|
1595
2397
|
/**
|
|
1596
2398
|
* Track a metric value for achievements
|
|
@@ -1647,86 +2449,14 @@ const useSimpleAchievements = () => {
|
|
|
1647
2449
|
* @returns Import result with success status and any errors
|
|
1648
2450
|
*/
|
|
1649
2451
|
importData,
|
|
2452
|
+
/**
|
|
2453
|
+
* Get all achievements with their unlock status
|
|
2454
|
+
* @returns Array of achievements with isUnlocked boolean property
|
|
2455
|
+
*/
|
|
2456
|
+
getAllAchievements,
|
|
1650
2457
|
};
|
|
1651
2458
|
};
|
|
1652
2459
|
|
|
1653
|
-
const defaultStyles = {
|
|
1654
|
-
badgesButton: {
|
|
1655
|
-
backgroundColor: '#4CAF50',
|
|
1656
|
-
color: 'white',
|
|
1657
|
-
padding: '10px 20px',
|
|
1658
|
-
border: 'none',
|
|
1659
|
-
borderRadius: '20px',
|
|
1660
|
-
cursor: 'pointer',
|
|
1661
|
-
display: 'flex',
|
|
1662
|
-
alignItems: 'center',
|
|
1663
|
-
gap: '8px',
|
|
1664
|
-
fontSize: '16px',
|
|
1665
|
-
boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
|
|
1666
|
-
transition: 'transform 0.2s ease-in-out',
|
|
1667
|
-
},
|
|
1668
|
-
badgesModal: {
|
|
1669
|
-
overlay: {
|
|
1670
|
-
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
|
1671
|
-
display: 'flex',
|
|
1672
|
-
alignItems: 'center',
|
|
1673
|
-
justifyContent: 'center',
|
|
1674
|
-
zIndex: 1000,
|
|
1675
|
-
},
|
|
1676
|
-
content: {
|
|
1677
|
-
position: 'relative',
|
|
1678
|
-
background: '#fff',
|
|
1679
|
-
borderRadius: '8px',
|
|
1680
|
-
padding: '20px',
|
|
1681
|
-
maxWidth: '500px',
|
|
1682
|
-
width: '90%',
|
|
1683
|
-
maxHeight: '80vh',
|
|
1684
|
-
overflow: 'auto',
|
|
1685
|
-
},
|
|
1686
|
-
header: {
|
|
1687
|
-
display: 'flex',
|
|
1688
|
-
justifyContent: 'space-between',
|
|
1689
|
-
alignItems: 'center',
|
|
1690
|
-
marginBottom: '20px',
|
|
1691
|
-
},
|
|
1692
|
-
closeButton: {
|
|
1693
|
-
background: 'none',
|
|
1694
|
-
border: 'none',
|
|
1695
|
-
fontSize: '24px',
|
|
1696
|
-
cursor: 'pointer',
|
|
1697
|
-
padding: '0',
|
|
1698
|
-
},
|
|
1699
|
-
achievementList: {
|
|
1700
|
-
display: 'flex',
|
|
1701
|
-
flexDirection: 'column',
|
|
1702
|
-
gap: '16px',
|
|
1703
|
-
},
|
|
1704
|
-
achievementItem: {
|
|
1705
|
-
display: 'flex',
|
|
1706
|
-
gap: '16px',
|
|
1707
|
-
padding: '16px',
|
|
1708
|
-
borderRadius: '8px',
|
|
1709
|
-
backgroundColor: '#f5f5f5',
|
|
1710
|
-
alignItems: 'center',
|
|
1711
|
-
},
|
|
1712
|
-
achievementTitle: {
|
|
1713
|
-
margin: '0',
|
|
1714
|
-
fontSize: '18px',
|
|
1715
|
-
fontWeight: 'bold',
|
|
1716
|
-
},
|
|
1717
|
-
achievementDescription: {
|
|
1718
|
-
margin: '4px 0 0 0',
|
|
1719
|
-
color: '#666',
|
|
1720
|
-
},
|
|
1721
|
-
achievementIcon: {
|
|
1722
|
-
fontSize: '32px',
|
|
1723
|
-
display: 'flex',
|
|
1724
|
-
alignItems: 'center',
|
|
1725
|
-
justifyContent: 'center',
|
|
1726
|
-
},
|
|
1727
|
-
},
|
|
1728
|
-
};
|
|
1729
|
-
|
|
1730
2460
|
/**
|
|
1731
2461
|
* Base class for chainable achievement configuration (Tier 2)
|
|
1732
2462
|
*/
|
|
@@ -2013,5 +2743,5 @@ class AchievementBuilder {
|
|
|
2013
2743
|
}
|
|
2014
2744
|
}
|
|
2015
2745
|
|
|
2016
|
-
export { AchievementBuilder, AchievementContext, AchievementError, AchievementProvider, AsyncStorageAdapter, BadgesButton, BadgesModal, ConfettiWrapper, ConfigurationError, ImportValidationError, IndexedDBStorage, LocalStorage, MemoryStorage, OfflineQueueStorage, RestApiStorage, StorageError, StorageQuotaError, StorageType, SyncError, createConfigHash, defaultAchievementIcons, defaultStyles, exportAchievementData, importAchievementData, isAchievementError, isAsyncStorage, isRecoverableError, isSimpleConfig, normalizeAchievements, useAchievements, useSimpleAchievements };
|
|
2746
|
+
export { AchievementBuilder, AchievementContext, AchievementError, AchievementProvider, AsyncStorageAdapter, BadgesButton, BadgesModal, BuiltInConfetti, BuiltInModal, BuiltInNotification, ConfettiWrapper, ConfigurationError, ImportValidationError, IndexedDBStorage, LocalStorage, MemoryStorage, OfflineQueueStorage, RestApiStorage, StorageError, StorageQuotaError, StorageType, SyncError, createConfigHash, defaultAchievementIcons, defaultStyles, exportAchievementData, importAchievementData, isAchievementError, isAsyncStorage, isRecoverableError, isSimpleConfig, normalizeAchievements, useAchievements, useSimpleAchievements, useWindowSize };
|
|
2017
2747
|
//# sourceMappingURL=index.js.map
|