@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,201 @@
1
+ import { View, StyleSheet, Pressable, Platform } from 'react-native';
2
+ import type { ViewStyle } from 'react-native';
3
+ import { theme } from '../../../styles/theme';
4
+ import type { ReactNode } from 'react';
5
+ import { Check, X } from 'lucide-react-native';
6
+ import { useResponsive } from '../../../hooks/useResponsive';
7
+ import { Typography } from '../../Typography/Typography';
8
+
9
+ export type AnswerOptionStatus = 'default' | 'selected' | 'correct' | 'wrong' | 'disabled';
10
+
11
+ /**
12
+ * Cartão usado em exercícios e quizzes para representar uma opção de resposta.
13
+ */
14
+ export interface AnswerOptionCardProps {
15
+ /** O estado atual da opção de resposta. Altera cores e ícones. */
16
+ status?: AnswerOptionStatus;
17
+ /** O texto principal da opção. */
18
+ label: string;
19
+ /** Nó renderizado à esquerda do texto (ex: letra 'A', 'B', 'C'). */
20
+ prefixNode?: ReactNode;
21
+ /** Nó renderizado à direita. Se omitido, renderiza automaticamente ícones de check/cross consoante o estado. */
22
+ suffixNode?: ReactNode;
23
+ /** Callback para o evento de clique. */
24
+ onPress?: () => void;
25
+ /** Estilos adicionais para o container. */
26
+ style?: ViewStyle | ViewStyle[];
27
+ }
28
+
29
+ export const AnswerOptionCard = ({
30
+ status = 'default',
31
+ label,
32
+ prefixNode,
33
+ suffixNode,
34
+ onPress,
35
+ style,
36
+ }: AnswerOptionCardProps) => {
37
+ const { scale, scaleText } = useResponsive();
38
+
39
+ const getStyles = () => {
40
+ switch (status) {
41
+ case 'selected':
42
+ return {
43
+ borderColor: theme.colors.primary,
44
+ backgroundColor: theme.colors.primaryLight,
45
+ shadowColor: 'rgba(242,151,75,0.25)',
46
+ textColor: theme.colors.brown[800],
47
+ };
48
+ case 'correct':
49
+ return {
50
+ borderColor: theme.colors.success,
51
+ backgroundColor: '#eafaf2',
52
+ shadowColor: 'rgba(22,145,90,0.2)',
53
+ textColor: theme.colors.brown[800],
54
+ };
55
+ case 'wrong':
56
+ return {
57
+ borderColor: theme.colors.error,
58
+ backgroundColor: '#fdf0ee',
59
+ shadowColor: 'rgba(207,91,72,0.2)',
60
+ textColor: theme.colors.brown[800],
61
+ };
62
+ case 'disabled':
63
+ return {
64
+ borderColor: theme.colors.brown[100],
65
+ backgroundColor: theme.colors.white,
66
+ shadowColor: theme.colors.brown[100],
67
+ textColor: theme.colors.brown[300],
68
+ };
69
+ case 'default':
70
+ default:
71
+ return {
72
+ borderColor: theme.colors.brown[100],
73
+ backgroundColor: theme.colors.white,
74
+ shadowColor: theme.colors.brown[100],
75
+ textColor: theme.colors.brown[800],
76
+ };
77
+ }
78
+ };
79
+
80
+ const vStyles = getStyles();
81
+
82
+ const webShadow = Platform.OS === 'web' ? {
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ boxShadow: `0 3px 0 ${vStyles.shadowColor}` as any,
85
+ } : {
86
+ borderBottomWidth: 5,
87
+ borderBottomColor: vStyles.shadowColor,
88
+ };
89
+
90
+ const renderSuffix = () => {
91
+ if (status === 'correct') {
92
+ return (
93
+ <View style={[
94
+ styles.suffixCircle,
95
+ {
96
+ width: scale(24),
97
+ height: scale(24),
98
+ borderRadius: scale(12),
99
+ marginLeft: scale(12),
100
+ backgroundColor: theme.colors.success,
101
+ }
102
+ ]}>
103
+ <Check stroke={theme.colors.white} strokeWidth={2.4} size={scale(14)} />
104
+ </View>
105
+ );
106
+ }
107
+ if (status === 'wrong') {
108
+ return (
109
+ <View style={[
110
+ styles.suffixCircle,
111
+ {
112
+ width: scale(24),
113
+ height: scale(24),
114
+ borderRadius: scale(12),
115
+ marginLeft: scale(12),
116
+ backgroundColor: theme.colors.error,
117
+ }
118
+ ]}>
119
+ <X stroke={theme.colors.white} strokeWidth={2.4} size={scale(14)} />
120
+ </View>
121
+ );
122
+ }
123
+ return suffixNode;
124
+ };
125
+
126
+ return (
127
+ <Pressable
128
+ style={[
129
+ styles.container,
130
+ {
131
+ borderColor: vStyles.borderColor,
132
+ backgroundColor: vStyles.backgroundColor,
133
+ paddingVertical: scale(10),
134
+ paddingHorizontal: scale(15),
135
+ borderRadius: scale(12),
136
+ minHeight: scale(56),
137
+ },
138
+ webShadow,
139
+ style,
140
+ ]}
141
+ onPress={status !== 'disabled' ? onPress : undefined}
142
+ disabled={status === 'disabled' || !onPress}
143
+ >
144
+ <View style={styles.contentRow}>
145
+ <View style={styles.leftSection}>
146
+ {prefixNode && <View style={[styles.prefixContainer, { marginRight: scale(12) }]}>{prefixNode}</View>}
147
+ <Typography
148
+ style={[
149
+ styles.label,
150
+ {
151
+ color: status === 'disabled' ? theme.colors.brown[300] : vStyles.textColor,
152
+ fontSize: scaleText(15),
153
+ }
154
+ ]}
155
+ >
156
+ {label}
157
+ </Typography>
158
+ </View>
159
+ {renderSuffix()}
160
+ </View>
161
+ </Pressable>
162
+ );
163
+ };
164
+
165
+ const styles = StyleSheet.create({
166
+ container: {
167
+ paddingVertical: 10,
168
+ paddingHorizontal: 15,
169
+ borderRadius: 12,
170
+ borderWidth: 2,
171
+ borderStyle: 'solid',
172
+ justifyContent: 'center',
173
+ minHeight: 56,
174
+ },
175
+ contentRow: {
176
+ flexDirection: 'row',
177
+ alignItems: 'center',
178
+ justifyContent: 'space-between',
179
+ width: '100%',
180
+ },
181
+ leftSection: {
182
+ flexDirection: 'row',
183
+ alignItems: 'center',
184
+ flex: 1,
185
+ },
186
+ prefixContainer: {
187
+ marginRight: 12,
188
+ },
189
+ label: {
190
+
191
+ fontSize: 15,
192
+ },
193
+ suffixCircle: {
194
+ width: 24,
195
+ height: 24,
196
+ borderRadius: 12,
197
+ alignItems: 'center',
198
+ justifyContent: 'center',
199
+ marginLeft: 12,
200
+ },
201
+ });
@@ -0,0 +1,148 @@
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 { BaseCard } from './BaseCard';
5
+ import { theme } from '../../../styles/theme';
6
+ import { Typography } from '../../Typography/Typography';
7
+
8
+ const meta = {
9
+ title: 'Components/Cards/BaseCard',
10
+ component: BaseCard,
11
+ parameters: {
12
+ layout: 'padded',
13
+ docs: {
14
+ description: {
15
+ component: 'O **BaseCard** encapsula os estados nativos (hover, pressed, disabled) e estilos (bordas, sombras).\n\n### Exemplo de Uso\n```tsx\n<BaseCard \n onPress={() => console.log("Clicado!")}\n isSelected={true}\n paddingHorizontal={20}\n paddingVertical={16}\n>\n <Typography>Conteúdo dentro do cartão</Typography>\n</BaseCard>\n```',
16
+ }
17
+ }
18
+ },
19
+ tags: ['autodocs'],
20
+ argTypes: {
21
+ backgroundColor: { control: 'color' },
22
+ borderColor: { control: 'color' },
23
+ borderWidth: { control: 'number' },
24
+ borderRadius: { control: 'number' },
25
+ borderStyle: { control: { type: 'select', options: ['solid', 'dashed'] } },
26
+ shadow: { control: { type: 'select', options: ['none', 'sm', 'md', 'lg'] } },
27
+ isSelected: { control: 'boolean' },
28
+ opacity: { control: { type: 'number', min: 0, max: 1, step: 0.1 } },
29
+ },
30
+ decorators: [
31
+ (Story) => (
32
+ <View style={{ padding: 24, maxWidth: 400, backgroundColor: '#f5f0eb' }}>
33
+ <Story />
34
+ </View>
35
+ ),
36
+ ],
37
+ } satisfies Meta<any>;
38
+
39
+ export default meta;
40
+ type Story = StoryObj<any>;
41
+
42
+ export const Default: Story = {
43
+ args: {
44
+ shadow: 'none',
45
+ children: (
46
+ <View style={{ gap: 4 }}>
47
+ <Typography style={{ fontFamily: theme.fonts.title, fontWeight: '700', fontSize: 16, color: '#51332d' }}>
48
+ Card Content Title
49
+ </Typography>
50
+ <Typography style={{ fontFamily: theme.fonts.body, fontWeight: '500', fontSize: 12, color: '#9a8478' }}>
51
+ This is a simple BaseCard container. It holds any children React elements and inherits standard typography.
52
+ </Typography>
53
+ </View>
54
+ ),
55
+ },
56
+ };
57
+
58
+ export const Dashed: Story = {
59
+ args: {
60
+ borderStyle: 'dashed',
61
+ borderColor: '#d8c7b8',
62
+ children: (
63
+ <View>
64
+ <Typography style={{ fontFamily: theme.fonts.body, fontWeight: '700', fontSize: 13, color: '#cf7a30' }}>
65
+ EM CURSO
66
+ </Typography>
67
+ </View>
68
+ ),
69
+ },
70
+ };
71
+
72
+ export const Interactive: Story = {
73
+ args: {
74
+ shadow: 'sm',
75
+ onPress: () => alert('Card clicked!'),
76
+ children: (
77
+ <View style={{ gap: 4 }}>
78
+ <Typography style={{ fontFamily: theme.fonts.title, fontWeight: '700', fontSize: 16, color: '#51332d' }}>
79
+ Clickable Card
80
+ </Typography>
81
+ <Typography style={{ fontFamily: theme.fonts.body, fontWeight: '500', fontSize: 12, color: '#9a8478' }}>
82
+ Tap on this card to trigger an alert and show selection animations.
83
+ </Typography>
84
+ </View>
85
+ ),
86
+ },
87
+ };
88
+
89
+ export const WithDivider: Story = {
90
+ name: "Com Divisor (Divider)",
91
+ args: {
92
+ children: (
93
+ <View>
94
+ {/* Header Area */}
95
+ <View style={{ gap: 4 }}>
96
+ <Typography style={{ fontFamily: theme.fonts.title, fontWeight: '700', fontSize: 16, color: '#51332d' }}>
97
+ Prof. Camille
98
+ </Typography>
99
+ <Typography style={{ fontFamily: theme.fonts.body, fontSize: 12, color: '#9a8478' }}>
100
+ Conversação A1
101
+ </Typography>
102
+ </View>
103
+
104
+ {/* Divider */}
105
+ <BaseCard.Divider />
106
+
107
+ {/* Footer Area */}
108
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
109
+ <Typography style={{ fontFamily: theme.fonts.body, fontWeight: '700', fontSize: 13, color: '#51332d' }}>
110
+ Ter, 24 Mar
111
+ </Typography>
112
+ <Typography style={{ fontFamily: theme.fonts.body, fontWeight: '700', fontSize: 13, color: '#51332d' }}>
113
+ 17:30
114
+ </Typography>
115
+ </View>
116
+ </View>
117
+ ),
118
+ },
119
+ };
120
+
121
+ export const SoftVariant: Story = {
122
+ args: {
123
+ variant: 'soft',
124
+ children: (
125
+ <View style={{ gap: 8 }}>
126
+ <Typography variant="h3">Je m'appelle So|</Typography>
127
+ <Typography variant="bodyMd" color={theme.colors.brown[500]}>
128
+ /ʒə m a.pɛl/ · "chamo-me"
129
+ </Typography>
130
+ </View>
131
+ ),
132
+ },
133
+ };
134
+
135
+ export const SoftVariantNoBorder: Story = {
136
+ args: {
137
+ variant: 'soft',
138
+ borderWidth: 0,
139
+ children: (
140
+ <View style={{ gap: 8 }}>
141
+ <Typography variant="h3">Bonjour !</Typography>
142
+ <Typography variant="bodyMd" color={theme.colors.brown[500]}>
143
+ /bɔ̃.ʒuʁ/ · "bom dia"
144
+ </Typography>
145
+ </View>
146
+ ),
147
+ },
148
+ };
@@ -0,0 +1,181 @@
1
+ import type { ReactNode } from 'react';
2
+ import { View, Pressable } from 'react-native';
3
+ import type { ViewStyle, DimensionValue } from 'react-native';
4
+ import { theme } from '../../../styles/theme';
5
+ import { useResponsive } from '../../../hooks/useResponsive';
6
+
7
+ /**
8
+ * Cartão base e fundacional do Design System.
9
+ * Fornece a estrutura comum de bordas, fundos, sombras e estados interativos
10
+ * (hover, selected, pressed, disabled) sobre a qual outros cartões (ex: TeacherCard) são construídos.
11
+ */
12
+ export interface BaseCardProps {
13
+ /** O conteúdo a ser renderizado dentro do cartão. */
14
+ children?: ReactNode;
15
+
16
+ /** Variante de estilo do cartão */
17
+ variant?: 'default' | 'soft';
18
+
19
+ // Container styling
20
+ backgroundColor?: string;
21
+ borderRadius?: number;
22
+ borderWidth?: number;
23
+ borderColor?: string;
24
+ borderStyle?: 'solid' | 'dashed';
25
+
26
+ // Padding & Layout
27
+ padding?: number;
28
+ paddingVertical?: number;
29
+ paddingHorizontal?: number;
30
+
31
+ // Sizing
32
+ width?: DimensionValue;
33
+ height?: DimensionValue;
34
+ minHeight?: DimensionValue;
35
+
36
+ // Shadow presets: 'none' | 'sm' | 'md' | 'lg'
37
+ shadow?: 'none' | 'sm' | 'md' | 'lg';
38
+ shadowColor?: string;
39
+
40
+ // Interactive / Pressable behavior
41
+ onPress?: () => void;
42
+ disabled?: boolean;
43
+
44
+ // Selection state
45
+ isSelected?: boolean;
46
+ selectedBorderColor?: string;
47
+ selectedBackgroundColor?: string;
48
+ selectedShadowColor?: string;
49
+
50
+ // Opacity & other styles
51
+ opacity?: number;
52
+ overflow?: 'visible' | 'hidden';
53
+ style?: ViewStyle | ViewStyle[];
54
+ }
55
+
56
+ export const BaseCard = ({
57
+ children,
58
+ variant = 'default',
59
+ backgroundColor,
60
+ borderRadius = 16,
61
+ borderWidth = 1.5,
62
+ borderColor,
63
+ borderStyle = 'solid',
64
+ padding,
65
+ paddingVertical = 12,
66
+ paddingHorizontal = 14,
67
+ width,
68
+ height,
69
+ minHeight,
70
+ shadow = 'none',
71
+ shadowColor,
72
+ onPress,
73
+ disabled = false,
74
+ isSelected = false,
75
+ selectedBorderColor = '#f2974b',
76
+ selectedBackgroundColor = '#fef6ee',
77
+ selectedShadowColor,
78
+ opacity = 1.0,
79
+ overflow = 'hidden',
80
+ style,
81
+ }: BaseCardProps) => {
82
+ const { scale } = useResponsive();
83
+
84
+ // Define fallback colors based on variant if not explicitly provided
85
+ const resolvedBackgroundColor = backgroundColor || (variant === 'soft' ? theme.colors.brown[50] : theme.colors.white);
86
+ const resolvedBorderColor = borderColor || theme.colors.brown[100]; // #efe2d6
87
+
88
+ // Resolve shadow styles based on theme
89
+ const getShadowStyle = () => {
90
+ if (shadow === 'none') return {};
91
+ const baseShadow = theme.shadows[shadow];
92
+ if (shadowColor && baseShadow) {
93
+ return {
94
+ ...baseShadow,
95
+ shadowColor,
96
+ };
97
+ }
98
+ return baseShadow || {};
99
+ };
100
+
101
+ // Determine active styles based on selection state
102
+ const activeBg = isSelected ? selectedBackgroundColor : resolvedBackgroundColor;
103
+ const activeBorderColor = isSelected ? selectedBorderColor : resolvedBorderColor;
104
+ const activeBorderWidth = isSelected ? 2 : borderWidth;
105
+ const activeShadow = isSelected
106
+ ? {
107
+ shadowColor: selectedShadowColor || '#f2974b',
108
+ shadowOffset: { width: 0, height: scale(4) },
109
+ shadowOpacity: 0.14,
110
+ shadowRadius: scale(14),
111
+ elevation: 4,
112
+ }
113
+ : getShadowStyle();
114
+
115
+ // Consolidate card styles with responsive scaling
116
+ const cardStyle: ViewStyle = {
117
+ backgroundColor: activeBg,
118
+ borderRadius: scale(borderRadius),
119
+ borderWidth: borderWidth === 0 && !isSelected ? 0 : activeBorderWidth,
120
+ borderColor: activeBorderColor,
121
+ borderStyle,
122
+ padding: padding !== undefined ? scale(padding) : undefined,
123
+ paddingVertical: padding === undefined ? scale(paddingVertical) : undefined,
124
+ paddingHorizontal: padding === undefined ? scale(paddingHorizontal) : undefined,
125
+ width: width !== undefined ? width : '100%',
126
+ height,
127
+ minHeight,
128
+ opacity,
129
+ overflow,
130
+ ...activeShadow,
131
+ };
132
+
133
+ if (onPress) {
134
+ return (
135
+ <Pressable
136
+ onPress={onPress}
137
+ disabled={disabled}
138
+ style={({ pressed }) => [
139
+ cardStyle,
140
+ pressed && !disabled && { opacity: 0.9 },
141
+ ...(Array.isArray(style) ? style : [style].filter(Boolean) as ViewStyle[]),
142
+ ]}
143
+ >
144
+ {children}
145
+ </Pressable>
146
+ );
147
+ }
148
+
149
+ return (
150
+ <View style={[cardStyle, style]}>
151
+ {children}
152
+ </View>
153
+ );
154
+ };
155
+
156
+ // Divider subcomponent
157
+ BaseCard.Divider = ({
158
+ color = '#efe2d6',
159
+ thickness = 1.5,
160
+ verticalMargin = 12,
161
+ style,
162
+ }: {
163
+ color?: string;
164
+ thickness?: number;
165
+ verticalMargin?: number;
166
+ style?: ViewStyle | ViewStyle[];
167
+ }) => {
168
+ return (
169
+ <View
170
+ style={[
171
+ {
172
+ height: thickness,
173
+ backgroundColor: color,
174
+ marginVertical: verticalMargin,
175
+ width: '100%',
176
+ },
177
+ style,
178
+ ]}
179
+ />
180
+ );
181
+ };