@umituz/react-native-onboarding 1.0.9 → 2.0.1
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/README.md +369 -0
- package/package.json +11 -3
- package/src/domain/entities/OnboardingQuestion.ts +156 -0
- package/src/domain/entities/OnboardingSlide.ts +25 -0
- package/src/domain/entities/OnboardingUserData.ts +43 -0
- package/src/index.ts +24 -1
- package/src/infrastructure/storage/OnboardingStore.ts +67 -4
- package/src/presentation/components/OnboardingFooter.tsx +17 -2
- package/src/presentation/components/OnboardingSlide.tsx +7 -6
- package/src/presentation/components/QuestionSlide.tsx +177 -0
- package/src/presentation/components/questions/MultipleChoiceQuestion.tsx +143 -0
- package/src/presentation/components/questions/RatingQuestion.tsx +78 -0
- package/src/presentation/components/questions/SingleChoiceQuestion.tsx +119 -0
- package/src/presentation/components/questions/SliderQuestion.tsx +81 -0
- package/src/presentation/components/questions/TextInputQuestion.tsx +68 -0
- package/src/presentation/screens/OnboardingScreen.tsx +111 -8
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single Choice Question Component
|
|
3
|
+
*
|
|
4
|
+
* Radio button style question for single selection
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
|
|
9
|
+
import { AtomicIcon } from "@umituz/react-native-design-system-atoms";
|
|
10
|
+
import type { OnboardingQuestion, QuestionOption } from "../../../domain/entities/OnboardingQuestion";
|
|
11
|
+
|
|
12
|
+
export interface SingleChoiceQuestionProps {
|
|
13
|
+
question: OnboardingQuestion;
|
|
14
|
+
value: string | undefined;
|
|
15
|
+
onChange: (value: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SingleChoiceQuestion: React.FC<SingleChoiceQuestionProps> = ({
|
|
19
|
+
question,
|
|
20
|
+
value,
|
|
21
|
+
onChange,
|
|
22
|
+
}) => {
|
|
23
|
+
const isEmoji = (icon: string) =>
|
|
24
|
+
/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u.test(icon);
|
|
25
|
+
|
|
26
|
+
const renderOption = (option: QuestionOption) => {
|
|
27
|
+
const isSelected = value === option.id;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<TouchableOpacity
|
|
31
|
+
key={option.id}
|
|
32
|
+
style={[styles.option, isSelected && styles.optionSelected]}
|
|
33
|
+
onPress={() => onChange(option.id)}
|
|
34
|
+
activeOpacity={0.7}
|
|
35
|
+
>
|
|
36
|
+
{option.icon && (
|
|
37
|
+
<View style={styles.optionIcon}>
|
|
38
|
+
{isEmoji(option.icon) ? (
|
|
39
|
+
<Text style={styles.emoji}>{option.icon}</Text>
|
|
40
|
+
) : (
|
|
41
|
+
<AtomicIcon
|
|
42
|
+
name={option.icon as any}
|
|
43
|
+
customSize={24}
|
|
44
|
+
customColor={isSelected ? "#FFFFFF" : "rgba(255, 255, 255, 0.8)"}
|
|
45
|
+
/>
|
|
46
|
+
)}
|
|
47
|
+
</View>
|
|
48
|
+
)}
|
|
49
|
+
<Text style={[styles.optionLabel, isSelected && styles.optionLabelSelected]}>
|
|
50
|
+
{option.label}
|
|
51
|
+
</Text>
|
|
52
|
+
<View style={[styles.radio, isSelected && styles.radioSelected]}>
|
|
53
|
+
{isSelected && <View style={styles.radioInner} />}
|
|
54
|
+
</View>
|
|
55
|
+
</TouchableOpacity>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<View style={styles.container}>
|
|
61
|
+
{question.options?.map(renderOption)}
|
|
62
|
+
</View>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const styles = StyleSheet.create({
|
|
67
|
+
container: {
|
|
68
|
+
width: "100%",
|
|
69
|
+
gap: 12,
|
|
70
|
+
},
|
|
71
|
+
option: {
|
|
72
|
+
flexDirection: "row",
|
|
73
|
+
alignItems: "center",
|
|
74
|
+
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
|
75
|
+
borderRadius: 12,
|
|
76
|
+
padding: 16,
|
|
77
|
+
borderWidth: 2,
|
|
78
|
+
borderColor: "rgba(255, 255, 255, 0.2)",
|
|
79
|
+
},
|
|
80
|
+
optionSelected: {
|
|
81
|
+
backgroundColor: "rgba(255, 255, 255, 0.25)",
|
|
82
|
+
borderColor: "rgba(255, 255, 255, 0.5)",
|
|
83
|
+
},
|
|
84
|
+
optionIcon: {
|
|
85
|
+
marginRight: 12,
|
|
86
|
+
},
|
|
87
|
+
emoji: {
|
|
88
|
+
fontSize: 24,
|
|
89
|
+
},
|
|
90
|
+
optionLabel: {
|
|
91
|
+
flex: 1,
|
|
92
|
+
fontSize: 16,
|
|
93
|
+
color: "rgba(255, 255, 255, 0.9)",
|
|
94
|
+
fontWeight: "500",
|
|
95
|
+
},
|
|
96
|
+
optionLabelSelected: {
|
|
97
|
+
color: "#FFFFFF",
|
|
98
|
+
fontWeight: "600",
|
|
99
|
+
},
|
|
100
|
+
radio: {
|
|
101
|
+
width: 24,
|
|
102
|
+
height: 24,
|
|
103
|
+
borderRadius: 12,
|
|
104
|
+
borderWidth: 2,
|
|
105
|
+
borderColor: "rgba(255, 255, 255, 0.5)",
|
|
106
|
+
alignItems: "center",
|
|
107
|
+
justifyContent: "center",
|
|
108
|
+
},
|
|
109
|
+
radioSelected: {
|
|
110
|
+
borderColor: "#FFFFFF",
|
|
111
|
+
},
|
|
112
|
+
radioInner: {
|
|
113
|
+
width: 12,
|
|
114
|
+
height: 12,
|
|
115
|
+
borderRadius: 6,
|
|
116
|
+
backgroundColor: "#FFFFFF",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slider Question Component
|
|
3
|
+
*
|
|
4
|
+
* Slider for numeric value selection
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
9
|
+
import Slider from "@react-native-community/slider";
|
|
10
|
+
import type { OnboardingQuestion } from "../../../domain/entities/OnboardingQuestion";
|
|
11
|
+
|
|
12
|
+
export interface SliderQuestionProps {
|
|
13
|
+
question: OnboardingQuestion;
|
|
14
|
+
value: number | undefined;
|
|
15
|
+
onChange: (value: number) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SliderQuestion: React.FC<SliderQuestionProps> = ({
|
|
19
|
+
question,
|
|
20
|
+
value,
|
|
21
|
+
onChange,
|
|
22
|
+
}) => {
|
|
23
|
+
const { validation } = question;
|
|
24
|
+
const min = validation?.min ?? 0;
|
|
25
|
+
const max = validation?.max ?? 100;
|
|
26
|
+
const currentValue = value ?? min;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<View style={styles.container}>
|
|
30
|
+
<View style={styles.valueContainer}>
|
|
31
|
+
<Text style={styles.valueText}>{currentValue}</Text>
|
|
32
|
+
</View>
|
|
33
|
+
<Slider
|
|
34
|
+
style={styles.slider}
|
|
35
|
+
minimumValue={min}
|
|
36
|
+
maximumValue={max}
|
|
37
|
+
value={currentValue}
|
|
38
|
+
onValueChange={onChange}
|
|
39
|
+
minimumTrackTintColor="#FFFFFF"
|
|
40
|
+
maximumTrackTintColor="rgba(255, 255, 255, 0.3)"
|
|
41
|
+
thumbTintColor="#FFFFFF"
|
|
42
|
+
step={1}
|
|
43
|
+
/>
|
|
44
|
+
<View style={styles.labels}>
|
|
45
|
+
<Text style={styles.label}>{min}</Text>
|
|
46
|
+
<Text style={styles.label}>{max}</Text>
|
|
47
|
+
</View>
|
|
48
|
+
</View>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const styles = StyleSheet.create({
|
|
53
|
+
container: {
|
|
54
|
+
width: "100%",
|
|
55
|
+
paddingHorizontal: 8,
|
|
56
|
+
},
|
|
57
|
+
valueContainer: {
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
marginBottom: 16,
|
|
60
|
+
},
|
|
61
|
+
valueText: {
|
|
62
|
+
fontSize: 48,
|
|
63
|
+
fontWeight: "bold",
|
|
64
|
+
color: "#FFFFFF",
|
|
65
|
+
},
|
|
66
|
+
slider: {
|
|
67
|
+
width: "100%",
|
|
68
|
+
height: 40,
|
|
69
|
+
},
|
|
70
|
+
labels: {
|
|
71
|
+
flexDirection: "row",
|
|
72
|
+
justifyContent: "space-between",
|
|
73
|
+
marginTop: 8,
|
|
74
|
+
},
|
|
75
|
+
label: {
|
|
76
|
+
fontSize: 14,
|
|
77
|
+
color: "rgba(255, 255, 255, 0.7)",
|
|
78
|
+
fontWeight: "500",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Input Question Component
|
|
3
|
+
*
|
|
4
|
+
* Text input field for free-form text answers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { View, TextInput, StyleSheet, Text } from "react-native";
|
|
9
|
+
import type { OnboardingQuestion } from "../../../domain/entities/OnboardingQuestion";
|
|
10
|
+
|
|
11
|
+
export interface TextInputQuestionProps {
|
|
12
|
+
question: OnboardingQuestion;
|
|
13
|
+
value: string | undefined;
|
|
14
|
+
onChange: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const TextInputQuestion: React.FC<TextInputQuestionProps> = ({
|
|
18
|
+
question,
|
|
19
|
+
value = "",
|
|
20
|
+
onChange,
|
|
21
|
+
}) => {
|
|
22
|
+
const { validation } = question;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<View style={styles.container}>
|
|
26
|
+
<TextInput
|
|
27
|
+
style={styles.input}
|
|
28
|
+
value={value}
|
|
29
|
+
onChangeText={onChange}
|
|
30
|
+
placeholder={question.placeholder || "Type your answer..."}
|
|
31
|
+
placeholderTextColor="rgba(255, 255, 255, 0.5)"
|
|
32
|
+
maxLength={validation?.maxLength}
|
|
33
|
+
multiline={validation?.maxLength ? validation.maxLength > 100 : false}
|
|
34
|
+
numberOfLines={validation?.maxLength && validation.maxLength > 100 ? 4 : 1}
|
|
35
|
+
autoCapitalize="sentences"
|
|
36
|
+
autoCorrect={true}
|
|
37
|
+
/>
|
|
38
|
+
{validation?.maxLength && (
|
|
39
|
+
<Text style={styles.charCount}>
|
|
40
|
+
{value.length} / {validation.maxLength}
|
|
41
|
+
</Text>
|
|
42
|
+
)}
|
|
43
|
+
</View>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const styles = StyleSheet.create({
|
|
48
|
+
container: {
|
|
49
|
+
width: "100%",
|
|
50
|
+
},
|
|
51
|
+
input: {
|
|
52
|
+
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
|
53
|
+
borderRadius: 12,
|
|
54
|
+
padding: 16,
|
|
55
|
+
fontSize: 16,
|
|
56
|
+
color: "#FFFFFF",
|
|
57
|
+
borderWidth: 2,
|
|
58
|
+
borderColor: "rgba(255, 255, 255, 0.3)",
|
|
59
|
+
minHeight: 56,
|
|
60
|
+
},
|
|
61
|
+
charCount: {
|
|
62
|
+
fontSize: 13,
|
|
63
|
+
color: "rgba(255, 255, 255, 0.6)",
|
|
64
|
+
textAlign: "right",
|
|
65
|
+
marginTop: 8,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Generic and reusable across hundreds of apps
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import React, { useMemo } from "react";
|
|
8
|
+
import React, { useMemo, useState, useEffect } from "react";
|
|
9
9
|
import { View, StyleSheet, StatusBar } from "react-native";
|
|
10
10
|
import { LinearGradient } from "expo-linear-gradient";
|
|
11
11
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
@@ -14,6 +14,7 @@ import { useOnboardingNavigation } from "../../infrastructure/hooks/useOnboardin
|
|
|
14
14
|
import { useOnboardingStore } from "../../infrastructure/storage/OnboardingStore";
|
|
15
15
|
import { OnboardingHeader } from "../components/OnboardingHeader";
|
|
16
16
|
import { OnboardingSlide as OnboardingSlideComponent } from "../components/OnboardingSlide";
|
|
17
|
+
import { QuestionSlide } from "../components/QuestionSlide";
|
|
17
18
|
import { OnboardingFooter } from "../components/OnboardingFooter";
|
|
18
19
|
|
|
19
20
|
export interface OnboardingScreenProps extends OnboardingOptions {
|
|
@@ -83,8 +84,25 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
83
84
|
}) => {
|
|
84
85
|
const insets = useSafeAreaInsets();
|
|
85
86
|
const onboardingStore = useOnboardingStore();
|
|
87
|
+
const [currentAnswer, setCurrentAnswer] = useState<any>(undefined);
|
|
88
|
+
|
|
89
|
+
// Filter slides based on skipIf conditions
|
|
90
|
+
const filteredSlides = useMemo(() => {
|
|
91
|
+
const userData = onboardingStore.getUserData();
|
|
92
|
+
return slides.filter((slide) => {
|
|
93
|
+
if (slide.skipIf) {
|
|
94
|
+
return !slide.skipIf(userData.answers);
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
});
|
|
98
|
+
}, [slides, onboardingStore]);
|
|
86
99
|
|
|
87
100
|
const handleComplete = async () => {
|
|
101
|
+
// Save current answer if exists
|
|
102
|
+
if (currentSlide.question && currentAnswer !== undefined) {
|
|
103
|
+
await onboardingStore.saveAnswer(currentSlide.question.id, currentAnswer);
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
await onboardingStore.complete(storageKey);
|
|
89
107
|
if (onComplete) {
|
|
90
108
|
await onComplete();
|
|
@@ -104,9 +122,14 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
104
122
|
goToPrevious,
|
|
105
123
|
isLastSlide,
|
|
106
124
|
isFirstSlide,
|
|
107
|
-
} = useOnboardingNavigation(
|
|
125
|
+
} = useOnboardingNavigation(filteredSlides.length, handleComplete, handleSkip);
|
|
126
|
+
|
|
127
|
+
const handleNext = async () => {
|
|
128
|
+
// Save current answer if exists
|
|
129
|
+
if (currentSlide.question && currentAnswer !== undefined) {
|
|
130
|
+
await onboardingStore.saveAnswer(currentSlide.question.id, currentAnswer);
|
|
131
|
+
}
|
|
108
132
|
|
|
109
|
-
const handleNext = () => {
|
|
110
133
|
if (isLastSlide) {
|
|
111
134
|
if (autoComplete) {
|
|
112
135
|
handleComplete();
|
|
@@ -115,12 +138,85 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
115
138
|
}
|
|
116
139
|
} else {
|
|
117
140
|
goToNext();
|
|
141
|
+
// Load next slide's answer
|
|
142
|
+
const nextSlide = filteredSlides[currentIndex + 1];
|
|
143
|
+
if (nextSlide?.question) {
|
|
144
|
+
const savedAnswer = onboardingStore.getAnswer(nextSlide.question.id);
|
|
145
|
+
setCurrentAnswer(savedAnswer);
|
|
146
|
+
} else {
|
|
147
|
+
setCurrentAnswer(undefined);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const handlePrevious = () => {
|
|
153
|
+
goToPrevious();
|
|
154
|
+
// Load previous slide's answer
|
|
155
|
+
if (currentIndex > 0) {
|
|
156
|
+
const prevSlide = filteredSlides[currentIndex - 1];
|
|
157
|
+
if (prevSlide?.question) {
|
|
158
|
+
const savedAnswer = onboardingStore.getAnswer(prevSlide.question.id);
|
|
159
|
+
setCurrentAnswer(savedAnswer);
|
|
160
|
+
} else {
|
|
161
|
+
setCurrentAnswer(undefined);
|
|
162
|
+
}
|
|
118
163
|
}
|
|
119
164
|
};
|
|
120
165
|
|
|
121
|
-
const currentSlide =
|
|
166
|
+
const currentSlide = filteredSlides[currentIndex];
|
|
122
167
|
const styles = useMemo(() => getStyles(insets), [insets]);
|
|
123
168
|
|
|
169
|
+
// Load current slide's answer on mount and when slide changes
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (currentSlide?.question) {
|
|
172
|
+
const savedAnswer = onboardingStore.getAnswer(currentSlide.question.id);
|
|
173
|
+
setCurrentAnswer(savedAnswer ?? currentSlide.question.defaultValue);
|
|
174
|
+
} else {
|
|
175
|
+
setCurrentAnswer(undefined);
|
|
176
|
+
}
|
|
177
|
+
}, [currentIndex, currentSlide, onboardingStore]);
|
|
178
|
+
|
|
179
|
+
// Validate current answer
|
|
180
|
+
const isAnswerValid = useMemo(() => {
|
|
181
|
+
if (!currentSlide?.question) return true;
|
|
182
|
+
|
|
183
|
+
const { validation } = currentSlide.question;
|
|
184
|
+
if (!validation) return true;
|
|
185
|
+
|
|
186
|
+
// Required validation
|
|
187
|
+
if (validation.required && !currentAnswer) return false;
|
|
188
|
+
|
|
189
|
+
// Type-specific validations
|
|
190
|
+
switch (currentSlide.question.type) {
|
|
191
|
+
case "multiple_choice":
|
|
192
|
+
if (validation.minSelections && (!currentAnswer || currentAnswer.length < validation.minSelections)) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
case "text_input":
|
|
197
|
+
if (validation.minLength && (!currentAnswer || currentAnswer.length < validation.minLength)) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
case "slider":
|
|
202
|
+
case "rating":
|
|
203
|
+
if (validation.min !== undefined && currentAnswer < validation.min) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (validation.max !== undefined && currentAnswer > validation.max) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Custom validator
|
|
213
|
+
if (validation.customValidator) {
|
|
214
|
+
return validation.customValidator(currentAnswer) === true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return true;
|
|
218
|
+
}, [currentSlide, currentAnswer]);
|
|
219
|
+
|
|
124
220
|
return (
|
|
125
221
|
<View style={styles.container}>
|
|
126
222
|
<StatusBar barStyle="light-content" />
|
|
@@ -133,13 +229,13 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
133
229
|
{renderHeader ? (
|
|
134
230
|
renderHeader({
|
|
135
231
|
isFirstSlide,
|
|
136
|
-
onBack:
|
|
232
|
+
onBack: handlePrevious,
|
|
137
233
|
onSkip: handleSkip,
|
|
138
234
|
})
|
|
139
235
|
) : (
|
|
140
236
|
<OnboardingHeader
|
|
141
237
|
isFirstSlide={isFirstSlide}
|
|
142
|
-
onBack={
|
|
238
|
+
onBack={handlePrevious}
|
|
143
239
|
onSkip={handleSkip}
|
|
144
240
|
showBackButton={showBackButton}
|
|
145
241
|
showSkipButton={showSkipButton}
|
|
@@ -148,13 +244,19 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
148
244
|
)}
|
|
149
245
|
{renderSlide ? (
|
|
150
246
|
renderSlide(currentSlide)
|
|
247
|
+
) : currentSlide.type === "question" && currentSlide.question ? (
|
|
248
|
+
<QuestionSlide
|
|
249
|
+
slide={currentSlide}
|
|
250
|
+
value={currentAnswer}
|
|
251
|
+
onChange={setCurrentAnswer}
|
|
252
|
+
/>
|
|
151
253
|
) : (
|
|
152
254
|
<OnboardingSlideComponent slide={currentSlide} />
|
|
153
255
|
)}
|
|
154
256
|
{renderFooter ? (
|
|
155
257
|
renderFooter({
|
|
156
258
|
currentIndex,
|
|
157
|
-
totalSlides:
|
|
259
|
+
totalSlides: filteredSlides.length,
|
|
158
260
|
isLastSlide,
|
|
159
261
|
onNext: handleNext,
|
|
160
262
|
onUpgrade,
|
|
@@ -163,7 +265,7 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
163
265
|
) : (
|
|
164
266
|
<OnboardingFooter
|
|
165
267
|
currentIndex={currentIndex}
|
|
166
|
-
totalSlides={
|
|
268
|
+
totalSlides={filteredSlides.length}
|
|
167
269
|
isLastSlide={isLastSlide}
|
|
168
270
|
onNext={handleNext}
|
|
169
271
|
showProgressBar={showProgressBar}
|
|
@@ -171,6 +273,7 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
171
273
|
showProgressText={showProgressText}
|
|
172
274
|
nextButtonText={nextButtonText}
|
|
173
275
|
getStartedButtonText={getStartedButtonText}
|
|
276
|
+
disabled={!isAnswerValid}
|
|
174
277
|
/>
|
|
175
278
|
)}
|
|
176
279
|
</View>
|