@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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/src/components/Buttons/IconButton/IconButton.tsx +2 -2
  3. package/src/components/Buttons/JourneyButton/JourneyButton.tsx +8 -2
  4. package/src/components/Buttons/MainButton/MainButton.tsx +0 -19
  5. package/src/components/Buttons/WordChip/WordChip.tsx +11 -5
  6. package/src/components/Cards/BoardingPassCard/BoardingPassCard.tsx +11 -25
  7. package/src/components/Cards/ModuleCard/ModuleCard.stories.tsx +29 -2
  8. package/src/components/Cards/ModuleCard/ModuleCard.tsx +207 -54
  9. package/src/components/Cards/Passport/PassportCover.tsx +3 -3
  10. package/src/components/Charts/WeeklyProgressChart/WeeklyProgressChart.tsx +8 -9
  11. package/src/components/DataDisplay/Avatar/Avatar.tsx +8 -9
  12. package/src/components/DataDisplay/Badge/Badge.tsx +13 -20
  13. package/src/components/DataDisplay/InstructionBubble/InstructionBubble.tsx +2 -2
  14. package/src/components/DataDisplay/LevelBadge/LevelBadge.tsx +2 -15
  15. package/src/components/DataDisplay/NumberBadge/NumberBadge.tsx +1 -2
  16. package/src/components/DataDisplay/StatBadge/StatBadge.tsx +1 -1
  17. package/src/components/DataDisplay/Tag/Tag.tsx +11 -23
  18. package/src/components/Feedback/FeedbackBottomSheet/FeedbackBottomSheet.tsx +4 -13
  19. package/src/components/Headers/CountryHeader/CountryHeader.tsx +3 -3
  20. package/src/components/Headers/ProfileHeader/ProfileHeader.tsx +8 -5
  21. package/src/components/Inputs/FillInTheBlank/FillInTheBlank.tsx +5 -5
  22. package/src/components/Inputs/TextInput/TextInput.tsx +4 -4
  23. package/src/components/Navigation/BottomTabBar/BottomTabBarItem.tsx +1 -1
  24. package/src/components/Navigation/ProgressMap/ProgressMap.tsx +9 -9
  25. package/src/components/Navigation/WorldProgressMap/WorldProgressMap.tsx +9 -9
  26. package/src/components/Typography/Typography.tsx +16 -10
  27. package/src/styles/theme.ts +39 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xetwa/design-system",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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', fontSize: 10 }}>Missing</Typography>
164
+ <Typography variant="caption" style={{ color: 'red' }}>Missing</Typography>
165
165
  )}
166
166
 
167
167
  {!!text && (
168
- <Typography style={[styles.text, { color: getTextColor(), fontSize: scaleText(size === 'sm' ? 10 : 12), marginTop: scale(4) }]}>
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(10), marginBottom: scale(4) }]}
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 style={[styles.title, { color: getTextColor(), fontSize: scaleText(20) }]}>
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, { fontSize: scaleText(10) }]}>CARTÃO DE EMBARQUE</Typography>
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, { fontSize: scaleText(22) }]}>{originCode}</Typography>
170
- <Typography style={[styles.cityName, { fontSize: scaleText(9) }]}>{originName}</Typography>
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, { fontSize: scaleText(22) }]}>{destCode}</Typography>
182
- <Typography style={[styles.cityName, { fontSize: scaleText(9) }]}>{destName}</Typography>
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, { fontSize: scaleText(17) }]}>{countryName}</Typography>
209
- <Typography style={[styles.countrySubtitle, { fontSize: scaleText(11) }]}>{countrySubtitle}</Typography>
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, { fontSize: scaleText(9) }]}>MÓDULOS</Typography>
224
- <Typography style={[styles.footerValue, { fontSize: scaleText(16) }]}>{modulesCount}</Typography>
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, { fontSize: scaleText(9) }]}>FOCO</Typography>
228
- <Typography style={[styles.footerValue, { fontSize: scaleText(16) }]}>{focusText}</Typography>
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, ou o módulo "dark" final). */
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
- const titleFontSize = size === 'xl' ? scaleText(18) : size === 'lg' ? scaleText(16) : isMobileScreen ? scaleText(14) : scaleText(16);
168
- const subtitleFontSize = size === 'xl' ? scaleText(14) : size === 'lg' ? scaleText(13) : isMobileScreen ? scaleText(10) : scaleText(12);
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
- <Pressable
172
- style={[
173
- styles.container,
174
- containerStyle,
175
- status === 'active' && activeShadow,
176
- {
177
- paddingVertical: cardPaddingV,
178
- paddingHorizontal: cardPaddingH,
179
- borderRadius: scale(18),
180
- },
181
- style,
182
- ]}
183
- onPress={onPress}
184
- disabled={!onPress || (status === 'locked' && !onPress)}
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 variant="h2" style={[
193
- styles.title,
194
- {
195
- color: titleStyleColor,
196
- fontSize: titleFontSize,
197
- marginBottom: scale(2),
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
- <Typography variant="bodyMd" style={[
205
- styles.subtitle,
206
- {
207
- color: subtitleStyleColor,
208
- fontSize: subtitleFontSize,
209
- }
210
- ]}>
211
- {subtitle}
212
- {subtitle && subtext && ' '}
213
- {subtext && <Typography style={styles.subtext}>{subtext}</Typography>}
214
- </Typography>
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
- {(tagLabel || status === 'locked' || status === 'dark') && (
304
+ {(status === 'locked' || status === 'dark' || status === 'completed') && (
219
305
  <View style={[styles.rightContent, { marginLeft: colGap }]}>
220
- {tagLabel && status === 'active' && (
221
- <Tag
222
- label={tagLabel}
223
- variant="primary"
224
- size={isMobileScreen ? 'xs' : 'sm'}
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, { fontSize: scaleText(12), marginBottom: scale(48) }]}>
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, { fontSize: scaleText(32) }]}>
51
+ <Typography variant="display1" style={[styles.title]}>
52
52
  PASSEPORT
53
53
  </Typography>
54
- <Typography style={[styles.subtitle, { fontSize: scaleText(10), marginTop: scale(8) }]}>
54
+ <Typography variant="caption" style={[styles.subtitle, { marginTop: scale(8) }]}>
55
55
  FRANCOPHONIE · MONDE
56
56
  </Typography>
57
57
  </View>