@xetwa/design-system 1.0.23 → 1.0.25
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/package.json
CHANGED
|
@@ -15,7 +15,7 @@ const meta = {
|
|
|
15
15
|
layout: 'padded',
|
|
16
16
|
docs: {
|
|
17
17
|
description: {
|
|
18
|
-
component: 'O **AnswerOptionCard** é utilizado nos ecrãs de Lição/Quiz para exibir as múltiplas escolhas de um exercício.\n\n### Exemplo de Uso\n```tsx\n<AnswerOptionCard \n status="default"\n label="Boa noite"\n
|
|
18
|
+
component: 'O **AnswerOptionCard** é utilizado nos ecrãs de Lição/Quiz para exibir as múltiplas escolhas de um exercício.\n\n### Exemplo de Uso\n```tsx\n<AnswerOptionCard \n status="default"\n label="Boa noite"\n prefix="A"\n onPress={() => selecionar("A")}\n/>\n```',
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
},
|
|
@@ -36,43 +36,13 @@ const meta = {
|
|
|
36
36
|
export default meta;
|
|
37
37
|
type Story = StoryObj<any>;
|
|
38
38
|
|
|
39
|
-
const CircleLetter = ({ letter, selected = false, correct = false }: { letter: string; selected?: boolean; correct?: boolean }) => {
|
|
40
|
-
let bgColor = theme.colors.brown[100];
|
|
41
|
-
let textColor = theme.colors.brown[500];
|
|
42
39
|
|
|
43
|
-
if (selected) {
|
|
44
|
-
bgColor = theme.colors.primary;
|
|
45
|
-
textColor = theme.colors.white;
|
|
46
|
-
} else if (correct) {
|
|
47
|
-
bgColor = theme.colors.success;
|
|
48
|
-
textColor = theme.colors.white;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<View style={{
|
|
53
|
-
width: 28,
|
|
54
|
-
height: 28,
|
|
55
|
-
borderRadius: 14,
|
|
56
|
-
backgroundColor: bgColor,
|
|
57
|
-
alignItems: 'center',
|
|
58
|
-
justifyContent: 'center',
|
|
59
|
-
}}>
|
|
60
|
-
<Typography style={{
|
|
61
|
-
|
|
62
|
-
fontSize: 14,
|
|
63
|
-
color: textColor,
|
|
64
|
-
}}>
|
|
65
|
-
{letter}
|
|
66
|
-
</Typography>
|
|
67
|
-
</View>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
40
|
|
|
71
41
|
export const Default: Story = {
|
|
72
42
|
args: {
|
|
73
43
|
status: 'default',
|
|
74
44
|
label: 'Boa noite',
|
|
75
|
-
|
|
45
|
+
prefix: 'A',
|
|
76
46
|
},
|
|
77
47
|
};
|
|
78
48
|
|
|
@@ -118,19 +88,19 @@ export const InteractiveDemo: Story = {
|
|
|
118
88
|
<AnswerOptionCard
|
|
119
89
|
status={getStatus('A')}
|
|
120
90
|
label="Boa noite"
|
|
121
|
-
|
|
91
|
+
prefix="A"
|
|
122
92
|
onPress={() => handlePress('A')}
|
|
123
93
|
/>
|
|
124
94
|
<AnswerOptionCard
|
|
125
95
|
status={getStatus('B')}
|
|
126
96
|
label="Bom dia / Olá"
|
|
127
|
-
|
|
97
|
+
prefix="B"
|
|
128
98
|
onPress={() => handlePress('B')}
|
|
129
99
|
/>
|
|
130
100
|
<AnswerOptionCard
|
|
131
101
|
status={getStatus('C')}
|
|
132
102
|
label="Obrigado"
|
|
133
|
-
|
|
103
|
+
prefix="C"
|
|
134
104
|
onPress={() => handlePress('C')}
|
|
135
105
|
/>
|
|
136
106
|
|
|
@@ -172,11 +142,11 @@ export const Grid: Story = {
|
|
|
172
142
|
},
|
|
173
143
|
render: () => (
|
|
174
144
|
<View style={{ gap: 16 }}>
|
|
175
|
-
<AnswerOptionCard status="default" label="Boa noite"
|
|
176
|
-
<AnswerOptionCard status="selected" label="Bom dia / Olá"
|
|
177
|
-
<AnswerOptionCard status="correct" label="Bom dia / Olá"
|
|
178
|
-
<AnswerOptionCard status="wrong" label="Obrigado"
|
|
179
|
-
<AnswerOptionCard status="disabled" label="Por favor"
|
|
145
|
+
<AnswerOptionCard status="default" label="Boa noite" prefix="A" />
|
|
146
|
+
<AnswerOptionCard status="selected" label="Bom dia / Olá" prefix="B" />
|
|
147
|
+
<AnswerOptionCard status="correct" label="Bom dia / Olá" prefix="B" />
|
|
148
|
+
<AnswerOptionCard status="wrong" label="Obrigado" prefix="C" />
|
|
149
|
+
<AnswerOptionCard status="disabled" label="Por favor" prefix="D" />
|
|
180
150
|
</View>
|
|
181
151
|
),
|
|
182
152
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { View, StyleSheet, Pressable, Platform } from 'react-native';
|
|
1
|
+
import { View, StyleSheet, Pressable, Platform, Animated } from 'react-native';
|
|
2
|
+
import React from 'react';
|
|
2
3
|
import type { ViewStyle } from 'react-native';
|
|
3
4
|
import { theme } from '../../../styles/theme';
|
|
4
5
|
import type { ReactNode } from 'react';
|
|
@@ -20,10 +21,10 @@ export interface AnswerOptionCardProps {
|
|
|
20
21
|
prefixNode?: ReactNode;
|
|
21
22
|
/** Letra ou número a exibir automaticamente do lado esquerdo. */
|
|
22
23
|
prefix?: string | number;
|
|
23
|
-
/** Nó renderizado à direita. Se omitido, renderiza automaticamente ícones de check/cross consoante o estado. */
|
|
24
|
-
suffixNode?: ReactNode;
|
|
25
24
|
/** Callback para o evento de clique. */
|
|
26
25
|
onPress?: () => void;
|
|
26
|
+
/** Define o tamanho do componente. */
|
|
27
|
+
size?: 'sm' | 'md' | 'lg';
|
|
27
28
|
/** Estilos adicionais para o container. */
|
|
28
29
|
style?: ViewStyle | ViewStyle[];
|
|
29
30
|
}
|
|
@@ -33,11 +34,30 @@ export const AnswerOptionCard = ({
|
|
|
33
34
|
label,
|
|
34
35
|
prefixNode,
|
|
35
36
|
prefix,
|
|
36
|
-
suffixNode,
|
|
37
37
|
onPress,
|
|
38
|
+
size = 'md',
|
|
38
39
|
style,
|
|
39
40
|
}: AnswerOptionCardProps) => {
|
|
40
41
|
const { scale, scaleText } = useResponsive();
|
|
42
|
+
const scaleAnim = React.useRef(new Animated.Value(1)).current;
|
|
43
|
+
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (status !== 'default' && status !== 'disabled') {
|
|
46
|
+
Animated.sequence([
|
|
47
|
+
Animated.timing(scaleAnim, {
|
|
48
|
+
toValue: 0.95,
|
|
49
|
+
duration: 100,
|
|
50
|
+
useNativeDriver: true,
|
|
51
|
+
}),
|
|
52
|
+
Animated.spring(scaleAnim, {
|
|
53
|
+
toValue: 1,
|
|
54
|
+
friction: 4,
|
|
55
|
+
tension: 40,
|
|
56
|
+
useNativeDriver: true,
|
|
57
|
+
}),
|
|
58
|
+
]).start();
|
|
59
|
+
}
|
|
60
|
+
}, [status, scaleAnim]);
|
|
41
61
|
|
|
42
62
|
const getStyles = () => {
|
|
43
63
|
switch (status) {
|
|
@@ -123,36 +143,88 @@ export const AnswerOptionCard = ({
|
|
|
123
143
|
</View>
|
|
124
144
|
);
|
|
125
145
|
}
|
|
126
|
-
return
|
|
146
|
+
return null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const getPaddingVertical = () => {
|
|
150
|
+
switch (size) {
|
|
151
|
+
case 'lg': return scale(14);
|
|
152
|
+
case 'sm': return scale(8);
|
|
153
|
+
default: return scale(10);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const getPaddingHorizontal = () => {
|
|
158
|
+
switch (size) {
|
|
159
|
+
case 'lg': return scale(20);
|
|
160
|
+
case 'sm': return scale(12);
|
|
161
|
+
default: return scale(15);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const getMinHeight = () => {
|
|
166
|
+
switch (size) {
|
|
167
|
+
case 'lg': return scale(64);
|
|
168
|
+
case 'sm': return scale(48);
|
|
169
|
+
default: return scale(56);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const getBorderRadius = () => {
|
|
174
|
+
switch (size) {
|
|
175
|
+
case 'lg': return scale(16);
|
|
176
|
+
case 'sm': return scale(10);
|
|
177
|
+
default: return scale(12);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const getFontSize = () => {
|
|
182
|
+
switch (size) {
|
|
183
|
+
case 'lg': return scaleText(18);
|
|
184
|
+
case 'sm': return scaleText(14);
|
|
185
|
+
default: return scaleText(16);
|
|
186
|
+
}
|
|
127
187
|
};
|
|
128
188
|
|
|
129
189
|
return (
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<View style={styles.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
190
|
+
<Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
|
|
191
|
+
<Pressable
|
|
192
|
+
style={[
|
|
193
|
+
styles.container,
|
|
194
|
+
{
|
|
195
|
+
borderColor: vStyles.borderColor,
|
|
196
|
+
backgroundColor: vStyles.backgroundColor,
|
|
197
|
+
paddingVertical: getPaddingVertical(),
|
|
198
|
+
paddingHorizontal: getPaddingHorizontal(),
|
|
199
|
+
borderRadius: getBorderRadius(),
|
|
200
|
+
minHeight: getMinHeight(),
|
|
201
|
+
},
|
|
202
|
+
webShadow,
|
|
203
|
+
style,
|
|
204
|
+
]}
|
|
205
|
+
onPress={status !== 'disabled' ? onPress : undefined}
|
|
206
|
+
disabled={status === 'disabled' || !onPress}
|
|
207
|
+
>
|
|
208
|
+
<View style={styles.contentRow}>
|
|
209
|
+
<View style={styles.leftSection}>
|
|
210
|
+
{prefixNode && <View style={[styles.prefixContainer, { marginRight: scale(12) }]}>{prefixNode}</View>}
|
|
211
|
+
{prefix && !prefixNode && (
|
|
212
|
+
<View style={[
|
|
213
|
+
styles.prefixContainer,
|
|
214
|
+
{
|
|
215
|
+
marginRight: scale(12),
|
|
216
|
+
width: scale(28),
|
|
217
|
+
height: scale(28),
|
|
218
|
+
borderRadius: scale(14),
|
|
219
|
+
backgroundColor: status === 'correct' ? theme.colors.success : status === 'wrong' ? theme.colors.error : theme.colors.brown[100],
|
|
220
|
+
alignItems: 'center',
|
|
221
|
+
justifyContent: 'center'
|
|
222
|
+
}
|
|
223
|
+
]}>
|
|
152
224
|
<Typography style={{
|
|
153
|
-
color: status === '
|
|
225
|
+
color: status === 'correct' || status === 'wrong' ? theme.colors.white : theme.colors.brown[500],
|
|
154
226
|
fontWeight: 'bold',
|
|
155
|
-
fontSize: scaleText(
|
|
227
|
+
fontSize: scaleText(14)
|
|
156
228
|
}}>
|
|
157
229
|
{prefix}
|
|
158
230
|
</Typography>
|
|
@@ -163,7 +235,7 @@ export const AnswerOptionCard = ({
|
|
|
163
235
|
styles.label,
|
|
164
236
|
{
|
|
165
237
|
color: status === 'disabled' ? theme.colors.brown[300] : vStyles.textColor,
|
|
166
|
-
fontSize:
|
|
238
|
+
fontSize: getFontSize(),
|
|
167
239
|
}
|
|
168
240
|
]}
|
|
169
241
|
>
|
|
@@ -173,6 +245,7 @@ export const AnswerOptionCard = ({
|
|
|
173
245
|
{renderSuffix()}
|
|
174
246
|
</View>
|
|
175
247
|
</Pressable>
|
|
248
|
+
</Animated.View>
|
|
176
249
|
);
|
|
177
250
|
};
|
|
178
251
|
|