@xetwa/design-system 1.0.35 → 1.0.37
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/components/Buttons/IconButton/IconButton.tsx +2 -2
- package/src/components/Buttons/JourneyButton/JourneyButton.tsx +8 -2
- package/src/components/Buttons/MainButton/MainButton.tsx +0 -19
- package/src/components/Buttons/WordChip/WordChip.tsx +11 -5
- package/src/components/Cards/BoardingPassCard/BoardingPassCard.tsx +11 -25
- package/src/components/Cards/ModuleCard/ModuleCard.stories.tsx +29 -2
- package/src/components/Cards/ModuleCard/ModuleCard.tsx +207 -54
- package/src/components/Cards/Passport/PassportCover.tsx +3 -3
- package/src/components/Charts/WeeklyProgressChart/WeeklyProgressChart.tsx +8 -9
- package/src/components/DataDisplay/Avatar/Avatar.tsx +8 -9
- package/src/components/DataDisplay/Badge/Badge.tsx +13 -20
- package/src/components/DataDisplay/InstructionBubble/InstructionBubble.tsx +2 -2
- package/src/components/DataDisplay/LevelBadge/LevelBadge.tsx +2 -15
- package/src/components/DataDisplay/NumberBadge/NumberBadge.tsx +1 -2
- package/src/components/DataDisplay/StatBadge/StatBadge.tsx +1 -1
- package/src/components/DataDisplay/Tag/Tag.tsx +11 -23
- package/src/components/Feedback/FeedbackBottomSheet/FeedbackBottomSheet.tsx +4 -13
- package/src/components/Headers/CountryHeader/CountryHeader.tsx +3 -3
- package/src/components/Headers/ProfileHeader/ProfileHeader.tsx +8 -5
- package/src/components/Inputs/FillInTheBlank/FillInTheBlank.tsx +5 -5
- package/src/components/Inputs/TextInput/TextInput.tsx +4 -4
- package/src/components/Navigation/BottomTabBar/BottomTabBarItem.tsx +1 -1
- package/src/components/Navigation/ProgressMap/ProgressMap.tsx +9 -9
- package/src/components/Navigation/WorldProgressMap/WorldProgressMap.tsx +9 -9
- package/src/components/Typography/Typography.tsx +16 -10
- package/src/styles/theme.ts +39 -24
package/package.json
CHANGED
|
@@ -161,11 +161,11 @@ export const IconButton = React.forwardRef<React.ElementRef<typeof Pressable>, I
|
|
|
161
161
|
strokeWidth={text ? 2.5 : 3}
|
|
162
162
|
/>
|
|
163
163
|
) : (
|
|
164
|
-
<Typography style={{ color: 'red'
|
|
164
|
+
<Typography variant="caption" style={{ color: 'red' }}>Missing</Typography>
|
|
165
165
|
)}
|
|
166
166
|
|
|
167
167
|
{!!text && (
|
|
168
|
-
<Typography style={[styles.text, { color: getTextColor(),
|
|
168
|
+
<Typography variant={size === 'sm' ? 'caption' : 'bodySm'} style={[styles.text, { color: getTextColor(), marginTop: scale(4) }]}>
|
|
169
169
|
{text}
|
|
170
170
|
</Typography>
|
|
171
171
|
)}
|
|
@@ -138,13 +138,19 @@ export const JourneyButton = React.forwardRef<React.ElementRef<typeof Pressable>
|
|
|
138
138
|
|
|
139
139
|
<View style={styles.textContainer}>
|
|
140
140
|
<Typography
|
|
141
|
+
variant="label"
|
|
141
142
|
numberOfLines={1}
|
|
142
143
|
adjustsFontSizeToFit
|
|
143
|
-
style={[styles.subtitle, { color: getTextColor(), fontSize: scaleText(
|
|
144
|
+
style={[styles.subtitle, { color: getTextColor(), fontSize: scaleText(11), lineHeight: scaleText(16), marginBottom: scale(4) }]}
|
|
144
145
|
>
|
|
145
146
|
{subtitle}
|
|
146
147
|
</Typography>
|
|
147
|
-
<Typography
|
|
148
|
+
<Typography
|
|
149
|
+
variant="btnLg"
|
|
150
|
+
numberOfLines={1}
|
|
151
|
+
adjustsFontSizeToFit
|
|
152
|
+
style={[styles.title, { color: getTextColor(), fontSize: scaleText(17), lineHeight: scaleText(22) }]}
|
|
153
|
+
>
|
|
148
154
|
{title}
|
|
149
155
|
</Typography>
|
|
150
156
|
</View>
|
|
@@ -140,11 +140,6 @@ export const MainButton = React.forwardRef<React.ElementRef<typeof Pressable>, M
|
|
|
140
140
|
const textStyles: TextStyle[] = [
|
|
141
141
|
styles.textBase,
|
|
142
142
|
{
|
|
143
|
-
fontSize: {
|
|
144
|
-
sm: scaleText(12),
|
|
145
|
-
md: scaleText(16),
|
|
146
|
-
lg: scaleText(18),
|
|
147
|
-
}[size],
|
|
148
143
|
letterSpacing: {
|
|
149
144
|
sm: scale(0.48),
|
|
150
145
|
md: scale(0.64),
|
|
@@ -243,21 +238,7 @@ const styles = StyleSheet.create({
|
|
|
243
238
|
textAlign: 'center',
|
|
244
239
|
textTransform: 'uppercase',
|
|
245
240
|
},
|
|
246
|
-
text_sm: {
|
|
247
|
-
fontSize: 12,
|
|
248
|
-
|
|
249
|
-
letterSpacing: 12 * 0.04, // 0.48
|
|
250
|
-
},
|
|
251
|
-
text_md: {
|
|
252
|
-
fontSize: 16,
|
|
253
241
|
|
|
254
|
-
letterSpacing: 16 * 0.04, // 0.64
|
|
255
|
-
},
|
|
256
|
-
text_lg: {
|
|
257
|
-
fontSize: 18,
|
|
258
|
-
|
|
259
|
-
letterSpacing: 18 * 0.04, // 0.72
|
|
260
|
-
},
|
|
261
242
|
|
|
262
243
|
textHidden: {
|
|
263
244
|
color: 'transparent',
|
|
@@ -105,6 +105,15 @@ export const WordChip = ({
|
|
|
105
105
|
// Press state visual adjustments
|
|
106
106
|
const showPressed = isPressed && !isDisabled;
|
|
107
107
|
|
|
108
|
+
const getTypographyVariant = () => {
|
|
109
|
+
switch (size) {
|
|
110
|
+
case 'lg': return 'btnLg';
|
|
111
|
+
case 'sm': return 'bodyMd';
|
|
112
|
+
case 'md':
|
|
113
|
+
default: return 'bodyLg';
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
108
117
|
return (
|
|
109
118
|
<Animated.View style={[{ transform: [{ scale: scaleAnim }] }, width ? { width } : null, style]}>
|
|
110
119
|
<Pressable
|
|
@@ -136,12 +145,9 @@ export const WordChip = ({
|
|
|
136
145
|
},
|
|
137
146
|
(fullWidth || width) ? { width: '100%' } : null
|
|
138
147
|
]}>
|
|
139
|
-
<Typography style={[
|
|
148
|
+
<Typography variant={getTypographyVariant()} style={[
|
|
140
149
|
styles.text,
|
|
141
|
-
{
|
|
142
|
-
color: currentStyles.text,
|
|
143
|
-
fontSize: scaleText(size === 'lg' ? 17 : size === 'sm' ? 13 : 15),
|
|
144
|
-
}
|
|
150
|
+
{ color: currentStyles.text }
|
|
145
151
|
]}>
|
|
146
152
|
{word}
|
|
147
153
|
</Typography>
|
|
@@ -158,7 +158,7 @@ export const BoardingPassCard = ({
|
|
|
158
158
|
]} />
|
|
159
159
|
|
|
160
160
|
<View style={[styles.headerRow, { marginBottom: scale(16) }]}>
|
|
161
|
-
<Typography style={[styles.headerText
|
|
161
|
+
<Typography variant="caption" style={[styles.headerText]}>CARTÃO DE EMBARQUE</Typography>
|
|
162
162
|
<View style={styles.headerPlaneContainer}>
|
|
163
163
|
<Plane stroke={theme.colors.white} strokeWidth={2.2} size={scale(22)} />
|
|
164
164
|
</View>
|
|
@@ -166,8 +166,8 @@ export const BoardingPassCard = ({
|
|
|
166
166
|
|
|
167
167
|
<View style={styles.routeRow}>
|
|
168
168
|
<View style={styles.cityCol}>
|
|
169
|
-
<Typography style={[styles.cityCode
|
|
170
|
-
<Typography style={[styles.cityName
|
|
169
|
+
<Typography variant="h1" style={[styles.cityCode]}>{originCode}</Typography>
|
|
170
|
+
<Typography variant="caption" style={[styles.cityName]}>{originName}</Typography>
|
|
171
171
|
</View>
|
|
172
172
|
|
|
173
173
|
<View style={[styles.flightLineContainer, { height: scale(30), marginHorizontal: scale(12) }]}>
|
|
@@ -178,8 +178,8 @@ export const BoardingPassCard = ({
|
|
|
178
178
|
</View>
|
|
179
179
|
|
|
180
180
|
<View style={[styles.cityCol, { alignItems: 'flex-end' }]}>
|
|
181
|
-
<Typography style={[styles.cityCode
|
|
182
|
-
<Typography style={[styles.cityName
|
|
181
|
+
<Typography variant="h1" style={[styles.cityCode]}>{destCode}</Typography>
|
|
182
|
+
<Typography variant="caption" style={[styles.cityName]}>{destName}</Typography>
|
|
183
183
|
</View>
|
|
184
184
|
</View>
|
|
185
185
|
</View>
|
|
@@ -205,8 +205,8 @@ export const BoardingPassCard = ({
|
|
|
205
205
|
{flagIconNode ? flagIconNode : <Flag countryCode={countryName} size={scale(40)} />}
|
|
206
206
|
</View>
|
|
207
207
|
<View style={styles.countryContent}>
|
|
208
|
-
<Typography style={[styles.countryName
|
|
209
|
-
<Typography style={[styles.countrySubtitle
|
|
208
|
+
<Typography variant="h2" style={[styles.countryName]}>{countryName}</Typography>
|
|
209
|
+
<Typography variant="bodySm" style={[styles.countrySubtitle]}>{countrySubtitle}</Typography>
|
|
210
210
|
</View>
|
|
211
211
|
</View>
|
|
212
212
|
|
|
@@ -220,12 +220,12 @@ export const BoardingPassCard = ({
|
|
|
220
220
|
|
|
221
221
|
<View style={[styles.footerRow, { gap: scale(14) }]}>
|
|
222
222
|
<View style={styles.footerCol}>
|
|
223
|
-
<Typography style={[styles.footerLabel
|
|
224
|
-
<Typography style={[styles.footerValue
|
|
223
|
+
<Typography variant="caption" style={[styles.footerLabel]}>MÓDULOS</Typography>
|
|
224
|
+
<Typography variant="h3" style={[styles.footerValue]}>{modulesCount}</Typography>
|
|
225
225
|
</View>
|
|
226
226
|
<View style={styles.footerCol}>
|
|
227
|
-
<Typography style={[styles.footerLabel
|
|
228
|
-
<Typography style={[styles.footerValue
|
|
227
|
+
<Typography variant="caption" style={[styles.footerLabel]}>FOCO</Typography>
|
|
228
|
+
<Typography variant="h3" style={[styles.footerValue]}>{focusText}</Typography>
|
|
229
229
|
</View>
|
|
230
230
|
</View>
|
|
231
231
|
</View>
|
|
@@ -267,8 +267,6 @@ const styles = StyleSheet.create({
|
|
|
267
267
|
transform: [{ rotate: '0deg' }],
|
|
268
268
|
},
|
|
269
269
|
headerText: {
|
|
270
|
-
|
|
271
|
-
fontSize: 10,
|
|
272
270
|
letterSpacing: 1.2,
|
|
273
271
|
textTransform: 'uppercase',
|
|
274
272
|
color: 'rgba(255, 255, 255, 0.75)',
|
|
@@ -282,13 +280,9 @@ const styles = StyleSheet.create({
|
|
|
282
280
|
alignItems: 'flex-start',
|
|
283
281
|
},
|
|
284
282
|
cityCode: {
|
|
285
|
-
|
|
286
|
-
fontSize: 22,
|
|
287
283
|
color: theme.colors.white,
|
|
288
284
|
},
|
|
289
285
|
cityName: {
|
|
290
|
-
|
|
291
|
-
fontSize: 9,
|
|
292
286
|
color: 'rgba(255, 255, 255, 0.75)',
|
|
293
287
|
marginTop: 2,
|
|
294
288
|
},
|
|
@@ -345,14 +339,10 @@ const styles = StyleSheet.create({
|
|
|
345
339
|
flex: 1,
|
|
346
340
|
},
|
|
347
341
|
countryName: {
|
|
348
|
-
|
|
349
|
-
fontSize: 17,
|
|
350
342
|
color: '#51332d',
|
|
351
343
|
marginBottom: 2,
|
|
352
344
|
},
|
|
353
345
|
countrySubtitle: {
|
|
354
|
-
|
|
355
|
-
fontSize: 11,
|
|
356
346
|
color: '#9a8478',
|
|
357
347
|
},
|
|
358
348
|
divider: {
|
|
@@ -370,15 +360,11 @@ const styles = StyleSheet.create({
|
|
|
370
360
|
flex: 1,
|
|
371
361
|
},
|
|
372
362
|
footerLabel: {
|
|
373
|
-
|
|
374
|
-
fontSize: 9,
|
|
375
363
|
textTransform: 'uppercase',
|
|
376
364
|
color: '#b9a392',
|
|
377
365
|
marginBottom: 4,
|
|
378
366
|
},
|
|
379
367
|
footerValue: {
|
|
380
|
-
|
|
381
|
-
fontSize: 16,
|
|
382
368
|
color: '#51332d',
|
|
383
369
|
},
|
|
384
370
|
});
|
|
@@ -10,13 +10,13 @@ const meta = {
|
|
|
10
10
|
layout: 'padded',
|
|
11
11
|
docs: {
|
|
12
12
|
description: {
|
|
13
|
-
component: 'O **ModuleCard** exibe as lições no percurso principal do utilizador (a "Tree" de aprendizagem).\n\nPossui diferentes estados (`active`, `locked`, `completed`, `dark`) e pode incluir barras de progresso fragmentadas.\n\n### Exemplo de Uso\n```tsx\n<ModuleCard \n status="active"\n number={2}\n title="No mercado de Dakar"\n subtitle="Regatear, números, comida"\n progress={0.6}\n segments={5}\n tagLabel="EM CURSO"\n/>\n```',
|
|
13
|
+
component: 'O **ModuleCard** exibe as lições no percurso principal do utilizador (a "Tree" de aprendizagem).\n\nPossui diferentes estados (`active`, `locked`, `completed`, `dark`, `active-dark`) e pode incluir barras de progresso fragmentadas.\n\n### Animações\n- **active**: O cartão inteiro é animado e reativo ao toque.\n- **active-dark**: O cartão é fixo, apenas os botões de ação ("Agendar Aula" e "Rever") são animados individualmente.\n- **completed**: O cartão é fixo, apenas o botão de revisão ("Rever Módulo" ou o ícone) tem animação individual.\n\n### Exemplo de Uso\n```tsx\n<ModuleCard \n status="active"\n number={2}\n title="No mercado de Dakar"\n subtitle="Regatear, números, comida"\n progress={0.6}\n segments={5}\n tagLabel="EM CURSO"\n/>\n```',
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
tags: ['autodocs'],
|
|
18
18
|
argTypes: {
|
|
19
|
-
status: { control: 'radio', options: ['active', 'locked', 'completed', 'dark'] },
|
|
19
|
+
status: { control: 'radio', options: ['active', 'locked', 'completed', 'dark', 'active-dark'] },
|
|
20
20
|
number: { control: 'number' },
|
|
21
21
|
title: { control: 'text' },
|
|
22
22
|
subtitle: { control: 'text' },
|
|
@@ -68,6 +68,26 @@ export const DarkLocked: Story = {
|
|
|
68
68
|
},
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
+
export const ActiveDark: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
status: 'active-dark',
|
|
74
|
+
title: 'No mercado de Dakar',
|
|
75
|
+
subtitle: 'Regatear, números, comida',
|
|
76
|
+
subtext: '• 3/5',
|
|
77
|
+
tagLabel: 'AULA MARCADA',
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const Completed: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
status: 'completed',
|
|
84
|
+
number: 1,
|
|
85
|
+
title: 'Saudações & teranga',
|
|
86
|
+
subtitle: 'Concluído',
|
|
87
|
+
subtext: '• 5/5',
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
71
91
|
export const Grid: Story = {
|
|
72
92
|
args: {
|
|
73
93
|
status: 'active',
|
|
@@ -103,6 +123,13 @@ export const Grid: Story = {
|
|
|
103
123
|
title="Aula ao vivo + Teste final"
|
|
104
124
|
subtitle="Acaba os 4 módulos para desbloquear"
|
|
105
125
|
/>
|
|
126
|
+
<ModuleCard
|
|
127
|
+
status="active-dark"
|
|
128
|
+
title="Aula de revisão agendada"
|
|
129
|
+
subtitle="Conversação e prática"
|
|
130
|
+
subtext="• 4/4"
|
|
131
|
+
tagLabel="AULA MARCADA"
|
|
132
|
+
/>
|
|
106
133
|
</View>
|
|
107
134
|
),
|
|
108
135
|
};
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
import { View, StyleSheet, Pressable } from 'react-native';
|
|
1
|
+
import { View, StyleSheet, Pressable, Animated } from 'react-native';
|
|
2
2
|
import type { ViewStyle } from 'react-native';
|
|
3
3
|
import { theme } from '../../../styles/theme';
|
|
4
4
|
import { Tag } from '../../DataDisplay/Tag/Tag';
|
|
5
5
|
import { ProgressBar } from '../../DataDisplay/ProgressBar/ProgressBar';
|
|
6
6
|
import { Badge } from '../../DataDisplay/Badge/Badge';
|
|
7
|
-
import { Lock, Check, GraduationCap } from 'lucide-react-native';
|
|
7
|
+
import { Lock, Check, GraduationCap, RotateCcw, CalendarPlus } from 'lucide-react-native';
|
|
8
8
|
import { useResponsive } from '../../../hooks/useResponsive';
|
|
9
9
|
import { Typography } from '../../Typography/Typography';
|
|
10
|
+
import React, { useRef } from 'react';
|
|
10
11
|
|
|
11
|
-
export type ModuleCardStatus = 'active' | 'locked' | 'completed' | 'dark';
|
|
12
|
+
export type ModuleCardStatus = 'active' | 'locked' | 'completed' | 'dark' | 'active-dark';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Cartão que representa uma lição ou módulo no caminho de aprendizagem.
|
|
16
|
+
*
|
|
17
|
+
* Regras de Animação e Clique:
|
|
18
|
+
* - `active`: O cartão como um todo tem animação de escala e é clicável via `onPress`.
|
|
19
|
+
* - `active-dark`: O cartão é estático. Apenas os botões internos de "Agendar" (`onSchedulePress`) e "Rever" (`onReviewPress`) têm animação individual.
|
|
20
|
+
* - `completed`: O cartão é estático. Apenas o ícone/botão superior direito de "Rever" (`onReviewPress`) tem animação individual.
|
|
21
|
+
* - `locked` / `dark`: O cartão não é interativo.
|
|
15
22
|
*/
|
|
16
23
|
export interface ModuleCardProps {
|
|
17
|
-
/** Estado atual da lição (ativa, trancada, concluída,
|
|
24
|
+
/** Estado atual da lição (ativa, trancada, concluída, etc). */
|
|
18
25
|
status: ModuleCardStatus;
|
|
19
26
|
/** Número da lição (ex: 1, 2, 3). */
|
|
20
27
|
number?: number;
|
|
@@ -38,6 +45,10 @@ export interface ModuleCardProps {
|
|
|
38
45
|
/** Cor customizada para o subtítulo. */
|
|
39
46
|
subtitleColor?: string;
|
|
40
47
|
style?: ViewStyle | ViewStyle[];
|
|
48
|
+
/** Função de clique no botão de Agendar (modo active/dark). */
|
|
49
|
+
onSchedulePress?: () => void;
|
|
50
|
+
/** Função de clique no botão de Rever (modo completed/active/dark). */
|
|
51
|
+
onReviewPress?: () => void;
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
export const ModuleCard = ({
|
|
@@ -54,11 +65,53 @@ export const ModuleCard = ({
|
|
|
54
65
|
subtitleColor,
|
|
55
66
|
onPress,
|
|
56
67
|
style,
|
|
68
|
+
onSchedulePress,
|
|
69
|
+
onReviewPress,
|
|
57
70
|
}: ModuleCardProps) => {
|
|
58
71
|
const { scale, scaleText, width } = useResponsive();
|
|
59
|
-
|
|
60
72
|
const isMobileScreen = width < 400;
|
|
61
73
|
|
|
74
|
+
const scheduleScale = useRef(new Animated.Value(1)).current;
|
|
75
|
+
const reviewScale = useRef(new Animated.Value(1)).current;
|
|
76
|
+
const completedReviewScale = useRef(new Animated.Value(1)).current;
|
|
77
|
+
const scaleAnim = useRef(new Animated.Value(1)).current;
|
|
78
|
+
|
|
79
|
+
const handlePressIn = () => {
|
|
80
|
+
if (status !== 'active') return;
|
|
81
|
+
Animated.timing(scaleAnim, {
|
|
82
|
+
toValue: 0.95,
|
|
83
|
+
duration: 100,
|
|
84
|
+
useNativeDriver: true,
|
|
85
|
+
}).start();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handlePressOut = () => {
|
|
89
|
+
if (status !== 'active') return;
|
|
90
|
+
Animated.spring(scaleAnim, {
|
|
91
|
+
toValue: 1,
|
|
92
|
+
friction: 4,
|
|
93
|
+
tension: 40,
|
|
94
|
+
useNativeDriver: true,
|
|
95
|
+
}).start();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const animateBtnIn = (anim: Animated.Value) => {
|
|
99
|
+
Animated.timing(anim, {
|
|
100
|
+
toValue: 0.9,
|
|
101
|
+
duration: 100,
|
|
102
|
+
useNativeDriver: true,
|
|
103
|
+
}).start();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const animateBtnOut = (anim: Animated.Value) => {
|
|
107
|
+
Animated.spring(anim, {
|
|
108
|
+
toValue: 1,
|
|
109
|
+
friction: 4,
|
|
110
|
+
tension: 40,
|
|
111
|
+
useNativeDriver: true,
|
|
112
|
+
}).start();
|
|
113
|
+
};
|
|
114
|
+
|
|
62
115
|
const getContainerStyle = () => {
|
|
63
116
|
switch (status) {
|
|
64
117
|
case 'active':
|
|
@@ -81,6 +134,7 @@ export const ModuleCard = ({
|
|
|
81
134
|
...theme.shadows.sm,
|
|
82
135
|
};
|
|
83
136
|
case 'dark':
|
|
137
|
+
case 'active-dark':
|
|
84
138
|
return {
|
|
85
139
|
borderColor: theme.colors.brown[800],
|
|
86
140
|
borderWidth: 0,
|
|
@@ -96,6 +150,7 @@ export const ModuleCard = ({
|
|
|
96
150
|
case 'locked':
|
|
97
151
|
return theme.colors.brown[400];
|
|
98
152
|
case 'dark':
|
|
153
|
+
case 'active-dark':
|
|
99
154
|
return theme.colors.white;
|
|
100
155
|
default:
|
|
101
156
|
return theme.colors.brown[800];
|
|
@@ -104,7 +159,7 @@ export const ModuleCard = ({
|
|
|
104
159
|
|
|
105
160
|
const containerStyle = getContainerStyle();
|
|
106
161
|
const titleStyleColor = getTextColor();
|
|
107
|
-
const subtitleStyleColor = subtitleColor || (status === 'completed' ? theme.colors.success : status === 'locked' || status === 'dark' ? theme.colors.brown[300] : theme.colors.brown[500]);
|
|
162
|
+
const subtitleStyleColor = subtitleColor || (status === 'completed' ? theme.colors.success : status === 'locked' || status === 'dark' || status === 'active-dark' ? theme.colors.brown[300] : theme.colors.brown[500]);
|
|
108
163
|
|
|
109
164
|
const activeShadow = status === 'active' ? {
|
|
110
165
|
shadowColor: theme.colors.primary,
|
|
@@ -151,6 +206,8 @@ export const ModuleCard = ({
|
|
|
151
206
|
</View>
|
|
152
207
|
);
|
|
153
208
|
}
|
|
209
|
+
|
|
210
|
+
|
|
154
211
|
|
|
155
212
|
return (
|
|
156
213
|
<Badge
|
|
@@ -164,65 +221,101 @@ export const ModuleCard = ({
|
|
|
164
221
|
const colGap = size === 'xl' ? scale(16) : size === 'lg' ? scale(14) : isMobileScreen ? scale(8) : scale(12);
|
|
165
222
|
const cardPaddingV = size === 'xl' ? scale(20) : size === 'lg' ? scale(16) : isMobileScreen ? scale(10) : scale(14);
|
|
166
223
|
const cardPaddingH = size === 'xl' ? scale(20) : size === 'lg' ? scale(18) : isMobileScreen ? scale(12) : scale(15);
|
|
167
|
-
|
|
168
|
-
const
|
|
224
|
+
|
|
225
|
+
const titleVariant = size === 'xl' ? 'h1' : size === 'lg' ? 'h2' : 'h3';
|
|
226
|
+
|
|
227
|
+
const subtitleVariant = size === 'xl' ? 'bodyLg' : size === 'lg' ? 'bodyMd' : 'bodySm';
|
|
228
|
+
|
|
229
|
+
let displaySubtext = subtext;
|
|
230
|
+
if (status === 'active-dark') {
|
|
231
|
+
if (subtext) {
|
|
232
|
+
if (subtext.trim().startsWith('•')) {
|
|
233
|
+
displaySubtext = `Aula de revisão ${subtext.trim()}`;
|
|
234
|
+
} else {
|
|
235
|
+
displaySubtext = `Aula de revisão • ${subtext}`;
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
displaySubtext = 'Aula de revisão';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
169
241
|
|
|
170
242
|
return (
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
243
|
+
<Animated.View style={[{ transform: [{ scale: scaleAnim }] }, style]}>
|
|
244
|
+
<Pressable
|
|
245
|
+
style={[
|
|
246
|
+
styles.container,
|
|
247
|
+
containerStyle,
|
|
248
|
+
status === 'active' && activeShadow,
|
|
249
|
+
{
|
|
250
|
+
paddingVertical: cardPaddingV,
|
|
251
|
+
paddingHorizontal: cardPaddingH,
|
|
252
|
+
borderRadius: scale(18),
|
|
253
|
+
},
|
|
254
|
+
]}
|
|
255
|
+
onPress={onPress}
|
|
256
|
+
onPressIn={handlePressIn}
|
|
257
|
+
onPressOut={handlePressOut}
|
|
258
|
+
disabled={status === 'locked'}
|
|
259
|
+
>
|
|
186
260
|
<View style={styles.topRow}>
|
|
187
261
|
<View style={styles.leftCol}>
|
|
188
262
|
{renderLeftIconBox()}
|
|
189
263
|
</View>
|
|
190
264
|
|
|
191
265
|
<View style={[styles.content, { marginLeft: colGap }]}>
|
|
192
|
-
<Typography
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
266
|
+
<Typography
|
|
267
|
+
variant={titleVariant}
|
|
268
|
+
style={[
|
|
269
|
+
styles.title,
|
|
270
|
+
{
|
|
271
|
+
color: titleStyleColor,
|
|
272
|
+
marginBottom: scale(2),
|
|
273
|
+
}
|
|
274
|
+
]}
|
|
275
|
+
>
|
|
200
276
|
{title}
|
|
201
277
|
</Typography>
|
|
202
278
|
|
|
203
|
-
{(subtitle || subtext) && (
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
279
|
+
{(subtitle || subtext || displaySubtext) && (
|
|
280
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginTop: scale(2) }}>
|
|
281
|
+
<Typography variant={subtitleVariant} style={[
|
|
282
|
+
styles.subtitle,
|
|
283
|
+
{
|
|
284
|
+
color: subtitleStyleColor,
|
|
285
|
+
flexShrink: 1, // Para o texto encolher se a tag ocupar espaço
|
|
286
|
+
}
|
|
287
|
+
]}>
|
|
288
|
+
{status !== 'active-dark' && subtitle}
|
|
289
|
+
{status !== 'active-dark' && subtitle && displaySubtext && ' '}
|
|
290
|
+
{displaySubtext && <Typography variant={subtitleVariant} style={styles.subtext}>{displaySubtext}</Typography>}
|
|
291
|
+
</Typography>
|
|
292
|
+
{tagLabel && (status === 'active-dark' || status === 'active') && (
|
|
293
|
+
<Tag
|
|
294
|
+
label={tagLabel}
|
|
295
|
+
variant="primary"
|
|
296
|
+
size={isMobileScreen ? 'xs' : 'sm'}
|
|
297
|
+
style={{ marginLeft: scale(8) }}
|
|
298
|
+
/>
|
|
299
|
+
)}
|
|
300
|
+
</View>
|
|
215
301
|
)}
|
|
216
302
|
</View>
|
|
217
303
|
|
|
218
|
-
{(
|
|
304
|
+
{(status === 'locked' || status === 'dark' || status === 'completed') && (
|
|
219
305
|
<View style={[styles.rightContent, { marginLeft: colGap }]}>
|
|
220
|
-
{
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
306
|
+
{status === 'completed' && (
|
|
307
|
+
<Animated.View style={{ transform: [{ scale: completedReviewScale }] }}>
|
|
308
|
+
<Pressable
|
|
309
|
+
style={styles.reviewIconButton}
|
|
310
|
+
onPress={onReviewPress || (() => {})}
|
|
311
|
+
onPressIn={() => animateBtnIn(completedReviewScale)}
|
|
312
|
+
onPressOut={() => animateBtnOut(completedReviewScale)}
|
|
313
|
+
hitSlop={10}
|
|
314
|
+
>
|
|
315
|
+
<RotateCcw stroke={theme.colors.primary} strokeWidth={2.5} size={isMobileScreen ? scale(20) : scale(24)} />
|
|
316
|
+
<Typography variant="label" style={[styles.reviewIconText, { fontSize: scaleText(9) }]}>REVER</Typography>
|
|
317
|
+
</Pressable>
|
|
318
|
+
</Animated.View>
|
|
226
319
|
)}
|
|
227
320
|
{(status === 'locked' || status === 'dark') && (
|
|
228
321
|
<Lock
|
|
@@ -244,7 +337,40 @@ export const ModuleCard = ({
|
|
|
244
337
|
/>
|
|
245
338
|
</View>
|
|
246
339
|
)}
|
|
340
|
+
|
|
341
|
+
{status === 'active-dark' && (
|
|
342
|
+
<View style={styles.compactDarkFooter}>
|
|
343
|
+
<Animated.View style={{ transform: [{ scale: scheduleScale }] }}>
|
|
344
|
+
<Pressable
|
|
345
|
+
onPress={onSchedulePress || (() => {})}
|
|
346
|
+
onPressIn={() => animateBtnIn(scheduleScale)}
|
|
347
|
+
onPressOut={() => animateBtnOut(scheduleScale)}
|
|
348
|
+
hitSlop={8}
|
|
349
|
+
style={{ flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.primary, paddingHorizontal: scale(14), paddingVertical: scale(6), borderRadius: 100 }}
|
|
350
|
+
>
|
|
351
|
+
<CalendarPlus stroke={theme.colors.white} strokeWidth={2.5} size={scale(14)} />
|
|
352
|
+
<Typography variant="label" style={{ color: theme.colors.white, fontSize: scaleText(12), fontWeight: 'bold', marginLeft: scale(6) }}>Agendar Aula</Typography>
|
|
353
|
+
</Pressable>
|
|
354
|
+
</Animated.View>
|
|
355
|
+
|
|
356
|
+
<Animated.View style={{ transform: [{ scale: reviewScale }] }}>
|
|
357
|
+
<Pressable
|
|
358
|
+
style={styles.compactReviewBtn}
|
|
359
|
+
onPress={onReviewPress || (() => {})}
|
|
360
|
+
onPressIn={() => animateBtnIn(reviewScale)}
|
|
361
|
+
onPressOut={() => animateBtnOut(reviewScale)}
|
|
362
|
+
hitSlop={8}
|
|
363
|
+
>
|
|
364
|
+
<RotateCcw stroke={theme.colors.primary} strokeWidth={2.5} size={scale(16)} />
|
|
365
|
+
<Typography variant="label" style={[styles.reviewIconText, { color: theme.colors.primary, fontSize: scaleText(10), marginLeft: scale(4), marginTop: 0 }]}>REVER</Typography>
|
|
366
|
+
</Pressable>
|
|
367
|
+
</Animated.View>
|
|
368
|
+
</View>
|
|
369
|
+
)}
|
|
370
|
+
|
|
371
|
+
|
|
247
372
|
</Pressable>
|
|
373
|
+
</Animated.View>
|
|
248
374
|
);
|
|
249
375
|
};
|
|
250
376
|
|
|
@@ -288,13 +414,9 @@ const styles = StyleSheet.create({
|
|
|
288
414
|
borderStyle: 'dashed' as const,
|
|
289
415
|
},
|
|
290
416
|
title: {
|
|
291
|
-
|
|
292
|
-
fontSize: 16,
|
|
293
417
|
marginBottom: 4,
|
|
294
418
|
},
|
|
295
419
|
subtitle: {
|
|
296
|
-
|
|
297
|
-
fontSize: 12,
|
|
298
420
|
},
|
|
299
421
|
subtext: {
|
|
300
422
|
color: theme.colors.brown[400],
|
|
@@ -309,4 +431,35 @@ const styles = StyleSheet.create({
|
|
|
309
431
|
marginLeft: 12,
|
|
310
432
|
flexShrink: 0,
|
|
311
433
|
},
|
|
434
|
+
reviewIconButton: {
|
|
435
|
+
alignItems: 'center',
|
|
436
|
+
justifyContent: 'center',
|
|
437
|
+
padding: 4,
|
|
438
|
+
},
|
|
439
|
+
reviewIconText: {
|
|
440
|
+
color: theme.colors.primary,
|
|
441
|
+
marginTop: 2,
|
|
442
|
+
textAlign: 'center',
|
|
443
|
+
},
|
|
444
|
+
compactDarkFooter: {
|
|
445
|
+
flexDirection: 'row',
|
|
446
|
+
alignItems: 'center',
|
|
447
|
+
justifyContent: 'space-between',
|
|
448
|
+
marginTop: 10,
|
|
449
|
+
paddingTop: 8,
|
|
450
|
+
borderTopWidth: 1,
|
|
451
|
+
borderTopColor: 'rgba(255,255,255,0.1)',
|
|
452
|
+
},
|
|
453
|
+
compactScheduleBtn: {
|
|
454
|
+
flexDirection: 'row',
|
|
455
|
+
alignItems: 'center',
|
|
456
|
+
},
|
|
457
|
+
compactReviewBtn: {
|
|
458
|
+
flexDirection: 'row',
|
|
459
|
+
alignItems: 'center',
|
|
460
|
+
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
461
|
+
paddingHorizontal: 8,
|
|
462
|
+
paddingVertical: 4,
|
|
463
|
+
borderRadius: 8,
|
|
464
|
+
},
|
|
312
465
|
});
|
|
@@ -27,7 +27,7 @@ export const PassportCover = () => {
|
|
|
27
27
|
borderRadius: scale(8)
|
|
28
28
|
}]}>
|
|
29
29
|
|
|
30
|
-
<Typography style={[styles.republique, {
|
|
30
|
+
<Typography variant="label" style={[styles.republique, { marginBottom: scale(48) }]}>
|
|
31
31
|
RÉPUBLIQUE DE LA XETWA
|
|
32
32
|
</Typography>
|
|
33
33
|
|
|
@@ -48,10 +48,10 @@ export const PassportCover = () => {
|
|
|
48
48
|
</View>
|
|
49
49
|
|
|
50
50
|
<View style={styles.titleContainer}>
|
|
51
|
-
<Typography style={[styles.title
|
|
51
|
+
<Typography variant="display1" style={[styles.title]}>
|
|
52
52
|
PASSEPORT
|
|
53
53
|
</Typography>
|
|
54
|
-
<Typography style={[styles.subtitle, {
|
|
54
|
+
<Typography variant="caption" style={[styles.subtitle, { marginTop: scale(8) }]}>
|
|
55
55
|
FRANCOPHONIE · MONDE
|
|
56
56
|
</Typography>
|
|
57
57
|
</View>
|