@umituz/react-native-design-system 2.6.128 → 2.8.0

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 (66) hide show
  1. package/package.json +4 -2
  2. package/src/exports/onboarding.ts +6 -0
  3. package/src/index.ts +5 -0
  4. package/src/molecules/navigation/utils/AppNavigation.ts +53 -28
  5. package/src/molecules/splash/hooks/useSplashFlow.ts +33 -7
  6. package/src/onboarding/domain/entities/OnboardingOptions.ts +104 -0
  7. package/src/onboarding/domain/entities/OnboardingQuestion.ts +165 -0
  8. package/src/onboarding/domain/entities/OnboardingSlide.ts +152 -0
  9. package/src/onboarding/domain/entities/OnboardingUserData.ts +43 -0
  10. package/src/onboarding/hooks/useOnboardingFlow.ts +50 -0
  11. package/src/onboarding/index.ts +108 -0
  12. package/src/onboarding/infrastructure/hooks/__tests__/useOnboardingAnswers.test.ts +163 -0
  13. package/src/onboarding/infrastructure/hooks/__tests__/useOnboardingNavigation.test.ts +121 -0
  14. package/src/onboarding/infrastructure/hooks/useOnboardingAnswers.ts +69 -0
  15. package/src/onboarding/infrastructure/hooks/useOnboardingNavigation.ts +75 -0
  16. package/src/onboarding/infrastructure/services/SlideManager.ts +53 -0
  17. package/src/onboarding/infrastructure/services/ValidationManager.ts +127 -0
  18. package/src/onboarding/infrastructure/storage/OnboardingStore.ts +99 -0
  19. package/src/onboarding/infrastructure/storage/OnboardingStoreActions.ts +50 -0
  20. package/src/onboarding/infrastructure/storage/OnboardingStoreSelectors.ts +25 -0
  21. package/src/onboarding/infrastructure/storage/OnboardingStoreState.ts +22 -0
  22. package/src/onboarding/infrastructure/storage/__tests__/OnboardingStore.test.ts +85 -0
  23. package/src/onboarding/infrastructure/storage/actions/answerActions.ts +47 -0
  24. package/src/onboarding/infrastructure/storage/actions/completeAction.ts +45 -0
  25. package/src/onboarding/infrastructure/storage/actions/index.ts +22 -0
  26. package/src/onboarding/infrastructure/storage/actions/initializeAction.ts +40 -0
  27. package/src/onboarding/infrastructure/storage/actions/resetAction.ts +37 -0
  28. package/src/onboarding/infrastructure/storage/actions/skipAction.ts +46 -0
  29. package/src/onboarding/infrastructure/storage/actions/storageHelpers.ts +60 -0
  30. package/src/onboarding/infrastructure/utils/arrayUtils.ts +28 -0
  31. package/src/onboarding/infrastructure/utils/backgroundUtils.ts +38 -0
  32. package/src/onboarding/infrastructure/utils/layouts/collageLayout.ts +81 -0
  33. package/src/onboarding/infrastructure/utils/layouts/gridLayouts.ts +78 -0
  34. package/src/onboarding/infrastructure/utils/layouts/honeycombLayout.ts +36 -0
  35. package/src/onboarding/infrastructure/utils/layouts/index.ts +12 -0
  36. package/src/onboarding/infrastructure/utils/layouts/layoutTypes.ts +37 -0
  37. package/src/onboarding/infrastructure/utils/layouts/masonryLayout.ts +37 -0
  38. package/src/onboarding/infrastructure/utils/layouts/scatteredLayout.ts +34 -0
  39. package/src/onboarding/infrastructure/utils/layouts/screenDimensions.ts +11 -0
  40. package/src/onboarding/infrastructure/utils/layouts/tilesLayout.ts +34 -0
  41. package/src/onboarding/presentation/components/BackgroundImageCollage.tsx +90 -0
  42. package/src/onboarding/presentation/components/BackgroundVideo.tsx +24 -0
  43. package/src/onboarding/presentation/components/BaseSlide.tsx +47 -0
  44. package/src/onboarding/presentation/components/OnboardingBackground.tsx +91 -0
  45. package/src/onboarding/presentation/components/OnboardingFooter.tsx +151 -0
  46. package/src/onboarding/presentation/components/OnboardingHeader.tsx +92 -0
  47. package/src/onboarding/presentation/components/OnboardingResetSetting.tsx +70 -0
  48. package/src/onboarding/presentation/components/OnboardingScreenContent.tsx +146 -0
  49. package/src/onboarding/presentation/components/OnboardingSlide.tsx +124 -0
  50. package/src/onboarding/presentation/components/QuestionRenderer.tsx +60 -0
  51. package/src/onboarding/presentation/components/QuestionSlide.tsx +67 -0
  52. package/src/onboarding/presentation/components/QuestionSlideHeader.tsx +75 -0
  53. package/src/onboarding/presentation/components/questions/MultipleChoiceQuestion.tsx +74 -0
  54. package/src/onboarding/presentation/components/questions/QuestionOptionItem.tsx +115 -0
  55. package/src/onboarding/presentation/components/questions/RatingQuestion.tsx +66 -0
  56. package/src/onboarding/presentation/components/questions/SingleChoiceQuestion.tsx +117 -0
  57. package/src/onboarding/presentation/components/questions/TextInputQuestion.tsx +71 -0
  58. package/src/onboarding/presentation/hooks/__tests__/useOnboardingContainerStyle.test.ts +96 -0
  59. package/src/onboarding/presentation/hooks/useOnboardingContainerStyle.ts +37 -0
  60. package/src/onboarding/presentation/hooks/useOnboardingGestures.ts +45 -0
  61. package/src/onboarding/presentation/hooks/useOnboardingScreenHandlers.ts +114 -0
  62. package/src/onboarding/presentation/hooks/useOnboardingScreenState.ts +146 -0
  63. package/src/onboarding/presentation/providers/OnboardingProvider.tsx +51 -0
  64. package/src/onboarding/presentation/screens/OnboardingScreen.tsx +189 -0
  65. package/src/onboarding/presentation/types/OnboardingProps.ts +46 -0
  66. package/src/onboarding/presentation/types/OnboardingTheme.ts +27 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Question Renderer Component
3
+ * Single Responsibility: Render appropriate question component based on type
4
+ */
5
+
6
+ import React from "react";
7
+ import type { OnboardingQuestion } from "../../domain/entities/OnboardingQuestion";
8
+ import { SingleChoiceQuestion } from "./questions/SingleChoiceQuestion";
9
+ import { MultipleChoiceQuestion } from "./questions/MultipleChoiceQuestion";
10
+ import { TextInputQuestion } from "./questions/TextInputQuestion";
11
+ import { RatingQuestion } from "./questions/RatingQuestion";
12
+
13
+ export interface QuestionRendererProps {
14
+ question: OnboardingQuestion;
15
+ value: any;
16
+ onChange: (value: any) => void;
17
+ }
18
+
19
+ export const QuestionRenderer = ({
20
+ question,
21
+ value,
22
+ onChange,
23
+ }: QuestionRendererProps) => {
24
+ switch (question.type) {
25
+ case "single_choice":
26
+ return (
27
+ <SingleChoiceQuestion
28
+ question={question}
29
+ value={value}
30
+ onChange={onChange}
31
+ />
32
+ );
33
+ case "multiple_choice":
34
+ return (
35
+ <MultipleChoiceQuestion
36
+ question={question}
37
+ value={value}
38
+ onChange={onChange}
39
+ />
40
+ );
41
+ case "text_input":
42
+ return (
43
+ <TextInputQuestion
44
+ question={question}
45
+ value={value}
46
+ onChange={onChange}
47
+ />
48
+ );
49
+ case "rating":
50
+ return (
51
+ <RatingQuestion
52
+ question={question}
53
+ value={value}
54
+ onChange={onChange}
55
+ />
56
+ );
57
+ default:
58
+ return null;
59
+ }
60
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * QuestionSlide Component
3
+ * Single Responsibility: Render a question-type slide
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { AtomicText } from "@umituz/react-native-design-system";
9
+ import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
10
+ import { QuestionSlideHeader } from "./QuestionSlideHeader";
11
+ import { QuestionRenderer } from "./QuestionRenderer";
12
+ import { BaseSlide } from "./BaseSlide";
13
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
14
+ import { useLocalization } from "@umituz/react-native-localization";
15
+
16
+ export interface QuestionSlideProps {
17
+ slide: OnboardingSlide;
18
+ value: any;
19
+ onChange: (value: any) => void;
20
+ variant?: "default" | "card" | "minimal" | "fullscreen";
21
+ }
22
+
23
+ export const QuestionSlide = ({
24
+ slide,
25
+ value,
26
+ onChange,
27
+ variant: _variant = "default",
28
+ }: QuestionSlideProps) => {
29
+ const {
30
+ theme: { colors },
31
+ } = useOnboardingProvider();
32
+ const { t } = useLocalization();
33
+ const { question } = slide;
34
+
35
+ if (!question) return null;
36
+
37
+ return (
38
+ <BaseSlide contentPosition={slide.contentPosition}>
39
+ <QuestionSlideHeader slide={slide} />
40
+
41
+ <View style={styles.questionContainer}>
42
+ <QuestionRenderer question={question} value={value} onChange={onChange} />
43
+ </View>
44
+
45
+ {question.validation?.required && !value && (
46
+ <AtomicText
47
+ type="labelSmall"
48
+ style={[styles.requiredHint, { color: colors.errorColor }]}
49
+ >
50
+ {t("onboarding.fieldRequired")}
51
+ </AtomicText>
52
+ )}
53
+ </BaseSlide>
54
+ );
55
+ };
56
+
57
+ const styles = StyleSheet.create({
58
+ questionContainer: {
59
+ marginTop: 24,
60
+ width: "100%",
61
+ },
62
+ requiredHint: {
63
+ marginTop: 12,
64
+ textAlign: "center",
65
+ fontWeight: "600",
66
+ },
67
+ });
@@ -0,0 +1,75 @@
1
+ import React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
+ import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
5
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
6
+
7
+ export interface QuestionSlideHeaderProps {
8
+ slide: OnboardingSlide;
9
+ }
10
+
11
+ export const QuestionSlideHeader = ({ slide }: QuestionSlideHeaderProps) => {
12
+ const {
13
+ theme: { colors },
14
+ } = useOnboardingProvider();
15
+ const isEmoji = slide.iconType === 'emoji';
16
+
17
+ return (
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>
28
+ ) : (
29
+ <AtomicIcon name={slide.icon as any} customSize={48} customColor={colors.textColor} />
30
+ )}
31
+ </View>
32
+
33
+ <AtomicText type="headlineMedium" style={[styles.title, { color: colors.textColor }]}>
34
+ {slide.title}
35
+ </AtomicText>
36
+
37
+ {slide.description && (
38
+ <AtomicText type="bodyMedium" style={[styles.description, { color: colors.subTextColor }]}>
39
+ {slide.description}
40
+ </AtomicText>
41
+ )}
42
+ </View>
43
+ );
44
+ };
45
+
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
+
71
+
72
+
73
+
74
+
75
+
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Multiple Choice Question Component
3
+ * Single Responsibility: Render multiple choice question with options
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { AtomicText } from "@umituz/react-native-design-system";
9
+ import { useOnboardingProvider } from "../../providers/OnboardingProvider";
10
+ import { ensureArray } from "../../../infrastructure/utils/arrayUtils";
11
+ import type { OnboardingQuestion } from "../../../domain/entities/OnboardingQuestion";
12
+ import { QuestionOptionItem } from "./QuestionOptionItem";
13
+
14
+ export interface MultipleChoiceQuestionProps {
15
+ question: OnboardingQuestion;
16
+ value: string[] | undefined;
17
+ onChange: (value: string[]) => void;
18
+ }
19
+
20
+ export const MultipleChoiceQuestion = ({
21
+ question,
22
+ value,
23
+ onChange,
24
+ }: MultipleChoiceQuestionProps) => {
25
+ const {
26
+ theme: { colors },
27
+ } = useOnboardingProvider();
28
+
29
+ const safeValue = ensureArray(value);
30
+
31
+ const handleToggle = (optionId: string) => {
32
+ const newValue = safeValue.includes(optionId)
33
+ ? safeValue.filter((id) => id !== optionId)
34
+ : [...safeValue, optionId];
35
+
36
+ if (question.validation?.maxSelections && newValue.length > question.validation.maxSelections) {
37
+ return;
38
+ }
39
+ onChange(newValue);
40
+ };
41
+
42
+ return (
43
+ <View style={styles.container}>
44
+ {question.options?.map((option) => (
45
+ <QuestionOptionItem
46
+ key={option.id}
47
+ option={option}
48
+ isSelected={safeValue.includes(option.id)}
49
+ onPress={() => handleToggle(option.id)}
50
+ colors={colors}
51
+ />
52
+ ))}
53
+ {question.validation?.maxSelections && (
54
+ <AtomicText
55
+ type="labelSmall"
56
+ style={[styles.hint, { color: colors.subTextColor }]}
57
+ >
58
+ Select up to {question.validation.maxSelections} options
59
+ </AtomicText>
60
+ )}
61
+ </View>
62
+ );
63
+ };
64
+
65
+ const styles = StyleSheet.create({
66
+ container: {
67
+ width: "100%",
68
+ gap: 12,
69
+ },
70
+ hint: {
71
+ textAlign: "center",
72
+ marginTop: 8,
73
+ },
74
+ });
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Question Option Item Component
3
+ * Single Responsibility: Render a single selectable option
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
9
+ import type { QuestionOption } from "../../../domain/entities/OnboardingQuestion";
10
+ import type { OnboardingColors } from "../../types/OnboardingTheme";
11
+
12
+ export interface QuestionOptionItemProps {
13
+ option: QuestionOption;
14
+ isSelected: boolean;
15
+ onPress: () => void;
16
+ colors: OnboardingColors;
17
+ }
18
+
19
+ export const QuestionOptionItem = ({
20
+ option,
21
+ isSelected,
22
+ onPress,
23
+ colors,
24
+ }: QuestionOptionItemProps) => {
25
+ const isEmoji = option.iconType === 'emoji';
26
+
27
+ return (
28
+ <TouchableOpacity
29
+ style={[
30
+ styles.option,
31
+ {
32
+ backgroundColor: isSelected ? colors.iconBg : colors.featureItemBg,
33
+ borderColor: isSelected ? colors.iconColor : colors.headerButtonBorder,
34
+ borderWidth: isSelected ? 2 : 1,
35
+ }
36
+ ]}
37
+ onPress={onPress}
38
+ activeOpacity={0.8}
39
+ >
40
+ {option.icon && (
41
+ <View style={[
42
+ styles.optionIcon,
43
+ { backgroundColor: isSelected ? colors.iconColor : colors.featureItemBg }
44
+ ]}>
45
+ {isEmoji ? (
46
+ <AtomicText style={{ fontSize: 24 }}>{option.icon}</AtomicText>
47
+ ) : (
48
+ <AtomicIcon
49
+ name={option.icon as any}
50
+ customSize={20}
51
+ customColor={isSelected ? colors.buttonTextColor : colors.subTextColor}
52
+ />
53
+ )}
54
+ </View>
55
+ )}
56
+ <AtomicText
57
+ type="bodyLarge"
58
+ style={[
59
+ styles.optionLabel,
60
+ {
61
+ color: isSelected ? colors.textColor : colors.subTextColor,
62
+ fontWeight: isSelected ? '700' : '500'
63
+ }
64
+ ]}
65
+ >
66
+ {option.label}
67
+ </AtomicText>
68
+ <View style={[
69
+ styles.checkbox,
70
+ {
71
+ borderColor: isSelected ? colors.iconColor : colors.headerButtonBorder,
72
+ backgroundColor: isSelected ? colors.iconColor : 'transparent',
73
+ borderWidth: isSelected ? 0 : 2,
74
+ }
75
+ ]}>
76
+ {isSelected && (
77
+ <AtomicIcon
78
+ name="checkmark"
79
+ customSize={16}
80
+ customColor={colors.buttonTextColor}
81
+ />
82
+ )}
83
+ </View>
84
+ </TouchableOpacity>
85
+ );
86
+ };
87
+
88
+ const styles = StyleSheet.create({
89
+ option: {
90
+ flexDirection: "row",
91
+ alignItems: "center",
92
+ borderRadius: 20,
93
+ padding: 16,
94
+ marginBottom: 8,
95
+ },
96
+ optionIcon: {
97
+ width: 40,
98
+ height: 40,
99
+ borderRadius: 20,
100
+ alignItems: 'center',
101
+ justifyContent: 'center',
102
+ marginRight: 16,
103
+ },
104
+ optionLabel: {
105
+ flex: 1,
106
+ fontSize: 16,
107
+ },
108
+ checkbox: {
109
+ width: 24,
110
+ height: 24,
111
+ borderRadius: 8,
112
+ alignItems: "center",
113
+ justifyContent: "center",
114
+ },
115
+ });
@@ -0,0 +1,66 @@
1
+ import React from "react";
2
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
3
+ import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
+ import { useOnboardingProvider } from "../../providers/OnboardingProvider";
5
+ import type { OnboardingQuestion } from "../../../domain/entities/OnboardingQuestion";
6
+
7
+ export interface RatingQuestionProps {
8
+ question: OnboardingQuestion;
9
+ value: number | undefined;
10
+ onChange: (value: number) => void;
11
+ }
12
+
13
+ export const RatingQuestion = ({
14
+ question,
15
+ value = 0,
16
+ onChange,
17
+ }: RatingQuestionProps) => {
18
+ const {
19
+ theme: { colors },
20
+ } = useOnboardingProvider();
21
+ const max = question.validation?.max ?? 5;
22
+
23
+ return (
24
+ <View style={styles.container}>
25
+ <View style={styles.stars}>
26
+ {Array.from({ length: max }).map((_, i) => {
27
+ const isFilled = i < value;
28
+ return (
29
+ <TouchableOpacity key={i} onPress={() => onChange(i + 1)} activeOpacity={0.8} style={styles.star}>
30
+ <AtomicIcon
31
+ name={isFilled ? "star" : "star-outline"}
32
+ customSize={48}
33
+ customColor={isFilled ? colors.iconColor : colors.headerButtonBorder}
34
+ />
35
+ </TouchableOpacity>
36
+ );
37
+ })}
38
+ </View>
39
+ {value > 0 && (
40
+ <AtomicText type="headlineSmall" style={[styles.valueText, { color: colors.textColor, marginTop: 12 }]}>
41
+ {value} <AtomicText type="bodyMedium" color="textSecondary">/ {max}</AtomicText>
42
+ </AtomicText>
43
+ )}
44
+ </View>
45
+ );
46
+ };
47
+
48
+ const styles = StyleSheet.create({
49
+ container: {
50
+ width: "100%",
51
+ alignItems: "center",
52
+ },
53
+ stars: {
54
+ flexDirection: "row",
55
+ gap: 12,
56
+ marginBottom: 20,
57
+ },
58
+ star: {
59
+ padding: 2,
60
+ },
61
+ valueText: {
62
+ fontWeight: "800",
63
+ },
64
+ });
65
+
66
+
@@ -0,0 +1,117 @@
1
+ import React from "react";
2
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
3
+ import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
+ import { useOnboardingProvider } from "../../providers/OnboardingProvider";
5
+ import type { OnboardingQuestion, QuestionOption } from "../../../domain/entities/OnboardingQuestion";
6
+
7
+ export interface SingleChoiceQuestionProps {
8
+ question: OnboardingQuestion;
9
+ value: string | undefined;
10
+ onChange: (value: string) => void;
11
+ }
12
+
13
+ export const SingleChoiceQuestion = ({
14
+ question,
15
+ value,
16
+ onChange,
17
+ }: SingleChoiceQuestionProps) => {
18
+ const {
19
+ theme: { colors },
20
+ } = useOnboardingProvider();
21
+
22
+ const renderOption = (option: QuestionOption) => {
23
+ const isSelected = value === option.id;
24
+ const isEmoji = option.iconType === 'emoji';
25
+
26
+ return (
27
+ <TouchableOpacity
28
+ key={option.id}
29
+ style={[
30
+ styles.option,
31
+ {
32
+ backgroundColor: isSelected ? colors.iconBg : colors.featureItemBg,
33
+ borderColor: isSelected ? colors.iconColor : colors.headerButtonBorder,
34
+ borderWidth: isSelected ? 2 : 1,
35
+ }
36
+ ]}
37
+ onPress={() => onChange(option.id)}
38
+ activeOpacity={0.8}
39
+ >
40
+ {option.icon && (
41
+ <View style={[
42
+ styles.optionIcon,
43
+ { backgroundColor: isSelected ? colors.iconColor : colors.featureItemBg }
44
+ ]}>
45
+ {isEmoji ? (
46
+ <AtomicText style={{ fontSize: 24 }}>{option.icon}</AtomicText>
47
+ ) : (
48
+ <AtomicIcon
49
+ name={option.icon as any}
50
+ customSize={20}
51
+ customColor={isSelected ? colors.buttonTextColor : colors.subTextColor}
52
+ />
53
+ )}
54
+ </View>
55
+ )}
56
+ <AtomicText type="bodyLarge" style={[styles.optionLabel, { color: isSelected ? colors.textColor : colors.subTextColor, fontWeight: isSelected ? '700' : '500' }]}>
57
+ {option.label}
58
+ </AtomicText>
59
+ <View style={[
60
+ styles.radioOuter,
61
+ { borderColor: isSelected ? colors.iconColor : colors.headerButtonBorder }
62
+ ]}>
63
+ {isSelected && (
64
+ <View style={[styles.radioInner, { backgroundColor: colors.iconColor }]} />
65
+ )}
66
+ </View>
67
+ </TouchableOpacity>
68
+ );
69
+ };
70
+
71
+ return (
72
+ <View style={styles.container}>
73
+ {question.options?.map(renderOption)}
74
+ </View>
75
+ );
76
+ };
77
+
78
+ const styles = StyleSheet.create({
79
+ container: {
80
+ width: "100%",
81
+ gap: 12,
82
+ },
83
+ option: {
84
+ flexDirection: "row",
85
+ alignItems: "center",
86
+ borderRadius: 20,
87
+ padding: 16,
88
+ marginBottom: 8,
89
+ },
90
+ optionIcon: {
91
+ width: 40,
92
+ height: 40,
93
+ borderRadius: 20,
94
+ alignItems: 'center',
95
+ justifyContent: 'center',
96
+ marginRight: 16,
97
+ },
98
+ optionLabel: {
99
+ flex: 1,
100
+ fontSize: 16,
101
+ },
102
+ radioOuter: {
103
+ width: 24,
104
+ height: 24,
105
+ borderRadius: 12,
106
+ borderWidth: 2,
107
+ alignItems: 'center',
108
+ justifyContent: 'center',
109
+ },
110
+ radioInner: {
111
+ width: 12,
112
+ height: 12,
113
+ borderRadius: 6,
114
+ },
115
+ });
116
+
117
+
@@ -0,0 +1,71 @@
1
+ import React from "react";
2
+ import { View, TextInput, StyleSheet } from "react-native";
3
+ import { AtomicText } from "@umituz/react-native-design-system";
4
+ import { useOnboardingProvider } from "../../providers/OnboardingProvider";
5
+ import type { OnboardingQuestion } from "../../../domain/entities/OnboardingQuestion";
6
+
7
+ export interface TextInputQuestionProps {
8
+ question: OnboardingQuestion;
9
+ value: string | undefined;
10
+ onChange: (value: string) => void;
11
+ }
12
+
13
+ export const TextInputQuestion = ({
14
+ question,
15
+ value = "",
16
+ onChange,
17
+ }: TextInputQuestionProps) => {
18
+ const {
19
+ theme: { colors },
20
+ } = useOnboardingProvider();
21
+ const { validation } = question;
22
+
23
+ return (
24
+ <View style={styles.container}>
25
+ <TextInput
26
+ style={[
27
+ styles.input,
28
+ {
29
+ backgroundColor: colors.featureItemBg,
30
+ borderColor: value ? colors.iconColor : colors.headerButtonBorder,
31
+ color: colors.textColor,
32
+ }
33
+ ]}
34
+ value={value}
35
+ onChangeText={onChange}
36
+ placeholder={question.placeholder}
37
+ placeholderTextColor={colors.subTextColor}
38
+ maxLength={validation?.maxLength}
39
+ multiline={(validation?.maxLength ?? 0) > 100}
40
+ numberOfLines={(validation?.maxLength ?? 0) > 100 ? 5 : 1}
41
+ autoCapitalize="sentences"
42
+ autoCorrect={true}
43
+ textAlignVertical="top"
44
+ />
45
+ {validation?.maxLength && (
46
+ <AtomicText type="labelSmall" style={[styles.charCount, { color: colors.subTextColor }]}>
47
+ {value.length} / {validation.maxLength}
48
+ </AtomicText>
49
+ )}
50
+ </View>
51
+ );
52
+ };
53
+
54
+ const styles = StyleSheet.create({
55
+ container: {
56
+ width: "100%",
57
+ },
58
+ input: {
59
+ borderRadius: 16,
60
+ padding: 18,
61
+ fontSize: 16,
62
+ borderWidth: 2,
63
+ minHeight: 60,
64
+ },
65
+ charCount: {
66
+ textAlign: "right",
67
+ marginTop: 8,
68
+ fontWeight: "600",
69
+ },
70
+ });
71
+