@xetwa/design-system 1.0.40 → 1.0.42
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/Cards/BoardingPassCard/BoardingPassCard.tsx +25 -22
- package/src/components/Cards/ModuleCard/ModuleCard.tsx +6 -7
- package/src/components/Navigation/TabBar/TabBar.stories.tsx +80 -0
- package/src/components/Navigation/TabBar/TabBar.tsx +245 -0
- package/src/index.ts +1 -0
- package/src/styles/theme.ts +111 -30
- package/src/utils/iconMap.ts +9 -1
package/package.json
CHANGED
|
@@ -132,17 +132,17 @@ export const BoardingPassCard = ({
|
|
|
132
132
|
styles.container,
|
|
133
133
|
{
|
|
134
134
|
borderRadius: scale(20),
|
|
135
|
-
minHeight: scale(
|
|
135
|
+
minHeight: scale(240),
|
|
136
136
|
},
|
|
137
137
|
style
|
|
138
138
|
]}>
|
|
139
139
|
{/* Top Section */}
|
|
140
140
|
<View style={[
|
|
141
|
-
styles.topSection,
|
|
142
|
-
{
|
|
141
|
+
styles.topSection,
|
|
142
|
+
{
|
|
143
143
|
backgroundColor: currentHeaderBg,
|
|
144
|
-
paddingTop: scale(
|
|
145
|
-
paddingBottom: scale(
|
|
144
|
+
paddingTop: scale(24),
|
|
145
|
+
paddingBottom: scale(24),
|
|
146
146
|
paddingHorizontal: scale(18),
|
|
147
147
|
}
|
|
148
148
|
]}>
|
|
@@ -158,7 +158,7 @@ export const BoardingPassCard = ({
|
|
|
158
158
|
]} />
|
|
159
159
|
|
|
160
160
|
<View style={[styles.headerRow, { marginBottom: scale(16) }]}>
|
|
161
|
-
<Typography variant="
|
|
161
|
+
<Typography variant="labelHeavy" 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 variant="
|
|
170
|
-
<Typography variant="
|
|
169
|
+
<Typography variant="display2" style={[styles.cityCode]}>{originCode}</Typography>
|
|
170
|
+
<Typography variant="small" 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 variant="
|
|
182
|
-
<Typography variant="
|
|
181
|
+
<Typography variant="display2" style={[styles.cityCode]}>{destCode}</Typography>
|
|
182
|
+
<Typography variant="small" style={[styles.cityName]}>{destName}</Typography>
|
|
183
183
|
</View>
|
|
184
184
|
</View>
|
|
185
185
|
</View>
|
|
@@ -188,7 +188,7 @@ export const BoardingPassCard = ({
|
|
|
188
188
|
<View style={[
|
|
189
189
|
styles.bottomSection,
|
|
190
190
|
{
|
|
191
|
-
paddingVertical: scale(
|
|
191
|
+
paddingVertical: scale(20),
|
|
192
192
|
paddingHorizontal: scale(18),
|
|
193
193
|
}
|
|
194
194
|
]}>
|
|
@@ -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 variant="
|
|
209
|
-
<Typography variant="
|
|
208
|
+
<Typography variant="subtitle1" style={[styles.countryName]}>{countryName}</Typography>
|
|
209
|
+
<Typography variant="label" 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 variant="
|
|
224
|
-
<Typography variant="
|
|
223
|
+
<Typography variant="labelHeavy" style={[styles.footerLabel]}>MÓDULOS</Typography>
|
|
224
|
+
<Typography variant="subtitle1" style={[styles.footerValue]}>{modulesCount}</Typography>
|
|
225
225
|
</View>
|
|
226
226
|
<View style={styles.footerCol}>
|
|
227
|
-
<Typography variant="
|
|
228
|
-
<Typography variant="
|
|
227
|
+
<Typography variant="labelHeavy" style={[styles.footerLabel]}>FOCO</Typography>
|
|
228
|
+
<Typography variant="subtitle1" style={[styles.footerValue]}>{focusText}</Typography>
|
|
229
229
|
</View>
|
|
230
230
|
</View>
|
|
231
231
|
</View>
|
|
@@ -241,6 +241,10 @@ const styles = StyleSheet.create({
|
|
|
241
241
|
minHeight: 230,
|
|
242
242
|
...theme.shadows.lg,
|
|
243
243
|
},
|
|
244
|
+
headerText: {
|
|
245
|
+
color: theme.colors.white,
|
|
246
|
+
opacity: 0.9,
|
|
247
|
+
},
|
|
244
248
|
topSection: {
|
|
245
249
|
paddingTop: 16,
|
|
246
250
|
paddingBottom: 16,
|
|
@@ -266,11 +270,6 @@ const styles = StyleSheet.create({
|
|
|
266
270
|
headerPlaneContainer: {
|
|
267
271
|
transform: [{ rotate: '0deg' }],
|
|
268
272
|
},
|
|
269
|
-
headerText: {
|
|
270
|
-
letterSpacing: 1.2,
|
|
271
|
-
textTransform: 'uppercase',
|
|
272
|
-
color: 'rgba(255, 255, 255, 0.75)',
|
|
273
|
-
},
|
|
274
273
|
routeRow: {
|
|
275
274
|
flexDirection: 'row',
|
|
276
275
|
alignItems: 'center',
|
|
@@ -345,6 +344,10 @@ const styles = StyleSheet.create({
|
|
|
345
344
|
countrySubtitle: {
|
|
346
345
|
color: '#9a8478',
|
|
347
346
|
},
|
|
347
|
+
footerLabel: {
|
|
348
|
+
color: theme.colors.brown[500],
|
|
349
|
+
marginBottom: 4,
|
|
350
|
+
},
|
|
348
351
|
divider: {
|
|
349
352
|
borderTopWidth: 1.5,
|
|
350
353
|
borderColor: '#f3e7da',
|
|
@@ -235,9 +235,8 @@ export const ModuleCard = ({
|
|
|
235
235
|
const cardPaddingV = size === 'xl' ? scale(20) : size === 'lg' ? scale(16) : isMobileScreen ? scale(10) : scale(14);
|
|
236
236
|
const cardPaddingH = size === 'xl' ? scale(20) : size === 'lg' ? scale(18) : isMobileScreen ? scale(12) : scale(15);
|
|
237
237
|
|
|
238
|
-
const titleVariant = size === 'xl' ? '
|
|
239
|
-
|
|
240
|
-
const subtitleVariant = size === 'xl' ? 'bodyLg' : size === 'lg' ? 'bodyMd' : 'bodySm';
|
|
238
|
+
const titleVariant = size === 'xl' ? 'h4' : size === 'lg' ? 'h6' : 'subtitle1';
|
|
239
|
+
const subtitleVariant = size === 'xl' ? 'bodySm' : size === 'lg' ? 'caption' : 'label';
|
|
241
240
|
|
|
242
241
|
|
|
243
242
|
|
|
@@ -330,7 +329,7 @@ export const ModuleCard = ({
|
|
|
330
329
|
hitSlop={10}
|
|
331
330
|
>
|
|
332
331
|
<RotateCcw stroke={theme.colors.primary} strokeWidth={2.5} size={isMobileScreen ? scale(20) : scale(24)} />
|
|
333
|
-
<Typography variant="
|
|
332
|
+
<Typography variant="smallHeavy" style={[styles.reviewIconText, { fontSize: scaleText(9) }]}>REVER</Typography>
|
|
334
333
|
</Pressable>
|
|
335
334
|
</Animated.View>
|
|
336
335
|
) : null}
|
|
@@ -368,7 +367,7 @@ export const ModuleCard = ({
|
|
|
368
367
|
style={{ flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.primary, paddingHorizontal: scale(14), paddingVertical: scale(6), borderRadius: 100 }}
|
|
369
368
|
>
|
|
370
369
|
<CalendarPlus stroke={theme.colors.white} strokeWidth={2.5} size={scale(14)} />
|
|
371
|
-
<Typography variant="
|
|
370
|
+
<Typography variant="bodySmHeavy" style={{ color: theme.colors.white, marginLeft: scale(6) }}>Agendar Aula</Typography>
|
|
372
371
|
</Pressable>
|
|
373
372
|
</Animated.View>
|
|
374
373
|
)}
|
|
@@ -383,7 +382,7 @@ export const ModuleCard = ({
|
|
|
383
382
|
style={{ flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.primary, paddingHorizontal: scale(14), paddingVertical: scale(6), borderRadius: 100 }}
|
|
384
383
|
>
|
|
385
384
|
<PenTool stroke={theme.colors.white} strokeWidth={2.5} size={scale(14)} />
|
|
386
|
-
<Typography variant="
|
|
385
|
+
<Typography variant="bodySmHeavy" style={{ color: theme.colors.white, marginLeft: scale(6) }}>Fazer Exame</Typography>
|
|
387
386
|
</Pressable>
|
|
388
387
|
</Animated.View>
|
|
389
388
|
)}
|
|
@@ -399,7 +398,7 @@ export const ModuleCard = ({
|
|
|
399
398
|
hitSlop={8}
|
|
400
399
|
>
|
|
401
400
|
<RotateCcw stroke={theme.colors.primary} strokeWidth={2.5} size={scale(16)} />
|
|
402
|
-
<Typography variant="
|
|
401
|
+
<Typography variant="smallHeavy" style={[styles.reviewIconText, { color: theme.colors.primary, marginLeft: scale(4), marginTop: 0 }]}>REVER</Typography>
|
|
403
402
|
</Pressable>
|
|
404
403
|
</Animated.View>
|
|
405
404
|
)}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { TabBar } from './TabBar';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Navigation/TabBar',
|
|
8
|
+
component: TabBar,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'fullscreen',
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: 'TabBar component with 3 variants (A: Pill, B: Top Bar, C: Dot below) and notification badges.',
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
argTypes: {
|
|
19
|
+
variant: { control: 'radio', options: ['A', 'B', 'C'] },
|
|
20
|
+
safeAreaBottom: { control: 'number' },
|
|
21
|
+
activeItemId: { control: 'text' },
|
|
22
|
+
},
|
|
23
|
+
decorators: [
|
|
24
|
+
(Story) => (
|
|
25
|
+
<View style={{ flex: 1, justifyContent: 'flex-end', backgroundColor: '#f5f0eb' }}>
|
|
26
|
+
<Story />
|
|
27
|
+
</View>
|
|
28
|
+
),
|
|
29
|
+
],
|
|
30
|
+
} satisfies Meta<typeof TabBar>;
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
type Story = StoryObj<typeof TabBar>;
|
|
34
|
+
|
|
35
|
+
const defaultItems = [
|
|
36
|
+
{ id: 'inicio', label: 'Início', icon: 'Home' },
|
|
37
|
+
{ id: 'percurso', label: 'Percurso', icon: 'Compass' },
|
|
38
|
+
{ id: 'aulas', label: 'Aulas', icon: 'GraduationCap' },
|
|
39
|
+
{ id: 'dossier', label: 'Dossier', hasBadge: true, icon: 'FolderOpen' },
|
|
40
|
+
{ id: 'perfil', label: 'Perfil', icon: 'User' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export const VariantB: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
items: defaultItems,
|
|
46
|
+
activeItemId: 'inicio',
|
|
47
|
+
variant: 'B',
|
|
48
|
+
safeAreaBottom: 20,
|
|
49
|
+
},
|
|
50
|
+
render: function Render(args) {
|
|
51
|
+
const [activeId, setActiveId] = useState(args.activeItemId);
|
|
52
|
+
return <TabBar {...args} activeItemId={activeId} onItemPress={setActiveId} />;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const VariantA: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
items: defaultItems,
|
|
59
|
+
activeItemId: 'inicio',
|
|
60
|
+
variant: 'A',
|
|
61
|
+
safeAreaBottom: 20,
|
|
62
|
+
},
|
|
63
|
+
render: function Render(args) {
|
|
64
|
+
const [activeId, setActiveId] = useState(args.activeItemId);
|
|
65
|
+
return <TabBar {...args} activeItemId={activeId} onItemPress={setActiveId} />;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const VariantC: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
items: defaultItems,
|
|
72
|
+
activeItemId: 'inicio',
|
|
73
|
+
variant: 'C',
|
|
74
|
+
safeAreaBottom: 20,
|
|
75
|
+
},
|
|
76
|
+
render: function Render(args) {
|
|
77
|
+
const [activeId, setActiveId] = useState(args.activeItemId);
|
|
78
|
+
return <TabBar {...args} activeItemId={activeId} onItemPress={setActiveId} />;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
@@ -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(11.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(30) : scale(26);
|
|
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
|
@@ -45,6 +45,7 @@ export * from './components/Inputs/TextInput/TextInput';
|
|
|
45
45
|
export * from './components/Mascots/Vava/Vava';
|
|
46
46
|
export * from './components/Mascots/Yaya/Yaya';
|
|
47
47
|
export * from './components/Navigation/BottomTabBar/BottomTabBar';
|
|
48
|
+
export * from './components/Navigation/TabBar/TabBar';
|
|
48
49
|
export * from './components/Navigation/PaginationDots/PaginationDots';
|
|
49
50
|
export * from './components/Navigation/ProgressMap/ProgressMap';
|
|
50
51
|
export * from './components/Navigation/WorldProgressMap/WorldProgressMap';
|
package/src/styles/theme.ts
CHANGED
|
@@ -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 }
|
|
9
9
|
}) as { fontFamily: string; fontWeight?: '400' | '500' | '600' | '700' | '800' | '900' };
|
|
10
10
|
};
|
|
11
11
|
|
|
@@ -172,86 +172,167 @@ 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:
|
|
179
|
-
desktopFontSize:
|
|
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:
|
|
184
|
-
lineHeight:
|
|
185
|
-
desktopFontSize:
|
|
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:
|
|
191
|
+
lineHeight: 22,
|
|
191
192
|
desktopFontSize: 28,
|
|
192
193
|
},
|
|
193
194
|
h2: {
|
|
194
|
-
...font('Fredoka-
|
|
195
|
-
fontSize:
|
|
196
|
-
lineHeight:
|
|
197
|
-
desktopFontSize:
|
|
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:
|
|
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,
|
|
209
|
+
lineHeight: 22,
|
|
210
|
+
desktopFontSize: 22,
|
|
211
|
+
},
|
|
212
|
+
h5: {
|
|
213
|
+
...font('Fredoka-Bold', '700'),
|
|
214
|
+
fontSize: 18,
|
|
202
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
|
-
|
|
224
|
+
subtitle1: {
|
|
225
|
+
...font('Fredoka-SemiBold', '600'),
|
|
226
|
+
fontSize: 16,
|
|
227
|
+
lineHeight: 16, // line-height 1
|
|
228
|
+
desktopFontSize: 18,
|
|
229
|
+
},
|
|
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: {
|
|
206
239
|
...font('Nunito-Black', '900'),
|
|
207
|
-
fontSize:
|
|
208
|
-
lineHeight:
|
|
209
|
-
|
|
210
|
-
desktopFontSize: 11,
|
|
240
|
+
fontSize: 16,
|
|
241
|
+
lineHeight: 24,
|
|
242
|
+
desktopFontSize: 18,
|
|
211
243
|
},
|
|
212
244
|
bodyLg: {
|
|
213
|
-
...font('Nunito-
|
|
214
|
-
fontSize:
|
|
245
|
+
...font('Nunito-Black', '900'),
|
|
246
|
+
fontSize: 16,
|
|
215
247
|
lineHeight: 24,
|
|
216
|
-
desktopFontSize:
|
|
248
|
+
desktopFontSize: 18,
|
|
249
|
+
},
|
|
250
|
+
bodyMdHeavy: {
|
|
251
|
+
...font('Nunito-Black', '900'),
|
|
252
|
+
fontSize: 15,
|
|
253
|
+
lineHeight: 22,
|
|
254
|
+
desktopFontSize: 16,
|
|
217
255
|
},
|
|
218
256
|
bodyMd: {
|
|
219
|
-
...font('Nunito-
|
|
220
|
-
fontSize:
|
|
257
|
+
...font('Nunito-Black', '900'),
|
|
258
|
+
fontSize: 15,
|
|
221
259
|
lineHeight: 22,
|
|
260
|
+
desktopFontSize: 16,
|
|
261
|
+
},
|
|
262
|
+
bodySmHeavy: {
|
|
263
|
+
...font('Nunito-Black', '900'),
|
|
264
|
+
fontSize: 13.5,
|
|
265
|
+
lineHeight: 20,
|
|
222
266
|
desktopFontSize: 15,
|
|
223
267
|
},
|
|
224
268
|
bodySm: {
|
|
225
|
-
...font('Nunito-
|
|
226
|
-
fontSize:
|
|
227
|
-
lineHeight:
|
|
228
|
-
desktopFontSize:
|
|
269
|
+
...font('Nunito-Black', '900'),
|
|
270
|
+
fontSize: 13.5,
|
|
271
|
+
lineHeight: 20,
|
|
272
|
+
desktopFontSize: 15,
|
|
273
|
+
},
|
|
274
|
+
captionItalic: {
|
|
275
|
+
...font('Nunito-SemiBoldItalic', '600'),
|
|
276
|
+
fontSize: 12.5,
|
|
277
|
+
lineHeight: 18.75, // line-height 1.5 * 12.5
|
|
278
|
+
desktopFontSize: 14,
|
|
279
|
+
fontStyle: 'italic',
|
|
229
280
|
},
|
|
230
281
|
caption: {
|
|
282
|
+
...font('Nunito-Bold', '700'),
|
|
283
|
+
fontSize: 12.5,
|
|
284
|
+
lineHeight: 18,
|
|
285
|
+
desktopFontSize: 14,
|
|
286
|
+
},
|
|
287
|
+
labelHeavy: {
|
|
288
|
+
...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:
|
|
289
|
+
// wait, refiz para respeitar o 800
|
|
231
290
|
...font('Nunito-ExtraBold', '800'),
|
|
232
|
-
fontSize:
|
|
291
|
+
fontSize: 11,
|
|
292
|
+
lineHeight: 16,
|
|
293
|
+
letterSpacing: 0.88, // 11 * 0.08em
|
|
294
|
+
textTransform: 'uppercase',
|
|
295
|
+
desktopFontSize: 12,
|
|
296
|
+
},
|
|
297
|
+
label: {
|
|
298
|
+
...font('Nunito-Bold', '700'),
|
|
299
|
+
fontSize: 11,
|
|
233
300
|
lineHeight: 16,
|
|
301
|
+
desktopFontSize: 12,
|
|
302
|
+
},
|
|
303
|
+
smallHeavy: {
|
|
304
|
+
...font('Nunito-Black', '900'),
|
|
305
|
+
fontSize: 10,
|
|
306
|
+
lineHeight: 14,
|
|
234
307
|
desktopFontSize: 11,
|
|
235
308
|
},
|
|
309
|
+
small: {
|
|
310
|
+
...font('Nunito-Bold', '700'),
|
|
311
|
+
fontSize: 10,
|
|
312
|
+
lineHeight: 14,
|
|
313
|
+
desktopFontSize: 11,
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
// Botões (mantemos originais porque são usados no MainButton)
|
|
236
317
|
btnLg: {
|
|
237
318
|
...font('Nunito-Black', '900'),
|
|
238
319
|
fontSize: 16,
|
|
239
320
|
lineHeight: 20,
|
|
240
|
-
letterSpacing: 0.64,
|
|
321
|
+
letterSpacing: 0.64,
|
|
241
322
|
desktopFontSize: 17,
|
|
242
323
|
},
|
|
243
324
|
btnMd: {
|
|
244
325
|
...font('Nunito-Black', '900'),
|
|
245
326
|
fontSize: 14,
|
|
246
327
|
lineHeight: 18,
|
|
247
|
-
letterSpacing: 0.56,
|
|
328
|
+
letterSpacing: 0.56,
|
|
248
329
|
desktopFontSize: 15,
|
|
249
330
|
},
|
|
250
331
|
btnSm: {
|
|
251
332
|
...font('Nunito-Black', '900'),
|
|
252
333
|
fontSize: 12,
|
|
253
334
|
lineHeight: 16,
|
|
254
|
-
letterSpacing: 0.48,
|
|
335
|
+
letterSpacing: 0.48,
|
|
255
336
|
desktopFontSize: 12,
|
|
256
337
|
},
|
|
257
338
|
},
|
package/src/utils/iconMap.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Play, Volume2, Volume1, ArrowRight, X, Check, ChevronRight, ChevronLeft, Info, AlertTriangle, Book, Mic } from 'lucide-react-native';
|
|
1
|
+
import { Play, Volume2, Volume1, ArrowRight, X, Check, ChevronRight, ChevronLeft, Info, AlertTriangle, Book, Mic, Home, Compass, GraduationCap, FolderOpen, User, ShoppingCart, FileText, Wallet } from 'lucide-react-native';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Para evitar que o Vite e o Metro (React Native) tentem empacotar os 1400+ ícones
|
|
@@ -21,4 +21,12 @@ export const IconMap: Record<string, any> = {
|
|
|
21
21
|
AlertTriangle,
|
|
22
22
|
Book,
|
|
23
23
|
Mic,
|
|
24
|
+
Home,
|
|
25
|
+
Compass,
|
|
26
|
+
GraduationCap,
|
|
27
|
+
FolderOpen,
|
|
28
|
+
User,
|
|
29
|
+
ShoppingCart,
|
|
30
|
+
FileText,
|
|
31
|
+
Wallet,
|
|
24
32
|
};
|