@umituz/react-native-onboarding 2.6.6 → 2.6.8

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-onboarding",
3
- "version": "2.6.6",
3
+ "version": "2.6.8",
4
4
  "description": "Advanced onboarding flow for React Native apps with personalization questions, theme-aware colors, animations, and customizable slides. SOLID, DRY, KISS principles applied.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,85 @@
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 { SliderQuestion } from "./questions/SliderQuestion";
12
+ import { RatingQuestion } from "./questions/RatingQuestion";
13
+
14
+ export interface QuestionRendererProps {
15
+ question: OnboardingQuestion;
16
+ value: any;
17
+ onChange: (value: any) => void;
18
+ SliderComponent?: React.ComponentType<{
19
+ style?: any;
20
+ minimumValue: number;
21
+ maximumValue: number;
22
+ value: number;
23
+ onValueChange: (value: number) => void;
24
+ minimumTrackTintColor?: string;
25
+ maximumTrackTintColor?: string;
26
+ thumbTintColor?: string;
27
+ step?: number;
28
+ }>;
29
+ }
30
+
31
+ export const QuestionRenderer: React.FC<QuestionRendererProps> = ({
32
+ question,
33
+ value,
34
+ onChange,
35
+ SliderComponent,
36
+ }) => {
37
+ switch (question.type) {
38
+ case "single_choice":
39
+ return (
40
+ <SingleChoiceQuestion
41
+ question={question}
42
+ value={value}
43
+ onChange={onChange}
44
+ />
45
+ );
46
+ case "multiple_choice":
47
+ return (
48
+ <MultipleChoiceQuestion
49
+ question={question}
50
+ value={value}
51
+ onChange={onChange}
52
+ />
53
+ );
54
+ case "text_input":
55
+ return (
56
+ <TextInputQuestion
57
+ question={question}
58
+ value={value}
59
+ onChange={onChange}
60
+ />
61
+ );
62
+ case "slider":
63
+ if (!SliderComponent) {
64
+ return null;
65
+ }
66
+ return (
67
+ <SliderQuestion
68
+ question={question}
69
+ value={value}
70
+ onChange={onChange}
71
+ SliderComponent={SliderComponent}
72
+ />
73
+ );
74
+ case "rating":
75
+ return (
76
+ <RatingQuestion
77
+ question={question}
78
+ value={value}
79
+ onChange={onChange}
80
+ />
81
+ );
82
+ default:
83
+ return null;
84
+ }
85
+ };
@@ -1,25 +1,31 @@
1
1
  /**
2
2
  * Question Slide Component
3
- *
4
- * Displays a personalization question slide
3
+ * Single Responsibility: Display question slide with header and question
5
4
  */
6
5
 
7
6
  import React, { useMemo } from "react";
8
7
  import { View, Text, StyleSheet, ScrollView } from "react-native";
9
- import { AtomicIcon } from "@umituz/react-native-design-system-atoms";
10
- import { useAppDesignTokens, withAlpha } from "@umituz/react-native-design-system-theme";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
11
9
  import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
12
- import { SingleChoiceQuestion } from "./questions/SingleChoiceQuestion";
13
- import { MultipleChoiceQuestion } from "./questions/MultipleChoiceQuestion";
14
- import { TextInputQuestion } from "./questions/TextInputQuestion";
15
- import { SliderQuestion } from "./questions/SliderQuestion";
16
- import { RatingQuestion } from "./questions/RatingQuestion";
10
+ import { QuestionSlideHeader } from "./QuestionSlideHeader";
11
+ import { QuestionRenderer } from "./QuestionRenderer";
17
12
 
18
13
  export interface QuestionSlideProps {
19
14
  slide: OnboardingSlide;
20
15
  value: any;
21
16
  onChange: (value: any) => void;
22
17
  useGradient?: boolean;
18
+ SliderComponent?: React.ComponentType<{
19
+ style?: any;
20
+ minimumValue: number;
21
+ maximumValue: number;
22
+ value: number;
23
+ onValueChange: (value: number) => void;
24
+ minimumTrackTintColor?: string;
25
+ maximumTrackTintColor?: string;
26
+ thumbTintColor?: string;
27
+ step?: number;
28
+ }>;
23
29
  }
24
30
 
25
31
  export const QuestionSlide: React.FC<QuestionSlideProps> = ({
@@ -27,6 +33,7 @@ export const QuestionSlide: React.FC<QuestionSlideProps> = ({
27
33
  value,
28
34
  onChange,
29
35
  useGradient = false,
36
+ SliderComponent,
30
37
  }) => {
31
38
  const tokens = useAppDesignTokens();
32
39
  const styles = useMemo(() => getStyles(tokens, useGradient), [tokens, useGradient]);
@@ -36,55 +43,24 @@ export const QuestionSlide: React.FC<QuestionSlideProps> = ({
36
43
  return null;
37
44
  }
38
45
 
39
- // Check if icon is an emoji or Lucide icon name
40
- const isEmoji = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u.test(slide.icon);
41
-
42
- const renderQuestion = () => {
43
- switch (question.type) {
44
- case "single_choice":
45
- return (
46
- <SingleChoiceQuestion
47
- question={question}
48
- value={value}
49
- onChange={onChange}
50
- />
51
- );
52
- case "multiple_choice":
53
- return (
54
- <MultipleChoiceQuestion
55
- question={question}
56
- value={value}
57
- onChange={onChange}
58
- />
59
- );
60
- case "text_input":
61
- return (
62
- <TextInputQuestion
63
- question={question}
64
- value={value}
65
- onChange={onChange}
66
- />
67
- );
68
- case "slider":
69
- return (
70
- <SliderQuestion
71
- question={question}
72
- value={value}
73
- onChange={onChange}
74
- />
75
- );
76
- case "rating":
77
- return (
78
- <RatingQuestion
79
- question={question}
80
- value={value}
81
- onChange={onChange}
82
- />
83
- );
84
- default:
85
- return null;
86
- }
87
- };
46
+ const content = (
47
+ <>
48
+ <QuestionSlideHeader slide={slide} useGradient={useGradient} />
49
+
50
+ <View style={styles.questionContainer}>
51
+ <QuestionRenderer
52
+ question={question}
53
+ value={value}
54
+ onChange={onChange}
55
+ SliderComponent={SliderComponent}
56
+ />
57
+ </View>
58
+
59
+ {question.validation?.required && !value && (
60
+ <Text style={styles.requiredHint}>* This field is required</Text>
61
+ )}
62
+ </>
63
+ );
88
64
 
89
65
  return (
90
66
  <ScrollView
@@ -92,75 +68,18 @@ export const QuestionSlide: React.FC<QuestionSlideProps> = ({
92
68
  showsVerticalScrollIndicator={false}
93
69
  >
94
70
  {useGradient ? (
95
- // Gradient kullanıldığında card olmadan direkt içerik
96
- <>
97
- {/* Icon */}
98
- <View style={styles.iconContainer}>
99
- {isEmoji ? (
100
- <Text style={styles.icon}>{slide.icon}</Text>
101
- ) : (
102
- <AtomicIcon
103
- name={slide.icon as any}
104
- customSize={48}
105
- customColor="#FFFFFF"
106
- />
107
- )}
108
- </View>
109
-
110
- {/* Title */}
111
- <Text style={styles.title}>{slide.title}</Text>
112
-
113
- {/* Description */}
114
- {slide.description && (
115
- <Text style={styles.description}>{slide.description}</Text>
116
- )}
117
-
118
- {/* Question */}
119
- <View style={styles.questionContainer}>{renderQuestion()}</View>
120
-
121
- {/* Validation hint */}
122
- {question.validation?.required && !value && (
123
- <Text style={styles.requiredHint}>* This field is required</Text>
124
- )}
125
- </>
71
+ content
126
72
  ) : (
127
- // Normal modda card ile
128
- <View style={styles.slideContent}>
129
- {/* Icon */}
130
- <View style={styles.iconContainer}>
131
- {isEmoji ? (
132
- <Text style={styles.icon}>{slide.icon}</Text>
133
- ) : (
134
- <AtomicIcon
135
- name={slide.icon as any}
136
- customSize={48}
137
- customColor={tokens.colors.textPrimary}
138
- />
139
- )}
140
- </View>
141
-
142
- {/* Title */}
143
- <Text style={styles.title}>{slide.title}</Text>
144
-
145
- {/* Description */}
146
- {slide.description && (
147
- <Text style={styles.description}>{slide.description}</Text>
148
- )}
149
-
150
- {/* Question */}
151
- <View style={styles.questionContainer}>{renderQuestion()}</View>
152
-
153
- {/* Validation hint */}
154
- {question.validation?.required && !value && (
155
- <Text style={styles.requiredHint}>* This field is required</Text>
156
- )}
157
- </View>
73
+ <View style={styles.slideContent}>{content}</View>
158
74
  )}
159
75
  </ScrollView>
160
76
  );
161
77
  };
162
78
 
163
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>, useGradient: boolean) =>
79
+ const getStyles = (
80
+ tokens: ReturnType<typeof useAppDesignTokens>,
81
+ useGradient: boolean,
82
+ ) =>
164
83
  StyleSheet.create({
165
84
  content: {
166
85
  flexGrow: 1,
@@ -187,38 +106,6 @@ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>, useGradient: b
187
106
  shadowRadius: 8,
188
107
  elevation: 4,
189
108
  },
190
- iconContainer: {
191
- width: 96,
192
- height: 96,
193
- borderRadius: 48,
194
- backgroundColor: useGradient
195
- ? "rgba(255, 255, 255, 0.25)"
196
- : withAlpha(tokens.colors.primary, 0.2),
197
- alignItems: "center",
198
- justifyContent: "center",
199
- marginBottom: 24,
200
- borderWidth: 2,
201
- borderColor: useGradient
202
- ? "rgba(255, 255, 255, 0.4)"
203
- : withAlpha(tokens.colors.primary, 0.4),
204
- },
205
- icon: {
206
- fontSize: 48,
207
- },
208
- title: {
209
- fontSize: 24,
210
- fontWeight: "bold",
211
- color: useGradient ? "#FFFFFF" : tokens.colors.textPrimary,
212
- textAlign: "center",
213
- marginBottom: 12,
214
- },
215
- description: {
216
- fontSize: 15,
217
- color: useGradient ? "rgba(255, 255, 255, 0.9)" : tokens.colors.textSecondary,
218
- textAlign: "center",
219
- lineHeight: 22,
220
- marginBottom: 24,
221
- },
222
109
  questionContainer: {
223
110
  width: "100%",
224
111
  marginTop: 8,
@@ -230,4 +117,3 @@ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>, useGradient: b
230
117
  marginTop: 12,
231
118
  },
232
119
  });
233
-
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Question Slide Header Component
3
+ * Single Responsibility: Display slide header (icon, title, description)
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, Text, StyleSheet } from "react-native";
8
+ import { AtomicIcon } from "@umituz/react-native-design-system-atoms";
9
+ import { useAppDesignTokens, withAlpha } from "@umituz/react-native-design-system-theme";
10
+ import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
11
+
12
+ export interface QuestionSlideHeaderProps {
13
+ slide: OnboardingSlide;
14
+ useGradient: boolean;
15
+ }
16
+
17
+ const isEmoji = (icon: string): boolean =>
18
+ /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u.test(icon);
19
+
20
+ export const QuestionSlideHeader: React.FC<QuestionSlideHeaderProps> = ({
21
+ slide,
22
+ useGradient,
23
+ }) => {
24
+ const tokens = useAppDesignTokens();
25
+ const styles = getStyles(tokens, useGradient);
26
+
27
+ return (
28
+ <>
29
+ <View style={styles.iconContainer}>
30
+ {isEmoji(slide.icon) ? (
31
+ <Text style={styles.icon}>{slide.icon}</Text>
32
+ ) : (
33
+ <AtomicIcon
34
+ name={slide.icon as any}
35
+ customSize={48}
36
+ customColor={useGradient ? "#FFFFFF" : tokens.colors.textPrimary}
37
+ />
38
+ )}
39
+ </View>
40
+
41
+ <Text style={styles.title}>{slide.title}</Text>
42
+
43
+ {slide.description && <Text style={styles.description}>{slide.description}</Text>}
44
+ </>
45
+ );
46
+ };
47
+
48
+ const getStyles = (
49
+ tokens: ReturnType<typeof useAppDesignTokens>,
50
+ useGradient: boolean,
51
+ ) =>
52
+ StyleSheet.create({
53
+ iconContainer: {
54
+ width: 96,
55
+ height: 96,
56
+ borderRadius: 48,
57
+ backgroundColor: useGradient
58
+ ? "rgba(255, 255, 255, 0.25)"
59
+ : withAlpha(tokens.colors.primary, 0.2),
60
+ alignItems: "center",
61
+ justifyContent: "center",
62
+ marginBottom: 24,
63
+ borderWidth: 2,
64
+ borderColor: useGradient
65
+ ? "rgba(255, 255, 255, 0.4)"
66
+ : withAlpha(tokens.colors.primary, 0.4),
67
+ },
68
+ icon: {
69
+ fontSize: 48,
70
+ },
71
+ title: {
72
+ fontSize: 24,
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
+ });
86
+
@@ -1,28 +1,43 @@
1
1
  /**
2
2
  * Slider Question Component
3
- *
4
- * Slider for numeric value selection
3
+ * Single Responsibility: Display slider for numeric value selection
5
4
  */
6
5
 
7
- import React from "react";
6
+ import React, { useMemo } from "react";
8
7
  import { View, Text, StyleSheet } from "react-native";
9
- import Slider from "@react-native-community/slider";
10
8
  import type { OnboardingQuestion } from "../../../domain/entities/OnboardingQuestion";
11
9
 
12
10
  export interface SliderQuestionProps {
13
11
  question: OnboardingQuestion;
14
12
  value: number | undefined;
15
13
  onChange: (value: number) => void;
14
+ SliderComponent: React.ComponentType<{
15
+ style?: any;
16
+ minimumValue: number;
17
+ maximumValue: number;
18
+ value: number;
19
+ onValueChange: (value: number) => void;
20
+ minimumTrackTintColor?: string;
21
+ maximumTrackTintColor?: string;
22
+ thumbTintColor?: string;
23
+ step?: number;
24
+ }>;
16
25
  }
17
26
 
27
+ const getSliderConfig = (question: OnboardingQuestion) => {
28
+ const { validation } = question;
29
+ const min = validation?.min ?? 0;
30
+ const max = validation?.max ?? 100;
31
+ return { min, max };
32
+ };
33
+
18
34
  export const SliderQuestion: React.FC<SliderQuestionProps> = ({
19
35
  question,
20
36
  value,
21
37
  onChange,
38
+ SliderComponent,
22
39
  }) => {
23
- const { validation } = question;
24
- const min = validation?.min ?? 0;
25
- const max = validation?.max ?? 100;
40
+ const { min, max } = useMemo(() => getSliderConfig(question), [question]);
26
41
  const currentValue = value ?? min;
27
42
 
28
43
  return (
@@ -30,7 +45,7 @@ export const SliderQuestion: React.FC<SliderQuestionProps> = ({
30
45
  <View style={styles.valueContainer}>
31
46
  <Text style={styles.valueText}>{currentValue}</Text>
32
47
  </View>
33
- <Slider
48
+ <SliderComponent
34
49
  style={styles.slider}
35
50
  minimumValue={min}
36
51
  maximumValue={max}
@@ -78,4 +93,3 @@ const styles = StyleSheet.create({
78
93
  fontWeight: "500",
79
94
  },
80
95
  });
81
-
@@ -62,6 +62,23 @@ export interface OnboardingScreenProps extends OnboardingOptions {
62
62
  * When true, shows premium paywall before completing onboarding
63
63
  */
64
64
  showPaywallOnComplete?: boolean;
65
+
66
+ /**
67
+ * Slider component for slider questions
68
+ * Required if using slider question type
69
+ * Import from @react-native-community/slider
70
+ */
71
+ SliderComponent?: React.ComponentType<{
72
+ style?: any;
73
+ minimumValue: number;
74
+ maximumValue: number;
75
+ value: number;
76
+ onValueChange: (value: number) => void;
77
+ minimumTrackTintColor?: string;
78
+ maximumTrackTintColor?: string;
79
+ thumbTintColor?: string;
80
+ step?: number;
81
+ }>;
65
82
  }
66
83
 
67
84
  /**
@@ -89,6 +106,7 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
89
106
  onUpgrade,
90
107
  showPaywallOnComplete = false,
91
108
  useGradient: globalUseGradient = false,
109
+ SliderComponent,
92
110
  }) => {
93
111
  const insets = useSafeAreaInsets();
94
112
  const tokens = useAppDesignTokens();
@@ -231,6 +249,7 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
231
249
  value={currentAnswer}
232
250
  onChange={setCurrentAnswer}
233
251
  useGradient={useGradient}
252
+ SliderComponent={SliderComponent}
234
253
  />
235
254
  ) : (
236
255
  <OnboardingSlideComponent slide={currentSlide} useGradient={useGradient} />