@xetwa/design-system 1.0.2

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 (168) hide show
  1. package/.github/workflows/publish.yml +30 -0
  2. package/.storybook/main.ts +17 -0
  3. package/.storybook/preview.tsx +65 -0
  4. package/eslint.config.js +22 -0
  5. package/index.html +13 -0
  6. package/package.json +55 -0
  7. package/public/favicon.svg +1 -0
  8. package/public/icons.svg +24 -0
  9. package/src/App.css +184 -0
  10. package/src/App.tsx +122 -0
  11. package/src/BordasSombras.mdx +343 -0
  12. package/src/Colors.mdx +393 -0
  13. package/src/GrelhaLayout.mdx +375 -0
  14. package/src/Icons.mdx +42 -0
  15. package/src/MovimentoBreakpoints.mdx +379 -0
  16. package/src/Spacing.mdx +231 -0
  17. package/src/Typography.mdx +321 -0
  18. package/src/assets/Mascots/vava/estaticos/vava-acenar.svg +3 -0
  19. package/src/assets/Mascots/vava/estaticos/vava-acertou.svg +3 -0
  20. package/src/assets/Mascots/vava/estaticos/vava-celebrar.svg +3 -0
  21. package/src/assets/Mascots/vava/estaticos/vava-com-autocolantes.svg +3 -0
  22. package/src/assets/Mascots/vava/estaticos/vava-dormir.svg +3 -0
  23. package/src/assets/Mascots/vava/estaticos/vava-errou.svg +3 -0
  24. package/src/assets/Mascots/vava/estaticos/vava-feliz.svg +3 -0
  25. package/src/assets/Mascots/vava/estaticos/vava-neutra.svg +3 -0
  26. package/src/assets/Mascots/vava/estaticos/vava-original.svg +3 -0
  27. package/src/assets/Mascots/vava/estaticos/vava-pensar.svg +3 -0
  28. package/src/assets/Mascots/vava/estaticos/vava-surpresa.svg +3 -0
  29. package/src/assets/Mascots/yaya/avatar/yaya-avatar-falar.svg +7 -0
  30. package/src/assets/Mascots/yaya/avatar/yaya-avatar-feliz.svg +7 -0
  31. package/src/assets/Mascots/yaya/avatar/yaya-avatar-transparente.svg +6 -0
  32. package/src/assets/Mascots/yaya/estaticos/yaya-acertou.svg +13 -0
  33. package/src/assets/Mascots/yaya/estaticos/yaya-celebrar.svg +13 -0
  34. package/src/assets/Mascots/yaya/estaticos/yaya-dormir.svg +13 -0
  35. package/src/assets/Mascots/yaya/estaticos/yaya-errou.svg +13 -0
  36. package/src/assets/Mascots/yaya/estaticos/yaya-falar.svg +13 -0
  37. package/src/assets/Mascots/yaya/estaticos/yaya-feliz.svg +13 -0
  38. package/src/assets/Mascots/yaya/estaticos/yaya-neutra.svg +13 -0
  39. package/src/assets/Mascots/yaya/estaticos/yaya-pensar.svg +13 -0
  40. package/src/assets/Mascots/yaya/estaticos/yaya-surpresa.svg +13 -0
  41. package/src/assets/hero.png +0 -0
  42. package/src/assets/react.svg +1 -0
  43. package/src/assets/vite.svg +1 -0
  44. package/src/components/Buttons/BackButton/BackButton.stories.tsx +86 -0
  45. package/src/components/Buttons/BackButton/BackButton.tsx +107 -0
  46. package/src/components/Buttons/FabButton/FabButton.stories.tsx +74 -0
  47. package/src/components/Buttons/FabButton/FabButton.tsx +161 -0
  48. package/src/components/Buttons/IconButton/IconButton.stories.tsx +99 -0
  49. package/src/components/Buttons/IconButton/IconButton.tsx +189 -0
  50. package/src/components/Buttons/JourneyButton/JourneyButton.stories.tsx +80 -0
  51. package/src/components/Buttons/JourneyButton/JourneyButton.tsx +194 -0
  52. package/src/components/Buttons/MainButton/MainButton.stories.tsx +144 -0
  53. package/src/components/Buttons/MainButton/MainButton.tsx +258 -0
  54. package/src/components/Buttons/WordChip/WordChip.stories.tsx +84 -0
  55. package/src/components/Buttons/WordChip/WordChip.tsx +157 -0
  56. package/src/components/Buttons/WordChip/index.ts +1 -0
  57. package/src/components/Cards/AnswerOptionCard/AnswerOptionCard.stories.tsx +181 -0
  58. package/src/components/Cards/AnswerOptionCard/AnswerOptionCard.tsx +201 -0
  59. package/src/components/Cards/BaseCard/BaseCard.stories.tsx +148 -0
  60. package/src/components/Cards/BaseCard/BaseCard.tsx +181 -0
  61. package/src/components/Cards/BoardingPassCard/BoardingPassCard.stories.tsx +205 -0
  62. package/src/components/Cards/BoardingPassCard/BoardingPassCard.tsx +384 -0
  63. package/src/components/Cards/BoardingPassCard/FlightIcon.tsx +21 -0
  64. package/src/components/Cards/BoardingPassCard/FlightIcon.web.tsx +18 -0
  65. package/src/components/Cards/CountryCard/CountryCard.stories.tsx +97 -0
  66. package/src/components/Cards/CountryCard/CountryCard.tsx +185 -0
  67. package/src/components/Cards/LevelProgressCard/LevelProgressCard.stories.tsx +61 -0
  68. package/src/components/Cards/LevelProgressCard/LevelProgressCard.tsx +143 -0
  69. package/src/components/Cards/ModuleCard/ModuleCard.stories.tsx +108 -0
  70. package/src/components/Cards/ModuleCard/ModuleCard.tsx +304 -0
  71. package/src/components/Cards/ModuleCompletionCard/ModuleCompletionCard.stories.tsx +53 -0
  72. package/src/components/Cards/ModuleCompletionCard/ModuleCompletionCard.tsx +167 -0
  73. package/src/components/Cards/Passport/PassportCover.tsx +114 -0
  74. package/src/components/Cards/Passport/PassportIDPage.tsx +217 -0
  75. package/src/components/Cards/Passport/PassportStampPage.tsx +253 -0
  76. package/src/components/Cards/Passport/PassportViewer.stories.tsx +95 -0
  77. package/src/components/Cards/Passport/PassportViewer.tsx +328 -0
  78. package/src/components/Cards/PlanCard/PlanCard.stories.tsx +159 -0
  79. package/src/components/Cards/PlanCard/PlanCard.tsx +317 -0
  80. package/src/components/Cards/PricingCard/PricingCard.stories.tsx +83 -0
  81. package/src/components/Cards/PricingCard/PricingCard.tsx +144 -0
  82. package/src/components/Cards/SelectionCard/SelectionCard.stories.tsx +77 -0
  83. package/src/components/Cards/SelectionCard/SelectionCard.tsx +111 -0
  84. package/src/components/Cards/TeacherCard/TeacherCard.stories.tsx +126 -0
  85. package/src/components/Cards/TeacherCard/TeacherCard.tsx +236 -0
  86. package/src/components/Charts/WeeklyProgressChart/WeeklyProgressChart.stories.tsx +89 -0
  87. package/src/components/Charts/WeeklyProgressChart/WeeklyProgressChart.tsx +179 -0
  88. package/src/components/Charts/WeeklyProgressChart/index.ts +1 -0
  89. package/src/components/DataDisplay/Avatar/Avatar.stories.tsx +85 -0
  90. package/src/components/DataDisplay/Avatar/Avatar.tsx +118 -0
  91. package/src/components/DataDisplay/Avatar/index.ts +1 -0
  92. package/src/components/DataDisplay/Badge/Badge.stories.tsx +62 -0
  93. package/src/components/DataDisplay/Badge/Badge.tsx +146 -0
  94. package/src/components/DataDisplay/Flag/Flag.stories.tsx +98 -0
  95. package/src/components/DataDisplay/Flag/Flag.tsx +347 -0
  96. package/src/components/DataDisplay/Flag/Flag.web.tsx +334 -0
  97. package/src/components/DataDisplay/InstructionBubble/InstructionBubble.stories.tsx +80 -0
  98. package/src/components/DataDisplay/InstructionBubble/InstructionBubble.tsx +86 -0
  99. package/src/components/DataDisplay/LevelBadge/LevelBadge.stories.tsx +99 -0
  100. package/src/components/DataDisplay/LevelBadge/LevelBadge.tsx +118 -0
  101. package/src/components/DataDisplay/ProgressBar/ProgressBar.stories.tsx +95 -0
  102. package/src/components/DataDisplay/ProgressBar/ProgressBar.tsx +117 -0
  103. package/src/components/DataDisplay/StatBadge/StatBadge.stories.tsx +82 -0
  104. package/src/components/DataDisplay/StatBadge/StatBadge.tsx +135 -0
  105. package/src/components/DataDisplay/StatBadge/index.ts +1 -0
  106. package/src/components/DataDisplay/Tag/Tag.stories.tsx +63 -0
  107. package/src/components/DataDisplay/Tag/Tag.tsx +148 -0
  108. package/src/components/Feedback/FeedbackBottomSheet/FeedbackBottomSheet.stories.tsx +74 -0
  109. package/src/components/Feedback/FeedbackBottomSheet/FeedbackBottomSheet.tsx +200 -0
  110. package/src/components/Forms/Checkbox/Checkbox.stories.tsx +60 -0
  111. package/src/components/Forms/Checkbox/Checkbox.tsx +127 -0
  112. package/src/components/Forms/Checkbox/index.ts +1 -0
  113. package/src/components/Forms/DaySelector/DaySelector.stories.tsx +62 -0
  114. package/src/components/Forms/DaySelector/DaySelector.tsx +133 -0
  115. package/src/components/Forms/Radio/Radio.stories.tsx +61 -0
  116. package/src/components/Forms/Radio/Radio.tsx +141 -0
  117. package/src/components/Forms/Radio/index.ts +1 -0
  118. package/src/components/Forms/SegmentedToggle/SegmentedToggle.stories.tsx +67 -0
  119. package/src/components/Forms/SegmentedToggle/SegmentedToggle.tsx +150 -0
  120. package/src/components/Forms/SegmentedToggle/index.ts +1 -0
  121. package/src/components/Forms/Select/Select.stories.tsx +59 -0
  122. package/src/components/Forms/Select/Select.tsx +255 -0
  123. package/src/components/Forms/Select/index.ts +1 -0
  124. package/src/components/Forms/Switch/Switch.stories.tsx +57 -0
  125. package/src/components/Forms/Switch/Switch.tsx +120 -0
  126. package/src/components/Forms/Switch/index.ts +1 -0
  127. package/src/components/Forms/TimeSlot/TimeSlot.stories.tsx +62 -0
  128. package/src/components/Forms/TimeSlot/TimeSlot.tsx +116 -0
  129. package/src/components/Headers/CountryHeader/CountryHeader.stories.tsx +114 -0
  130. package/src/components/Headers/CountryHeader/CountryHeader.tsx +147 -0
  131. package/src/components/Headers/ProfileHeader/ProfileHeader.stories.tsx +141 -0
  132. package/src/components/Headers/ProfileHeader/ProfileHeader.tsx +233 -0
  133. package/src/components/Inputs/FillInTheBlank/FillInTheBlank.stories.tsx +333 -0
  134. package/src/components/Inputs/FillInTheBlank/FillInTheBlank.tsx +287 -0
  135. package/src/components/Inputs/FillInTheBlank/index.ts +1 -0
  136. package/src/components/Inputs/TextInput/TextInput.stories.tsx +103 -0
  137. package/src/components/Inputs/TextInput/TextInput.tsx +187 -0
  138. package/src/components/Mascots/Vava/Vava.stories.tsx +114 -0
  139. package/src/components/Mascots/Vava/Vava.tsx +276 -0
  140. package/src/components/Mascots/Yaya/Yaya.stories.tsx +98 -0
  141. package/src/components/Mascots/Yaya/Yaya.tsx +233 -0
  142. package/src/components/Navigation/BottomTabBar/BottomTabBar.stories.tsx +68 -0
  143. package/src/components/Navigation/BottomTabBar/BottomTabBar.tsx +85 -0
  144. package/src/components/Navigation/BottomTabBar/BottomTabBarItem.tsx +68 -0
  145. package/src/components/Navigation/BottomTabBar/index.ts +2 -0
  146. package/src/components/Navigation/PaginationDots/PaginationDots.stories.tsx +62 -0
  147. package/src/components/Navigation/PaginationDots/PaginationDots.tsx +81 -0
  148. package/src/components/Navigation/PaginationDots/index.ts +1 -0
  149. package/src/components/Navigation/ProgressMap/ProgressMap.stories.tsx +116 -0
  150. package/src/components/Navigation/ProgressMap/ProgressMap.tsx +827 -0
  151. package/src/components/Navigation/WorldProgressMap/WorldProgressMap.stories.tsx +169 -0
  152. package/src/components/Navigation/WorldProgressMap/WorldProgressMap.tsx +790 -0
  153. package/src/components/Typography/Typography.stories.tsx +127 -0
  154. package/src/components/Typography/Typography.tsx +53 -0
  155. package/src/hooks/useResponsive.ts +41 -0
  156. package/src/hooks/useTheme.ts +32 -0
  157. package/src/index.css +355 -0
  158. package/src/index.ts +28 -0
  159. package/src/main.tsx +10 -0
  160. package/src/mocks/codegenNativeComponent.ts +5 -0
  161. package/src/styles/countryThemes.ts +79 -0
  162. package/src/styles/theme.ts +297 -0
  163. package/src/utils/iconMap.ts +23 -0
  164. package/tsconfig.app.json +25 -0
  165. package/tsconfig.json +7 -0
  166. package/tsconfig.node.json +24 -0
  167. package/vite.config.ts +51 -0
  168. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,205 @@
1
+ // eslint-disable-next-line storybook/no-renderer-packages
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { View } from 'react-native';
4
+ import { BoardingPassCard } from './BoardingPassCard';
5
+
6
+ const meta = {
7
+ title: 'Components/Cards/BoardingPassCard',
8
+ component: BoardingPassCard,
9
+ parameters: {
10
+ layout: 'padded',
11
+ docs: {
12
+ description: {
13
+ component: 'O **BoardingPassCard** exibe o nível atual do utilizador como se fosse um cartão de embarque.\n\nA sua cor superior altera consoante o `countryName` (ex: azul para França, verde escuro para Senegal).\n\n### Exemplo de Uso\n```tsx\n<BoardingPassCard \n originCode="DKR"\n originName="Dakar"\n destCode="ABJ"\n destName="Abidjan"\n countryName="Côte d\'Ivoire"\n countrySubtitle="Abidjan · nível B2"\n modulesCount={4}\n focusText="Oralidade rápida"\n/>\n```',
14
+ }
15
+ }
16
+ },
17
+ tags: ['autodocs'],
18
+ argTypes: {
19
+ originCode: { control: 'text' },
20
+ originName: { control: 'text' },
21
+ destCode: { control: 'text' },
22
+ destName: { control: 'text' },
23
+ countryName: {
24
+ control: 'select',
25
+ options: [
26
+ 'França',
27
+ 'Bélgica',
28
+ 'Suíça',
29
+ 'Luxemburgo',
30
+ 'Mónaco',
31
+ 'Canadá',
32
+ "Côte d'Ivoire",
33
+ 'Senegal',
34
+ 'Camarões',
35
+ 'República Democrática do Congo',
36
+ 'Madagascar',
37
+ 'Burkina Faso',
38
+ 'Níger',
39
+ 'Mali',
40
+ 'Chade',
41
+ 'Guiné',
42
+ 'Ruanda',
43
+ 'Burundi',
44
+ 'Benim',
45
+ 'Togo',
46
+ 'República Centro-Africana',
47
+ 'Congo',
48
+ 'Gabão',
49
+ 'Guiné Equatorial',
50
+ 'Djibouti',
51
+ 'Comores',
52
+ 'Vanuatu',
53
+ 'Seychelles',
54
+ 'Haiti',
55
+ ],
56
+ },
57
+ countrySubtitle: { control: 'text' },
58
+ modulesCount: { control: 'number' },
59
+ focusText: { control: 'text' },
60
+ headerColor: { control: 'color' },
61
+ },
62
+ decorators: [
63
+ (Story) => (
64
+ <View style={{ padding: 24, maxWidth: 400, backgroundColor: '#f5f0eb' }}>
65
+ <Story />
66
+ </View>
67
+ ),
68
+ ],
69
+ } satisfies Meta<any>;
70
+
71
+ export default meta;
72
+ type Story = StoryObj<any>;
73
+
74
+ export const CoteDIvoire: Story = {
75
+ name: "Côte d'Ivoire (Laranja)",
76
+ args: {
77
+ originCode: 'DKR',
78
+ originName: 'Dakar',
79
+ destCode: 'ABJ',
80
+ destName: 'Abidjan',
81
+ countryName: "Côte d'Ivoire",
82
+ countrySubtitle: 'Abidjan · nível B2',
83
+ modulesCount: 4,
84
+ focusText: 'Oralidade rápida',
85
+ },
86
+ };
87
+
88
+ export const France: Story = {
89
+ name: "França (Azul)",
90
+ args: {
91
+ originCode: 'LIS',
92
+ originName: 'Lisboa',
93
+ destCode: 'CDG',
94
+ destName: 'Paris',
95
+ countryName: 'França',
96
+ countrySubtitle: 'Paris · nível A1',
97
+ modulesCount: 5,
98
+ focusText: 'Saudações & básico',
99
+ },
100
+ };
101
+
102
+ export const Senegal: Story = {
103
+ name: "Senegal (Verde)",
104
+ args: {
105
+ originCode: 'CDG',
106
+ originName: 'Paris',
107
+ destCode: 'DKR',
108
+ destName: 'Dakar',
109
+ countryName: 'Senegal',
110
+ countrySubtitle: 'Dakar · nível A2',
111
+ modulesCount: 3,
112
+ focusText: 'Mercado & Números',
113
+ },
114
+ };
115
+
116
+ export const Canada: Story = {
117
+ name: "Canadá (Vermelho)",
118
+ args: {
119
+ originCode: 'CDG',
120
+ originName: 'Paris',
121
+ destCode: 'YQB',
122
+ destName: 'Quebec',
123
+ countryName: 'Canadá',
124
+ countrySubtitle: 'Quebec · nível C1',
125
+ modulesCount: 6,
126
+ focusText: 'Expressões locais',
127
+ },
128
+ };
129
+
130
+ export const Grid: Story = {
131
+ args: {
132
+ originCode: '',
133
+ originName: '',
134
+ destCode: '',
135
+ destName: '',
136
+ countryName: '',
137
+ countrySubtitle: '',
138
+ modulesCount: 0,
139
+ focusText: '',
140
+ },
141
+ render: () => (
142
+ <View style={{ gap: 24 }}>
143
+ <BoardingPassCard
144
+ originCode="DKR"
145
+ originName="Dakar"
146
+ destCode="ABJ"
147
+ destName="Abidjan"
148
+ countryName="Côte d'Ivoire"
149
+ countrySubtitle="Abidjan · nível B2"
150
+ modulesCount={4}
151
+ focusText="Oralidade rápida"
152
+ />
153
+ <BoardingPassCard
154
+ originCode="LIS"
155
+ originName="Lisboa"
156
+ destCode="CDG"
157
+ destName="Paris"
158
+ countryName="França"
159
+ countrySubtitle="Paris · nível A1"
160
+ modulesCount={5}
161
+ focusText="Saudações & básico"
162
+ />
163
+ <BoardingPassCard
164
+ originCode="CDG"
165
+ originName="Paris"
166
+ destCode="YQB"
167
+ destName="Quebec"
168
+ countryName="Canadá"
169
+ countrySubtitle="Quebec · nível C1"
170
+ modulesCount={6}
171
+ focusText="Expressões locais"
172
+ />
173
+ <BoardingPassCard
174
+ originCode="BRU"
175
+ originName="Bruxelas"
176
+ destCode="CDG"
177
+ destName="Paris"
178
+ countryName="Bélgica"
179
+ countrySubtitle="Bruxelas · nível B1"
180
+ modulesCount={8}
181
+ focusText="Gramática avançada"
182
+ />
183
+ <BoardingPassCard
184
+ originCode="CDG"
185
+ originName="Paris"
186
+ destCode="KGL"
187
+ destName="Kigali"
188
+ countryName="Ruanda"
189
+ countrySubtitle="Kigali · nível A2"
190
+ modulesCount={3}
191
+ focusText="Conversa informal"
192
+ />
193
+ <BoardingPassCard
194
+ originCode="CDG"
195
+ originName="Paris"
196
+ destCode="DKR"
197
+ destName="Dakar"
198
+ countryName="Senegal"
199
+ countrySubtitle="Dakar · nível A2"
200
+ modulesCount={3}
201
+ focusText="Vocabulário de viagem"
202
+ />
203
+ </View>
204
+ ),
205
+ };
@@ -0,0 +1,384 @@
1
+ import { View, StyleSheet } from 'react-native';
2
+ import type { ViewStyle } from 'react-native';
3
+ import { theme } from '../../../styles/theme';
4
+ import type { ReactNode } from 'react';
5
+ import { Plane } from 'lucide-react-native';
6
+ import { FlightIcon } from './FlightIcon';
7
+ import { Flag } from '../../DataDisplay/Flag/Flag';
8
+ import { useResponsive } from '../../../hooks/useResponsive';
9
+ import { Typography } from '../../Typography/Typography';
10
+
11
+ /**
12
+ * Cartão de embarque (Boarding Pass) para exibir viagens entre países.
13
+ * Usado para a navegação de níveis/módulos baseada em destinos.
14
+ */
15
+ export interface BoardingPassCardProps {
16
+ /** Código IATA ou sigla da origem (ex: LIS). */
17
+ originCode: string;
18
+ /** Nome da cidade de origem (ex: Lisboa). */
19
+ originName: string;
20
+ /** Código IATA ou sigla do destino (ex: CDG). */
21
+ destCode: string;
22
+ /** Nome da cidade de destino (ex: Paris). */
23
+ destName: string;
24
+ /** Nome do país de destino, usado também para determinar a cor principal do cartão e a bandeira se nenhuma for passada. */
25
+ countryName: string;
26
+ /** Subtítulo a exibir junto do nome do país (ex: 'Paris • nível A1'). */
27
+ countrySubtitle: string;
28
+ /** Nó opcional para substituir a bandeira nativa. */
29
+ flagIconNode?: ReactNode;
30
+ /** Número total de módulos desta viagem/nível. */
31
+ modulesCount: number;
32
+ /** O foco principal da aprendizagem (ex: 'Saudações & básico'). */
33
+ focusText: string;
34
+ /** Cor customizada para o cabeçalho (sobrepõe a cor deduzida do país). */
35
+ headerColor?: string;
36
+ style?: ViewStyle | ViewStyle[];
37
+ }
38
+
39
+ const normalizeCountryCode = (input: string): string => {
40
+ const code = input.toLowerCase().trim();
41
+
42
+ if (code === 'fr' || code === 'fra' || code === 'frança' || code === 'france') return 'FR';
43
+ if (code === 'be' || code === 'bel' || code === 'bélgica' || code === 'belgium') return 'BE';
44
+ if (code === 'ch' || code === 'che' || code === 'suíça' || code === 'switzerland') return 'CH';
45
+ if (code === 'lu' || code === 'lux' || code === 'luxemburgo' || code === 'luxembourg') return 'LU';
46
+ if (code === 'mc' || code === 'mco' || code === 'mónaco' || code === 'monaco') return 'MC';
47
+ if (code === 'ca' || code === 'can' || code === 'canadá' || code === 'canada') return 'CA';
48
+ if (code === 'ci' || code === 'civ' || code === "côte d'ivoire" || code === "cote d'ivoire" || code === 'marfim' || code === 'costa do marfim') return 'CI';
49
+ if (code === 'sn' || code === 'sen' || code === 'senegal') return 'SN';
50
+ if (code === 'cm' || code === 'cmr' || code === 'camarões' || code === 'cameroon') return 'CM';
51
+ if (code === 'cd' || code === 'cod' || code === 'rd congo' || code === 'republica democratica do congo' || code === 'república democrática do congo' || code === 'dr congo') return 'CD';
52
+ if (code === 'mg' || code === 'mdg' || code === 'madagascar') return 'MG';
53
+ if (code === 'bf' || code === 'bfa' || code === 'burkina faso') return 'BF';
54
+ if (code === 'ne' || code === 'ner' || code === 'níger' || code === 'niger') return 'NE';
55
+ if (code === 'ml' || code === 'mli' || code === 'mali') return 'ML';
56
+ if (code === 'td' || code === 'tcd' || code === 'chade' || code === 'chad') return 'TD';
57
+ if (code === 'gn' || code === 'gin' || code === 'guiné' || code === 'guinea') return 'GN';
58
+ if (code === 'rw' || code === 'rwa' || code === 'ruanda' || code === 'rwanda') return 'RW';
59
+ if (code === 'bi' || code === 'bdi' || code === 'burundi') return 'BI';
60
+ if (code === 'bj' || code === 'ben' || code === 'benim' || code === 'benin') return 'BJ';
61
+ if (code === 'tg' || code === 'tgo' || code === 'togo') return 'TG';
62
+ if (code === 'cf' || code === 'caf' || code === 'república centro-africana' || code === 'republica centro-africana' || code === 'central african republic') return 'CF';
63
+ if (code === 'cg' || code === 'cog' || code === 'congo' || code === 'república do congo' || code === 'republica do congo') return 'CG';
64
+ if (code === 'ga' || code === 'gab' || code === 'gabão' || code === 'gabon') return 'GA';
65
+ if (code === 'gq' || code === 'gnq' || code === 'guiné equatorial' || code === 'guine equatorial' || code === 'equatorial guinea') return 'GQ';
66
+ if (code === 'dj' || code === 'dji' || code === 'djibouti') return 'DJ';
67
+ if (code === 'km' || code === 'com' || code === 'comores' || code === 'comoros') return 'KM';
68
+ if (code === 'vu' || code === 'vut' || code === 'vanuatu') return 'VU';
69
+ if (code === 'sc' || code === 'syc' || code === 'seychelles') return 'SC';
70
+ if (code === 'ht' || code === 'hti' || code === 'haiti') return 'HT';
71
+
72
+ return 'UNKNOWN';
73
+ };
74
+
75
+ const COUNTRY_COLORS: Record<string, string> = {
76
+ FR: '#1E3A8A', // Deep Royal Blue (França)
77
+ BE: '#1A1A1A', // Velvet Charcoal (Bélgica)
78
+ CH: '#9B1B1B', // Rich Crimson (Suíça)
79
+ LU: '#0E7490', // Deep Teal / Slate Blue (Luxemburgo)
80
+ MC: '#BE123C', // Royal Scarlet (Mónaco)
81
+ CA: '#B91C1C', // Maple Crimson (Canadá)
82
+ CI: '#16915a', // Burnt Amber / Ochre (Côte d'Ivoire)
83
+ SN: '#0A5C36', // Deep Forest Pine (Senegal)
84
+ CM: '#0F5132', // Velvet Pine Green (Camarões)
85
+ CD: '#0284C7', // Vivid Cobalt (RD Congo)
86
+ MG: '#9F1239', // Roseate Crimson (Madagascar)
87
+ BF: '#0F766E', // Deep Spruce Teal (Burkina Faso)
88
+ NE: '#D97706', // Rich Gold Marigold (Níger)
89
+ ML: '#A16207', // Antique Brass / Ochre (Mali)
90
+ TD: '#172554', // Dark Midnight Navy (Chade)
91
+ GN: '#991B1B', // Carmine Red (Guiné)
92
+ RW: '#0891B2', // Clear Cerulean / Turquoise (Ruanda)
93
+ BI: '#14532D', // Imperial Emerald (Burundi)
94
+ BJ: '#15803D', // Saturated Basil (Benim)
95
+ TG: '#1B4332', // Jungle Spruce Green (Togo)
96
+ CF: '#1D4ED8', // Indigo Navy (República Centro-Africana)
97
+ CG: '#166534', // Saturated Emerald (Congo)
98
+ GA: '#0D9488', // Deep Tropical Teal (Gabão)
99
+ GQ: '#034F35', // Deep Jade Green (Guiné Equatorial)
100
+ DJ: '#0284C7', // Slate Blue (Djibouti)
101
+ KM: '#B45309', // Warm Ochre (Comores)
102
+ VU: '#7F1D1D', // Burgundy Maroon (Vanuatu)
103
+ SC: '#BE123C', // Deep Rose Scarlet (Seychelles)
104
+ HT: '#1E3A8A', // Deep Indigo (Haiti)
105
+ };
106
+
107
+ export const BoardingPassCard = ({
108
+ originCode,
109
+ originName,
110
+ destCode,
111
+ destName,
112
+ countryName,
113
+ countrySubtitle,
114
+ flagIconNode,
115
+ modulesCount,
116
+ focusText,
117
+ headerColor,
118
+ style,
119
+ }: BoardingPassCardProps) => {
120
+ const { scale, scaleText } = useResponsive();
121
+
122
+ const getHeaderBgColor = () => {
123
+ if (headerColor) return headerColor;
124
+ const normalized = normalizeCountryCode(countryName);
125
+ return COUNTRY_COLORS[normalized] || '#0F766E'; // Fallback to premium Spruce Teal
126
+ };
127
+
128
+ const currentHeaderBg = getHeaderBgColor();
129
+
130
+ return (
131
+ <View style={[
132
+ styles.container,
133
+ {
134
+ borderRadius: scale(20),
135
+ minHeight: scale(210),
136
+ },
137
+ style
138
+ ]}>
139
+ {/* Top Section */}
140
+ <View style={[
141
+ styles.topSection,
142
+ {
143
+ backgroundColor: currentHeaderBg,
144
+ paddingTop: scale(16),
145
+ paddingBottom: scale(16),
146
+ paddingHorizontal: scale(18),
147
+ }
148
+ ]}>
149
+ <View style={[
150
+ styles.decorativeCircle,
151
+ {
152
+ width: scale(140),
153
+ height: scale(140),
154
+ borderRadius: scale(70),
155
+ top: scale(-40),
156
+ right: scale(-30),
157
+ }
158
+ ]} />
159
+
160
+ <View style={[styles.headerRow, { marginBottom: scale(16) }]}>
161
+ <Typography style={[styles.headerText, { fontSize: scaleText(10) }]}>CARTÃO DE EMBARQUE</Typography>
162
+ <View style={styles.headerPlaneContainer}>
163
+ <Plane stroke={theme.colors.white} strokeWidth={2.2} size={scale(22)} />
164
+ </View>
165
+ </View>
166
+
167
+ <View style={styles.routeRow}>
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>
171
+ </View>
172
+
173
+ <View style={[styles.flightLineContainer, { height: scale(30), marginHorizontal: scale(12) }]}>
174
+ <View style={styles.dashedLine} />
175
+ <View style={styles.planeContainer}>
176
+ <FlightIcon color={theme.colors.white} size={scale(18)} />
177
+ </View>
178
+ </View>
179
+
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>
183
+ </View>
184
+ </View>
185
+ </View>
186
+
187
+ {/* Bottom Section */}
188
+ <View style={[
189
+ styles.bottomSection,
190
+ {
191
+ paddingVertical: scale(14),
192
+ paddingHorizontal: scale(18),
193
+ }
194
+ ]}>
195
+ <View style={styles.countryRow}>
196
+ <View style={[
197
+ styles.flagBox,
198
+ {
199
+ width: scale(40),
200
+ height: scale(40),
201
+ borderRadius: scale(20),
202
+ marginRight: scale(12),
203
+ }
204
+ ]}>
205
+ {flagIconNode ? flagIconNode : <Flag countryCode={countryName} size={scale(40)} />}
206
+ </View>
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>
210
+ </View>
211
+ </View>
212
+
213
+ <View style={[
214
+ styles.divider,
215
+ {
216
+ marginTop: scale(12),
217
+ marginBottom: scale(12),
218
+ }
219
+ ]} />
220
+
221
+ <View style={[styles.footerRow, { gap: scale(14) }]}>
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>
225
+ </View>
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>
229
+ </View>
230
+ </View>
231
+ </View>
232
+ </View>
233
+ );
234
+ };
235
+
236
+ const styles = StyleSheet.create({
237
+ container: {
238
+ borderRadius: 20,
239
+ backgroundColor: theme.colors.white,
240
+ overflow: 'hidden',
241
+ minHeight: 230,
242
+ ...theme.shadows.lg,
243
+ },
244
+ topSection: {
245
+ paddingTop: 16,
246
+ paddingBottom: 16,
247
+ paddingHorizontal: 18,
248
+ position: 'relative',
249
+ overflow: 'hidden',
250
+ },
251
+ decorativeCircle: {
252
+ position: 'absolute',
253
+ width: 140,
254
+ height: 140,
255
+ borderRadius: 70,
256
+ backgroundColor: 'rgba(255, 255, 255, 0.16)',
257
+ top: -40,
258
+ right: -30,
259
+ },
260
+ headerRow: {
261
+ flexDirection: 'row',
262
+ justifyContent: 'space-between',
263
+ alignItems: 'center',
264
+ marginBottom: 16,
265
+ },
266
+ headerPlaneContainer: {
267
+ transform: [{ rotate: '0deg' }],
268
+ },
269
+ headerText: {
270
+
271
+ fontSize: 10,
272
+ letterSpacing: 1.2,
273
+ textTransform: 'uppercase',
274
+ color: 'rgba(255, 255, 255, 0.75)',
275
+ },
276
+ routeRow: {
277
+ flexDirection: 'row',
278
+ alignItems: 'center',
279
+ justifyContent: 'space-between',
280
+ },
281
+ cityCol: {
282
+ alignItems: 'flex-start',
283
+ },
284
+ cityCode: {
285
+
286
+ fontSize: 22,
287
+ color: theme.colors.white,
288
+ },
289
+ cityName: {
290
+
291
+ fontSize: 9,
292
+ color: 'rgba(255, 255, 255, 0.75)',
293
+ marginTop: 2,
294
+ },
295
+ flightLineContainer: {
296
+ flexDirection: 'row',
297
+ flex: 1,
298
+ height: 30,
299
+ marginHorizontal: 12,
300
+ alignItems: 'center',
301
+ justifyContent: 'center',
302
+ position: 'relative',
303
+ },
304
+ dashedLine: {
305
+ position: 'absolute',
306
+ left: 0,
307
+ right: 0,
308
+ borderTopWidth: 2,
309
+ borderStyle: 'dashed' as const,
310
+ borderColor: 'rgba(255, 255, 255, 0.5)',
311
+ height: 0,
312
+ alignSelf: 'center',
313
+ },
314
+ planeContainer: {
315
+ transform: [{ rotate: '135deg' }],
316
+ marginHorizontal: 8,
317
+ alignSelf: 'center',
318
+ backgroundColor: 'transparent',
319
+ },
320
+ bottomSection: {
321
+ paddingVertical: 14,
322
+ paddingHorizontal: 18,
323
+ backgroundColor: theme.colors.white,
324
+ flex: 1,
325
+ justifyContent: 'space-between',
326
+ },
327
+ countryRow: {
328
+ flexDirection: 'row',
329
+ alignItems: 'center',
330
+ },
331
+ flagBox: {
332
+ width: 40,
333
+ height: 40,
334
+ borderRadius: 20,
335
+ marginRight: 12,
336
+ borderWidth: 2.5,
337
+ borderColor: theme.colors.white,
338
+ justifyContent: 'center',
339
+ alignItems: 'center',
340
+ backgroundColor: theme.colors.white,
341
+ overflow: 'hidden',
342
+ ...theme.shadows.sm,
343
+ },
344
+ countryContent: {
345
+ flex: 1,
346
+ },
347
+ countryName: {
348
+
349
+ fontSize: 17,
350
+ color: '#51332d',
351
+ marginBottom: 2,
352
+ },
353
+ countrySubtitle: {
354
+
355
+ fontSize: 11,
356
+ color: '#9a8478',
357
+ },
358
+ divider: {
359
+ borderTopWidth: 1.5,
360
+ borderColor: '#f3e7da',
361
+ marginTop: 12,
362
+ marginBottom: 12,
363
+ },
364
+ footerRow: {
365
+ flexDirection: 'row',
366
+ gap: 14,
367
+ },
368
+ footerCol: {
369
+ alignItems: 'flex-start',
370
+ flex: 1,
371
+ },
372
+ footerLabel: {
373
+
374
+ fontSize: 9,
375
+ textTransform: 'uppercase',
376
+ color: '#b9a392',
377
+ marginBottom: 4,
378
+ },
379
+ footerValue: {
380
+
381
+ fontSize: 16,
382
+ color: '#51332d',
383
+ },
384
+ });
@@ -0,0 +1,21 @@
1
+ import Svg, { Path } from 'react-native-svg';
2
+
3
+ interface FlightIconProps {
4
+ color: string;
5
+ size: number;
6
+ }
7
+
8
+ export const FlightIcon = ({ color, size }: FlightIconProps) => {
9
+ return (
10
+ <Svg
11
+ width={size}
12
+ height={size}
13
+ viewBox="0 0 24 24"
14
+ >
15
+ <Path
16
+ fill={color}
17
+ d="M21 16v-2l-8-5V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z M2.5 19h19v2h-19z"
18
+ />
19
+ </Svg>
20
+ );
21
+ };
@@ -0,0 +1,18 @@
1
+ interface FlightIconProps {
2
+ color: string;
3
+ size: number;
4
+ }
5
+
6
+ export const FlightIcon = ({ color, size }: FlightIconProps) => {
7
+ return (
8
+ <svg
9
+ width={size}
10
+ height={size}
11
+ viewBox="0 0 24 24"
12
+ fill={color}
13
+ style={{ display: 'block' }}
14
+ >
15
+ <path d="M21 16v-2l-8-5V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z M2.5 19h19v2h-19z" />
16
+ </svg>
17
+ );
18
+ };