@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,328 @@
1
+ import { useRef, useState, useMemo } from 'react';
2
+ import { View, StyleSheet, PanResponder, Platform, Animated } from 'react-native';
3
+ import { theme } from '../../../styles/theme';
4
+ import { Typography } from '../../Typography/Typography';
5
+ import { useResponsive } from '../../../hooks/useResponsive';
6
+ import { IconButton } from '../../Buttons/IconButton/IconButton';
7
+ import { PassportCover } from './PassportCover';
8
+ import { PassportIDPage, type PassportUserData } from './PassportIDPage';
9
+ import { PassportStampPage, type PassportStampPageProps } from './PassportStampPage';
10
+
11
+ export interface PassportViewerProps {
12
+ user: PassportUserData;
13
+ countries: Omit<PassportStampPageProps, 'stampRotation'>[];
14
+ onBack?: () => void;
15
+ }
16
+
17
+ const PASSPORT_ASPECT_RATIO = 1.4;
18
+
19
+ export const PassportViewer = ({ user, countries, onBack }: PassportViewerProps) => {
20
+ const { scale, scaleText, width: windowWidthReactive } = useResponsive();
21
+ const [activeIndex, setActiveIndex] = useState(0);
22
+
23
+ const windowWidth = Platform.OS === 'web' ? Math.min(windowWidthReactive, 430) : windowWidthReactive;
24
+ const paddingHorizontal = scale(32);
25
+ const passportWidth = windowWidth - paddingHorizontal * 2;
26
+ const passportHeight = passportWidth * PASSPORT_ASPECT_RATIO;
27
+
28
+ const totalPages = 2 + countries.length;
29
+
30
+ const flipAnim = useRef(new Animated.Value(0)).current;
31
+ const isAnimating = useRef(false);
32
+
33
+ // Pre-calculate rotations for current page
34
+ const rotateForward = flipAnim.interpolate({
35
+ inputRange: [0, 1],
36
+ outputRange: ['0deg', '-90deg'],
37
+ extrapolate: 'clamp'
38
+ });
39
+
40
+ // Pre-calculate rotations for previous page
41
+ const rotateBackward = flipAnim.interpolate({
42
+ inputRange: [-1, 0],
43
+ outputRange: ['0deg', '-90deg'],
44
+ extrapolate: 'clamp'
45
+ });
46
+
47
+ const panResponder = useRef(
48
+ PanResponder.create({
49
+ onStartShouldSetPanResponder: () => !isAnimating.current,
50
+ onMoveShouldSetPanResponder: (_, gestureState) => !isAnimating.current && Math.abs(gestureState.dx) > 10,
51
+ onPanResponderMove: (_, gestureState) => {
52
+ if (isAnimating.current) return;
53
+ let progress = -gestureState.dx / passportWidth;
54
+
55
+ if (activeIndex === 0 && progress < 0) progress = 0;
56
+ if (activeIndex === totalPages - 1 && progress > 0) progress = 0;
57
+
58
+ flipAnim.setValue(progress);
59
+ },
60
+ onPanResponderRelease: (_, gestureState) => {
61
+ if (isAnimating.current) return;
62
+ let progress = -gestureState.dx / passportWidth;
63
+ const velocity = -gestureState.vx;
64
+
65
+ let toValue = 0;
66
+ let nextIndex = activeIndex;
67
+
68
+ if (progress > 0.3 || velocity > 0.5) { // Flip forward
69
+ if (activeIndex < totalPages - 1) {
70
+ toValue = 1;
71
+ nextIndex = activeIndex + 1;
72
+ }
73
+ } else if (progress < -0.3 || velocity < -0.5) { // Flip backward
74
+ if (activeIndex > 0) {
75
+ toValue = -1;
76
+ nextIndex = activeIndex - 1;
77
+ }
78
+ }
79
+
80
+ if (toValue !== 0) {
81
+ isAnimating.current = true;
82
+ Animated.spring(flipAnim, {
83
+ toValue,
84
+ friction: 7,
85
+ tension: 30,
86
+ useNativeDriver: true
87
+ }).start(() => {
88
+ setActiveIndex(nextIndex);
89
+ flipAnim.setValue(0);
90
+ isAnimating.current = false;
91
+ });
92
+ } else {
93
+ // Snap back
94
+ Animated.spring(flipAnim, {
95
+ toValue: 0,
96
+ friction: 7,
97
+ tension: 40,
98
+ useNativeDriver: true
99
+ }).start();
100
+ }
101
+ }
102
+ })
103
+ ).current;
104
+
105
+ const flipTo = (direction: 'next' | 'prev') => {
106
+ if (isAnimating.current) return;
107
+
108
+ if (direction === 'next' && activeIndex < totalPages - 1) {
109
+ isAnimating.current = true;
110
+ Animated.spring(flipAnim, {
111
+ toValue: 1,
112
+ friction: 7,
113
+ tension: 30,
114
+ useNativeDriver: true
115
+ }).start(() => {
116
+ setActiveIndex(activeIndex + 1);
117
+ flipAnim.setValue(0);
118
+ isAnimating.current = false;
119
+ });
120
+ } else if (direction === 'prev' && activeIndex > 0) {
121
+ isAnimating.current = true;
122
+ Animated.spring(flipAnim, {
123
+ toValue: -1,
124
+ friction: 7,
125
+ tension: 30,
126
+ useNativeDriver: true
127
+ }).start(() => {
128
+ setActiveIndex(activeIndex - 1);
129
+ flipAnim.setValue(0);
130
+ isAnimating.current = false;
131
+ });
132
+ }
133
+ };
134
+
135
+ // Build array of page components
136
+ const pages = useMemo(() => {
137
+ const list = [
138
+ <PassportCover key="cover" />,
139
+ <PassportIDPage key="id" user={user} />
140
+ ];
141
+ countries.forEach((country, idx) => {
142
+ const rotation = ((idx * 13) % 20) - 10;
143
+ list.push(<PassportStampPage key={`country-${idx}`} {...country} stampRotation={rotation} />);
144
+ });
145
+ return list;
146
+ }, [user, countries]);
147
+
148
+ return (
149
+ <View style={styles.container}>
150
+ <View style={[styles.header, { padding: scale(24) }]}>
151
+ <View style={styles.headerLeft}>
152
+ <IconButton
153
+ iconName="ChevronLeft"
154
+ size="sm"
155
+ variant="secondary"
156
+ onPress={onBack}
157
+ />
158
+ <Typography style={[styles.headerTitle, { fontSize: scaleText(18), marginLeft: scale(16) }]}>
159
+ O meu passaporte
160
+ </Typography>
161
+ </View>
162
+ <Typography style={[styles.pageIndicator, { fontSize: scaleText(12) }]}>
163
+ {activeIndex + 1} / {totalPages}
164
+ </Typography>
165
+ </View>
166
+
167
+ <View style={[styles.bookContainer, { height: passportHeight }]} {...panResponder.panHandlers}>
168
+ {/* Render only visible layers to optimize performance */}
169
+
170
+ {/* BACKGROUND PAGE (Next Page) */}
171
+ {activeIndex < totalPages - 1 && (
172
+ <View style={[styles.pageLayer, { width: passportWidth, height: passportHeight, zIndex: 1 }]}>
173
+ {pages[activeIndex + 1]}
174
+ </View>
175
+ )}
176
+
177
+ {/* CURRENT PAGE (The one flipping forward) */}
178
+ <Animated.View style={[
179
+ styles.pageLayer,
180
+ {
181
+ width: passportWidth,
182
+ height: passportHeight,
183
+ zIndex: 2,
184
+ transform: [
185
+ { perspective: 800 },
186
+ { translateX: -passportWidth / 2 },
187
+ { rotateY: rotateForward },
188
+ { translateX: passportWidth / 2 }
189
+ ]
190
+ }
191
+ ]}>
192
+ {pages[activeIndex]}
193
+ </Animated.View>
194
+
195
+ {/* PREVIOUS PAGE (The one flipping backward from -90deg to 0) */}
196
+ {activeIndex > 0 && (
197
+ <Animated.View style={[
198
+ styles.pageLayer,
199
+ {
200
+ width: passportWidth,
201
+ height: passportHeight,
202
+ zIndex: 3,
203
+ transform: [
204
+ { perspective: 800 },
205
+ { translateX: -passportWidth / 2 },
206
+ { rotateY: rotateBackward },
207
+ { translateX: passportWidth / 2 }
208
+ ]
209
+ }
210
+ ]}>
211
+ {pages[activeIndex - 1]}
212
+ </Animated.View>
213
+ )}
214
+ </View>
215
+
216
+ <View style={[styles.footer, { paddingBottom: scale(32) }]}>
217
+ <View style={styles.paginationRow}>
218
+ <IconButton
219
+ iconName="ChevronLeft"
220
+ size="sm"
221
+ variant="primary"
222
+ onPress={() => flipTo('prev')}
223
+ disabled={activeIndex === 0 || isAnimating.current}
224
+ style={{ width: scale(48) }}
225
+ />
226
+
227
+ <View style={[styles.dotsContainer, { gap: scale(8) }]}>
228
+ {Array.from({ length: totalPages }).map((_, i) => (
229
+ <View
230
+ key={i}
231
+ style={[
232
+ styles.dot,
233
+ { width: scale(6), height: scale(6), borderRadius: scale(3) },
234
+ i === activeIndex && { backgroundColor: theme.colors.primary }
235
+ ]}
236
+ />
237
+ ))}
238
+ </View>
239
+
240
+ <IconButton
241
+ iconName="ChevronRight"
242
+ size="sm"
243
+ variant="primary"
244
+ onPress={() => flipTo('next')}
245
+ disabled={activeIndex === totalPages - 1 || isAnimating.current}
246
+ style={{ width: scale(48) }}
247
+ />
248
+ </View>
249
+
250
+ <Typography style={[styles.helperText, { fontSize: scaleText(11), marginTop: scale(16) }]}>
251
+ {activeIndex === 0 ? "Toca na capa ou arrasta" : "Arrasta a página para desfolhar"}
252
+ </Typography>
253
+ </View>
254
+ </View>
255
+ );
256
+ };
257
+
258
+ const styles = StyleSheet.create({
259
+ container: {
260
+ flex: 1,
261
+ backgroundColor: '#efe2d6',
262
+ width: '100%',
263
+ height: '100%',
264
+ },
265
+ header: {
266
+ flexDirection: 'row',
267
+ alignItems: 'center',
268
+ justifyContent: 'space-between',
269
+ },
270
+ headerLeft: {
271
+ flexDirection: 'row',
272
+ alignItems: 'center',
273
+ },
274
+ headerTitle: {
275
+
276
+ color: theme.colors.brown[800],
277
+ },
278
+ pageIndicator: {
279
+
280
+ color: theme.colors.brown[500],
281
+ letterSpacing: 1,
282
+ },
283
+ bookContainer: {
284
+ flex: 1,
285
+ alignItems: 'center',
286
+ justifyContent: 'center',
287
+ width: '100%',
288
+ position: 'relative',
289
+ },
290
+ pageLayer: {
291
+ position: 'absolute',
292
+ backfaceVisibility: 'hidden',
293
+ },
294
+ footer: {
295
+ alignItems: 'center',
296
+ justifyContent: 'flex-end',
297
+ backgroundColor: '#fff',
298
+ borderTopLeftRadius: 32,
299
+ borderTopRightRadius: 32,
300
+ paddingTop: 24,
301
+ },
302
+ paginationRow: {
303
+ flexDirection: 'row',
304
+ alignItems: 'center',
305
+ justifyContent: 'space-between',
306
+ width: '100%',
307
+ paddingHorizontal: 24,
308
+ },
309
+ navBtn: {
310
+ backgroundColor: '#efe2d6',
311
+ alignItems: 'center',
312
+ justifyContent: 'center',
313
+ },
314
+ navBtnActive: {
315
+ backgroundColor: theme.colors.primary,
316
+ },
317
+ dotsContainer: {
318
+ flexDirection: 'row',
319
+ alignItems: 'center',
320
+ },
321
+ dot: {
322
+ backgroundColor: '#e0c8b6',
323
+ },
324
+ helperText: {
325
+
326
+ color: theme.colors.brown[400],
327
+ }
328
+ });
@@ -0,0 +1,159 @@
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 { PlanCard } from './PlanCard';
5
+
6
+ const meta = {
7
+ title: 'Components/Cards/PlanCard',
8
+ component: PlanCard,
9
+ parameters: {
10
+ layout: 'padded',
11
+ docs: {
12
+ description: {
13
+ component: 'O **PlanCard** exibe o curso principal ativo do utilizador, o seu nível e o progresso em direção ao objetivo (ex: usado no dashboard principal).\n\n### Exemplo de Uso\n```tsx\n<PlanCard \n courseName="Francês"\n level="A2"\n currentTopic="No mercado de Dakar"\n progress={0.6}\n nextClassDate="Hoje, 19:00"\n/>\n```',
14
+ }
15
+ }
16
+ },
17
+ tags: ['autodocs'],
18
+ argTypes: {
19
+ tier: { control: { type: 'select', options: ['decouverte', 'explorateur', 'fluent'] } },
20
+ planName: { control: 'text' },
21
+ priceText: { control: 'text' },
22
+ pricePeriod: { control: 'text' },
23
+ isPopular: { control: 'boolean' },
24
+ },
25
+ decorators: [
26
+ (Story) => (
27
+ <View style={{ padding: 24, maxWidth: 400, backgroundColor: '#f5f0eb' }}>
28
+ <Story />
29
+ </View>
30
+ ),
31
+ ],
32
+ } satisfies Meta<any>;
33
+
34
+ export default meta;
35
+ type Story = StoryObj<any>;
36
+
37
+ export const Decouverte: Story = {
38
+ name: "Découverte (Grátis)",
39
+ args: {
40
+ tier: 'decouverte',
41
+ tierLabel: 'DÉCOUVERTE',
42
+ planName: 'Découverte',
43
+ priceText: 'Grátis',
44
+ pricePeriod: '/sempre',
45
+ includedItems: [
46
+ '1 aula por semana',
47
+ 'Acesso ao vocabulário base',
48
+ ],
49
+ excludedItems: [
50
+ 'Feedback personalizado',
51
+ 'Suporte prioritário',
52
+ ],
53
+ },
54
+ };
55
+
56
+ export const Explorateur: Story = {
57
+ name: "Explorateur (Recomendado)",
58
+ args: {
59
+ tier: 'explorateur',
60
+ tierLabel: 'RECOMENDADO',
61
+ planName: 'Explorateur',
62
+ priceText: '15€',
63
+ pricePeriod: '/mês',
64
+ isPopular: true,
65
+ includedItems: [
66
+ 'Aulas ilimitadas de conversação',
67
+ 'Dicionário inteligente completo',
68
+ 'Feedback de professores em 24h',
69
+ ],
70
+ excludedItems: [
71
+ 'Suporte prioritário 1-para-1',
72
+ ],
73
+ },
74
+ };
75
+
76
+ export const Fluent: Story = {
77
+ name: "Fluent (Premium)",
78
+ args: {
79
+ tier: 'fluent',
80
+ tierLabel: 'PREMIUM',
81
+ planName: 'Fluent',
82
+ priceText: '29€',
83
+ pricePeriod: '/mês',
84
+ includedItems: [
85
+ 'Tudo no plano Explorateur',
86
+ 'Suporte prioritário 1-para-1',
87
+ 'Mentoria mensal exclusiva',
88
+ ],
89
+ },
90
+ };
91
+
92
+ export const ExplorateurMini: Story = {
93
+ name: "Explorateur (Mini)",
94
+ args: {
95
+ variant: 'mini',
96
+ tier: 'explorateur',
97
+ tierLabel: 'EXPLORATEUR',
98
+ planName: 'Explorateur',
99
+ priceText: '15€',
100
+ pricePeriod: '/mês',
101
+ isPopular: true,
102
+ },
103
+ };
104
+
105
+ export const Stack: Story = {
106
+ name: "Stack de Planos (Grid)",
107
+ args: {
108
+ tier: 'decouverte',
109
+ planName: '',
110
+ priceText: '',
111
+ },
112
+ render: () => (
113
+ <View style={{ gap: 24 }}>
114
+ <PlanCard
115
+ tier="decouverte"
116
+ tierLabel="DÉCOUVERTE"
117
+ planName="Découverte"
118
+ priceText="Grátis"
119
+ pricePeriod="/sempre"
120
+ includedItems={[
121
+ '1 aula por semana',
122
+ 'Acesso ao vocabulário base',
123
+ ]}
124
+ excludedItems={[
125
+ 'Feedback de professores em 24h',
126
+ 'Suporte prioritário',
127
+ ]}
128
+ />
129
+ <PlanCard
130
+ tier="explorateur"
131
+ tierLabel="EXPLORATEUR"
132
+ planName="Explorateur"
133
+ priceText="15€"
134
+ pricePeriod="/mês"
135
+ isPopular
136
+ includedItems={[
137
+ 'Aulas ilimitadas de conversação',
138
+ 'Dicionário inteligente completo',
139
+ 'Feedback de professores em 24h',
140
+ ]}
141
+ excludedItems={[
142
+ 'Suporte prioritário',
143
+ ]}
144
+ />
145
+ <PlanCard
146
+ tier="fluent"
147
+ tierLabel="FLUENT"
148
+ planName="Fluent"
149
+ priceText="29€"
150
+ pricePeriod="/mês"
151
+ includedItems={[
152
+ 'Tudo no plano Explorateur',
153
+ 'Suporte prioritário 1-para-1',
154
+ 'Mentoria mensal exclusiva',
155
+ ]}
156
+ />
157
+ </View>
158
+ ),
159
+ };