@xetwa/design-system 1.0.48 → 1.0.50
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/Chip/Chip.tsx +5 -1
- package/src/components/DataDisplay/CircularProgress/CircularProgress.stories.tsx +67 -0
- package/src/components/DataDisplay/CircularProgress/CircularProgress.tsx +92 -0
- package/src/components/DataDisplay/ProgressBar/ProgressBar.tsx +1 -1
- package/src/components/DataDisplay/Stamp/Stamp.stories.tsx +153 -0
- package/src/components/DataDisplay/Stamp/Stamp.tsx +173 -0
- package/src/components/DataDisplay/StampCard/StampCard.stories.tsx +275 -0
- package/src/components/DataDisplay/StampCard/StampCard.tsx +196 -0
- package/src/components/Headers/CountryHeader/CountryHeader.tsx +34 -8
- package/src/components/Headers/RouteHeader/RouteHeader.stories.tsx +28 -0
- package/src/components/Headers/RouteHeader/RouteHeader.tsx +85 -16
- package/src/components/Navigation/PaginationDots/PaginationDots.stories.tsx +20 -4
- package/src/components/Navigation/PaginationDots/PaginationDots.tsx +23 -9
- package/src/index.ts +3 -0
- package/src/styles/theme.ts +34 -0
- package/src/utils/iconMap.ts +13 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { StampCard } from './StampCard';
|
|
5
|
+
import { theme } from '../../../styles/theme';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* \`StampCard\` é um contentor elegante que exibe um \`Stamp\` no topo e
|
|
9
|
+
* informações de contexto (Bandeira, Local, e Subtítulo) na base.
|
|
10
|
+
* É perfeito para ser usado como card de conquista ou progresso numa grelha.
|
|
11
|
+
*
|
|
12
|
+
* ### Características
|
|
13
|
+
* - **Modular:** Recebe diretamente as propriedades do \`Stamp\` interno.
|
|
14
|
+
* - **Design Alinhado:** A cor do local mapeia automaticamente com a cor do carimbo.
|
|
15
|
+
* - **Container Clean:** Borda redonda e fundo sólido.
|
|
16
|
+
*/
|
|
17
|
+
const meta = {
|
|
18
|
+
title: 'Components/Cards/StampCard',
|
|
19
|
+
component: StampCard,
|
|
20
|
+
parameters: {
|
|
21
|
+
layout: 'centered',
|
|
22
|
+
docs: {
|
|
23
|
+
description: {
|
|
24
|
+
component: `O \`StampCard\` é um contentor elegante que exibe um \`Stamp\` no topo e informações de contexto (Bandeira, Local, e Subtítulo) na base.
|
|
25
|
+
É perfeito para ser usado como card de conquista ou progresso numa grelha.
|
|
26
|
+
|
|
27
|
+
### Características
|
|
28
|
+
- **Modular:** Recebe diretamente as propriedades do \`Stamp\` interno.
|
|
29
|
+
- **Design Alinhado:** A cor do local mapeia automaticamente com a cor do carimbo.
|
|
30
|
+
- **Container Clean:** Borda redonda e fundo sólido.
|
|
31
|
+
|
|
32
|
+
### Exemplo de Uso
|
|
33
|
+
\`\`\`tsx
|
|
34
|
+
import { StampCard } from '@/components/DataDisplay/StampCard/StampCard';
|
|
35
|
+
import { theme } from '@/styles/theme';
|
|
36
|
+
|
|
37
|
+
<StampCard
|
|
38
|
+
stamp={{
|
|
39
|
+
color: theme.colors.error,
|
|
40
|
+
title: 'PARIS',
|
|
41
|
+
topLabel: 'FRANCE',
|
|
42
|
+
bottomLabels: ['NIVEAU A1', 'ABR 2026'],
|
|
43
|
+
size: 'md',
|
|
44
|
+
position: 'center',
|
|
45
|
+
}}
|
|
46
|
+
countryCode="FR"
|
|
47
|
+
locationName="Paris"
|
|
48
|
+
subtitle="A1 • concluído"
|
|
49
|
+
variant="warning"
|
|
50
|
+
warningText="Visto a expirar!"
|
|
51
|
+
/>
|
|
52
|
+
\`\`\`
|
|
53
|
+
`
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
tags: ['autodocs'],
|
|
58
|
+
argTypes: {
|
|
59
|
+
width: {
|
|
60
|
+
control: 'text',
|
|
61
|
+
description: 'Largura do cartão (ex: 320 ou "100%").',
|
|
62
|
+
},
|
|
63
|
+
height: {
|
|
64
|
+
control: 'text',
|
|
65
|
+
description: 'Altura do cartão.',
|
|
66
|
+
},
|
|
67
|
+
variant: {
|
|
68
|
+
control: 'radio',
|
|
69
|
+
options: ['default', 'highlight', 'warning'],
|
|
70
|
+
description: 'Variante visual do cartão.',
|
|
71
|
+
},
|
|
72
|
+
warningText: {
|
|
73
|
+
control: 'text',
|
|
74
|
+
description: 'Texto exibido no topo quando variant="warning".',
|
|
75
|
+
if: { arg: 'variant', eq: 'warning' },
|
|
76
|
+
},
|
|
77
|
+
countryCode: {
|
|
78
|
+
control: 'text',
|
|
79
|
+
description: 'Código ISO ou nome do país para gerar a bandeira.',
|
|
80
|
+
},
|
|
81
|
+
locationName: {
|
|
82
|
+
control: 'text',
|
|
83
|
+
description: 'Nome do local em destaque.',
|
|
84
|
+
},
|
|
85
|
+
subtitle: {
|
|
86
|
+
control: 'text',
|
|
87
|
+
description: 'Subtítulo cinzento/castanho inferior.',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
} satisfies Meta<typeof StampCard>;
|
|
91
|
+
|
|
92
|
+
export default meta;
|
|
93
|
+
type Story = StoryObj<typeof meta>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Exemplo padrão conforme a especificação do design.
|
|
97
|
+
*/
|
|
98
|
+
export const Default: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
stamp: {
|
|
101
|
+
color: theme.colors.error,
|
|
102
|
+
title: 'LYON',
|
|
103
|
+
topLabel: 'FRANCE',
|
|
104
|
+
bottomLabels: ['NIVEAU A1', 'ABR 2026'],
|
|
105
|
+
size: 'md',
|
|
106
|
+
position: 'center',
|
|
107
|
+
},
|
|
108
|
+
countryCode: 'FR',
|
|
109
|
+
locationName: 'Lyon',
|
|
110
|
+
subtitle: 'A1 • concluído',
|
|
111
|
+
variant: 'default',
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Exemplo de um cartão em destaque (highlight) com a borda espessa
|
|
117
|
+
* que herda a cor do carimbo interno.
|
|
118
|
+
*/
|
|
119
|
+
export const Highlight: Story = {
|
|
120
|
+
args: {
|
|
121
|
+
stamp: {
|
|
122
|
+
color: theme.colors.primary,
|
|
123
|
+
title: 'DAKAR',
|
|
124
|
+
topLabel: 'SÉNÉGAL',
|
|
125
|
+
bottomLabels: ['NIVEAU B1', 'EM CURSO'],
|
|
126
|
+
size: 'md',
|
|
127
|
+
position: 'center',
|
|
128
|
+
},
|
|
129
|
+
countryCode: 'SN',
|
|
130
|
+
locationName: 'Dakar',
|
|
131
|
+
subtitle: 'B1 • em curso',
|
|
132
|
+
variant: 'highlight',
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Exemplo de um cartão com aviso crítico (warning),
|
|
138
|
+
* sobrepondo uma badge no topo.
|
|
139
|
+
*/
|
|
140
|
+
export const Warning: Story = {
|
|
141
|
+
args: {
|
|
142
|
+
stamp: {
|
|
143
|
+
color: theme.colors.primaryDark,
|
|
144
|
+
title: 'BRUXELLES',
|
|
145
|
+
topLabel: 'BELGIQUE',
|
|
146
|
+
bottomLabels: ['NIVEAU A2', 'JUL 2026'],
|
|
147
|
+
size: 'md',
|
|
148
|
+
position: 'left',
|
|
149
|
+
},
|
|
150
|
+
countryCode: 'BE',
|
|
151
|
+
locationName: 'Bruxelles',
|
|
152
|
+
subtitle: 'A2 • renovar visto',
|
|
153
|
+
variant: 'warning',
|
|
154
|
+
warningText: 'Visto a expirar!',
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Exemplo demonstrando o card num estado de progresso contínuo
|
|
160
|
+
* (Ex: ainda em curso).
|
|
161
|
+
*/
|
|
162
|
+
export const InProgress: Story = {
|
|
163
|
+
args: {
|
|
164
|
+
stamp: {
|
|
165
|
+
color: theme.colors.primary,
|
|
166
|
+
title: 'DAKAR',
|
|
167
|
+
topLabel: 'SÉNÉGAL',
|
|
168
|
+
bottomLabels: ['NIVEAU B1', 'EM CURSO'],
|
|
169
|
+
size: 'md',
|
|
170
|
+
position: 'left',
|
|
171
|
+
},
|
|
172
|
+
countryCode: 'SN',
|
|
173
|
+
locationName: 'Dakar',
|
|
174
|
+
subtitle: 'B1 • a decorrer',
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Grelha de vários cards para testar a escalabilidade visual
|
|
180
|
+
* e mostrar como se comportam lado a lado.
|
|
181
|
+
*/
|
|
182
|
+
export const Grid: Story = {
|
|
183
|
+
render: () => (
|
|
184
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 24, justifyContent: 'center' }}>
|
|
185
|
+
<StampCard
|
|
186
|
+
stamp={{
|
|
187
|
+
color: theme.colors.info,
|
|
188
|
+
title: 'PARIS',
|
|
189
|
+
topLabel: 'FRANCE',
|
|
190
|
+
bottomLabels: ['NIVEAU A2'],
|
|
191
|
+
size: 'md',
|
|
192
|
+
position: 'right',
|
|
193
|
+
}}
|
|
194
|
+
countryCode="FR"
|
|
195
|
+
locationName="Paris"
|
|
196
|
+
subtitle="A2 • concluído"
|
|
197
|
+
/>
|
|
198
|
+
<StampCard
|
|
199
|
+
stamp={{
|
|
200
|
+
color: theme.colors.success,
|
|
201
|
+
title: 'GENEVE',
|
|
202
|
+
topLabel: 'SUISSE',
|
|
203
|
+
bottomLabels: ['NIVEAU C1'],
|
|
204
|
+
size: 'md',
|
|
205
|
+
position: 'left',
|
|
206
|
+
}}
|
|
207
|
+
countryCode="CH"
|
|
208
|
+
locationName="Geneve"
|
|
209
|
+
subtitle="C1 • em curso"
|
|
210
|
+
variant="highlight"
|
|
211
|
+
/>
|
|
212
|
+
<StampCard
|
|
213
|
+
stamp={{
|
|
214
|
+
color: theme.colors.error,
|
|
215
|
+
title: 'ROMA',
|
|
216
|
+
topLabel: 'ITALIA',
|
|
217
|
+
bottomLabels: ['NIVEAU B2'],
|
|
218
|
+
size: 'md',
|
|
219
|
+
position: 'center',
|
|
220
|
+
}}
|
|
221
|
+
countryCode="IT"
|
|
222
|
+
locationName="Roma"
|
|
223
|
+
subtitle="B2 • atrasado"
|
|
224
|
+
variant="warning"
|
|
225
|
+
warningText="Atenção necessária"
|
|
226
|
+
/>
|
|
227
|
+
</View>
|
|
228
|
+
),
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Exemplo demonstrando a passagem de uma \`width\` customizada.
|
|
233
|
+
* O cartão estica-se para preencher a largura fornecida, mantendo
|
|
234
|
+
* os elementos internos perfeitamente centrados.
|
|
235
|
+
*/
|
|
236
|
+
export const CustomWidth: Story = {
|
|
237
|
+
args: {
|
|
238
|
+
width: 320,
|
|
239
|
+
stamp: {
|
|
240
|
+
color: theme.colors.primary,
|
|
241
|
+
title: 'LISBOA',
|
|
242
|
+
topLabel: 'PORTUGAL',
|
|
243
|
+
bottomLabels: ['NIVEAU C2'],
|
|
244
|
+
size: 'lg',
|
|
245
|
+
position: 'center',
|
|
246
|
+
},
|
|
247
|
+
countryCode: 'PT',
|
|
248
|
+
locationName: 'Lisboa',
|
|
249
|
+
subtitle: 'C2 • concluído',
|
|
250
|
+
variant: 'default',
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Exemplo demonstrando a passagem de uma \`height\` customizada.
|
|
256
|
+
* O cartão estica-se verticalmente, e o carimbo afasta-se das informações
|
|
257
|
+
* graças ao \`justifyContent: 'space-between'\`.
|
|
258
|
+
*/
|
|
259
|
+
export const CustomHeight: Story = {
|
|
260
|
+
args: {
|
|
261
|
+
height: 450,
|
|
262
|
+
stamp: {
|
|
263
|
+
color: theme.colors.success,
|
|
264
|
+
title: 'ZURICH',
|
|
265
|
+
topLabel: 'SUISSE',
|
|
266
|
+
bottomLabels: ['NIVEAU B2'],
|
|
267
|
+
size: 'lg',
|
|
268
|
+
position: 'right',
|
|
269
|
+
},
|
|
270
|
+
countryCode: 'CH',
|
|
271
|
+
locationName: 'Zurich',
|
|
272
|
+
subtitle: 'B2 • concluído',
|
|
273
|
+
variant: 'default',
|
|
274
|
+
},
|
|
275
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { View, StyleSheet } from 'react-native';
|
|
2
|
+
import type { ViewStyle, DimensionValue } from 'react-native';
|
|
3
|
+
import { Typography } from '../../Typography/Typography';
|
|
4
|
+
import { Stamp } from '../Stamp/Stamp';
|
|
5
|
+
import { Flag } from '../Flag/Flag';
|
|
6
|
+
import { BaseCard } from '../../Cards/BaseCard/BaseCard';
|
|
7
|
+
import { theme } from '../../../styles/theme';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* O \`StampCard\` é um cartão desenhado para exibir o progresso ou conquista num destino (carimbo).
|
|
11
|
+
* Usa o \`BaseCard\` como base, herda bordas e fundos do Design System, e disponibiliza 3 variantes visuais.
|
|
12
|
+
*
|
|
13
|
+
* ### Exemplo de Uso
|
|
14
|
+
* \`\`\`tsx
|
|
15
|
+
* import { StampCard } from '@/components/DataDisplay/StampCard/StampCard';
|
|
16
|
+
* import { theme } from '@/styles/theme';
|
|
17
|
+
*
|
|
18
|
+
* <StampCard
|
|
19
|
+
* stamp={{
|
|
20
|
+
* color: theme.colors.error,
|
|
21
|
+
* title: 'PARIS',
|
|
22
|
+
* topLabel: 'FRANCE',
|
|
23
|
+
* bottomLabels: ['NIVEAU A1', 'ABR 2026'],
|
|
24
|
+
* size: 'md',
|
|
25
|
+
* position: 'center',
|
|
26
|
+
* }}
|
|
27
|
+
* countryCode="FR"
|
|
28
|
+
* locationName="Paris"
|
|
29
|
+
* subtitle="A1 • concluído"
|
|
30
|
+
* variant="warning"
|
|
31
|
+
* warningText="Visto a expirar!"
|
|
32
|
+
* />
|
|
33
|
+
* \`\`\`
|
|
34
|
+
*/
|
|
35
|
+
export interface StampCardProps {
|
|
36
|
+
/**
|
|
37
|
+
* Propriedades para configurar o carimbo que aparece dentro do card.
|
|
38
|
+
*/
|
|
39
|
+
stamp: {
|
|
40
|
+
color: string;
|
|
41
|
+
title: string;
|
|
42
|
+
topLabel?: string;
|
|
43
|
+
bottomLabels?: string[];
|
|
44
|
+
size?: 'sm' | 'md' | 'lg';
|
|
45
|
+
position?: 'left' | 'center' | 'right';
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Código ISO do país ou nome para exibir a bandeira (ex: 'FR', 'France').
|
|
49
|
+
* Usa a componente Flag do sistema.
|
|
50
|
+
*/
|
|
51
|
+
countryCode?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Nome do local que aparece ao lado da bandeira (ex: 'Lyon').
|
|
54
|
+
* Fica com a mesma cor principal do carimbo.
|
|
55
|
+
*/
|
|
56
|
+
locationName: string;
|
|
57
|
+
/**
|
|
58
|
+
* Subtítulo informativo (ex: 'A1 • concluído').
|
|
59
|
+
*/
|
|
60
|
+
subtitle: string;
|
|
61
|
+
/**
|
|
62
|
+
* Largura customizada para o card.
|
|
63
|
+
*/
|
|
64
|
+
width?: DimensionValue;
|
|
65
|
+
/**
|
|
66
|
+
* Altura customizada para o card.
|
|
67
|
+
*/
|
|
68
|
+
height?: DimensionValue;
|
|
69
|
+
/**
|
|
70
|
+
* Variante visual do cartão:
|
|
71
|
+
* - `default`: Borda subtil neutra.
|
|
72
|
+
* - `highlight`: Borda espessa com a mesma cor do carimbo.
|
|
73
|
+
* - `warning`: Idêntico ao highlight, mas com badge a sobrepor no topo.
|
|
74
|
+
*/
|
|
75
|
+
variant?: 'default' | 'highlight' | 'warning';
|
|
76
|
+
/**
|
|
77
|
+
* Texto para a badge de aviso (apenas renderizada se variant='warning').
|
|
78
|
+
* Opcional, por defeito será 'Visto a expirar!'.
|
|
79
|
+
*/
|
|
80
|
+
warningText?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Estilo customizado para o card (opcional).
|
|
83
|
+
*/
|
|
84
|
+
style?: ViewStyle;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const StampCard = ({
|
|
88
|
+
stamp,
|
|
89
|
+
countryCode,
|
|
90
|
+
locationName,
|
|
91
|
+
subtitle,
|
|
92
|
+
width,
|
|
93
|
+
height,
|
|
94
|
+
variant = 'default',
|
|
95
|
+
warningText = 'Visto a expirar!',
|
|
96
|
+
style,
|
|
97
|
+
}: StampCardProps) => {
|
|
98
|
+
|
|
99
|
+
const isHighlight = variant === 'highlight' || variant === 'warning';
|
|
100
|
+
const resolvedBorderColor = isHighlight ? stamp.color : theme.colors.brown[100];
|
|
101
|
+
const resolvedBorderWidth = isHighlight ? 4 : 2;
|
|
102
|
+
|
|
103
|
+
// Proteção contra inputs inválidos do painel do Storybook (ex: objetos vazios ou strings vazias)
|
|
104
|
+
const safeWidth = (typeof width === 'object' || width === '') ? undefined : width as DimensionValue;
|
|
105
|
+
const safeHeight = (typeof height === 'object' || height === '') ? undefined : height as DimensionValue;
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<BaseCard
|
|
109
|
+
width={safeWidth}
|
|
110
|
+
height={safeHeight}
|
|
111
|
+
borderColor={resolvedBorderColor}
|
|
112
|
+
borderWidth={resolvedBorderWidth}
|
|
113
|
+
borderRadius={theme.radius.xxl}
|
|
114
|
+
overflow="visible"
|
|
115
|
+
style={[
|
|
116
|
+
styles.card,
|
|
117
|
+
variant === 'warning' && { paddingTop: 44 }, // Espaço extra para compensar a tag flutuante
|
|
118
|
+
style
|
|
119
|
+
]}
|
|
120
|
+
>
|
|
121
|
+
|
|
122
|
+
{variant === 'warning' ? (
|
|
123
|
+
<View style={[styles.warningBadge, { backgroundColor: stamp.color }]}>
|
|
124
|
+
<Typography variant="labelHeavy" color={theme.colors.white} style={{ textTransform: 'none' }}>
|
|
125
|
+
{warningText}
|
|
126
|
+
</Typography>
|
|
127
|
+
</View>
|
|
128
|
+
) : null}
|
|
129
|
+
|
|
130
|
+
{/* Stamp Container */}
|
|
131
|
+
<View style={styles.stampContainer}>
|
|
132
|
+
<Stamp {...stamp} />
|
|
133
|
+
</View>
|
|
134
|
+
|
|
135
|
+
{/* Info Container */}
|
|
136
|
+
<View style={styles.infoContainer}>
|
|
137
|
+
|
|
138
|
+
<View style={styles.locationRow}>
|
|
139
|
+
{countryCode ? (
|
|
140
|
+
<Flag
|
|
141
|
+
countryCode={countryCode}
|
|
142
|
+
size={20}
|
|
143
|
+
style={{ marginRight: theme.spacing.xs }}
|
|
144
|
+
/>
|
|
145
|
+
) : null}
|
|
146
|
+
<Typography
|
|
147
|
+
variant="h5"
|
|
148
|
+
color={stamp.color}
|
|
149
|
+
>
|
|
150
|
+
{locationName}
|
|
151
|
+
</Typography>
|
|
152
|
+
</View>
|
|
153
|
+
|
|
154
|
+
<Typography
|
|
155
|
+
variant="subtitle1"
|
|
156
|
+
color={theme.colors.brown[500]}
|
|
157
|
+
style={{ marginTop: 2 }}
|
|
158
|
+
>
|
|
159
|
+
{subtitle}
|
|
160
|
+
</Typography>
|
|
161
|
+
|
|
162
|
+
</View>
|
|
163
|
+
|
|
164
|
+
</BaseCard>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const styles = StyleSheet.create({
|
|
169
|
+
card: {
|
|
170
|
+
padding: theme.spacing.space8,
|
|
171
|
+
alignItems: 'center',
|
|
172
|
+
justifyContent: 'space-between',
|
|
173
|
+
alignSelf: 'flex-start', // Adapta-se ao conteúdo por defeito
|
|
174
|
+
},
|
|
175
|
+
warningBadge: {
|
|
176
|
+
position: 'absolute',
|
|
177
|
+
top: -18, // Sobe para quebrar a borda superior (ajustado para a nova borda branca)
|
|
178
|
+
alignSelf: 'center',
|
|
179
|
+
paddingHorizontal: 16,
|
|
180
|
+
paddingVertical: 6,
|
|
181
|
+
borderRadius: theme.radius.full,
|
|
182
|
+
zIndex: 10,
|
|
183
|
+
borderWidth: 4,
|
|
184
|
+
borderColor: theme.colors.white,
|
|
185
|
+
},
|
|
186
|
+
stampContainer: {
|
|
187
|
+
marginBottom: theme.spacing.space6,
|
|
188
|
+
},
|
|
189
|
+
infoContainer: {
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
},
|
|
192
|
+
locationRow: {
|
|
193
|
+
flexDirection: 'row',
|
|
194
|
+
alignItems: 'center',
|
|
195
|
+
},
|
|
196
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { View, StyleSheet, Animated, Easing } from 'react-native';
|
|
2
3
|
import type { ViewStyle } from 'react-native';
|
|
3
4
|
import { useResponsive } from '../../../hooks/useResponsive';
|
|
4
5
|
import { getCountryTheme } from '../../../styles/countryThemes';
|
|
@@ -27,6 +28,8 @@ export interface CountryHeaderProps {
|
|
|
27
28
|
* O developer deve passar o \`insets.top\` gerado pelo \`react-native-safe-area-context\`.
|
|
28
29
|
*/
|
|
29
30
|
safeAreaTop?: number;
|
|
31
|
+
/** Se deve ocultar a barra de progresso com animação */
|
|
32
|
+
hideProgressBar?: boolean;
|
|
30
33
|
/** Estilos customizados opcionais para o container principal */
|
|
31
34
|
style?: ViewStyle | ViewStyle[];
|
|
32
35
|
}
|
|
@@ -40,11 +43,23 @@ export const CountryHeader = ({
|
|
|
40
43
|
progressSegments = 4,
|
|
41
44
|
onBackPress,
|
|
42
45
|
safeAreaTop = 0,
|
|
46
|
+
hideProgressBar = false,
|
|
43
47
|
style,
|
|
44
48
|
}: CountryHeaderProps) => {
|
|
45
49
|
const { scale, scaleText } = useResponsive();
|
|
46
50
|
const countryTheme = getCountryTheme(countryCode);
|
|
47
51
|
|
|
52
|
+
const progressAnim = useRef(new Animated.Value(hideProgressBar ? 0 : 1)).current;
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
Animated.timing(progressAnim, {
|
|
56
|
+
toValue: hideProgressBar ? 0 : 1,
|
|
57
|
+
duration: 300,
|
|
58
|
+
easing: Easing.inOut(Easing.ease),
|
|
59
|
+
useNativeDriver: false,
|
|
60
|
+
}).start();
|
|
61
|
+
}, [hideProgressBar, progressAnim]);
|
|
62
|
+
|
|
48
63
|
return (
|
|
49
64
|
<View style={[styles.container, { backgroundColor: countryTheme.bg }, style]}>
|
|
50
65
|
{/* Círculo decorativo de fundo */}
|
|
@@ -87,13 +102,24 @@ export const CountryHeader = ({
|
|
|
87
102
|
</View>
|
|
88
103
|
|
|
89
104
|
{/* Progress Bar */}
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
<Animated.View
|
|
106
|
+
style={{
|
|
107
|
+
opacity: progressAnim,
|
|
108
|
+
height: progressAnim.interpolate({
|
|
109
|
+
inputRange: [0, 1],
|
|
110
|
+
outputRange: [0, 12],
|
|
111
|
+
}),
|
|
112
|
+
overflow: 'hidden',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<ProgressBar
|
|
116
|
+
progress={progress}
|
|
117
|
+
segments={progressSegments}
|
|
118
|
+
size="sm"
|
|
119
|
+
fillColor={countryTheme.progressFill}
|
|
120
|
+
trackColor={countryTheme.progressTrack}
|
|
121
|
+
/>
|
|
122
|
+
</Animated.View>
|
|
97
123
|
</View>
|
|
98
124
|
</View>
|
|
99
125
|
);
|
|
@@ -67,6 +67,13 @@ export const MapScreen = () => {
|
|
|
67
67
|
control: 'radio',
|
|
68
68
|
options: ['continuous', 'segmented'],
|
|
69
69
|
},
|
|
70
|
+
variant: {
|
|
71
|
+
control: 'radio',
|
|
72
|
+
options: ['default', 'minimal'],
|
|
73
|
+
},
|
|
74
|
+
notificationCount: {
|
|
75
|
+
control: 'number',
|
|
76
|
+
},
|
|
70
77
|
},
|
|
71
78
|
} satisfies Meta<typeof RouteHeader>;
|
|
72
79
|
|
|
@@ -74,6 +81,7 @@ export default meta;
|
|
|
74
81
|
type Story = StoryObj<typeof meta>;
|
|
75
82
|
|
|
76
83
|
export const Continuous: Story = {
|
|
84
|
+
render: (args) => <RouteHeader {...args} />,
|
|
77
85
|
args: {
|
|
78
86
|
routeLabel: 'ROTA DA FRANCOFONIA',
|
|
79
87
|
countryCode: 'SN',
|
|
@@ -88,6 +96,7 @@ export const Continuous: Story = {
|
|
|
88
96
|
};
|
|
89
97
|
|
|
90
98
|
export const Segmented: Story = {
|
|
99
|
+
render: (args) => <RouteHeader {...args} />,
|
|
91
100
|
args: {
|
|
92
101
|
routeLabel: 'ROTA DA FRANCOFONIA',
|
|
93
102
|
stepLabel: 'paragem 5 de 8',
|
|
@@ -102,3 +111,22 @@ export const Segmented: Story = {
|
|
|
102
111
|
safeAreaTop: 20,
|
|
103
112
|
},
|
|
104
113
|
};
|
|
114
|
+
|
|
115
|
+
export const Minimal: Story = {
|
|
116
|
+
render: (args) => <RouteHeader {...args} />,
|
|
117
|
+
args: {
|
|
118
|
+
routeLabel: 'Francofonia',
|
|
119
|
+
stepLabel: '5/8',
|
|
120
|
+
countryCode: 'SN',
|
|
121
|
+
title: 'Dakar',
|
|
122
|
+
subtitle: 'B2',
|
|
123
|
+
progressText: '',
|
|
124
|
+
progressSubText: '',
|
|
125
|
+
progress: 0.625,
|
|
126
|
+
progressType: 'segmented',
|
|
127
|
+
totalSegments: 8,
|
|
128
|
+
variant: 'minimal',
|
|
129
|
+
notificationCount: 3,
|
|
130
|
+
safeAreaTop: 20,
|
|
131
|
+
},
|
|
132
|
+
};
|