@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.
- package/.github/workflows/publish.yml +30 -0
- package/.storybook/main.ts +17 -0
- package/.storybook/preview.tsx +65 -0
- package/eslint.config.js +22 -0
- package/index.html +13 -0
- package/package.json +55 -0
- package/public/favicon.svg +1 -0
- package/public/icons.svg +24 -0
- package/src/App.css +184 -0
- package/src/App.tsx +122 -0
- package/src/BordasSombras.mdx +343 -0
- package/src/Colors.mdx +393 -0
- package/src/GrelhaLayout.mdx +375 -0
- package/src/Icons.mdx +42 -0
- package/src/MovimentoBreakpoints.mdx +379 -0
- package/src/Spacing.mdx +231 -0
- package/src/Typography.mdx +321 -0
- package/src/assets/Mascots/vava/estaticos/vava-acenar.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-acertou.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-celebrar.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-com-autocolantes.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-dormir.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-errou.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-feliz.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-neutra.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-original.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-pensar.svg +3 -0
- package/src/assets/Mascots/vava/estaticos/vava-surpresa.svg +3 -0
- package/src/assets/Mascots/yaya/avatar/yaya-avatar-falar.svg +7 -0
- package/src/assets/Mascots/yaya/avatar/yaya-avatar-feliz.svg +7 -0
- package/src/assets/Mascots/yaya/avatar/yaya-avatar-transparente.svg +6 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-acertou.svg +13 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-celebrar.svg +13 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-dormir.svg +13 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-errou.svg +13 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-falar.svg +13 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-feliz.svg +13 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-neutra.svg +13 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-pensar.svg +13 -0
- package/src/assets/Mascots/yaya/estaticos/yaya-surpresa.svg +13 -0
- package/src/assets/hero.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/vite.svg +1 -0
- package/src/components/Buttons/BackButton/BackButton.stories.tsx +86 -0
- package/src/components/Buttons/BackButton/BackButton.tsx +107 -0
- package/src/components/Buttons/FabButton/FabButton.stories.tsx +74 -0
- package/src/components/Buttons/FabButton/FabButton.tsx +161 -0
- package/src/components/Buttons/IconButton/IconButton.stories.tsx +99 -0
- package/src/components/Buttons/IconButton/IconButton.tsx +189 -0
- package/src/components/Buttons/JourneyButton/JourneyButton.stories.tsx +80 -0
- package/src/components/Buttons/JourneyButton/JourneyButton.tsx +194 -0
- package/src/components/Buttons/MainButton/MainButton.stories.tsx +144 -0
- package/src/components/Buttons/MainButton/MainButton.tsx +258 -0
- package/src/components/Buttons/WordChip/WordChip.stories.tsx +84 -0
- package/src/components/Buttons/WordChip/WordChip.tsx +157 -0
- package/src/components/Buttons/WordChip/index.ts +1 -0
- package/src/components/Cards/AnswerOptionCard/AnswerOptionCard.stories.tsx +181 -0
- package/src/components/Cards/AnswerOptionCard/AnswerOptionCard.tsx +201 -0
- package/src/components/Cards/BaseCard/BaseCard.stories.tsx +148 -0
- package/src/components/Cards/BaseCard/BaseCard.tsx +181 -0
- package/src/components/Cards/BoardingPassCard/BoardingPassCard.stories.tsx +205 -0
- package/src/components/Cards/BoardingPassCard/BoardingPassCard.tsx +384 -0
- package/src/components/Cards/BoardingPassCard/FlightIcon.tsx +21 -0
- package/src/components/Cards/BoardingPassCard/FlightIcon.web.tsx +18 -0
- package/src/components/Cards/CountryCard/CountryCard.stories.tsx +97 -0
- package/src/components/Cards/CountryCard/CountryCard.tsx +185 -0
- package/src/components/Cards/LevelProgressCard/LevelProgressCard.stories.tsx +61 -0
- package/src/components/Cards/LevelProgressCard/LevelProgressCard.tsx +143 -0
- package/src/components/Cards/ModuleCard/ModuleCard.stories.tsx +108 -0
- package/src/components/Cards/ModuleCard/ModuleCard.tsx +304 -0
- package/src/components/Cards/ModuleCompletionCard/ModuleCompletionCard.stories.tsx +53 -0
- package/src/components/Cards/ModuleCompletionCard/ModuleCompletionCard.tsx +167 -0
- package/src/components/Cards/Passport/PassportCover.tsx +114 -0
- package/src/components/Cards/Passport/PassportIDPage.tsx +217 -0
- package/src/components/Cards/Passport/PassportStampPage.tsx +253 -0
- package/src/components/Cards/Passport/PassportViewer.stories.tsx +95 -0
- package/src/components/Cards/Passport/PassportViewer.tsx +328 -0
- package/src/components/Cards/PlanCard/PlanCard.stories.tsx +159 -0
- package/src/components/Cards/PlanCard/PlanCard.tsx +317 -0
- package/src/components/Cards/PricingCard/PricingCard.stories.tsx +83 -0
- package/src/components/Cards/PricingCard/PricingCard.tsx +144 -0
- package/src/components/Cards/SelectionCard/SelectionCard.stories.tsx +77 -0
- package/src/components/Cards/SelectionCard/SelectionCard.tsx +111 -0
- package/src/components/Cards/TeacherCard/TeacherCard.stories.tsx +126 -0
- package/src/components/Cards/TeacherCard/TeacherCard.tsx +236 -0
- package/src/components/Charts/WeeklyProgressChart/WeeklyProgressChart.stories.tsx +89 -0
- package/src/components/Charts/WeeklyProgressChart/WeeklyProgressChart.tsx +179 -0
- package/src/components/Charts/WeeklyProgressChart/index.ts +1 -0
- package/src/components/DataDisplay/Avatar/Avatar.stories.tsx +85 -0
- package/src/components/DataDisplay/Avatar/Avatar.tsx +118 -0
- package/src/components/DataDisplay/Avatar/index.ts +1 -0
- package/src/components/DataDisplay/Badge/Badge.stories.tsx +62 -0
- package/src/components/DataDisplay/Badge/Badge.tsx +146 -0
- package/src/components/DataDisplay/Flag/Flag.stories.tsx +98 -0
- package/src/components/DataDisplay/Flag/Flag.tsx +347 -0
- package/src/components/DataDisplay/Flag/Flag.web.tsx +334 -0
- package/src/components/DataDisplay/InstructionBubble/InstructionBubble.stories.tsx +80 -0
- package/src/components/DataDisplay/InstructionBubble/InstructionBubble.tsx +86 -0
- package/src/components/DataDisplay/LevelBadge/LevelBadge.stories.tsx +99 -0
- package/src/components/DataDisplay/LevelBadge/LevelBadge.tsx +118 -0
- package/src/components/DataDisplay/ProgressBar/ProgressBar.stories.tsx +95 -0
- package/src/components/DataDisplay/ProgressBar/ProgressBar.tsx +117 -0
- package/src/components/DataDisplay/StatBadge/StatBadge.stories.tsx +82 -0
- package/src/components/DataDisplay/StatBadge/StatBadge.tsx +135 -0
- package/src/components/DataDisplay/StatBadge/index.ts +1 -0
- package/src/components/DataDisplay/Tag/Tag.stories.tsx +63 -0
- package/src/components/DataDisplay/Tag/Tag.tsx +148 -0
- package/src/components/Feedback/FeedbackBottomSheet/FeedbackBottomSheet.stories.tsx +74 -0
- package/src/components/Feedback/FeedbackBottomSheet/FeedbackBottomSheet.tsx +200 -0
- package/src/components/Forms/Checkbox/Checkbox.stories.tsx +60 -0
- package/src/components/Forms/Checkbox/Checkbox.tsx +127 -0
- package/src/components/Forms/Checkbox/index.ts +1 -0
- package/src/components/Forms/DaySelector/DaySelector.stories.tsx +62 -0
- package/src/components/Forms/DaySelector/DaySelector.tsx +133 -0
- package/src/components/Forms/Radio/Radio.stories.tsx +61 -0
- package/src/components/Forms/Radio/Radio.tsx +141 -0
- package/src/components/Forms/Radio/index.ts +1 -0
- package/src/components/Forms/SegmentedToggle/SegmentedToggle.stories.tsx +67 -0
- package/src/components/Forms/SegmentedToggle/SegmentedToggle.tsx +150 -0
- package/src/components/Forms/SegmentedToggle/index.ts +1 -0
- package/src/components/Forms/Select/Select.stories.tsx +59 -0
- package/src/components/Forms/Select/Select.tsx +255 -0
- package/src/components/Forms/Select/index.ts +1 -0
- package/src/components/Forms/Switch/Switch.stories.tsx +57 -0
- package/src/components/Forms/Switch/Switch.tsx +120 -0
- package/src/components/Forms/Switch/index.ts +1 -0
- package/src/components/Forms/TimeSlot/TimeSlot.stories.tsx +62 -0
- package/src/components/Forms/TimeSlot/TimeSlot.tsx +116 -0
- package/src/components/Headers/CountryHeader/CountryHeader.stories.tsx +114 -0
- package/src/components/Headers/CountryHeader/CountryHeader.tsx +147 -0
- package/src/components/Headers/ProfileHeader/ProfileHeader.stories.tsx +141 -0
- package/src/components/Headers/ProfileHeader/ProfileHeader.tsx +233 -0
- package/src/components/Inputs/FillInTheBlank/FillInTheBlank.stories.tsx +333 -0
- package/src/components/Inputs/FillInTheBlank/FillInTheBlank.tsx +287 -0
- package/src/components/Inputs/FillInTheBlank/index.ts +1 -0
- package/src/components/Inputs/TextInput/TextInput.stories.tsx +103 -0
- package/src/components/Inputs/TextInput/TextInput.tsx +187 -0
- package/src/components/Mascots/Vava/Vava.stories.tsx +114 -0
- package/src/components/Mascots/Vava/Vava.tsx +276 -0
- package/src/components/Mascots/Yaya/Yaya.stories.tsx +98 -0
- package/src/components/Mascots/Yaya/Yaya.tsx +233 -0
- package/src/components/Navigation/BottomTabBar/BottomTabBar.stories.tsx +68 -0
- package/src/components/Navigation/BottomTabBar/BottomTabBar.tsx +85 -0
- package/src/components/Navigation/BottomTabBar/BottomTabBarItem.tsx +68 -0
- package/src/components/Navigation/BottomTabBar/index.ts +2 -0
- package/src/components/Navigation/PaginationDots/PaginationDots.stories.tsx +62 -0
- package/src/components/Navigation/PaginationDots/PaginationDots.tsx +81 -0
- package/src/components/Navigation/PaginationDots/index.ts +1 -0
- package/src/components/Navigation/ProgressMap/ProgressMap.stories.tsx +116 -0
- package/src/components/Navigation/ProgressMap/ProgressMap.tsx +827 -0
- package/src/components/Navigation/WorldProgressMap/WorldProgressMap.stories.tsx +169 -0
- package/src/components/Navigation/WorldProgressMap/WorldProgressMap.tsx +790 -0
- package/src/components/Typography/Typography.stories.tsx +127 -0
- package/src/components/Typography/Typography.tsx +53 -0
- package/src/hooks/useResponsive.ts +41 -0
- package/src/hooks/useTheme.ts +32 -0
- package/src/index.css +355 -0
- package/src/index.ts +28 -0
- package/src/main.tsx +10 -0
- package/src/mocks/codegenNativeComponent.ts +5 -0
- package/src/styles/countryThemes.ts +79 -0
- package/src/styles/theme.ts +297 -0
- package/src/utils/iconMap.ts +23 -0
- package/tsconfig.app.json +25 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +24 -0
- package/vite.config.ts +51 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
|
|
2
|
+
// eslint-disable-next-line storybook/no-renderer-packages
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { TextInput } from './TextInput';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Components/Inputs/TextInput',
|
|
9
|
+
component: TextInput,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
argTypes: {
|
|
15
|
+
label: {
|
|
16
|
+
control: 'text',
|
|
17
|
+
},
|
|
18
|
+
error: {
|
|
19
|
+
control: 'text',
|
|
20
|
+
},
|
|
21
|
+
helperText: {
|
|
22
|
+
control: 'text',
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
control: 'radio',
|
|
26
|
+
options: ['sm', 'md', 'lg'],
|
|
27
|
+
},
|
|
28
|
+
disabled: {
|
|
29
|
+
control: 'boolean',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
decorators: [
|
|
33
|
+
(Story) => (
|
|
34
|
+
<View style={{ padding: 24, width: '100%', minWidth: 320, alignItems: 'center' }}>
|
|
35
|
+
<Story />
|
|
36
|
+
</View>
|
|
37
|
+
),
|
|
38
|
+
],
|
|
39
|
+
} satisfies Meta<any>;
|
|
40
|
+
|
|
41
|
+
export default meta;
|
|
42
|
+
type Story = StoryObj<any>;
|
|
43
|
+
|
|
44
|
+
// Default State
|
|
45
|
+
export const Default: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
label: 'Nome',
|
|
48
|
+
placeholder: 'Ex: Sofia',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Filled State
|
|
53
|
+
export const Filled: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
label: 'Email',
|
|
56
|
+
value: 'sofia@xetwa.com',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Error State
|
|
61
|
+
export const Error: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
label: 'Email',
|
|
64
|
+
value: 'sofia@',
|
|
65
|
+
error: 'Endereço de email inválido',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Helper Text
|
|
70
|
+
export const WithHelperText: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
label: 'Password',
|
|
73
|
+
placeholder: 'A tua senha secreta',
|
|
74
|
+
secureTextEntry: true,
|
|
75
|
+
helperText: 'A password deve ter pelo menos 8 caracteres.',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Disabled
|
|
80
|
+
export const Disabled: Story = {
|
|
81
|
+
args: {
|
|
82
|
+
label: 'Nome de Utilizador',
|
|
83
|
+
value: 'sofia_xetwa',
|
|
84
|
+
disabled: true,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Sizes
|
|
89
|
+
export const Small: Story = {
|
|
90
|
+
args: {
|
|
91
|
+
label: 'Pesquisa',
|
|
92
|
+
placeholder: 'O que procuras?',
|
|
93
|
+
size: 'sm',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const Large: Story = {
|
|
98
|
+
args: {
|
|
99
|
+
label: 'Código de Convite',
|
|
100
|
+
placeholder: 'XET-1234',
|
|
101
|
+
size: 'lg',
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useState, forwardRef } from 'react';
|
|
2
|
+
import { View, TextInput as RNTextInput, StyleSheet, Platform } from 'react-native';
|
|
3
|
+
import type {
|
|
4
|
+
ViewStyle,
|
|
5
|
+
TextStyle,
|
|
6
|
+
TextInputProps as RNTextInputProps,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import { theme } from '../../../styles/theme';
|
|
9
|
+
import { useResponsive } from '../../../hooks/useResponsive';
|
|
10
|
+
import { Typography } from '../../Typography/Typography';
|
|
11
|
+
|
|
12
|
+
export interface TextInputProps extends Omit<RNTextInputProps, 'style'> {
|
|
13
|
+
label?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
helperText?: string;
|
|
16
|
+
size?: 'sm' | 'md' | 'lg';
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
style?: ViewStyle | ViewStyle[];
|
|
19
|
+
inputStyle?: TextStyle | TextStyle[];
|
|
20
|
+
containerStyle?: ViewStyle | ViewStyle[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TextInput = forwardRef<RNTextInput, TextInputProps>(
|
|
24
|
+
(
|
|
25
|
+
{
|
|
26
|
+
label,
|
|
27
|
+
error,
|
|
28
|
+
helperText,
|
|
29
|
+
size = 'md',
|
|
30
|
+
disabled = false,
|
|
31
|
+
style,
|
|
32
|
+
inputStyle,
|
|
33
|
+
containerStyle,
|
|
34
|
+
onFocus,
|
|
35
|
+
onBlur,
|
|
36
|
+
...props
|
|
37
|
+
},
|
|
38
|
+
ref
|
|
39
|
+
) => {
|
|
40
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
41
|
+
const { scale, scaleText } = useResponsive();
|
|
42
|
+
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
const handleFocus = (e: any) => {
|
|
45
|
+
setIsFocused(true);
|
|
46
|
+
if (onFocus) onFocus(e);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
const handleBlur = (e: any) => {
|
|
51
|
+
setIsFocused(false);
|
|
52
|
+
if (onBlur) onBlur(e);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Calculate dynamic styles
|
|
56
|
+
const hasError = !!error;
|
|
57
|
+
|
|
58
|
+
const getWrapperStyles = (): ViewStyle => {
|
|
59
|
+
let borderColor = theme.colors.brown[100];
|
|
60
|
+
let backgroundColor = theme.colors.white;
|
|
61
|
+
let shadowStyle: ViewStyle = {};
|
|
62
|
+
|
|
63
|
+
if (disabled) {
|
|
64
|
+
backgroundColor = theme.colors.brown[50];
|
|
65
|
+
} else if (hasError) {
|
|
66
|
+
borderColor = theme.colors.error;
|
|
67
|
+
backgroundColor = '#fff9f8';
|
|
68
|
+
if (Platform.OS === 'web') {
|
|
69
|
+
shadowStyle = { boxShadow: `0 0 0 ${scale(3)}px rgba(207, 91, 72, 0.18)` } as unknown as ViewStyle;
|
|
70
|
+
} else {
|
|
71
|
+
shadowStyle = { ...theme.shadows.sm, shadowColor: theme.colors.error };
|
|
72
|
+
}
|
|
73
|
+
} else if (isFocused) {
|
|
74
|
+
borderColor = theme.colors.primary;
|
|
75
|
+
if (Platform.OS === 'web') {
|
|
76
|
+
shadowStyle = { boxShadow: `0 0 0 ${scale(3)}px rgba(242, 151, 75, 0.22)` } as unknown as ViewStyle;
|
|
77
|
+
} else {
|
|
78
|
+
shadowStyle = theme.shadows.focus;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sizeHeight = {
|
|
83
|
+
sm: scale(42),
|
|
84
|
+
md: scale(50),
|
|
85
|
+
lg: scale(58),
|
|
86
|
+
}[size];
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
borderWidth: 2,
|
|
90
|
+
borderColor,
|
|
91
|
+
backgroundColor,
|
|
92
|
+
height: sizeHeight,
|
|
93
|
+
...shadowStyle,
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
...(disabled && Platform.OS === 'web' ? { cursor: 'not-allowed' as any } : {}),
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const messageText = error || helperText;
|
|
100
|
+
const messageColor = error ? theme.colors.error : theme.colors.brown[500];
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<View style={[styles.root, style]}>
|
|
104
|
+
{label && (
|
|
105
|
+
<Typography style={[
|
|
106
|
+
styles.label,
|
|
107
|
+
{ fontSize: scaleText(12), marginBottom: scale(6) },
|
|
108
|
+
disabled && { color: theme.colors.brown[400] }
|
|
109
|
+
]}>
|
|
110
|
+
{label}
|
|
111
|
+
</Typography>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
<View style={[
|
|
115
|
+
styles.inputWrapper,
|
|
116
|
+
getWrapperStyles(),
|
|
117
|
+
{
|
|
118
|
+
borderRadius: scale(14),
|
|
119
|
+
paddingHorizontal: scale(16),
|
|
120
|
+
},
|
|
121
|
+
containerStyle
|
|
122
|
+
]}>
|
|
123
|
+
<RNTextInput
|
|
124
|
+
ref={ref}
|
|
125
|
+
editable={!disabled}
|
|
126
|
+
onFocus={handleFocus}
|
|
127
|
+
onBlur={handleBlur}
|
|
128
|
+
placeholderTextColor={theme.colors.brown[300]}
|
|
129
|
+
style={[
|
|
130
|
+
styles.input,
|
|
131
|
+
{
|
|
132
|
+
fontSize: scaleText(15),
|
|
133
|
+
},
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
135
|
+
Platform.OS === 'web' && ({ outlineStyle: 'none' } as any),
|
|
136
|
+
disabled && { color: theme.colors.brown[400] },
|
|
137
|
+
inputStyle,
|
|
138
|
+
]}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
</View>
|
|
142
|
+
|
|
143
|
+
{messageText && (
|
|
144
|
+
<Typography style={[styles.message, { color: messageColor, fontSize: scaleText(12), marginTop: scale(5) }]}>
|
|
145
|
+
{messageText}
|
|
146
|
+
</Typography>
|
|
147
|
+
)}
|
|
148
|
+
</View>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
TextInput.displayName = 'TextInput';
|
|
154
|
+
|
|
155
|
+
const styles = StyleSheet.create({
|
|
156
|
+
root: {
|
|
157
|
+
width: '100%',
|
|
158
|
+
maxWidth: 480, // Default constraint for desktop
|
|
159
|
+
},
|
|
160
|
+
label: {
|
|
161
|
+
|
|
162
|
+
fontSize: 12,
|
|
163
|
+
color: theme.colors.brown[800],
|
|
164
|
+
marginBottom: 6,
|
|
165
|
+
},
|
|
166
|
+
inputWrapper: {
|
|
167
|
+
borderRadius: 14,
|
|
168
|
+
paddingHorizontal: 16,
|
|
169
|
+
justifyContent: 'center',
|
|
170
|
+
flexDirection: 'row',
|
|
171
|
+
alignItems: 'center',
|
|
172
|
+
overflow: 'hidden',
|
|
173
|
+
},
|
|
174
|
+
input: {
|
|
175
|
+
flex: 1,
|
|
176
|
+
height: '100%',
|
|
177
|
+
|
|
178
|
+
fontSize: 15,
|
|
179
|
+
color: theme.colors.brown[800],
|
|
180
|
+
padding: 0, // Reset RN default paddings
|
|
181
|
+
},
|
|
182
|
+
message: {
|
|
183
|
+
|
|
184
|
+
fontSize: 12,
|
|
185
|
+
marginTop: 5,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Vava } from './Vava';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Mascots/Vava',
|
|
6
|
+
component: Vava,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
},
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
argTypes: {
|
|
12
|
+
variant: {
|
|
13
|
+
control: 'select',
|
|
14
|
+
options: [
|
|
15
|
+
'original',
|
|
16
|
+
'com-autocolantes',
|
|
17
|
+
'acenar',
|
|
18
|
+
'feliz',
|
|
19
|
+
'neutra',
|
|
20
|
+
'surpresa',
|
|
21
|
+
'pensar',
|
|
22
|
+
'errou',
|
|
23
|
+
'acertou',
|
|
24
|
+
'dormir',
|
|
25
|
+
'celebrar',
|
|
26
|
+
],
|
|
27
|
+
description: 'A expressão ou estado da mascote Vava.',
|
|
28
|
+
},
|
|
29
|
+
size: {
|
|
30
|
+
control: { type: 'range', min: 50, max: 500, step: 10 },
|
|
31
|
+
description: 'O tamanho do componente (largura e altura em pixels).',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
} satisfies Meta<any>;
|
|
35
|
+
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<any>;
|
|
38
|
+
|
|
39
|
+
export const Default: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
variant: 'original',
|
|
42
|
+
size: 200,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const ComAutocolantes: Story = {
|
|
47
|
+
args: {
|
|
48
|
+
variant: 'com-autocolantes',
|
|
49
|
+
size: 200,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Acenar: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
variant: 'acenar',
|
|
56
|
+
size: 200,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const Feliz: Story = {
|
|
61
|
+
args: {
|
|
62
|
+
variant: 'feliz',
|
|
63
|
+
size: 200,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const Neutra: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
variant: 'neutra',
|
|
70
|
+
size: 200,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const Surpresa: Story = {
|
|
75
|
+
args: {
|
|
76
|
+
variant: 'surpresa',
|
|
77
|
+
size: 200,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const Pensar: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
variant: 'pensar',
|
|
84
|
+
size: 200,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const Errou: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
variant: 'errou',
|
|
91
|
+
size: 200,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const Acertou: Story = {
|
|
96
|
+
args: {
|
|
97
|
+
variant: 'acertou',
|
|
98
|
+
size: 200,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const Dormir: Story = {
|
|
103
|
+
args: {
|
|
104
|
+
variant: 'dormir',
|
|
105
|
+
size: 200,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const Celebrar: Story = {
|
|
110
|
+
args: {
|
|
111
|
+
variant: 'celebrar',
|
|
112
|
+
size: 200,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Svg, { Path, Rect, Circle, Ellipse, G, Text as SvgText } from 'react-native-svg';
|
|
3
|
+
|
|
4
|
+
export type VavaVariant =
|
|
5
|
+
| 'original'
|
|
6
|
+
| 'com-autocolantes'
|
|
7
|
+
| 'acenar'
|
|
8
|
+
| 'feliz'
|
|
9
|
+
| 'neutra'
|
|
10
|
+
| 'surpresa'
|
|
11
|
+
| 'pensar'
|
|
12
|
+
| 'errou'
|
|
13
|
+
| 'acertou'
|
|
14
|
+
| 'dormir'
|
|
15
|
+
| 'celebrar';
|
|
16
|
+
|
|
17
|
+
export interface VavaProps {
|
|
18
|
+
/**
|
|
19
|
+
* A expressão ou estado da mascote Vava.
|
|
20
|
+
* @default 'original'
|
|
21
|
+
*/
|
|
22
|
+
variant?: VavaVariant;
|
|
23
|
+
/**
|
|
24
|
+
* O tamanho do componente (largura e altura em pixels).
|
|
25
|
+
* @default 200
|
|
26
|
+
*/
|
|
27
|
+
size?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Se verdadeiro, renderiza apenas o rosto/cabeça da mascote em formato Avatar.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
avatarOnly?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Componente Vava - A mascote oficial do Xetlingo.
|
|
37
|
+
*
|
|
38
|
+
* Este componente renderiza de forma estática, vetorial e performática as diferentes expressões da Vava.
|
|
39
|
+
* É compatível tanto com React Native (iOS/Android) quanto com React Native Web.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* // Vava neutra com tamanho padrão (200px)
|
|
44
|
+
* <Vava variant="neutra" />
|
|
45
|
+
*
|
|
46
|
+
* // Vava celebrando com 150px de tamanho
|
|
47
|
+
* <Vava variant="celebrar" size={150} />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export const Vava: React.FC<VavaProps> = ({ variant = 'original', size = 200, avatarOnly = false }) => {
|
|
51
|
+
// Normalizar variantes equivalentes nos ficheiros de assets
|
|
52
|
+
const normalizedVariant = variant === 'com-autocolantes' ? 'original' : variant === 'acenar' ? 'feliz' : variant;
|
|
53
|
+
|
|
54
|
+
const renderFace = () => {
|
|
55
|
+
switch (normalizedVariant) {
|
|
56
|
+
case 'feliz':
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
{/* Olhos felizes fechados */}
|
|
60
|
+
<Path d="M73 95 Q80 86 87 95" stroke="#15100d" strokeWidth={3.2} fill="none" strokeLinecap="round" />
|
|
61
|
+
<Path d="M113 95 Q120 86 127 95" stroke="#15100d" strokeWidth={3.2} fill="none" strokeLinecap="round" />
|
|
62
|
+
{/* Boca feliz aberta */}
|
|
63
|
+
<Path d="M90 100 Q100 111 110 100 Z" fill="#fbe6cf" />
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
case 'neutra':
|
|
68
|
+
return (
|
|
69
|
+
<>
|
|
70
|
+
{/* Olhos abertos com pupilas e brilho */}
|
|
71
|
+
<Ellipse cx={80} cy={92} rx={8} ry={10} fill="#fff" />
|
|
72
|
+
<Ellipse cx={120} cy={92} rx={8} ry={10} fill="#fff" />
|
|
73
|
+
<Circle cx={81} cy={94} r={4.2} fill="#15100d" />
|
|
74
|
+
<Circle cx={121} cy={94} r={4.2} fill="#15100d" />
|
|
75
|
+
<Circle cx={79} cy={90} r={1.6} fill="#fff" />
|
|
76
|
+
<Circle cx={119} cy={90} r={1.6} fill="#fff" />
|
|
77
|
+
{/* Boca sorridente fechada */}
|
|
78
|
+
<Path d="M92 102 Q100 109 108 102" fill="none" stroke="#fbe6cf" strokeWidth={3} strokeLinecap="round" />
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
case 'surpresa':
|
|
83
|
+
return (
|
|
84
|
+
<>
|
|
85
|
+
{/* Olhos bem abertos e redondos */}
|
|
86
|
+
<Circle cx={80} cy={92} r={10} fill="#fff" />
|
|
87
|
+
<Circle cx={120} cy={92} r={10} fill="#fff" />
|
|
88
|
+
<Circle cx={80} cy={93} r={5} fill="#15100d" />
|
|
89
|
+
<Circle cx={120} cy={93} r={5} fill="#15100d" />
|
|
90
|
+
<Circle cx={78} cy={90} r={2} fill="#fff" />
|
|
91
|
+
<Circle cx={118} cy={90} r={2} fill="#fff" />
|
|
92
|
+
{/* Boca redonda aberta de surpresa */}
|
|
93
|
+
<Ellipse cx={100} cy={105} rx={6} ry={7} fill="#fbe6cf" />
|
|
94
|
+
</>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
case 'pensar':
|
|
98
|
+
return (
|
|
99
|
+
<>
|
|
100
|
+
{/* Olhos a olhar para cima e para a direita */}
|
|
101
|
+
<Ellipse cx={80} cy={92} rx={8} ry={10} fill="#fff" />
|
|
102
|
+
<Ellipse cx={120} cy={92} rx={8} ry={10} fill="#fff" />
|
|
103
|
+
<Circle cx={82} cy={88} r={4} fill="#15100d" />
|
|
104
|
+
<Circle cx={122} cy={88} r={4} fill="#15100d" />
|
|
105
|
+
{/* Boca de dúvida (traço reto ligeiramente inclinado) */}
|
|
106
|
+
<Path d="M93 105 L105 103" stroke="#fbe6cf" strokeWidth={3} fill="none" strokeLinecap="round" />
|
|
107
|
+
{/* Ponto de interrogação flutuante */}
|
|
108
|
+
<SvgText x="50" y="50" fontFamily="sans-serif" fontWeight="700" fontSize={30} fill="#f2974b">?</SvgText>
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
case 'errou':
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
{/* Sobrancelhas de preocupação */}
|
|
116
|
+
<Path d="M70 84 l11 4" stroke="#15100d" strokeWidth={2.4} strokeLinecap="round" />
|
|
117
|
+
<Path d="M130 84 l-11 4" stroke="#15100d" strokeWidth={2.4} strokeLinecap="round" />
|
|
118
|
+
{/* Olhos tensos a olhar para baixo */}
|
|
119
|
+
<Ellipse cx={80} cy={95} rx={7.5} ry={9} fill="#fff" />
|
|
120
|
+
<Ellipse cx={120} cy={95} rx={7.5} ry={9} fill="#fff" />
|
|
121
|
+
<Circle cx={81} cy={97} r={4} fill="#15100d" />
|
|
122
|
+
<Circle cx={121} cy={97} r={4} fill="#15100d" />
|
|
123
|
+
{/* Boca triste para baixo */}
|
|
124
|
+
<Path d="M94 107 Q100 102 106 107" stroke="#fbe6cf" strokeWidth={3} fill="none" strokeLinecap="round" />
|
|
125
|
+
{/* Gota de suor fria na bochecha */}
|
|
126
|
+
<Path d="M134 84 q3.6 5 0 9 q-3.6 -4 0 -9 Z" fill="#7ab8e0" />
|
|
127
|
+
{/* Ícone flutuante de Erro (Círculo Vermelho com X) */}
|
|
128
|
+
{!avatarOnly && (
|
|
129
|
+
<G transform="translate(150, 58)">
|
|
130
|
+
<Circle r={16} fill="#cf5b48" />
|
|
131
|
+
<Path d="M-6 -6 l12 12 M6 -6 l-12 12" stroke="#fff" strokeWidth={3.5} strokeLinecap="round" />
|
|
132
|
+
</G>
|
|
133
|
+
)}
|
|
134
|
+
</>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
case 'acertou':
|
|
138
|
+
return (
|
|
139
|
+
<>
|
|
140
|
+
{/* Olhos felizes fechados */}
|
|
141
|
+
<Path d="M73 95 Q80 86 87 95" stroke="#15100d" strokeWidth={3.2} fill="none" strokeLinecap="round" />
|
|
142
|
+
<Path d="M113 95 Q120 86 127 95" stroke="#15100d" strokeWidth={3.2} fill="none" strokeLinecap="round" />
|
|
143
|
+
{/* Boca feliz aberta */}
|
|
144
|
+
<Path d="M90 100 Q100 112 110 100 Z" fill="#fbe6cf" />
|
|
145
|
+
{/* Ícone flutuante de Sucesso (Círculo Verde com Checkmark) */}
|
|
146
|
+
{!avatarOnly && (
|
|
147
|
+
<G transform="translate(150, 58)">
|
|
148
|
+
<Circle r={16} fill="#16915a" />
|
|
149
|
+
<Path d="M-7 0 l4 5 9 -10" stroke="#fff" strokeWidth={3.5} fill="none" strokeLinecap="round" strokeLinejoin="round" />
|
|
150
|
+
</G>
|
|
151
|
+
)}
|
|
152
|
+
</>
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
case 'dormir':
|
|
156
|
+
return (
|
|
157
|
+
<>
|
|
158
|
+
{/* Olhos fechados em curva suave de sono */}
|
|
159
|
+
<Path d="M72 93 Q80 99 88 93" stroke="#15100d" strokeWidth={3} fill="none" strokeLinecap="round" />
|
|
160
|
+
<Path d="M112 93 Q120 99 128 93" stroke="#15100d" strokeWidth={3} fill="none" strokeLinecap="round" />
|
|
161
|
+
{/* Boca em pequeno círculo aberto de sono */}
|
|
162
|
+
<Circle cx={100} cy={105} r={3} fill="none" stroke="#fbe6cf" strokeWidth={2.5} />
|
|
163
|
+
{/* Letras de sono flutuantes (z Z) */}
|
|
164
|
+
<SvgText x="50" y="50" fontFamily="sans-serif" fontWeight="700" fontSize={15} fill="#9a8478">z</SvgText>
|
|
165
|
+
<SvgText x="50" y="50" fontFamily="sans-serif" fontWeight="700" fontSize={20} fill="#9a8478">Z</SvgText>
|
|
166
|
+
</>
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
case 'celebrar':
|
|
170
|
+
return (
|
|
171
|
+
<>
|
|
172
|
+
{/* Olhos felizes fechados */}
|
|
173
|
+
<Path d="M73 95 Q80 86 87 95" stroke="#15100d" strokeWidth={3.2} fill="none" strokeLinecap="round" />
|
|
174
|
+
<Path d="M113 95 Q120 86 127 95" stroke="#15100d" strokeWidth={3.2} fill="none" strokeLinecap="round" />
|
|
175
|
+
{/* Boca aberta de celebração sorrindo com interior à mostra */}
|
|
176
|
+
<Path d="M87 99 Q100 116 113 99 Q100 106 87 99 Z" fill="#fff" />
|
|
177
|
+
<Path d="M89 100 Q100 113 111 100" fill="#fbe6cf" />
|
|
178
|
+
</>
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
case 'original':
|
|
182
|
+
default:
|
|
183
|
+
return (
|
|
184
|
+
<>
|
|
185
|
+
{/* Olhos felizes fechados (padrão) */}
|
|
186
|
+
<Path d="M73 95 Q80 86 87 95" stroke="#15100d" strokeWidth={3.2} fill="none" strokeLinecap="round" />
|
|
187
|
+
<Path d="M113 95 Q120 86 127 95" stroke="#15100d" strokeWidth={3.2} fill="none" strokeLinecap="round" />
|
|
188
|
+
{/* Boca feliz aberta */}
|
|
189
|
+
<Path d="M90 100 Q100 111 110 100 Z" fill="#fbe6cf" />
|
|
190
|
+
|
|
191
|
+
{/* Autocolantes/Stickers de Bandeiras espalhados pelo corpo */}
|
|
192
|
+
{/* Bandeira de Portugal (inferior esquerda) */}
|
|
193
|
+
<G transform="translate(60, 148) rotate(-10)">
|
|
194
|
+
<Rect x={-12} y={-9} width={24} height={18} rx={3} fill="#fff" />
|
|
195
|
+
<Rect x={-10} y={-7} width={6.5} height={14} fill="#2a4b9b" />
|
|
196
|
+
<Rect x={-3.5} y={-7} width={6.5} height={14} fill="#fff" />
|
|
197
|
+
<Rect x={3} y={-7} width={6.5} height={14} fill="#d34b3e" />
|
|
198
|
+
</G>
|
|
199
|
+
{/* Bandeira do Brasil (inferior direita) */}
|
|
200
|
+
<G transform="translate(140, 150) rotate(9)">
|
|
201
|
+
<Rect x={-12} y={-9} width={24} height={18} rx={3} fill="#fff" />
|
|
202
|
+
<Rect x={-10} y={-7} width={6.5} height={14} fill="#16915a" />
|
|
203
|
+
<Rect x={-3.5} y={-7} width={6.5} height={14} fill="#fcd116" />
|
|
204
|
+
<Rect x={3} y={-7} width={6.5} height={14} fill="#d12e2e" />
|
|
205
|
+
<SvgText x={-0.2} y={2} textAnchor="middle" fontSize={8} fill="#16915a">★</SvgText>
|
|
206
|
+
</G>
|
|
207
|
+
{/* Bandeira de Espanha (bochecha direita) */}
|
|
208
|
+
<G transform="translate(142, 82) rotate(8)">
|
|
209
|
+
<Rect x={-12} y={-9} width={24} height={18} rx={3} fill="#fff" />
|
|
210
|
+
<Rect x={-10} y={-7} width={6.5} height={14} fill="#15100d" />
|
|
211
|
+
<Rect x={-3.5} y={-7} width={6.5} height={14} fill="#f6c343" />
|
|
212
|
+
<Rect x={3} y={-7} width={6.5} height={14} fill="#d34b3e" />
|
|
213
|
+
</G>
|
|
214
|
+
{/* Bandeira de França (bochecha esquerda) */}
|
|
215
|
+
<G transform="translate(58, 82) rotate(-8)">
|
|
216
|
+
<Rect x={-11} y={-9} width={22} height={18} rx={3} fill="#fff" />
|
|
217
|
+
<Rect x={-9} y={-7} width={5} height={14} fill="#0050a4" />
|
|
218
|
+
<Rect x={-4} y={-7} width={8} height={14} fill="#fff" />
|
|
219
|
+
<Rect x={4} y={-7} width={5} height={14} fill="#d52b1e" />
|
|
220
|
+
<Path d="M0 -3 l2 3 -2 3 -2 -3 Z" fill="#d52b1e" />
|
|
221
|
+
</G>
|
|
222
|
+
</>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<Svg
|
|
229
|
+
width={size}
|
|
230
|
+
height={size}
|
|
231
|
+
viewBox={avatarOnly ? "35 45 130 130" : "0 0 200 200"}
|
|
232
|
+
role="img"
|
|
233
|
+
aria-label={`Mascote Vava: ${variant}`}
|
|
234
|
+
>
|
|
235
|
+
{/* 1. Antena Superior */}
|
|
236
|
+
<Path
|
|
237
|
+
d="M84 64 q0 -20 16 -20 q16 0 16 20"
|
|
238
|
+
fill="none"
|
|
239
|
+
stroke="#3f2620"
|
|
240
|
+
strokeWidth={7}
|
|
241
|
+
strokeLinecap="round"
|
|
242
|
+
/>
|
|
243
|
+
{/* Bases da Antena */}
|
|
244
|
+
<Rect x={84} y={58} width={12} height={10} rx={2} fill="#cf7a30" />
|
|
245
|
+
<Rect x={104} y={58} width={12} height={10} rx={2} fill="#cf7a30" />
|
|
246
|
+
|
|
247
|
+
{/* 2. Corpo do Tablet (Chassis) */}
|
|
248
|
+
<Rect x={44} y={64} width={112} height={104} rx={16} fill="#51332d" />
|
|
249
|
+
|
|
250
|
+
{/* 3. Moldura Interna do Ecrã */}
|
|
251
|
+
<Rect
|
|
252
|
+
x={50}
|
|
253
|
+
y={70}
|
|
254
|
+
width={100}
|
|
255
|
+
height={92}
|
|
256
|
+
rx={12}
|
|
257
|
+
fill="none"
|
|
258
|
+
stroke="#6b4a33"
|
|
259
|
+
strokeWidth={2.5}
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
{/* 4. Faixa Inferior Laranja do Tablet */}
|
|
263
|
+
<Rect x={44} y={116} width={112} height={15} fill="#f2974b" />
|
|
264
|
+
|
|
265
|
+
{/* 5. Botão Home Central */}
|
|
266
|
+
<Rect x={92} y={112} width={16} height={23} rx={3} fill="#cf7a30" />
|
|
267
|
+
|
|
268
|
+
{/* 6. Bochechas Coradas */}
|
|
269
|
+
<Circle cx={66} cy={106} r={6} fill="#f2974b" opacity={0.4} />
|
|
270
|
+
<Circle cx={134} cy={106} r={6} fill="#f2974b" opacity={0.4} />
|
|
271
|
+
|
|
272
|
+
{/* 7. Expressão Facial & Elementos de Expressão (Diferentes de acordo com a Variante) */}
|
|
273
|
+
{renderFace()}
|
|
274
|
+
</Svg>
|
|
275
|
+
);
|
|
276
|
+
};
|