@umituz/react-native-settings 4.23.129 → 4.23.131
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/package.json +1 -1
- package/src/domains/gamification/components/AchievementCard.tsx +71 -29
- package/src/domains/gamification/components/GamificationScreen/GamificationScreenWithConfig.tsx +2 -3
- package/src/domains/gamification/components/LevelProgress.tsx +72 -28
- package/src/domains/gamification/components/PointsBadge.tsx +11 -7
- package/src/domains/gamification/components/StatsCard.tsx +79 -32
- package/src/domains/gamification/components/StreakDisplay.tsx +98 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.131",
|
|
4
4
|
"description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { View, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
-
import { useAppDesignTokens, AtomicText, withAlpha } from "@umituz/react-native-design-system";
|
|
8
|
+
import { useAppDesignTokens, AtomicText, withAlpha, useResponsive } from "@umituz/react-native-design-system";
|
|
9
9
|
|
|
10
10
|
export interface AchievementCardProps {
|
|
11
11
|
title: string;
|
|
@@ -41,24 +41,44 @@ export const AchievementCard: React.FC<AchievementCardProps> = ({
|
|
|
41
41
|
subtextColor,
|
|
42
42
|
}) => {
|
|
43
43
|
const tokens = useAppDesignTokens();
|
|
44
|
-
|
|
44
|
+
const { getFontSize, getIconSize } = useResponsive();
|
|
45
|
+
|
|
45
46
|
const finalUnlockedColor = unlockedColor || tokens.colors.success;
|
|
46
47
|
const finalLockedColor = lockedColor || tokens.colors.textDisabled;
|
|
47
48
|
const finalBackgroundColor = backgroundColor || tokens.colors.surface;
|
|
48
49
|
const finalTextColor = textColor || tokens.colors.textPrimary;
|
|
49
50
|
const finalSubtextColor = subtextColor || tokens.colors.textSecondary;
|
|
50
|
-
|
|
51
|
+
|
|
51
52
|
const accentColor = isUnlocked ? finalUnlockedColor : finalLockedColor;
|
|
53
|
+
const titleFontSize = getFontSize(16);
|
|
54
|
+
const descriptionFontSize = getFontSize(13);
|
|
55
|
+
const checkmarkFontSize = getFontSize(14);
|
|
56
|
+
const iconSize = getIconSize(48);
|
|
57
|
+
const checkmarkSize = getIconSize(24);
|
|
52
58
|
|
|
53
59
|
return (
|
|
54
60
|
<View
|
|
55
61
|
style={[
|
|
56
62
|
styles.container,
|
|
57
|
-
{
|
|
63
|
+
{
|
|
64
|
+
backgroundColor: finalBackgroundColor,
|
|
65
|
+
borderColor: withAlpha(accentColor, 0.4),
|
|
66
|
+
padding: tokens.spacing.md,
|
|
67
|
+
borderRadius: tokens.borders.radius.md,
|
|
68
|
+
gap: tokens.spacing.md,
|
|
69
|
+
},
|
|
58
70
|
containerStyle,
|
|
59
71
|
]}
|
|
60
72
|
>
|
|
61
|
-
<View style={[
|
|
73
|
+
<View style={[
|
|
74
|
+
styles.iconContainer,
|
|
75
|
+
{
|
|
76
|
+
backgroundColor: withAlpha(accentColor, 0.2),
|
|
77
|
+
width: iconSize,
|
|
78
|
+
height: iconSize,
|
|
79
|
+
borderRadius: iconSize / 2,
|
|
80
|
+
}
|
|
81
|
+
]}>
|
|
62
82
|
{icon}
|
|
63
83
|
</View>
|
|
64
84
|
|
|
@@ -66,22 +86,45 @@ export const AchievementCard: React.FC<AchievementCardProps> = ({
|
|
|
66
86
|
<AtomicText
|
|
67
87
|
style={[
|
|
68
88
|
styles.title,
|
|
69
|
-
{
|
|
89
|
+
{
|
|
90
|
+
color: isUnlocked ? finalTextColor : finalSubtextColor,
|
|
91
|
+
fontSize: titleFontSize,
|
|
92
|
+
},
|
|
70
93
|
titleStyle,
|
|
71
94
|
]}
|
|
72
95
|
>
|
|
73
96
|
{title}
|
|
74
97
|
</AtomicText>
|
|
75
|
-
<AtomicText style={[
|
|
98
|
+
<AtomicText style={[
|
|
99
|
+
styles.description,
|
|
100
|
+
{
|
|
101
|
+
color: finalSubtextColor,
|
|
102
|
+
fontSize: descriptionFontSize,
|
|
103
|
+
marginTop: tokens.spacing.xs,
|
|
104
|
+
},
|
|
105
|
+
descriptionStyle
|
|
106
|
+
]}>
|
|
76
107
|
{description}
|
|
77
108
|
</AtomicText>
|
|
78
109
|
|
|
79
110
|
{!isUnlocked && (
|
|
80
|
-
<View style={[
|
|
111
|
+
<View style={[
|
|
112
|
+
styles.progressBar,
|
|
113
|
+
{
|
|
114
|
+
backgroundColor: withAlpha(finalLockedColor, 0.4),
|
|
115
|
+
marginTop: tokens.spacing.sm,
|
|
116
|
+
borderRadius: tokens.borders.radius.xs,
|
|
117
|
+
},
|
|
118
|
+
progressBarStyle
|
|
119
|
+
]}>
|
|
81
120
|
<View
|
|
82
121
|
style={[
|
|
83
122
|
styles.progressFill,
|
|
84
|
-
{
|
|
123
|
+
{
|
|
124
|
+
width: `${progress}%`,
|
|
125
|
+
backgroundColor: accentColor,
|
|
126
|
+
borderRadius: tokens.borders.radius.xs,
|
|
127
|
+
},
|
|
85
128
|
]}
|
|
86
129
|
/>
|
|
87
130
|
</View>
|
|
@@ -89,8 +132,24 @@ export const AchievementCard: React.FC<AchievementCardProps> = ({
|
|
|
89
132
|
</View>
|
|
90
133
|
|
|
91
134
|
{isUnlocked && (
|
|
92
|
-
<View style={[
|
|
93
|
-
|
|
135
|
+
<View style={[
|
|
136
|
+
styles.checkmark,
|
|
137
|
+
{
|
|
138
|
+
backgroundColor: finalUnlockedColor,
|
|
139
|
+
width: checkmarkSize,
|
|
140
|
+
height: checkmarkSize,
|
|
141
|
+
borderRadius: checkmarkSize / 2,
|
|
142
|
+
}
|
|
143
|
+
]}>
|
|
144
|
+
<AtomicText style={[
|
|
145
|
+
styles.checkmarkText,
|
|
146
|
+
{
|
|
147
|
+
color: tokens.colors.onPrimary,
|
|
148
|
+
fontSize: checkmarkFontSize,
|
|
149
|
+
}
|
|
150
|
+
]}>
|
|
151
|
+
✓
|
|
152
|
+
</AtomicText>
|
|
94
153
|
</View>
|
|
95
154
|
)}
|
|
96
155
|
</View>
|
|
@@ -101,15 +160,9 @@ const styles = StyleSheet.create({
|
|
|
101
160
|
container: {
|
|
102
161
|
flexDirection: "row",
|
|
103
162
|
alignItems: "center",
|
|
104
|
-
padding: 12,
|
|
105
|
-
borderRadius: 12,
|
|
106
163
|
borderWidth: 1,
|
|
107
|
-
gap: 12,
|
|
108
164
|
},
|
|
109
165
|
iconContainer: {
|
|
110
|
-
width: 48,
|
|
111
|
-
height: 48,
|
|
112
|
-
borderRadius: 24,
|
|
113
166
|
justifyContent: "center",
|
|
114
167
|
alignItems: "center",
|
|
115
168
|
},
|
|
@@ -117,32 +170,21 @@ const styles = StyleSheet.create({
|
|
|
117
170
|
flex: 1,
|
|
118
171
|
},
|
|
119
172
|
title: {
|
|
120
|
-
fontSize: 16,
|
|
121
173
|
fontWeight: "600",
|
|
122
174
|
},
|
|
123
|
-
description: {
|
|
124
|
-
fontSize: 13,
|
|
125
|
-
marginTop: 2,
|
|
126
|
-
},
|
|
175
|
+
description: {},
|
|
127
176
|
progressBar: {
|
|
128
177
|
height: 4,
|
|
129
|
-
borderRadius: 2,
|
|
130
|
-
marginTop: 8,
|
|
131
178
|
overflow: "hidden",
|
|
132
179
|
},
|
|
133
180
|
progressFill: {
|
|
134
181
|
height: "100%",
|
|
135
|
-
borderRadius: 2,
|
|
136
182
|
},
|
|
137
183
|
checkmark: {
|
|
138
|
-
width: 24,
|
|
139
|
-
height: 24,
|
|
140
|
-
borderRadius: 12,
|
|
141
184
|
justifyContent: "center",
|
|
142
185
|
alignItems: "center",
|
|
143
186
|
},
|
|
144
187
|
checkmarkText: {
|
|
145
|
-
fontSize: 14,
|
|
146
188
|
fontWeight: "bold",
|
|
147
189
|
},
|
|
148
190
|
});
|
package/src/domains/gamification/components/GamificationScreen/GamificationScreenWithConfig.tsx
CHANGED
|
@@ -54,7 +54,6 @@ export const GamificationScreenWithConfig: React.FC<GamificationConfigProps> = (
|
|
|
54
54
|
streakProps: {
|
|
55
55
|
current: streak.current,
|
|
56
56
|
longest: streak.longest,
|
|
57
|
-
currentLabel: config.translations.currentStreak,
|
|
58
57
|
bestLabel: config.translations.bestStreak,
|
|
59
58
|
daysLabel: config.translations.days,
|
|
60
59
|
},
|
|
@@ -62,12 +61,12 @@ export const GamificationScreenWithConfig: React.FC<GamificationConfigProps> = (
|
|
|
62
61
|
{
|
|
63
62
|
label: config.translations.pointsLabel,
|
|
64
63
|
value: points,
|
|
65
|
-
icon: "
|
|
64
|
+
icon: "⭐",
|
|
66
65
|
},
|
|
67
66
|
{
|
|
68
67
|
label: config.translations.totalCompletedLabel,
|
|
69
68
|
value: totalTasksCompleted,
|
|
70
|
-
icon: "
|
|
69
|
+
icon: "✅",
|
|
71
70
|
},
|
|
72
71
|
],
|
|
73
72
|
achievements: achievementItems,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { View, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
-
import { useAppDesignTokens, AtomicText, withAlpha } from "@umituz/react-native-design-system";
|
|
8
|
+
import { useAppDesignTokens, AtomicText, withAlpha, useResponsive } from "@umituz/react-native-design-system";
|
|
9
9
|
|
|
10
10
|
export interface LevelProgressProps {
|
|
11
11
|
level: number;
|
|
@@ -49,39 +49,99 @@ export const LevelProgress: React.FC<LevelProgressProps> = ({
|
|
|
49
49
|
subtextColor,
|
|
50
50
|
}) => {
|
|
51
51
|
const tokens = useAppDesignTokens();
|
|
52
|
-
|
|
52
|
+
const { getFontSize, getIconSize } = useResponsive();
|
|
53
|
+
|
|
53
54
|
const finalPrimaryColor = primaryColor || tokens.colors.primary;
|
|
54
55
|
const finalSecondaryColor = secondaryColor || tokens.colors.surfaceSecondary;
|
|
55
56
|
const finalBackgroundColor = backgroundColor || tokens.colors.surface;
|
|
56
57
|
const finalTextColor = textColor || tokens.colors.textPrimary;
|
|
57
58
|
const finalSubtextColor = subtextColor || tokens.colors.textSecondary;
|
|
58
59
|
|
|
60
|
+
const titleFontSize = getFontSize(18);
|
|
61
|
+
const subtitleFontSize = getFontSize(14);
|
|
62
|
+
const badgeSize = getIconSize(52);
|
|
63
|
+
const badgeFontSize = getFontSize(20);
|
|
64
|
+
const progressBarHeight = getFontSize(10);
|
|
65
|
+
|
|
59
66
|
return (
|
|
60
|
-
<View style={[
|
|
61
|
-
|
|
67
|
+
<View style={[
|
|
68
|
+
styles.container,
|
|
69
|
+
{
|
|
70
|
+
backgroundColor: finalBackgroundColor,
|
|
71
|
+
borderRadius: tokens.borders.radius.lg,
|
|
72
|
+
padding: tokens.spacing.lg,
|
|
73
|
+
},
|
|
74
|
+
containerStyle
|
|
75
|
+
]}>
|
|
76
|
+
<View style={[styles.header, { marginBottom: tokens.spacing.md }]}>
|
|
62
77
|
<View style={styles.titleSection}>
|
|
63
|
-
<AtomicText style={[
|
|
78
|
+
<AtomicText style={[
|
|
79
|
+
styles.levelTitle,
|
|
80
|
+
{
|
|
81
|
+
color: finalTextColor,
|
|
82
|
+
fontSize: titleFontSize,
|
|
83
|
+
},
|
|
84
|
+
titleStyle
|
|
85
|
+
]}>
|
|
64
86
|
{levelTitle}
|
|
65
87
|
</AtomicText>
|
|
66
88
|
{showPoints && (
|
|
67
|
-
<AtomicText style={[
|
|
89
|
+
<AtomicText style={[
|
|
90
|
+
styles.subtitle,
|
|
91
|
+
{
|
|
92
|
+
color: finalSubtextColor,
|
|
93
|
+
fontSize: subtitleFontSize,
|
|
94
|
+
marginTop: tokens.spacing.xs,
|
|
95
|
+
},
|
|
96
|
+
subtitleStyle
|
|
97
|
+
]}>
|
|
68
98
|
{points} / {points + pointsToNext}
|
|
69
99
|
</AtomicText>
|
|
70
100
|
)}
|
|
71
101
|
</View>
|
|
72
102
|
|
|
73
|
-
<View style={[
|
|
74
|
-
|
|
103
|
+
<View style={[
|
|
104
|
+
styles.badge,
|
|
105
|
+
{
|
|
106
|
+
backgroundColor: withAlpha(finalPrimaryColor, 0.2),
|
|
107
|
+
borderColor: withAlpha(finalPrimaryColor, 0.4),
|
|
108
|
+
width: badgeSize,
|
|
109
|
+
height: badgeSize,
|
|
110
|
+
borderRadius: badgeSize / 2,
|
|
111
|
+
borderWidth: 2,
|
|
112
|
+
},
|
|
113
|
+
badgeStyle
|
|
114
|
+
]}>
|
|
115
|
+
<AtomicText style={[
|
|
116
|
+
styles.badgeText,
|
|
117
|
+
{
|
|
118
|
+
color: finalPrimaryColor,
|
|
119
|
+
fontSize: badgeFontSize,
|
|
120
|
+
},
|
|
121
|
+
badgeTextStyle
|
|
122
|
+
]}>
|
|
75
123
|
{level}
|
|
76
124
|
</AtomicText>
|
|
77
125
|
</View>
|
|
78
126
|
</View>
|
|
79
127
|
|
|
80
|
-
<View style={[
|
|
128
|
+
<View style={[
|
|
129
|
+
styles.progressBar,
|
|
130
|
+
{
|
|
131
|
+
backgroundColor: finalSecondaryColor,
|
|
132
|
+
height: progressBarHeight,
|
|
133
|
+
borderRadius: tokens.borders.radius.sm,
|
|
134
|
+
},
|
|
135
|
+
progressBarStyle
|
|
136
|
+
]}>
|
|
81
137
|
<View
|
|
82
138
|
style={[
|
|
83
139
|
styles.progressFill,
|
|
84
|
-
{
|
|
140
|
+
{
|
|
141
|
+
width: `${Math.min(100, progress)}%`,
|
|
142
|
+
backgroundColor: finalPrimaryColor,
|
|
143
|
+
borderRadius: tokens.borders.radius.sm,
|
|
144
|
+
},
|
|
85
145
|
progressFillStyle,
|
|
86
146
|
]}
|
|
87
147
|
/>
|
|
@@ -91,46 +151,30 @@ export const LevelProgress: React.FC<LevelProgressProps> = ({
|
|
|
91
151
|
};
|
|
92
152
|
|
|
93
153
|
const styles = StyleSheet.create({
|
|
94
|
-
container: {
|
|
95
|
-
borderRadius: 16,
|
|
96
|
-
padding: 16,
|
|
97
|
-
},
|
|
154
|
+
container: {},
|
|
98
155
|
header: {
|
|
99
156
|
flexDirection: "row",
|
|
100
157
|
justifyContent: "space-between",
|
|
101
158
|
alignItems: "center",
|
|
102
|
-
marginBottom: 12,
|
|
103
159
|
},
|
|
104
160
|
titleSection: {
|
|
105
161
|
flex: 1,
|
|
106
162
|
},
|
|
107
163
|
levelTitle: {
|
|
108
|
-
fontSize: 18,
|
|
109
164
|
fontWeight: "700",
|
|
110
165
|
},
|
|
111
|
-
subtitle: {
|
|
112
|
-
fontSize: 14,
|
|
113
|
-
marginTop: 2,
|
|
114
|
-
},
|
|
166
|
+
subtitle: {},
|
|
115
167
|
badge: {
|
|
116
|
-
width: 48,
|
|
117
|
-
height: 48,
|
|
118
|
-
borderRadius: 24,
|
|
119
168
|
justifyContent: "center",
|
|
120
169
|
alignItems: "center",
|
|
121
|
-
borderWidth: 2,
|
|
122
170
|
},
|
|
123
171
|
badgeText: {
|
|
124
|
-
fontSize: 18,
|
|
125
172
|
fontWeight: "bold",
|
|
126
173
|
},
|
|
127
174
|
progressBar: {
|
|
128
|
-
height: 8,
|
|
129
|
-
borderRadius: 4,
|
|
130
175
|
overflow: "hidden",
|
|
131
176
|
},
|
|
132
177
|
progressFill: {
|
|
133
178
|
height: "100%",
|
|
134
|
-
borderRadius: 4,
|
|
135
179
|
},
|
|
136
180
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { View, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
-
import { useAppDesignTokens, AtomicText, withAlpha } from "@umituz/react-native-design-system";
|
|
8
|
+
import { useAppDesignTokens, AtomicText, withAlpha, useResponsive } from "@umituz/react-native-design-system";
|
|
9
9
|
|
|
10
10
|
export interface PointsBadgeProps {
|
|
11
11
|
points: number;
|
|
@@ -27,20 +27,28 @@ export const PointsBadge: React.FC<PointsBadgeProps> = ({
|
|
|
27
27
|
borderColor,
|
|
28
28
|
}) => {
|
|
29
29
|
const tokens = useAppDesignTokens();
|
|
30
|
+
const { getFontSize } = useResponsive();
|
|
30
31
|
const finalTextColor = textColor || tokens.colors.primary;
|
|
31
32
|
const finalBackgroundColor = backgroundColor || withAlpha(finalTextColor, 0.1);
|
|
32
33
|
const finalBorderColor = borderColor || withAlpha(finalTextColor, 0.2);
|
|
34
|
+
const fontSize = getFontSize(16);
|
|
33
35
|
|
|
34
36
|
return (
|
|
35
37
|
<View
|
|
36
38
|
style={[
|
|
37
39
|
styles.container,
|
|
38
|
-
{
|
|
40
|
+
{
|
|
41
|
+
backgroundColor: finalBackgroundColor,
|
|
42
|
+
borderColor: finalBorderColor,
|
|
43
|
+
paddingHorizontal: tokens.spacing.md,
|
|
44
|
+
paddingVertical: tokens.spacing.xs,
|
|
45
|
+
borderRadius: tokens.borders.radius.full,
|
|
46
|
+
},
|
|
39
47
|
containerStyle,
|
|
40
48
|
]}
|
|
41
49
|
>
|
|
42
50
|
{icon}
|
|
43
|
-
<AtomicText style={[styles.text, { color: finalTextColor }, textStyle]}>
|
|
51
|
+
<AtomicText style={[styles.text, { color: finalTextColor, fontSize }, textStyle]}>
|
|
44
52
|
{points}
|
|
45
53
|
</AtomicText>
|
|
46
54
|
</View>
|
|
@@ -52,13 +60,9 @@ const styles = StyleSheet.create({
|
|
|
52
60
|
flexDirection: "row",
|
|
53
61
|
alignItems: "center",
|
|
54
62
|
gap: 6,
|
|
55
|
-
paddingHorizontal: 12,
|
|
56
|
-
paddingVertical: 6,
|
|
57
|
-
borderRadius: 20,
|
|
58
63
|
borderWidth: 1,
|
|
59
64
|
},
|
|
60
65
|
text: {
|
|
61
|
-
fontSize: 16,
|
|
62
66
|
fontWeight: "bold",
|
|
63
67
|
},
|
|
64
68
|
});
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* StatsCard Component
|
|
3
|
-
*
|
|
2
|
+
* StatsCard Component - Modern Design
|
|
3
|
+
* Clean card-based stat display with emoji icons
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { View, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
-
import { useAppDesignTokens, AtomicText,
|
|
8
|
+
import { useAppDesignTokens, AtomicText, useResponsive } from "@umituz/react-native-design-system";
|
|
9
9
|
|
|
10
10
|
export interface StatsCardProps {
|
|
11
11
|
value: number;
|
|
12
12
|
label: string;
|
|
13
|
-
icon
|
|
13
|
+
icon?: React.ReactNode | string;
|
|
14
14
|
suffix?: string;
|
|
15
15
|
containerStyle?: ViewStyle;
|
|
16
16
|
valueStyle?: TextStyle;
|
|
@@ -31,35 +31,86 @@ export const StatsCard: React.FC<StatsCardProps> = ({
|
|
|
31
31
|
labelStyle,
|
|
32
32
|
accentColor,
|
|
33
33
|
backgroundColor,
|
|
34
|
-
textColor,
|
|
34
|
+
textColor: _textColor,
|
|
35
35
|
subtextColor,
|
|
36
36
|
}) => {
|
|
37
37
|
const tokens = useAppDesignTokens();
|
|
38
|
-
|
|
38
|
+
const { getFontSize } = useResponsive();
|
|
39
|
+
|
|
39
40
|
const finalAccentColor = accentColor || tokens.colors.primary;
|
|
40
41
|
const finalBackgroundColor = backgroundColor || tokens.colors.surface;
|
|
41
|
-
const finalTextColor = textColor || tokens.colors.textPrimary;
|
|
42
42
|
const finalSubtextColor = subtextColor || tokens.colors.textSecondary;
|
|
43
43
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const valueFontSize = getFontSize(48);
|
|
45
|
+
const suffixFontSize = getFontSize(16);
|
|
46
|
+
const labelFontSize = getFontSize(12);
|
|
47
|
+
const emojiSize = getFontSize(32);
|
|
47
48
|
|
|
48
49
|
return (
|
|
49
|
-
<View style={[
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
<View style={[
|
|
51
|
+
styles.container,
|
|
52
|
+
{
|
|
53
|
+
backgroundColor: finalBackgroundColor,
|
|
54
|
+
borderRadius: tokens.borders.radius.xl,
|
|
55
|
+
padding: tokens.spacing.xl,
|
|
56
|
+
shadowColor: "#000",
|
|
57
|
+
shadowOffset: { width: 0, height: 2 },
|
|
58
|
+
shadowOpacity: 0.1,
|
|
59
|
+
shadowRadius: 8,
|
|
60
|
+
elevation: 3,
|
|
61
|
+
},
|
|
62
|
+
containerStyle
|
|
63
|
+
]}>
|
|
64
|
+
{/* Emoji Icon at Top */}
|
|
65
|
+
{icon && (
|
|
66
|
+
<AtomicText style={[
|
|
67
|
+
styles.emoji,
|
|
68
|
+
{
|
|
69
|
+
fontSize: emojiSize,
|
|
70
|
+
marginBottom: tokens.spacing.sm,
|
|
71
|
+
}
|
|
72
|
+
]}>
|
|
73
|
+
{icon}
|
|
74
|
+
</AtomicText>
|
|
75
|
+
)}
|
|
76
|
+
|
|
77
|
+
{/* Large Value in Center */}
|
|
53
78
|
<View style={styles.valueRow}>
|
|
54
|
-
<AtomicText style={[
|
|
79
|
+
<AtomicText style={[
|
|
80
|
+
styles.value,
|
|
81
|
+
{
|
|
82
|
+
color: finalAccentColor,
|
|
83
|
+
fontSize: valueFontSize,
|
|
84
|
+
lineHeight: valueFontSize + 8,
|
|
85
|
+
},
|
|
86
|
+
valueStyle
|
|
87
|
+
]}>
|
|
55
88
|
{value}
|
|
56
89
|
</AtomicText>
|
|
57
90
|
{suffix && (
|
|
58
|
-
<AtomicText style={[
|
|
91
|
+
<AtomicText style={[
|
|
92
|
+
styles.suffix,
|
|
93
|
+
{
|
|
94
|
+
color: finalSubtextColor,
|
|
95
|
+
fontSize: suffixFontSize,
|
|
96
|
+
}
|
|
97
|
+
]}>
|
|
98
|
+
{suffix}
|
|
99
|
+
</AtomicText>
|
|
59
100
|
)}
|
|
60
101
|
</View>
|
|
61
|
-
|
|
62
|
-
|
|
102
|
+
|
|
103
|
+
{/* Small Label Below */}
|
|
104
|
+
<AtomicText style={[
|
|
105
|
+
styles.label,
|
|
106
|
+
{
|
|
107
|
+
color: finalSubtextColor,
|
|
108
|
+
fontSize: labelFontSize,
|
|
109
|
+
marginTop: tokens.spacing.xs,
|
|
110
|
+
},
|
|
111
|
+
labelStyle
|
|
112
|
+
]}>
|
|
113
|
+
{label.toUpperCase()}
|
|
63
114
|
</AtomicText>
|
|
64
115
|
</View>
|
|
65
116
|
);
|
|
@@ -69,16 +120,10 @@ const styles = StyleSheet.create({
|
|
|
69
120
|
container: {
|
|
70
121
|
flex: 1,
|
|
71
122
|
minWidth: "45%",
|
|
72
|
-
borderRadius: 12,
|
|
73
|
-
padding: 12,
|
|
74
|
-
},
|
|
75
|
-
iconContainer: {
|
|
76
|
-
width: 36,
|
|
77
|
-
height: 36,
|
|
78
|
-
borderRadius: 18,
|
|
79
123
|
alignItems: "center",
|
|
80
|
-
|
|
81
|
-
|
|
124
|
+
},
|
|
125
|
+
emoji: {
|
|
126
|
+
textAlign: "center",
|
|
82
127
|
},
|
|
83
128
|
valueRow: {
|
|
84
129
|
flexDirection: "row",
|
|
@@ -86,14 +131,16 @@ const styles = StyleSheet.create({
|
|
|
86
131
|
gap: 4,
|
|
87
132
|
},
|
|
88
133
|
value: {
|
|
89
|
-
|
|
90
|
-
|
|
134
|
+
fontWeight: "800",
|
|
135
|
+
letterSpacing: -1,
|
|
136
|
+
textAlign: "center",
|
|
91
137
|
},
|
|
92
138
|
suffix: {
|
|
93
|
-
|
|
139
|
+
fontWeight: "600",
|
|
94
140
|
},
|
|
95
141
|
label: {
|
|
96
|
-
|
|
97
|
-
|
|
142
|
+
fontWeight: "600",
|
|
143
|
+
letterSpacing: 0.5,
|
|
144
|
+
textAlign: "center",
|
|
98
145
|
},
|
|
99
146
|
});
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* StreakDisplay Component
|
|
3
|
-
*
|
|
2
|
+
* StreakDisplay Component - Modern Design
|
|
3
|
+
* Clean card-based design with emoji icons
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { View, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
-
import { useAppDesignTokens, AtomicText,
|
|
8
|
+
import { useAppDesignTokens, AtomicText, withAlpha, useResponsive } from "@umituz/react-native-design-system";
|
|
9
9
|
|
|
10
10
|
export interface StreakDisplayProps {
|
|
11
11
|
current: number;
|
|
12
12
|
longest: number;
|
|
13
|
-
currentLabel: string;
|
|
14
13
|
bestLabel: string;
|
|
15
14
|
daysLabel: string;
|
|
16
15
|
icon?: React.ReactNode | string;
|
|
@@ -26,51 +25,101 @@ export interface StreakDisplayProps {
|
|
|
26
25
|
export const StreakDisplay: React.FC<StreakDisplayProps> = ({
|
|
27
26
|
current,
|
|
28
27
|
longest,
|
|
29
|
-
currentLabel,
|
|
30
28
|
bestLabel,
|
|
31
29
|
daysLabel,
|
|
32
|
-
icon,
|
|
33
30
|
containerStyle,
|
|
34
31
|
numberStyle,
|
|
35
32
|
labelStyle,
|
|
36
33
|
primaryColor,
|
|
37
34
|
backgroundColor,
|
|
38
|
-
textColor,
|
|
35
|
+
textColor: _textColor,
|
|
39
36
|
subtextColor,
|
|
40
37
|
}) => {
|
|
41
38
|
const tokens = useAppDesignTokens();
|
|
42
|
-
|
|
39
|
+
const { getFontSize } = useResponsive();
|
|
40
|
+
|
|
43
41
|
const finalPrimaryColor = primaryColor || tokens.colors.primary;
|
|
44
42
|
const finalBackgroundColor = backgroundColor || tokens.colors.surface;
|
|
45
|
-
const finalTextColor = textColor || tokens.colors.textPrimary;
|
|
46
43
|
const finalSubtextColor = subtextColor || tokens.colors.textSecondary;
|
|
47
44
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
const streakNumberSize = getFontSize(48);
|
|
46
|
+
const streakLabelSize = getFontSize(13);
|
|
47
|
+
const bestNumberSize = getFontSize(20);
|
|
48
|
+
const bestLabelSize = getFontSize(11);
|
|
51
49
|
|
|
52
50
|
return (
|
|
53
|
-
<View style={[
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
<View style={[
|
|
52
|
+
styles.container,
|
|
53
|
+
{
|
|
54
|
+
backgroundColor: finalBackgroundColor,
|
|
55
|
+
borderRadius: tokens.borders.radius.xl,
|
|
56
|
+
padding: tokens.spacing.xl,
|
|
57
|
+
shadowColor: "#000",
|
|
58
|
+
shadowOffset: { width: 0, height: 2 },
|
|
59
|
+
shadowOpacity: 0.1,
|
|
60
|
+
shadowRadius: 8,
|
|
61
|
+
elevation: 3,
|
|
62
|
+
},
|
|
63
|
+
containerStyle
|
|
64
|
+
]}>
|
|
65
|
+
{/* Main Streak Section */}
|
|
66
|
+
<View style={styles.mainSection}>
|
|
67
|
+
<AtomicText style={styles.emoji}>🔥</AtomicText>
|
|
68
|
+
<View style={styles.streakNumbers}>
|
|
69
|
+
<View style={styles.currentStreak}>
|
|
70
|
+
<AtomicText style={[
|
|
71
|
+
styles.streakNumber,
|
|
72
|
+
{
|
|
73
|
+
color: finalPrimaryColor,
|
|
74
|
+
fontSize: streakNumberSize,
|
|
75
|
+
lineHeight: streakNumberSize + 8,
|
|
76
|
+
},
|
|
77
|
+
numberStyle
|
|
78
|
+
]}>
|
|
79
|
+
{current}
|
|
80
|
+
</AtomicText>
|
|
81
|
+
<AtomicText style={[
|
|
82
|
+
styles.streakLabel,
|
|
83
|
+
{
|
|
84
|
+
color: finalSubtextColor,
|
|
85
|
+
fontSize: streakLabelSize,
|
|
86
|
+
marginTop: tokens.spacing.xs,
|
|
87
|
+
},
|
|
88
|
+
labelStyle
|
|
89
|
+
]}>
|
|
90
|
+
{daysLabel.toUpperCase()}
|
|
91
|
+
</AtomicText>
|
|
92
|
+
</View>
|
|
63
93
|
</View>
|
|
64
|
-
<AtomicText style={[styles.currentLabel, { color: finalTextColor }]}>
|
|
65
|
-
{currentLabel}
|
|
66
|
-
</AtomicText>
|
|
67
94
|
</View>
|
|
68
95
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
{/* Best Streak Badge */}
|
|
97
|
+
<View style={[
|
|
98
|
+
styles.bestBadge,
|
|
99
|
+
{
|
|
100
|
+
backgroundColor: withAlpha(finalPrimaryColor, 0.12),
|
|
101
|
+
borderRadius: tokens.borders.radius.lg,
|
|
102
|
+
paddingHorizontal: tokens.spacing.md,
|
|
103
|
+
paddingVertical: tokens.spacing.md,
|
|
104
|
+
}
|
|
105
|
+
]}>
|
|
106
|
+
<AtomicText style={[
|
|
107
|
+
styles.bestLabel,
|
|
108
|
+
{
|
|
109
|
+
color: finalSubtextColor,
|
|
110
|
+
fontSize: bestLabelSize,
|
|
111
|
+
}
|
|
112
|
+
]}>
|
|
113
|
+
{bestLabel.toUpperCase()}
|
|
72
114
|
</AtomicText>
|
|
73
|
-
<AtomicText style={[
|
|
115
|
+
<AtomicText style={[
|
|
116
|
+
styles.bestNumber,
|
|
117
|
+
{
|
|
118
|
+
color: finalPrimaryColor,
|
|
119
|
+
fontSize: bestNumberSize,
|
|
120
|
+
marginTop: tokens.spacing.xs / 2,
|
|
121
|
+
}
|
|
122
|
+
]}>
|
|
74
123
|
{longest}
|
|
75
124
|
</AtomicText>
|
|
76
125
|
</View>
|
|
@@ -80,50 +129,42 @@ export const StreakDisplay: React.FC<StreakDisplayProps> = ({
|
|
|
80
129
|
|
|
81
130
|
const styles = StyleSheet.create({
|
|
82
131
|
container: {
|
|
83
|
-
borderRadius: 16,
|
|
84
|
-
padding: 16,
|
|
85
132
|
flexDirection: "row",
|
|
86
133
|
alignItems: "center",
|
|
87
134
|
justifyContent: "space-between",
|
|
88
135
|
},
|
|
89
|
-
|
|
136
|
+
mainSection: {
|
|
90
137
|
flexDirection: "row",
|
|
91
138
|
alignItems: "center",
|
|
92
|
-
gap:
|
|
139
|
+
gap: 16,
|
|
140
|
+
flex: 1,
|
|
93
141
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
height: 40,
|
|
97
|
-
justifyContent: "center",
|
|
98
|
-
alignItems: "center",
|
|
142
|
+
emoji: {
|
|
143
|
+
fontSize: 40,
|
|
99
144
|
},
|
|
100
|
-
|
|
101
|
-
|
|
145
|
+
streakNumbers: {
|
|
146
|
+
flex: 1,
|
|
102
147
|
},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
fontWeight: "bold",
|
|
148
|
+
currentStreak: {
|
|
149
|
+
alignItems: "flex-start",
|
|
106
150
|
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
151
|
+
streakNumber: {
|
|
152
|
+
fontWeight: "800",
|
|
153
|
+
letterSpacing: -1,
|
|
110
154
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
155
|
+
streakLabel: {
|
|
156
|
+
fontWeight: "600",
|
|
157
|
+
letterSpacing: 0.5,
|
|
114
158
|
},
|
|
115
|
-
|
|
116
|
-
paddingHorizontal: 12,
|
|
117
|
-
paddingVertical: 8,
|
|
118
|
-
borderRadius: 12,
|
|
159
|
+
bestBadge: {
|
|
119
160
|
alignItems: "center",
|
|
161
|
+
minWidth: 70,
|
|
120
162
|
},
|
|
121
163
|
bestLabel: {
|
|
122
|
-
|
|
123
|
-
|
|
164
|
+
fontWeight: "700",
|
|
165
|
+
letterSpacing: 0.5,
|
|
124
166
|
},
|
|
125
167
|
bestNumber: {
|
|
126
|
-
|
|
127
|
-
fontWeight: "bold",
|
|
168
|
+
fontWeight: "800",
|
|
128
169
|
},
|
|
129
170
|
});
|