@xetwa/design-system 1.0.39 → 1.0.41

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.
@@ -0,0 +1,245 @@
1
+ import React, { useRef, useEffect } from 'react';
2
+ import { View, StyleSheet, Pressable, Animated, Platform } from 'react-native';
3
+ import type { ViewStyle } from 'react-native';
4
+ import { theme } from '../../../styles/theme';
5
+ import { Typography } from '../../Typography/Typography';
6
+ import { useResponsive } from '../../../hooks/useResponsive';
7
+ import { IconMap } from '../../../utils/iconMap';
8
+
9
+ export type TabBarVariant = 'A' | 'B' | 'C';
10
+
11
+ export interface TabBarItem {
12
+ id: string;
13
+ label: string;
14
+ icon: string;
15
+ hasBadge?: boolean;
16
+ }
17
+
18
+ export interface TabBarProps {
19
+ items: TabBarItem[];
20
+ activeItemId: string;
21
+ onItemPress: (id: string) => void;
22
+ variant?: TabBarVariant;
23
+ safeAreaBottom?: number;
24
+ style?: ViewStyle | ViewStyle[];
25
+ }
26
+
27
+ const TabBarItemComponent = ({
28
+ item,
29
+ isActive,
30
+ variant,
31
+ iconSize,
32
+ onItemPress,
33
+ }: {
34
+ item: TabBarItem;
35
+ isActive: boolean;
36
+ variant: TabBarVariant;
37
+ iconSize: number;
38
+ onItemPress: (id: string) => void;
39
+ }) => {
40
+ const { scale, scaleText } = useResponsive();
41
+ const color = isActive ? theme.colors.primary : theme.colors.brown[300];
42
+
43
+ // Animação de escala ao pressionar
44
+ const scaleAnim = useRef(new Animated.Value(1)).current;
45
+
46
+ const handlePressIn = () => {
47
+ Animated.timing(scaleAnim, {
48
+ toValue: 0.9,
49
+ duration: 100,
50
+ useNativeDriver: true,
51
+ }).start();
52
+ };
53
+
54
+ const handlePressOut = () => {
55
+ Animated.spring(scaleAnim, {
56
+ toValue: 1,
57
+ friction: 4,
58
+ tension: 40,
59
+ useNativeDriver: true,
60
+ }).start();
61
+ };
62
+
63
+ // Animação de transição quando se torna ativo
64
+ const activeAnim = useRef(new Animated.Value(isActive ? 1 : 0)).current;
65
+ useEffect(() => {
66
+ Animated.spring(activeAnim, {
67
+ toValue: isActive ? 1 : 0,
68
+ friction: 7,
69
+ tension: 60,
70
+ useNativeDriver: true,
71
+ }).start();
72
+ }, [isActive]);
73
+
74
+ return (
75
+ <Pressable
76
+ onPress={() => onItemPress(item.id)}
77
+ onPressIn={handlePressIn}
78
+ onPressOut={handlePressOut}
79
+ style={styles.itemContainer}
80
+ >
81
+ <Animated.View style={[styles.itemInner, { transform: [{ scale: scaleAnim }] }]}>
82
+ {/* Variante B: Barra no topo */}
83
+ {variant === 'B' && (
84
+ <Animated.View
85
+ style={[
86
+ styles.indicatorB,
87
+ {
88
+ width: scale(28),
89
+ height: scale(4),
90
+ opacity: activeAnim,
91
+ transform: [
92
+ { translateY: activeAnim.interpolate({ inputRange: [0, 1], outputRange: [-4, 0] }) },
93
+ { scaleX: activeAnim.interpolate({ inputRange: [0, 1], outputRange: [0.4, 1] }) }
94
+ ]
95
+ }
96
+ ]}
97
+ />
98
+ )}
99
+
100
+ {/* Variante A: Pill no ícone */}
101
+ <View
102
+ style={[
103
+ styles.iconWrapper,
104
+ variant === 'A' && isActive && styles.pillA,
105
+ variant === 'A' && isActive && { paddingHorizontal: scale(16), paddingVertical: scale(6), borderRadius: scale(100) },
106
+ ]}
107
+ >
108
+ {(() => {
109
+ const IconComponent = IconMap[item.icon];
110
+ if (!IconComponent) return null;
111
+ return <IconComponent color={color} size={iconSize} strokeWidth={2.5} />;
112
+ })()}
113
+
114
+ {/* Badge de Notificação */}
115
+ {item.hasBadge && (
116
+ <View
117
+ style={[
118
+ styles.badge,
119
+ {
120
+ width: scale(9),
121
+ height: scale(9),
122
+ borderRadius: scale(4.5),
123
+ borderWidth: scale(2),
124
+ top: scale(-3),
125
+ right: scale(-4),
126
+ // Adjust badge position for pill
127
+ ...(variant === 'A' && isActive && { top: scale(3), right: scale(8) })
128
+ }
129
+ ]}
130
+ />
131
+ )}
132
+ </View>
133
+
134
+ <Typography
135
+ variant="smallHeavy"
136
+ style={[
137
+ styles.label,
138
+ { color, fontSize: scaleText(9.5) },
139
+ variant === 'C' && { marginBottom: scale(4) }
140
+ ]}
141
+ >
142
+ {item.label}
143
+ </Typography>
144
+
145
+ {/* Variante C: Dot abaixo da label */}
146
+ {variant === 'C' && isActive && (
147
+ <View style={[styles.dotC, { width: scale(4), height: scale(4), borderRadius: scale(2) }]} />
148
+ )}
149
+ </Animated.View>
150
+ </Pressable>
151
+ );
152
+ };
153
+
154
+ export const TabBar = ({
155
+ items,
156
+ activeItemId,
157
+ onItemPress,
158
+ variant = 'B',
159
+ safeAreaBottom = 0,
160
+ style,
161
+ }: TabBarProps) => {
162
+ const { scale, scaleText, width } = useResponsive();
163
+ const isDesktop = width >= 768;
164
+
165
+ const iconSize = isDesktop ? scale(22) : scale(21);
166
+ const bottomPadding = Math.max(scale(14), safeAreaBottom);
167
+
168
+ return (
169
+ <View
170
+ style={[
171
+ styles.container,
172
+ {
173
+ paddingBottom: bottomPadding,
174
+ paddingHorizontal: scale(6),
175
+ },
176
+ style,
177
+ ]}
178
+ >
179
+ {items.map((item) => (
180
+ <TabBarItemComponent
181
+ key={item.id}
182
+ item={item}
183
+ isActive={activeItemId === item.id}
184
+ variant={variant}
185
+ iconSize={iconSize}
186
+ onItemPress={onItemPress}
187
+ />
188
+ ))}
189
+ </View>
190
+ );
191
+ };
192
+
193
+ const styles = StyleSheet.create({
194
+ container: {
195
+ flexDirection: 'row',
196
+ justifyContent: 'space-around',
197
+ alignItems: 'flex-start',
198
+ backgroundColor: theme.colors.white,
199
+ borderTopWidth: 1.5,
200
+ borderTopColor: theme.colors.brown[100],
201
+ width: '100%',
202
+ minHeight: 56,
203
+ },
204
+ itemContainer: {
205
+ flex: 1,
206
+ alignItems: 'center',
207
+ justifyContent: 'flex-start',
208
+ minWidth: 60,
209
+ },
210
+ itemInner: {
211
+ alignItems: 'center',
212
+ paddingTop: 9, // Espaço entre border-top e ícone (Variant B spec)
213
+ position: 'relative',
214
+ width: '100%',
215
+ },
216
+ iconWrapper: {
217
+ position: 'relative',
218
+ marginBottom: 4,
219
+ alignItems: 'center',
220
+ justifyContent: 'center',
221
+ },
222
+ pillA: {
223
+ backgroundColor: theme.colors.primaryLight,
224
+ },
225
+ indicatorB: {
226
+ position: 'absolute',
227
+ top: 0,
228
+ backgroundColor: theme.colors.primary,
229
+ borderBottomLeftRadius: 4,
230
+ borderBottomRightRadius: 4,
231
+ },
232
+ label: {
233
+ textAlign: 'center',
234
+ },
235
+ dotC: {
236
+ position: 'absolute',
237
+ bottom: -6,
238
+ backgroundColor: theme.colors.primary,
239
+ },
240
+ badge: {
241
+ position: 'absolute',
242
+ backgroundColor: theme.colors.error,
243
+ borderColor: theme.colors.white,
244
+ },
245
+ });
package/src/index.ts CHANGED
@@ -1,43 +1,36 @@
1
- export * from './components/Headers/CountryHeader/CountryHeader';
2
- export * from './components/Buttons/MainButton/MainButton';
3
- export * from './components/Buttons/BaseButton/BaseButton';
4
1
  export * from './components/Buttons/BackButton/BackButton';
2
+ export * from './components/Buttons/BaseButton/BaseButton';
5
3
  export * from './components/Buttons/FabButton/FabButton';
6
- export * from './components/Cards/ModuleCard/ModuleCard';
7
- export * from './components/Cards/ModuleCompletionCard/ModuleCompletionCard';
8
- export * from './components/Cards/AnswerOptionCard/AnswerOptionCard';
9
- export * from './components/DataDisplay/ProgressBar/ProgressBar';
10
- export * from './components/DataDisplay/Tag/Tag';
11
- export * from './components/DataDisplay/Badge/Badge';
12
- export * from './components/Inputs/TextInput/TextInput';
13
- export * from './components/Typography/Typography';
14
- export * from './components/Cards/BoardingPassCard/BoardingPassCard';
15
- export * from './components/Cards/SelectionCard/SelectionCard';
16
- export * from './components/Cards/PricingCard/PricingCard';
17
- export * from './components/DataDisplay/Flag/Flag';
18
- export * from './components/Cards/BaseCard/BaseCard';
19
- export * from './components/Cards/TeacherCard/TeacherCard';
20
- export * from './components/Cards/CountryCard/CountryCard';
21
- export * from './components/Cards/PlanCard/PlanCard';
22
- export * from './components/DataDisplay/LevelBadge/LevelBadge';
23
- export * from './hooks/useResponsive';
24
- export * from './components/Mascots/Vava/Vava';
25
- export * from './components/Mascots/Yaya/Yaya';
26
- export * from './components/Buttons/WordChip/WordChip';
27
- export * from './components/Feedback/FeedbackBottomSheet/FeedbackBottomSheet';
28
- export * from './components/Headers/ProfileHeader/ProfileHeader';
29
- export * from './components/Inputs/FillInTheBlank/FillInTheBlank';
30
- export * from './components/Charts/WeeklyProgressChart';
31
- export * from './components/Navigation/ProgressMap/ProgressMap';
32
4
  export * from './components/Buttons/IconButton/IconButton';
33
5
  export * from './components/Buttons/JourneyButton/JourneyButton';
6
+ export * from './components/Buttons/MainButton/MainButton';
34
7
  export * from './components/Buttons/WordChip/WordChip';
8
+ export * from './components/Buttons/WordChipIcon/WordChipIcon';
9
+ export * from './components/Cards/AnswerOptionCard/AnswerOptionCard';
10
+ export * from './components/Cards/BaseCard/BaseCard';
11
+ export * from './components/Cards/BoardingPassCard/BoardingPassCard';
12
+ export * from './components/Cards/CountryCard/CountryCard';
35
13
  export * from './components/Cards/LevelProgressCard/LevelProgressCard';
14
+ export * from './components/Cards/ModuleCard/ModuleCard';
15
+ export * from './components/Cards/ModuleCompletionCard/ModuleCompletionCard';
36
16
  export * from './components/Cards/Passport/PassportViewer';
17
+ export * from './components/Cards/PlanCard/PlanCard';
18
+ export * from './components/Cards/PricingCard/PricingCard';
19
+ export * from './components/Cards/SelectionCard/SelectionCard';
20
+ export * from './components/Cards/TeacherCard/TeacherCard';
21
+ export * from './components/Charts/WeeklyProgressChart';
37
22
  export * from './components/DataDisplay/Avatar/Avatar';
23
+ export * from './components/DataDisplay/Badge/Badge';
24
+ export * from './components/DataDisplay/Flag/Flag';
38
25
  export * from './components/DataDisplay/InstructionBubble/InstructionBubble';
39
- export * from './components/DataDisplay/StatBadge/StatBadge';
26
+ export * from './components/DataDisplay/LevelBadge/LevelBadge';
40
27
  export * from './components/DataDisplay/NumberBadge/NumberBadge';
28
+ export * from './components/DataDisplay/ProgressBar/ProgressBar';
29
+ export * from './components/DataDisplay/StatBadge/StatBadge';
30
+ export * from './components/DataDisplay/Tag/Tag';
31
+ export * from './components/Feedback/BottomSheet/BottomSheet';
32
+ export * from './components/Feedback/FeedbackBottomSheet/FeedbackBottomSheet';
33
+ export * from './components/Forms/CalendarPicker/CalendarPicker';
41
34
  export * from './components/Forms/Checkbox/Checkbox';
42
35
  export * from './components/Forms/DaySelector/DaySelector';
43
36
  export * from './components/Forms/Radio/Radio';
@@ -45,6 +38,17 @@ export * from './components/Forms/SegmentedToggle/SegmentedToggle';
45
38
  export * from './components/Forms/Select/Select';
46
39
  export * from './components/Forms/Switch/Switch';
47
40
  export * from './components/Forms/TimeSlot/TimeSlot';
41
+ export * from './components/Headers/CountryHeader/CountryHeader';
42
+ export * from './components/Headers/ProfileHeader/ProfileHeader';
43
+ export * from './components/Inputs/FillInTheBlank/FillInTheBlank';
44
+ export * from './components/Inputs/TextInput/TextInput';
45
+ export * from './components/Mascots/Vava/Vava';
46
+ export * from './components/Mascots/Yaya/Yaya';
48
47
  export * from './components/Navigation/BottomTabBar/BottomTabBar';
48
+ export * from './components/Navigation/TabBar/TabBar';
49
49
  export * from './components/Navigation/PaginationDots/PaginationDots';
50
+ export * from './components/Navigation/ProgressMap/ProgressMap';
50
51
  export * from './components/Navigation/WorldProgressMap/WorldProgressMap';
52
+ export * from './components/Typography/Typography';
53
+ export * from './hooks/useResponsive';
54
+
@@ -5,7 +5,7 @@ const font = (family: string, webWeight: '400' | '500' | '600' | '700' | '800' |
5
5
  const webFamily = family.split('-')[0];
6
6
  return Platform.select({
7
7
  web: { fontFamily: webFamily, fontWeight: webWeight },
8
- default: { fontFamily: family }
8
+ default: { fontFamily: family, fontWeight: webWeight }
9
9
  }) as { fontFamily: string; fontWeight?: '400' | '500' | '600' | '700' | '800' | '900' };
10
10
  };
11
11
 
@@ -172,86 +172,161 @@ export const theme = {
172
172
  },
173
173
 
174
174
  typography: {
175
+ // FAMÍLIA FREDOKA (Títulos e Destaques)
175
176
  display1: {
176
177
  ...font('Fredoka-Bold', '700'),
177
178
  fontSize: 28,
178
- lineHeight: 34,
179
- desktopFontSize: 42,
179
+ lineHeight: 28, // ajustado para line-height 1 se não especificado
180
+ desktopFontSize: 34,
180
181
  },
181
182
  display2: {
182
183
  ...font('Fredoka-Bold', '700'),
183
- fontSize: 24,
184
- lineHeight: 30,
185
- desktopFontSize: 34,
184
+ fontSize: 27,
185
+ lineHeight: 27,
186
+ desktopFontSize: 32,
186
187
  },
187
188
  h1: {
188
189
  ...font('Fredoka-Bold', '700'),
189
190
  fontSize: 22,
190
- lineHeight: 28,
191
+ lineHeight: 22,
191
192
  desktopFontSize: 28,
192
193
  },
193
194
  h2: {
194
- ...font('Fredoka-SemiBold', '600'),
195
- fontSize: 18,
196
- lineHeight: 24,
197
- desktopFontSize: 22,
195
+ ...font('Fredoka-Bold', '700'),
196
+ fontSize: 20,
197
+ lineHeight: 20,
198
+ desktopFontSize: 24,
198
199
  },
199
200
  h3: {
200
201
  ...font('Fredoka-SemiBold', '600'),
201
- fontSize: 16,
202
+ fontSize: 20,
203
+ lineHeight: 24, // Sem line-height fixo no spec, 1.2
204
+ desktopFontSize: 24,
205
+ },
206
+ h4: {
207
+ ...font('Fredoka-SemiBold', '600'),
208
+ fontSize: 19,
202
209
  lineHeight: 22,
210
+ desktopFontSize: 22,
211
+ },
212
+ h5: {
213
+ ...font('Fredoka-Bold', '700'),
214
+ fontSize: 18,
215
+ lineHeight: 22,
216
+ desktopFontSize: 20,
217
+ },
218
+ h6: {
219
+ ...font('Fredoka-SemiBold', '600'),
220
+ fontSize: 17,
221
+ lineHeight: 18.7, // line-height 1.1 * 17
203
222
  desktopFontSize: 19,
204
223
  },
205
- label: {
206
- ...font('Nunito-Black', '900'),
207
- fontSize: 10,
208
- lineHeight: 14,
209
- letterSpacing: 1.2, // 10 * 0.12
210
- desktopFontSize: 11,
224
+ subtitle1: {
225
+ ...font('Fredoka-SemiBold', '600'),
226
+ fontSize: 16,
227
+ lineHeight: 16, // line-height 1
228
+ desktopFontSize: 18,
211
229
  },
212
- bodyLg: {
213
- ...font('Nunito-Bold', '700'),
214
- fontSize: 15,
230
+ subtitle2: {
231
+ ...font('Fredoka-Bold', '700'),
232
+ fontSize: 14,
233
+ lineHeight: 18,
234
+ desktopFontSize: 16,
235
+ },
236
+
237
+ // FAMÍLIA NUNITO (Corpo, Labels, Listas e Infos)
238
+ bodyLgHeavy: {
239
+ ...font('Nunito-ExtraBold', '800'),
240
+ fontSize: 16,
215
241
  lineHeight: 24,
216
- desktopFontSize: 17,
242
+ desktopFontSize: 18,
243
+ },
244
+ bodyMdHeavy: {
245
+ ...font('Nunito-ExtraBold', '800'),
246
+ fontSize: 15,
247
+ lineHeight: 22,
248
+ desktopFontSize: 16,
217
249
  },
218
250
  bodyMd: {
219
- ...font('Nunito-Bold', '700'),
220
- fontSize: 13.5,
251
+ ...font('Nunito-SemiBold', '600'),
252
+ fontSize: 15,
221
253
  lineHeight: 22,
254
+ desktopFontSize: 16,
255
+ },
256
+ bodySmHeavy: {
257
+ ...font('Nunito-ExtraBold', '800'),
258
+ fontSize: 13.5,
259
+ lineHeight: 20,
222
260
  desktopFontSize: 15,
223
261
  },
224
262
  bodySm: {
225
- ...font('Nunito-ExtraBold', '800'),
226
- fontSize: 12,
227
- lineHeight: 18,
228
- desktopFontSize: 13,
263
+ ...font('Nunito-Bold', '700'),
264
+ fontSize: 13.5,
265
+ lineHeight: 20,
266
+ desktopFontSize: 15,
267
+ },
268
+ captionItalic: {
269
+ ...font('Nunito-SemiBoldItalic', '600'),
270
+ fontSize: 12.5,
271
+ lineHeight: 18.75, // line-height 1.5 * 12.5
272
+ desktopFontSize: 14,
273
+ fontStyle: 'italic',
229
274
  },
230
275
  caption: {
276
+ ...font('Nunito-Bold', '700'),
277
+ fontSize: 12.5,
278
+ lineHeight: 18,
279
+ desktopFontSize: 14,
280
+ },
281
+ labelHeavy: {
282
+ ...font('Nunito-Black', '900'), // O spec diz 800, mas no Nunito "Black" (900) dá melhor leitura p/ labels, ou podemos usar ExtraBold (800). Vamos usar ExtraBold (800) como no spec:
283
+ // wait, refiz para respeitar o 800
231
284
  ...font('Nunito-ExtraBold', '800'),
232
- fontSize: 10,
285
+ fontSize: 11,
233
286
  lineHeight: 16,
287
+ letterSpacing: 0.88, // 11 * 0.08em
288
+ textTransform: 'uppercase',
289
+ desktopFontSize: 12,
290
+ },
291
+ label: {
292
+ ...font('Nunito-Bold', '700'),
293
+ fontSize: 11,
294
+ lineHeight: 16,
295
+ desktopFontSize: 12,
296
+ },
297
+ smallHeavy: {
298
+ ...font('Nunito-ExtraBold', '800'),
299
+ fontSize: 10,
300
+ lineHeight: 14,
234
301
  desktopFontSize: 11,
235
302
  },
303
+ small: {
304
+ ...font('Nunito-Bold', '700'),
305
+ fontSize: 10,
306
+ lineHeight: 14,
307
+ desktopFontSize: 11,
308
+ },
309
+
310
+ // Botões (mantemos originais porque são usados no MainButton)
236
311
  btnLg: {
237
312
  ...font('Nunito-Black', '900'),
238
313
  fontSize: 16,
239
314
  lineHeight: 20,
240
- letterSpacing: 0.64, // 16 * 0.04
315
+ letterSpacing: 0.64,
241
316
  desktopFontSize: 17,
242
317
  },
243
318
  btnMd: {
244
319
  ...font('Nunito-Black', '900'),
245
320
  fontSize: 14,
246
321
  lineHeight: 18,
247
- letterSpacing: 0.56, // 14 * 0.04
322
+ letterSpacing: 0.56,
248
323
  desktopFontSize: 15,
249
324
  },
250
325
  btnSm: {
251
326
  ...font('Nunito-Black', '900'),
252
327
  fontSize: 12,
253
328
  lineHeight: 16,
254
- letterSpacing: 0.48, // 12 * 0.04
329
+ letterSpacing: 0.48,
255
330
  desktopFontSize: 12,
256
331
  },
257
332
  },
@@ -1,4 +1,5 @@
1
- import { Play, Volume2, Volume1, ArrowRight, X, Check, ChevronRight, ChevronLeft, Info, AlertTriangle, Book, Mic } from 'lucide-react-native';
1
+ import { FileText } from 'lucide-react';
2
+ import { Play, Volume2, Volume1, ArrowRight, X, Check, ChevronRight, ChevronLeft, Info, AlertTriangle, Book, Mic, Home, Compass, GraduationCap, FolderOpen, User, ShoppingCart } from 'lucide-react-native';
2
3
 
3
4
  /**
4
5
  * Para evitar que o Vite e o Metro (React Native) tentem empacotar os 1400+ ícones
@@ -21,4 +22,11 @@ export const IconMap: Record<string, any> = {
21
22
  AlertTriangle,
22
23
  Book,
23
24
  Mic,
25
+ Home,
26
+ Compass,
27
+ GraduationCap,
28
+ FolderOpen,
29
+ User,
30
+ ShoppingCart,
31
+ FileText,
24
32
  };