@umituz/react-native-onboarding 3.3.8 → 3.3.10
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 +1 -1
- package/src/domain/entities/OnboardingSlide.ts +5 -0
- package/src/presentation/components/BackgroundVideo.tsx +8 -17
- package/src/presentation/components/OnboardingFooter.tsx +69 -112
- package/src/presentation/components/OnboardingHeader.tsx +47 -72
- package/src/presentation/components/OnboardingResetSetting.tsx +17 -51
- package/src/presentation/components/OnboardingScreenContent.tsx +2 -2
- package/src/presentation/components/OnboardingSlide.tsx +81 -71
- package/src/presentation/components/QuestionRenderer.tsx +2 -2
- package/src/presentation/components/QuestionSlide.tsx +42 -46
- package/src/presentation/components/QuestionSlideHeader.tsx +52 -67
- package/src/presentation/components/questions/MultipleChoiceQuestion.tsx +26 -55
- package/src/presentation/components/questions/RatingQuestion.tsx +24 -45
- package/src/presentation/components/questions/SingleChoiceQuestion.tsx +23 -49
- package/src/presentation/components/questions/TextInputQuestion.tsx +15 -20
- package/src/presentation/providers/OnboardingThemeProvider.tsx +98 -0
- package/src/presentation/screens/OnboardingScreen.tsx +34 -31
- package/src/presentation/styles/OnboardingSlideStyles.ts +0 -139
- package/src/presentation/styles/QuestionSlideStyles.ts +0 -78
|
@@ -1,96 +1,106 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Displays a single onboarding slide with icon, title, and description
|
|
5
|
-
* Supports multiple variants (default, card, minimal) and dark mode
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useMemo } from "react";
|
|
9
|
-
import { View, Text, ScrollView } from "react-native";
|
|
10
|
-
import { AtomicIcon } from "@umituz/react-native-design-system";
|
|
11
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, ScrollView, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
|
|
12
4
|
import type { OnboardingSlide as OnboardingSlideType } from "../../domain/entities/OnboardingSlide";
|
|
13
5
|
import type { OnboardingThemeVariant } from "../../domain/entities/OnboardingTheme";
|
|
14
|
-
import {
|
|
6
|
+
import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
|
|
15
7
|
|
|
16
8
|
export interface OnboardingSlideProps {
|
|
17
9
|
slide: OnboardingSlideType;
|
|
18
|
-
useGradient?: boolean;
|
|
19
10
|
variant?: OnboardingThemeVariant;
|
|
20
11
|
}
|
|
21
12
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
export const OnboardingSlide: React.FC<OnboardingSlideProps> = ({
|
|
13
|
+
export const OnboardingSlide = ({
|
|
25
14
|
slide,
|
|
26
|
-
useGradient = false,
|
|
27
15
|
variant = "default"
|
|
28
|
-
}) => {
|
|
29
|
-
const
|
|
30
|
-
const styles = useMemo(
|
|
31
|
-
() => createOnboardingStyles(tokens, useGradient, variant),
|
|
32
|
-
[tokens, useGradient, variant]
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const isEmoji = EMOJI_REGEX.test(slide.icon);
|
|
36
|
-
// Simple check for valid icon name - assuming string and not emoji
|
|
37
|
-
const isValidIconName = !isEmoji && typeof slide.icon === "string" && slide.icon.length > 0;
|
|
16
|
+
}: OnboardingSlideProps) => {
|
|
17
|
+
const { colors } = useOnboardingTheme();
|
|
38
18
|
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const renderIcon = () => {
|
|
43
|
-
if (isEmoji) {
|
|
44
|
-
return <Text style={{ fontSize: iconSize }}>{slide.icon}</Text>;
|
|
45
|
-
}
|
|
46
|
-
if (isValidIconName) {
|
|
47
|
-
return <AtomicIcon name={slide.icon} customSize={iconSize} customColor={iconColor} />;
|
|
48
|
-
}
|
|
49
|
-
// Fallback if no icon
|
|
50
|
-
if (slide.image) {
|
|
51
|
-
// TODO: Implement image support if needed, for now just placeholder
|
|
52
|
-
return <AtomicIcon name="image" customSize={iconSize} customColor={iconColor} />;
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const renderFeatures = () => {
|
|
58
|
-
if (!slide.features || slide.features.length === 0) return null;
|
|
59
|
-
return (
|
|
60
|
-
<View style={styles.featuresContainer}>
|
|
61
|
-
{slide.features.map((feature, index) => (
|
|
62
|
-
<View key={index} style={styles.featureItem}>
|
|
63
|
-
<Text style={styles.featureBullet}>•</Text>
|
|
64
|
-
<Text style={styles.featureText}>{feature}</Text>
|
|
65
|
-
</View>
|
|
66
|
-
))}
|
|
67
|
-
</View>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
19
|
+
const isEmoji = slide.iconType === 'emoji';
|
|
20
|
+
const hasIcon = slide.icon && slide.icon.length > 0;
|
|
21
|
+
const iconSize = variant === "minimal" ? 80 : 72;
|
|
70
22
|
|
|
71
23
|
return (
|
|
72
|
-
<ScrollView
|
|
73
|
-
contentContainerStyle={styles.content}
|
|
74
|
-
showsVerticalScrollIndicator={false}
|
|
75
|
-
bounces={false}
|
|
76
|
-
>
|
|
24
|
+
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false} bounces={false}>
|
|
77
25
|
<View style={styles.slideContainer}>
|
|
78
|
-
{
|
|
79
|
-
{(isEmoji || isValidIconName) && (
|
|
26
|
+
{hasIcon && (
|
|
80
27
|
<View style={styles.iconBox}>
|
|
81
|
-
{
|
|
28
|
+
{isEmoji ? (
|
|
29
|
+
<AtomicText style={{ fontSize: iconSize }}>{slide.icon}</AtomicText>
|
|
30
|
+
) : (
|
|
31
|
+
<AtomicIcon name={slide.icon} customSize={iconSize} customColor={colors.iconColor} />
|
|
32
|
+
)}
|
|
82
33
|
</View>
|
|
83
34
|
)}
|
|
84
35
|
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
36
|
+
<AtomicText type="headlineMedium" style={[styles.title, { color: colors.textColor }]}>
|
|
37
|
+
{slide.title}
|
|
38
|
+
</AtomicText>
|
|
39
|
+
|
|
40
|
+
<AtomicText type="bodyLarge" style={[styles.description, { color: colors.subTextColor }]}>
|
|
41
|
+
{slide.description}
|
|
42
|
+
</AtomicText>
|
|
88
43
|
|
|
89
|
-
{
|
|
90
|
-
|
|
44
|
+
{slide.features && slide.features.length > 0 && (
|
|
45
|
+
<View style={styles.featuresContainer}>
|
|
46
|
+
{slide.features.map((feature, index) => (
|
|
47
|
+
<View key={index} style={styles.featureItem}>
|
|
48
|
+
<AtomicIcon name="checkmark-circle" size="sm" customColor={colors.iconColor} />
|
|
49
|
+
<AtomicText type="bodyMedium" style={[styles.featureText, { color: colors.textColor }]}>
|
|
50
|
+
{feature}
|
|
51
|
+
</AtomicText>
|
|
52
|
+
</View>
|
|
53
|
+
))}
|
|
54
|
+
</View>
|
|
55
|
+
)}
|
|
91
56
|
</View>
|
|
92
57
|
</ScrollView>
|
|
93
58
|
);
|
|
94
59
|
};
|
|
95
60
|
|
|
61
|
+
const styles = StyleSheet.create({
|
|
62
|
+
content: {
|
|
63
|
+
flexGrow: 1,
|
|
64
|
+
paddingTop: 40,
|
|
65
|
+
},
|
|
66
|
+
slideContainer: {
|
|
67
|
+
paddingHorizontal: 30,
|
|
68
|
+
alignItems: "center",
|
|
69
|
+
},
|
|
70
|
+
iconBox: {
|
|
71
|
+
marginBottom: 40,
|
|
72
|
+
height: 120,
|
|
73
|
+
justifyContent: "center",
|
|
74
|
+
alignItems: "center",
|
|
75
|
+
},
|
|
76
|
+
title: {
|
|
77
|
+
textAlign: "center",
|
|
78
|
+
marginBottom: 16,
|
|
79
|
+
fontWeight: "800",
|
|
80
|
+
},
|
|
81
|
+
description: {
|
|
82
|
+
textAlign: "center",
|
|
83
|
+
lineHeight: 24,
|
|
84
|
+
marginBottom: 32,
|
|
85
|
+
opacity: 0.9,
|
|
86
|
+
},
|
|
87
|
+
featuresContainer: {
|
|
88
|
+
width: "100%",
|
|
89
|
+
gap: 12,
|
|
90
|
+
marginTop: 8,
|
|
91
|
+
},
|
|
92
|
+
featureItem: {
|
|
93
|
+
flexDirection: "row",
|
|
94
|
+
alignItems: "center",
|
|
95
|
+
gap: 12,
|
|
96
|
+
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
|
97
|
+
padding: 12,
|
|
98
|
+
borderRadius: 12,
|
|
99
|
+
},
|
|
100
|
+
featureText: {
|
|
101
|
+
flex: 1,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
|
|
96
106
|
|
|
@@ -16,11 +16,11 @@ export interface QuestionRendererProps {
|
|
|
16
16
|
onChange: (value: any) => void;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export const QuestionRenderer
|
|
19
|
+
export const QuestionRenderer = ({
|
|
20
20
|
question,
|
|
21
21
|
value,
|
|
22
22
|
onChange,
|
|
23
|
-
}) => {
|
|
23
|
+
}: QuestionRendererProps) => {
|
|
24
24
|
switch (question.type) {
|
|
25
25
|
case "single_choice":
|
|
26
26
|
return (
|
|
@@ -1,71 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { useMemo } from "react";
|
|
7
|
-
import { View, Text, ScrollView } from "react-native";
|
|
8
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, ScrollView, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicText } from "@umituz/react-native-design-system";
|
|
9
4
|
import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
|
|
10
5
|
import type { OnboardingThemeVariant } from "../../domain/entities/OnboardingTheme";
|
|
11
6
|
import { QuestionSlideHeader } from "./QuestionSlideHeader";
|
|
12
7
|
import { QuestionRenderer } from "./QuestionRenderer";
|
|
13
|
-
import {
|
|
8
|
+
import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
|
|
14
9
|
|
|
15
10
|
export interface QuestionSlideProps {
|
|
16
11
|
slide: OnboardingSlide;
|
|
17
12
|
value: any;
|
|
18
13
|
onChange: (value: any) => void;
|
|
19
|
-
useGradient?: boolean;
|
|
20
14
|
variant?: OnboardingThemeVariant;
|
|
21
15
|
}
|
|
22
16
|
|
|
23
|
-
export const QuestionSlide
|
|
17
|
+
export const QuestionSlide = ({
|
|
24
18
|
slide,
|
|
25
19
|
value,
|
|
26
20
|
onChange,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}) => {
|
|
30
|
-
const tokens = useAppDesignTokens();
|
|
31
|
-
const styles = useMemo(
|
|
32
|
-
() => createQuestionStyles(tokens, useGradient, variant),
|
|
33
|
-
[tokens, useGradient, variant]
|
|
34
|
-
);
|
|
21
|
+
}: QuestionSlideProps) => {
|
|
22
|
+
const { colors } = useOnboardingTheme();
|
|
35
23
|
const { question } = slide;
|
|
36
24
|
|
|
37
|
-
if (!question)
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const content = (
|
|
42
|
-
<>
|
|
43
|
-
<QuestionSlideHeader slide={slide} useGradient={useGradient} />
|
|
44
|
-
|
|
45
|
-
<View style={styles.questionContainer}>
|
|
46
|
-
<QuestionRenderer
|
|
47
|
-
question={question}
|
|
48
|
-
value={value}
|
|
49
|
-
onChange={onChange}
|
|
50
|
-
/>
|
|
51
|
-
</View>
|
|
52
|
-
|
|
53
|
-
{question.validation?.required && !value && (
|
|
54
|
-
<Text style={styles.requiredHint}>* This field is required</Text>
|
|
55
|
-
)}
|
|
56
|
-
</>
|
|
57
|
-
);
|
|
25
|
+
if (!question) return null;
|
|
58
26
|
|
|
59
27
|
return (
|
|
60
|
-
<ScrollView
|
|
61
|
-
contentContainerStyle={styles.content}
|
|
62
|
-
showsVerticalScrollIndicator={false}
|
|
63
|
-
bounces={false}
|
|
64
|
-
>
|
|
28
|
+
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false} bounces={false}>
|
|
65
29
|
<View style={styles.slideContainer}>
|
|
66
|
-
{
|
|
30
|
+
<QuestionSlideHeader slide={slide} />
|
|
31
|
+
|
|
32
|
+
<View style={styles.questionContainer}>
|
|
33
|
+
<QuestionRenderer question={question} value={value} onChange={onChange} />
|
|
34
|
+
</View>
|
|
35
|
+
|
|
36
|
+
{question.validation?.required && !value && (
|
|
37
|
+
<AtomicText
|
|
38
|
+
type="labelSmall"
|
|
39
|
+
style={[styles.requiredHint, { color: colors.errorColor }]}
|
|
40
|
+
>
|
|
41
|
+
* This field is required
|
|
42
|
+
</AtomicText>
|
|
43
|
+
)}
|
|
67
44
|
</View>
|
|
68
45
|
</ScrollView>
|
|
69
46
|
);
|
|
70
47
|
};
|
|
71
48
|
|
|
49
|
+
const styles = StyleSheet.create({
|
|
50
|
+
content: {
|
|
51
|
+
flexGrow: 1,
|
|
52
|
+
paddingTop: 40,
|
|
53
|
+
},
|
|
54
|
+
slideContainer: {
|
|
55
|
+
paddingHorizontal: 24,
|
|
56
|
+
},
|
|
57
|
+
questionContainer: {
|
|
58
|
+
marginTop: 24,
|
|
59
|
+
},
|
|
60
|
+
requiredHint: {
|
|
61
|
+
marginTop: 12,
|
|
62
|
+
textAlign: "center",
|
|
63
|
+
fontWeight: "600",
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
|
|
@@ -1,88 +1,73 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Question Slide Header Component
|
|
3
|
-
* Single Responsibility: Display slide header (icon, title, description)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React from "react";
|
|
7
|
-
import { View,
|
|
8
|
-
import { AtomicIcon } from "@umituz/react-native-design-system";
|
|
9
|
-
import { useAppDesignTokens, withAlpha } from "@umituz/react-native-design-system";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
|
|
10
4
|
import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
|
|
5
|
+
import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
|
|
11
6
|
|
|
12
7
|
export interface QuestionSlideHeaderProps {
|
|
13
8
|
slide: OnboardingSlide;
|
|
14
|
-
useGradient: boolean;
|
|
15
9
|
}
|
|
16
10
|
|
|
17
|
-
const
|
|
18
|
-
/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u.test(icon);
|
|
19
|
-
|
|
20
|
-
export const QuestionSlideHeader: React.FC<QuestionSlideHeaderProps> = ({
|
|
11
|
+
export const QuestionSlideHeader = ({
|
|
21
12
|
slide,
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const
|
|
25
|
-
const styles = getStyles(tokens, useGradient);
|
|
13
|
+
}: QuestionSlideHeaderProps) => {
|
|
14
|
+
const { colors } = useOnboardingTheme();
|
|
15
|
+
const isEmoji = slide.iconType === 'emoji';
|
|
26
16
|
|
|
27
17
|
return (
|
|
28
|
-
|
|
29
|
-
<View style={
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
<View style={styles.container}>
|
|
19
|
+
<View style={[
|
|
20
|
+
styles.iconContainer,
|
|
21
|
+
{
|
|
22
|
+
backgroundColor: colors.iconBg,
|
|
23
|
+
borderColor: colors.iconBorder,
|
|
24
|
+
}
|
|
25
|
+
]}>
|
|
26
|
+
{isEmoji ? (
|
|
27
|
+
<AtomicText style={{ fontSize: 48 }}>{slide.icon}</AtomicText>
|
|
32
28
|
) : (
|
|
33
|
-
<AtomicIcon
|
|
34
|
-
name={slide.icon as any}
|
|
35
|
-
customSize={48}
|
|
36
|
-
customColor={useGradient ? "#FFFFFF" : tokens.colors.textPrimary}
|
|
37
|
-
/>
|
|
29
|
+
<AtomicIcon name={slide.icon as any} customSize={48} customColor={colors.textColor} />
|
|
38
30
|
)}
|
|
39
31
|
</View>
|
|
40
32
|
|
|
41
|
-
<
|
|
33
|
+
<AtomicText type="headlineMedium" style={[styles.title, { color: colors.textColor }]}>
|
|
34
|
+
{slide.title}
|
|
35
|
+
</AtomicText>
|
|
42
36
|
|
|
43
|
-
{slide.description &&
|
|
44
|
-
|
|
37
|
+
{slide.description && (
|
|
38
|
+
<AtomicText type="bodyMedium" style={[styles.description, { color: colors.subTextColor }]}>
|
|
39
|
+
{slide.description}
|
|
40
|
+
</AtomicText>
|
|
41
|
+
)}
|
|
42
|
+
</View>
|
|
45
43
|
);
|
|
46
44
|
};
|
|
47
45
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
fontWeight: "bold",
|
|
74
|
-
color: useGradient ? "#FFFFFF" : tokens.colors.textPrimary,
|
|
75
|
-
textAlign: "center",
|
|
76
|
-
marginBottom: 12,
|
|
77
|
-
},
|
|
78
|
-
description: {
|
|
79
|
-
fontSize: 15,
|
|
80
|
-
color: useGradient ? "rgba(255, 255, 255, 0.9)" : tokens.colors.textSecondary,
|
|
81
|
-
textAlign: "center",
|
|
82
|
-
lineHeight: 22,
|
|
83
|
-
marginBottom: 24,
|
|
84
|
-
},
|
|
85
|
-
});
|
|
46
|
+
const styles = StyleSheet.create({
|
|
47
|
+
container: {
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
},
|
|
50
|
+
iconContainer: {
|
|
51
|
+
width: 96,
|
|
52
|
+
height: 96,
|
|
53
|
+
borderRadius: 48,
|
|
54
|
+
alignItems: "center",
|
|
55
|
+
justifyContent: "center",
|
|
56
|
+
marginBottom: 24,
|
|
57
|
+
borderWidth: 2,
|
|
58
|
+
},
|
|
59
|
+
title: {
|
|
60
|
+
fontWeight: "800",
|
|
61
|
+
textAlign: "center",
|
|
62
|
+
marginBottom: 12,
|
|
63
|
+
},
|
|
64
|
+
description: {
|
|
65
|
+
textAlign: "center",
|
|
66
|
+
lineHeight: 22,
|
|
67
|
+
marginBottom: 24,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
86
71
|
|
|
87
72
|
|
|
88
73
|
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Multiple Choice Question Component
|
|
3
|
-
*
|
|
4
|
-
* Checkbox style question for multiple selections
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import React from "react";
|
|
8
|
-
import { View,
|
|
9
|
-
import { AtomicIcon } from "@umituz/react-native-design-system";
|
|
10
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
2
|
+
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicIcon, AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
11
4
|
import type { OnboardingQuestion, QuestionOption } from "../../../domain/entities/OnboardingQuestion";
|
|
12
5
|
|
|
13
6
|
export interface MultipleChoiceQuestionProps {
|
|
@@ -16,17 +9,13 @@ export interface MultipleChoiceQuestionProps {
|
|
|
16
9
|
onChange: (value: string[]) => void;
|
|
17
10
|
}
|
|
18
11
|
|
|
19
|
-
export const MultipleChoiceQuestion
|
|
12
|
+
export const MultipleChoiceQuestion = ({
|
|
20
13
|
question,
|
|
21
14
|
value = [],
|
|
22
15
|
onChange,
|
|
23
|
-
}) => {
|
|
16
|
+
}: MultipleChoiceQuestionProps) => {
|
|
24
17
|
const tokens = useAppDesignTokens();
|
|
25
18
|
|
|
26
|
-
if (!tokens) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
19
|
const isEmoji = (icon: string) =>
|
|
31
20
|
/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u.test(icon);
|
|
32
21
|
|
|
@@ -35,57 +24,51 @@ export const MultipleChoiceQuestion: React.FC<MultipleChoiceQuestionProps> = ({
|
|
|
35
24
|
? value.filter((id) => id !== optionId)
|
|
36
25
|
: [...value, optionId];
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
question.validation?.maxSelections &&
|
|
41
|
-
newValue.length > question.validation.maxSelections
|
|
42
|
-
) {
|
|
27
|
+
if (question.validation?.maxSelections && newValue.length > question.validation.maxSelections) {
|
|
43
28
|
return;
|
|
44
29
|
}
|
|
45
|
-
|
|
46
30
|
onChange(newValue);
|
|
47
31
|
};
|
|
48
32
|
|
|
49
33
|
const renderOption = (option: QuestionOption) => {
|
|
50
34
|
const isSelected = value.includes(option.id);
|
|
35
|
+
const textColor = isSelected ? tokens.colors.textPrimary : tokens.colors.textSecondary;
|
|
51
36
|
|
|
52
37
|
return (
|
|
53
38
|
<TouchableOpacity
|
|
54
39
|
key={option.id}
|
|
55
|
-
style={[
|
|
40
|
+
style={[
|
|
41
|
+
styles.option,
|
|
42
|
+
{
|
|
43
|
+
backgroundColor: isSelected ? tokens.colors.primary + '10' : tokens.colors.surface,
|
|
44
|
+
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
45
|
+
}
|
|
46
|
+
]}
|
|
56
47
|
onPress={() => handleToggle(option.id)}
|
|
57
48
|
activeOpacity={0.7}
|
|
58
49
|
>
|
|
59
50
|
{option.icon && (
|
|
60
51
|
<View style={styles.optionIcon}>
|
|
61
52
|
{isEmoji(option.icon) ? (
|
|
62
|
-
<
|
|
53
|
+
<AtomicText style={{ fontSize: 24 }}>{option.icon}</AtomicText>
|
|
63
54
|
) : (
|
|
64
|
-
<AtomicIcon
|
|
65
|
-
name={option.icon as any}
|
|
66
|
-
customSize={24}
|
|
67
|
-
customColor={isSelected ? tokens.colors.textPrimary : tokens.colors.textSecondary}
|
|
68
|
-
/>
|
|
55
|
+
<AtomicIcon name={option.icon as any} customSize={24} customColor={textColor} />
|
|
69
56
|
)}
|
|
70
57
|
</View>
|
|
71
58
|
)}
|
|
72
|
-
<
|
|
73
|
-
styles.optionLabel,
|
|
74
|
-
isSelected && styles.optionLabelSelected,
|
|
75
|
-
{ color: isSelected ? tokens.colors.textPrimary : tokens.colors.textSecondary }
|
|
76
|
-
]}>
|
|
59
|
+
<AtomicText type="bodyLarge" style={[styles.optionLabel, { color: textColor, fontWeight: isSelected ? '700' : '500' }]}>
|
|
77
60
|
{option.label}
|
|
78
|
-
</
|
|
61
|
+
</AtomicText>
|
|
79
62
|
<View style={[
|
|
80
63
|
styles.checkbox,
|
|
81
|
-
isSelected && { borderWidth: 3 },
|
|
82
64
|
{
|
|
83
65
|
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
84
|
-
backgroundColor: isSelected ? tokens.colors.
|
|
66
|
+
backgroundColor: isSelected ? tokens.colors.primary : 'transparent',
|
|
67
|
+
borderWidth: isSelected ? 0 : 2,
|
|
85
68
|
}
|
|
86
69
|
]}>
|
|
87
70
|
{isSelected && (
|
|
88
|
-
<AtomicIcon name="
|
|
71
|
+
<AtomicIcon name="checkmark" customSize={16} customColor="#FFFFFF" />
|
|
89
72
|
)}
|
|
90
73
|
</View>
|
|
91
74
|
</TouchableOpacity>
|
|
@@ -96,9 +79,9 @@ export const MultipleChoiceQuestion: React.FC<MultipleChoiceQuestionProps> = ({
|
|
|
96
79
|
<View style={styles.container}>
|
|
97
80
|
{question.options?.map(renderOption)}
|
|
98
81
|
{question.validation?.maxSelections && (
|
|
99
|
-
<
|
|
82
|
+
<AtomicText type="labelSmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
|
|
100
83
|
Select up to {question.validation.maxSelections} options
|
|
101
|
-
</
|
|
84
|
+
</AtomicText>
|
|
102
85
|
)}
|
|
103
86
|
</View>
|
|
104
87
|
);
|
|
@@ -112,39 +95,27 @@ const styles = StyleSheet.create({
|
|
|
112
95
|
option: {
|
|
113
96
|
flexDirection: "row",
|
|
114
97
|
alignItems: "center",
|
|
115
|
-
borderRadius:
|
|
98
|
+
borderRadius: 16,
|
|
116
99
|
padding: 16,
|
|
117
100
|
borderWidth: 2,
|
|
118
101
|
},
|
|
119
|
-
optionSelected: {
|
|
120
|
-
borderWidth: 3,
|
|
121
|
-
},
|
|
122
102
|
optionIcon: {
|
|
123
103
|
marginRight: 12,
|
|
124
104
|
},
|
|
125
|
-
emoji: {
|
|
126
|
-
fontSize: 24,
|
|
127
|
-
},
|
|
128
105
|
optionLabel: {
|
|
129
106
|
flex: 1,
|
|
130
|
-
fontSize: 16,
|
|
131
|
-
fontWeight: "500",
|
|
132
|
-
},
|
|
133
|
-
optionLabelSelected: {
|
|
134
|
-
fontWeight: "600",
|
|
135
107
|
},
|
|
136
108
|
checkbox: {
|
|
137
109
|
width: 24,
|
|
138
110
|
height: 24,
|
|
139
|
-
borderRadius:
|
|
140
|
-
borderWidth: 2,
|
|
111
|
+
borderRadius: 8,
|
|
141
112
|
alignItems: "center",
|
|
142
113
|
justifyContent: "center",
|
|
143
114
|
},
|
|
144
115
|
hint: {
|
|
145
|
-
fontSize: 13,
|
|
146
116
|
textAlign: "center",
|
|
147
|
-
marginTop:
|
|
117
|
+
marginTop: 8,
|
|
148
118
|
},
|
|
149
119
|
});
|
|
150
120
|
|
|
121
|
+
|