@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,111 @@
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 { useResponsive } from '../../../hooks/useResponsive';
6
+ import { Typography } from '../../Typography/Typography';
7
+
8
+ /**
9
+ * Cartão genérico para selecionar opções simples, usado frequentemente em formulários de Onboarding.
10
+ */
11
+ export interface SelectionCardProps {
12
+ /** Texto da opção. */
13
+ label: string;
14
+ /** Ícone ou elemento a renderizar do lado esquerdo. */
15
+ iconNode?: ReactNode;
16
+ /** Se a opção está selecionada. */
17
+ selected?: boolean;
18
+ /** Função disparada ao clicar. */
19
+ onPress?: () => void;
20
+ style?: ViewStyle | ViewStyle[];
21
+ }
22
+
23
+ export const SelectionCard = ({
24
+ label,
25
+ iconNode,
26
+ selected = false,
27
+ onPress,
28
+ style,
29
+ }: SelectionCardProps) => {
30
+ const { scale, scaleText } = useResponsive();
31
+
32
+ return (
33
+ <Pressable
34
+ style={[
35
+ styles.container,
36
+ selected ? styles.selectedContainer : styles.defaultContainer,
37
+ {
38
+ padding: scale(16),
39
+ borderRadius: scale(16),
40
+ },
41
+ style,
42
+ ]}
43
+ onPress={onPress}
44
+ >
45
+ {iconNode && (
46
+ <View style={[
47
+ styles.iconBox,
48
+ selected ? styles.selectedIconBox : styles.defaultIconBox,
49
+ {
50
+ width: scale(44),
51
+ height: scale(44),
52
+ borderRadius: scale(12),
53
+ marginRight: scale(16),
54
+ }
55
+ ]}>
56
+ {iconNode}
57
+ </View>
58
+ )}
59
+ <Typography style={[styles.label, { fontSize: scaleText(16) }]}>{label}</Typography>
60
+ </Pressable>
61
+ );
62
+ };
63
+
64
+ const styles = StyleSheet.create({
65
+ container: {
66
+ flexDirection: 'row',
67
+ alignItems: 'center',
68
+ padding: 16,
69
+ borderRadius: 16,
70
+ borderWidth: 2,
71
+ borderStyle: 'solid',
72
+ width: '100%',
73
+ },
74
+ defaultContainer: {
75
+ borderColor: theme.colors.brown[100],
76
+ backgroundColor: theme.colors.white,
77
+ },
78
+ selectedContainer: {
79
+ borderColor: theme.colors.primary,
80
+ backgroundColor: theme.colors.primaryLight,
81
+ ...Platform.select({
82
+ web: { boxShadow: `0px 4px 0px rgba(242, 151, 75, 0.3)` },
83
+ default: {
84
+ shadowColor: theme.colors.primary,
85
+ shadowOffset: { width: 0, height: 4 },
86
+ shadowOpacity: 0.3,
87
+ shadowRadius: 0,
88
+ elevation: 0,
89
+ },
90
+ }),
91
+ },
92
+ iconBox: {
93
+ width: 44,
94
+ height: 44,
95
+ borderRadius: 12,
96
+ alignItems: 'center',
97
+ justifyContent: 'center',
98
+ marginRight: 16,
99
+ },
100
+ defaultIconBox: {
101
+ backgroundColor: theme.colors.brown[50],
102
+ },
103
+ selectedIconBox: {
104
+ backgroundColor: theme.colors.primary,
105
+ },
106
+ label: {
107
+
108
+ fontSize: 16,
109
+ color: theme.colors.brown[800],
110
+ },
111
+ });
@@ -0,0 +1,126 @@
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 { TeacherCard } from './TeacherCard';
5
+
6
+ const meta = {
7
+ title: 'Components/Cards/TeacherCard',
8
+ component: TeacherCard,
9
+ parameters: {
10
+ layout: 'padded',
11
+ docs: {
12
+ description: {
13
+ component: 'O **TeacherCard** é um dos componentes mais complexos e flexíveis do Design System.\n\n### Exemplo de Uso\n```tsx\n<TeacherCard \n name="Maria Silva"\n avatarInitials="MS"\n rating={4.8}\n classesCount={120}\n durationText="45 min"\n dateText="Hoje, 24 Jun"\n timeText="14:00 - 14:45"\n countryFlagName="br"\n onPress={() => console.log("Clicou no cartão!")}\n renderActionButton={() => (\n <Button variant="primary" size="sm" onPress={() => {}}>\n Marcar Aula\n </Button>\n )}\n/>\n```',
14
+ }
15
+ }
16
+ },
17
+ tags: ['autodocs'],
18
+ argTypes: {
19
+ name: { control: 'text' },
20
+ avatarInitials: { control: 'text' },
21
+ avatarUrl: { control: 'text' },
22
+
23
+
24
+ rating: { control: 'number' },
25
+ classesCount: { control: 'number' },
26
+ location: { control: 'text' },
27
+ durationText: { control: 'text' },
28
+ countryFlagName: { control: 'text' },
29
+ dateText: { control: 'text' },
30
+ timeText: { control: 'text' },
31
+ isSelected: { control: 'boolean' },
32
+ },
33
+ decorators: [
34
+ (Story) => (
35
+ <View style={{ backgroundColor: '#f5f0eb' }}>
36
+ <Story />
37
+ </View>
38
+ ),
39
+ ],
40
+ } satisfies Meta<any>;
41
+
42
+ export default meta;
43
+ type Story = StoryObj<any>;
44
+
45
+ export const Default: Story = {
46
+ args: {
47
+ name: 'Prof. Camille',
48
+ avatarInitials: 'C',
49
+
50
+ rating: 4.9,
51
+ durationText: '30 min',
52
+ },
53
+ };
54
+
55
+ export const Selected: Story = {
56
+ name: "Selecionado (Interactive)",
57
+ args: {
58
+ name: 'Prof. Camille',
59
+ avatarInitials: 'C',
60
+
61
+ rating: 4.9,
62
+ durationText: '30 min',
63
+ isSelected: true,
64
+ },
65
+ };
66
+
67
+ export const Scheduled: Story = {
68
+ name: "Com Agendamento (Scheduled)",
69
+ args: {
70
+ name: 'Prof. Aïssa',
71
+ avatarInitials: 'A',
72
+
73
+ location: 'Senegal',
74
+
75
+ countryFlagName: 'Senegal',
76
+ dateText: 'Ter, 24 Mar',
77
+ timeText: '17:30 · 30min',
78
+ },
79
+ };
80
+
81
+ export const FullProfileInfo: Story = {
82
+ name: "Perfil Detalhado (Full Info)",
83
+ args: {
84
+ name: 'Prof. Aïssa',
85
+ avatarInitials: 'A',
86
+ rating: 4.9,
87
+ location: 'Dakar',
88
+ classesCount: 320,
89
+ durationText: '30 min',
90
+ },
91
+ };
92
+
93
+ export const Grid: Story = {
94
+ args: {
95
+ name: '',
96
+ },
97
+ render: () => (
98
+ <View style={{ gap: 16 }}>
99
+ <TeacherCard
100
+ name="Prof. Camille"
101
+ avatarInitials="C"
102
+ specialty="Conversação A1"
103
+ rating={4.9}
104
+ durationText="30 min"
105
+ />
106
+ <TeacherCard
107
+ name="Prof. Aïssa"
108
+ avatarInitials="A"
109
+ specialty="Conversação A1"
110
+ rating={4.9}
111
+ durationText="30 min"
112
+ isSelected
113
+ />
114
+ <TeacherCard
115
+ name="Prof. Aïssa"
116
+ avatarInitials="A"
117
+ specialty="Aula + teste"
118
+ location="Senegal"
119
+ level="B2"
120
+ countryFlagName="Senegal"
121
+ dateText="Ter, 24 Mar"
122
+ timeText="17:30 · 30min"
123
+ />
124
+ </View>
125
+ ),
126
+ };
@@ -0,0 +1,236 @@
1
+ import { View, StyleSheet } from 'react-native';
2
+ import type { ViewStyle } from 'react-native';
3
+ import { BaseCard } from '../BaseCard/BaseCard';
4
+ import { Flag } from '../../DataDisplay/Flag/Flag';
5
+ import { Avatar } from '../../DataDisplay/Avatar/Avatar';
6
+ import { Calendar, Clock } from 'lucide-react-native';
7
+ import { useResponsive } from '../../../hooks/useResponsive';
8
+ import { Typography } from '../../Typography/Typography';
9
+
10
+ /**
11
+ * Cartão de apresentação de um Professor ou Curso.
12
+ * Suporta imagens, bandeiras, avaliações, botões de ação e rodapé de agendamento.
13
+ */
14
+ export interface TeacherCardProps {
15
+ // Identidade
16
+ /** Nome principal a exibir no cartão. */
17
+ name: string;
18
+ /** Letras para o avatar, caso a imagem falhe (ex: 'AM'). */
19
+ avatarInitials: string;
20
+ /** URL remoto da fotografia do professor. */
21
+ avatarUrl?: string;
22
+ /** Especialidade do professor. */
23
+ specialty?: string;
24
+ /** Nível de ensino. */
25
+ level?: string;
26
+
27
+ // Metadados
28
+ /** Etiqueta superior opcional (ex: 'Super Professor'). */
29
+ badgeText?: string;
30
+ /** Nota de avaliação (ex: 4.8). */
31
+ rating?: number;
32
+ /** Número de aulas lecionadas. */
33
+ classesCount?: number;
34
+ /** Localização (ex: 'Lisboa'). */
35
+ location?: string;
36
+ /** Texto livre a exibir ao lado da bandeira. */
37
+ durationText?: string;
38
+ /** Nome ou URL da bandeira. Em produção deverá mapear para um SVG real. */
39
+ countryFlagName?: string;
40
+
41
+ // Footer (Agendamento)
42
+ /** Texto à esquerda no rodapé (ex: data). O rodapé só aparece se houver dateText ou timeText. */
43
+ dateText?: string;
44
+ /** Texto à direita no rodapé (ex: hora). */
45
+ timeText?: string;
46
+
47
+ // Interação
48
+ /** Destaca visualmente o cartão com uma borda laranja quando ativo. */
49
+ isSelected?: boolean;
50
+ /** Callback ao clicar em qualquer parte do cartão. */
51
+ onPress?: () => void;
52
+
53
+ // Ações Extra
54
+ /** Elemento customizado para renderizar o botão principal ("Marcar Aula" / "Ver Perfil"). */
55
+ renderActionButton?: () => React.ReactNode;
56
+
57
+ style?: ViewStyle | ViewStyle[];
58
+ }
59
+
60
+ export const TeacherCard = ({
61
+ name,
62
+ avatarInitials = 'P',
63
+ avatarUrl,
64
+ specialty,
65
+ level,
66
+ rating,
67
+ classesCount,
68
+ location,
69
+ durationText,
70
+ countryFlagName,
71
+ dateText,
72
+ timeText,
73
+ isSelected = false,
74
+ onPress,
75
+ style,
76
+ }: TeacherCardProps) => {
77
+ const { scale, scaleText } = useResponsive();
78
+
79
+ // Star character in text (as requested by spec: "★ em texto (não ícone)")
80
+ const renderDetailsLine = () => {
81
+ const parts: string[] = [];
82
+ if (specialty) parts.push(specialty);
83
+ if (location) parts.push(location);
84
+ if (rating !== undefined) parts.push(`★ ${rating}`);
85
+ if (classesCount !== undefined) parts.push(`${classesCount} aulas`);
86
+
87
+ return parts.join(' · ');
88
+ };
89
+
90
+ const hasFooter = !!(dateText || timeText);
91
+
92
+ return (
93
+ <BaseCard
94
+ onPress={onPress}
95
+ isSelected={isSelected}
96
+ paddingVertical={scale(12)}
97
+ paddingHorizontal={scale(14)}
98
+ style={[{ maxWidth: 400, width: '100%' }, style] as any}
99
+ >
100
+ <View style={styles.topRow}>
101
+ {/* Left Side: Avatar */}
102
+ <Avatar
103
+ variant="circle"
104
+ size="md"
105
+ src={avatarUrl}
106
+ initials={avatarInitials}
107
+ />
108
+
109
+ {/* Center: Details */}
110
+ <View style={[styles.infoCol, { marginLeft: scale(12) }]}>
111
+ <Typography style={[styles.nameText, { fontSize: scaleText(16), lineHeight: scaleText(18), marginBottom: scale(4) }]}>
112
+ {name}
113
+ </Typography>
114
+
115
+ <Typography style={[styles.detailsText, { fontSize: scaleText(12), lineHeight: scaleText(14) }]}>
116
+ {renderDetailsLine()}
117
+ </Typography>
118
+
119
+ {level && (
120
+ <Typography style={[styles.levelText, { fontSize: scaleText(12), lineHeight: scaleText(14), marginTop: scale(2) }]}>
121
+ {level}
122
+ </Typography>
123
+ )}
124
+ </View>
125
+
126
+ {/* Right Side: Optional Duration Badge or Flag */}
127
+ {countryFlagName ? (
128
+ <View style={[styles.flagContainer, { marginLeft: scale(12) }]}>
129
+ <Flag countryCode={countryFlagName} size={scale(28)} />
130
+ </View>
131
+ ) : (
132
+ durationText && (
133
+ <View style={[
134
+ styles.durationChip,
135
+ {
136
+ paddingVertical: scale(5),
137
+ paddingHorizontal: scale(10),
138
+ borderRadius: scale(9),
139
+ marginLeft: scale(12),
140
+ }
141
+ ]}>
142
+ <Typography style={[styles.durationText, { fontSize: scaleText(11) }]}>{durationText}</Typography>
143
+ </View>
144
+ )
145
+ )}
146
+ </View>
147
+
148
+ {/* Optional Divider & Scheduling Details */}
149
+ {hasFooter && (
150
+ <View>
151
+ <BaseCard.Divider verticalMargin={scale(12)} />
152
+
153
+ <View style={[styles.footerRow, { gap: scale(16) }]}>
154
+ {dateText && (
155
+ <View style={[styles.footerItem, { gap: scale(6) }]}>
156
+ <Calendar stroke="#cf7a30" strokeWidth={2.2} size={scale(15)} />
157
+ <Typography style={[styles.footerText, { fontSize: scaleText(13) }]}>{dateText}</Typography>
158
+ </View>
159
+ )}
160
+
161
+ {timeText && (
162
+ <View style={[styles.footerItem, { gap: scale(6) }]}>
163
+ <Clock stroke="#cf7a30" strokeWidth={2.2} size={scale(15)} />
164
+ <Typography style={[styles.footerText, { fontSize: scaleText(13) }]}>{timeText}</Typography>
165
+ </View>
166
+ )}
167
+ </View>
168
+ </View>
169
+ )}
170
+ </BaseCard>
171
+ );
172
+ };
173
+
174
+ const styles = StyleSheet.create({
175
+ topRow: {
176
+ flexDirection: 'row',
177
+ alignItems: 'center',
178
+ },
179
+ infoCol: {
180
+ flex: 1,
181
+ marginLeft: 12,
182
+ justifyContent: 'center',
183
+ },
184
+ nameText: {
185
+
186
+ fontSize: 16,
187
+ color: '#51332d',
188
+ lineHeight: 18,
189
+ marginBottom: 4,
190
+ },
191
+ detailsText: {
192
+
193
+ fontSize: 12,
194
+ color: '#9a8478',
195
+ lineHeight: 14,
196
+ },
197
+ levelText: {
198
+
199
+ fontSize: 12,
200
+ color: '#9a8478',
201
+ lineHeight: 14,
202
+ marginTop: 2,
203
+ },
204
+ flagContainer: {
205
+ justifyContent: 'center',
206
+ alignItems: 'center',
207
+ marginLeft: 12,
208
+ },
209
+ durationChip: {
210
+ backgroundColor: '#fef0e2',
211
+ paddingVertical: 5,
212
+ paddingHorizontal: 10,
213
+ borderRadius: 9,
214
+ marginLeft: 12,
215
+ },
216
+ durationText: {
217
+
218
+ fontSize: 11,
219
+ color: '#cf7a30',
220
+ },
221
+ footerRow: {
222
+ flexDirection: 'row',
223
+ alignItems: 'center',
224
+ gap: 16,
225
+ },
226
+ footerItem: {
227
+ flexDirection: 'row',
228
+ alignItems: 'center',
229
+ gap: 6,
230
+ },
231
+ footerText: {
232
+
233
+ fontSize: 13,
234
+ color: '#51332d',
235
+ },
236
+ });
@@ -0,0 +1,89 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { View } from 'react-native';
3
+ import { WeeklyProgressChart } from './WeeklyProgressChart';
4
+ import { theme } from '../../../styles/theme';
5
+
6
+ const meta = {
7
+ title: 'Components/Charts/WeeklyProgressChart',
8
+ component: WeeklyProgressChart,
9
+ parameters: {
10
+ layout: 'padded',
11
+ docs: {
12
+ description: {
13
+ component: `O **WeeklyProgressChart** exibe um gráfico de barras animado para mostrar a atividade do utilizador (ex: XP) ao longo de uma semana.
14
+ A barra do dia atual pode ser destacada usando \`isCurrentDay: true\`.
15
+ Possui uma propriedade \`size\` ('sm', 'md', 'lg') para ajustar facilmente a escala do gráfico.
16
+ `
17
+ }
18
+ }
19
+ },
20
+ tags: ['autodocs'],
21
+ decorators: [
22
+ (Story) => (
23
+ <View style={{ width: '100%', maxWidth: 400, alignSelf: 'center', backgroundColor: theme.colors.white, padding: 24, borderRadius: 20 }}>
24
+ <Story />
25
+ </View>
26
+ ),
27
+ ],
28
+ } satisfies Meta<any>;
29
+
30
+ export default meta;
31
+ type Story = StoryObj<any>;
32
+
33
+ const mockData = [
34
+ { dayLabel: 'S', value: 180 },
35
+ { dayLabel: 'T', value: 250 },
36
+ { dayLabel: 'Q', value: 120 },
37
+ { dayLabel: 'Q', value: 220 },
38
+ { dayLabel: 'S', value: 320, isCurrentDay: true },
39
+ { dayLabel: 'S', value: 60 },
40
+ { dayLabel: 'D', value: 60 },
41
+ ];
42
+
43
+ export const Default: Story = {
44
+ name: 'Medidor Padrão (Medium)',
45
+ args: {
46
+ title: 'ESTA SEMANA',
47
+ subtitle: '320 XP',
48
+ data: mockData,
49
+ size: 'md',
50
+ },
51
+ };
52
+
53
+ export const Small: Story = {
54
+ name: 'Tamanho Pequeno (Small)',
55
+ args: {
56
+ title: 'ESTA SEMANA',
57
+ subtitle: '320 XP',
58
+ data: mockData,
59
+ size: 'sm',
60
+ },
61
+ };
62
+
63
+ export const Large: Story = {
64
+ name: 'Tamanho Grande (Large)',
65
+ args: {
66
+ title: 'ESTA SEMANA',
67
+ subtitle: '320 XP',
68
+ data: mockData,
69
+ size: 'lg',
70
+ },
71
+ };
72
+
73
+ export const EmptyWeek: Story = {
74
+ name: 'Semana Vazia (Sem XP)',
75
+ args: {
76
+ title: 'ESTA SEMANA',
77
+ subtitle: '0 XP',
78
+ data: [
79
+ { dayLabel: 'S', value: 0 },
80
+ { dayLabel: 'T', value: 0 },
81
+ { dayLabel: 'Q', value: 0 },
82
+ { dayLabel: 'Q', value: 0 },
83
+ { dayLabel: 'S', value: 0, isCurrentDay: true },
84
+ { dayLabel: 'S', value: 0 },
85
+ { dayLabel: 'D', value: 0 },
86
+ ],
87
+ size: 'md',
88
+ },
89
+ };