@umituz/react-native-onboarding 2.6.9 → 2.6.11

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 CHANGED
@@ -156,8 +156,11 @@ const slides: OnboardingSlide[] = [
156
156
  },
157
157
  ];
158
158
 
159
+ import Slider from '@react-native-community/slider';
160
+
159
161
  <OnboardingScreen
160
162
  slides={slides}
163
+ SliderComponent={Slider}
161
164
  onComplete={async () => {
162
165
  const userData = onboardingStore.getUserData();
163
166
  console.log('User answers:', userData.answers);
@@ -185,6 +188,7 @@ const slides: OnboardingSlide[] = [
185
188
  | `showProgressText` | `boolean` | `true` | Show progress text (1 of 5) |
186
189
  | `storageKey` | `string` | - | Custom storage key for completion state |
187
190
  | `autoComplete` | `boolean` | `false` | Auto-complete on last slide |
191
+ | `SliderComponent` | `React.ComponentType` | - | **Required if using slider questions.** Import from `@react-native-community/slider` |
188
192
 
189
193
  ### OnboardingSlide Interface
190
194
 
@@ -246,6 +250,18 @@ interface OnboardingSlide {
246
250
 
247
251
  #### Slider
248
252
 
253
+ **⚠️ Important:** When using slider questions, you must provide the `SliderComponent` prop to `OnboardingScreen`:
254
+
255
+ ```tsx
256
+ import Slider from '@react-native-community/slider';
257
+
258
+ <OnboardingScreen
259
+ slides={slides}
260
+ SliderComponent={Slider}
261
+ onComplete={handleComplete}
262
+ />
263
+ ```
264
+
249
265
  ```typescript
250
266
  {
251
267
  type: 'slider',
@@ -255,6 +271,8 @@ interface OnboardingSlide {
255
271
  }
256
272
  ```
257
273
 
274
+ **Note:** Make sure `@react-native-community/slider` is installed and properly linked (run `pod install` for iOS).
275
+
258
276
  #### Rating
259
277
 
260
278
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-onboarding",
3
- "version": "2.6.9",
3
+ "version": "2.6.11",
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",
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
+ import { View, Text } from "react-native";
7
8
  import type { OnboardingQuestion } from "../../domain/entities/OnboardingQuestion";
8
9
  import { SingleChoiceQuestion } from "./questions/SingleChoiceQuestion";
9
10
  import { MultipleChoiceQuestion } from "./questions/MultipleChoiceQuestion";
@@ -61,7 +62,19 @@ export const QuestionRenderer: React.FC<QuestionRendererProps> = ({
61
62
  );
62
63
  case "slider":
63
64
  if (!SliderComponent) {
64
- return null;
65
+ // Show helpful error message if SliderComponent is not provided
66
+ return (
67
+ <View style={{ padding: 20, alignItems: "center" }}>
68
+ <Text style={{ color: "#FFFFFF", textAlign: "center" }}>
69
+ Slider component is not available. Please provide SliderComponent prop
70
+ to OnboardingScreen.
71
+ </Text>
72
+ <Text style={{ color: "#FFFFFF", textAlign: "center", marginTop: 8, fontSize: 12 }}>
73
+ Example: SliderComponent={Slider} where Slider is imported from
74
+ "@react-native-community/slider"
75
+ </Text>
76
+ </View>
77
+ );
65
78
  }
66
79
  return (
67
80
  <SliderQuestion
@@ -0,0 +1,171 @@
1
+ /**
2
+ * useOnboardingScreenState Hook
3
+ * Single Responsibility: Manage onboarding screen state and handlers
4
+ */
5
+
6
+ import { useMemo } from "react";
7
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
+ import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
10
+ import { useOnboardingStore } from "../../infrastructure/storage/OnboardingStore";
11
+ import { useOnboardingNavigation } from "../../infrastructure/hooks/useOnboardingNavigation";
12
+ import { useOnboardingAnswers } from "../../infrastructure/hooks/useOnboardingAnswers";
13
+ import { OnboardingSlideService } from "../../infrastructure/services/OnboardingSlideService";
14
+ import { OnboardingValidationService } from "../../infrastructure/services/OnboardingValidationService";
15
+ import { shouldUseGradient } from "../../infrastructure/utils/gradientUtils";
16
+
17
+ export interface UseOnboardingScreenStateProps {
18
+ slides: OnboardingSlide[] | undefined;
19
+ storageKey?: string;
20
+ onComplete?: () => void | Promise<void>;
21
+ onSkip?: () => void | Promise<void>;
22
+ globalUseGradient?: boolean;
23
+ }
24
+
25
+ export interface UseOnboardingScreenStateReturn {
26
+ filteredSlides: OnboardingSlide[];
27
+ currentSlide: OnboardingSlide | undefined;
28
+ currentIndex: number;
29
+ isFirstSlide: boolean;
30
+ isLastSlide: boolean;
31
+ currentAnswer: any;
32
+ isAnswerValid: boolean;
33
+ useGradient: boolean;
34
+ containerStyle: any;
35
+ handleNext: () => Promise<void>;
36
+ handlePrevious: () => void;
37
+ handleSkip: () => Promise<void>;
38
+ setCurrentAnswer: (value: any) => void;
39
+ }
40
+
41
+ export function useOnboardingScreenState({
42
+ slides,
43
+ storageKey,
44
+ onComplete,
45
+ onSkip,
46
+ globalUseGradient = false,
47
+ }: UseOnboardingScreenStateProps): UseOnboardingScreenStateReturn {
48
+ const insets = useSafeAreaInsets();
49
+ const tokens = useAppDesignTokens();
50
+ const onboardingStore = useOnboardingStore();
51
+
52
+ // Filter slides using service
53
+ const filteredSlides = useMemo(() => {
54
+ if (!slides || !Array.isArray(slides) || slides.length === 0) {
55
+ return [];
56
+ }
57
+ const userData = onboardingStore.getUserData();
58
+ return OnboardingSlideService.filterSlides(slides, userData);
59
+ }, [slides, onboardingStore]);
60
+
61
+ // Navigation hook
62
+ const {
63
+ currentIndex,
64
+ goToNext,
65
+ goToPrevious,
66
+ complete: completeOnboarding,
67
+ skip: skipOnboarding,
68
+ isLastSlide,
69
+ isFirstSlide,
70
+ } = useOnboardingNavigation(
71
+ filteredSlides.length,
72
+ async () => {
73
+ await onboardingStore.complete(storageKey);
74
+ if (onComplete) {
75
+ await onComplete();
76
+ }
77
+ },
78
+ async () => {
79
+ await onboardingStore.skip(storageKey);
80
+ if (onSkip) {
81
+ await onSkip();
82
+ }
83
+ },
84
+ );
85
+
86
+ // Get current slide
87
+ const currentSlide = useMemo(
88
+ () => OnboardingSlideService.getSlideAtIndex(filteredSlides, currentIndex),
89
+ [filteredSlides, currentIndex],
90
+ );
91
+
92
+ // Answer management hook
93
+ const {
94
+ currentAnswer,
95
+ setCurrentAnswer,
96
+ loadAnswerForSlide,
97
+ saveCurrentAnswer,
98
+ } = useOnboardingAnswers(currentSlide);
99
+
100
+ // Handle next slide
101
+ const handleNext = async () => {
102
+ await saveCurrentAnswer(currentSlide);
103
+ if (isLastSlide) {
104
+ await completeOnboarding();
105
+ } else {
106
+ goToNext();
107
+ const nextSlide = OnboardingSlideService.getSlideAtIndex(
108
+ filteredSlides,
109
+ currentIndex + 1,
110
+ );
111
+ loadAnswerForSlide(nextSlide);
112
+ }
113
+ };
114
+
115
+ // Handle previous slide
116
+ const handlePrevious = () => {
117
+ goToPrevious();
118
+ const prevSlide = OnboardingSlideService.getSlideAtIndex(
119
+ filteredSlides,
120
+ currentIndex - 1,
121
+ );
122
+ loadAnswerForSlide(prevSlide);
123
+ };
124
+
125
+ // Handle skip
126
+ const handleSkip = async () => {
127
+ await skipOnboarding();
128
+ };
129
+
130
+ // Check if gradient should be used
131
+ const useGradient = shouldUseGradient(currentSlide, globalUseGradient);
132
+
133
+ // Validate answer using service
134
+ const isAnswerValid = useMemo(() => {
135
+ if (!currentSlide?.question) {
136
+ return true;
137
+ }
138
+ return OnboardingValidationService.validateAnswer(
139
+ currentSlide.question,
140
+ currentAnswer,
141
+ );
142
+ }, [currentSlide, currentAnswer]);
143
+
144
+ // Container style
145
+ const containerStyle = useMemo(
146
+ () => [
147
+ {
148
+ paddingTop: insets.top,
149
+ backgroundColor: useGradient ? "transparent" : tokens.colors.backgroundPrimary,
150
+ },
151
+ ],
152
+ [insets.top, useGradient, tokens.colors.backgroundPrimary],
153
+ );
154
+
155
+ return {
156
+ filteredSlides,
157
+ currentSlide,
158
+ currentIndex,
159
+ isFirstSlide,
160
+ isLastSlide,
161
+ currentAnswer,
162
+ isAnswerValid,
163
+ useGradient,
164
+ containerStyle,
165
+ handleNext,
166
+ handlePrevious,
167
+ handleSkip,
168
+ setCurrentAnswer,
169
+ };
170
+ }
171
+
@@ -7,17 +7,10 @@
7
7
  * This component only handles UI coordination - no business logic
8
8
  */
9
9
 
10
- import React, { useMemo } from "react";
10
+ import React from "react";
11
11
  import { StyleSheet } from "react-native";
12
- import { useSafeAreaInsets } from "react-native-safe-area-context";
13
- import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
14
12
  import type { OnboardingOptions } from "../../domain/entities/OnboardingOptions";
15
- import { useOnboardingNavigation } from "../../infrastructure/hooks/useOnboardingNavigation";
16
- import { useOnboardingAnswers } from "../../infrastructure/hooks/useOnboardingAnswers";
17
- import { useOnboardingStore } from "../../infrastructure/storage/OnboardingStore";
18
- import { OnboardingSlideService } from "../../infrastructure/services/OnboardingSlideService";
19
- import { OnboardingValidationService } from "../../infrastructure/services/OnboardingValidationService";
20
- import { shouldUseGradient } from "../../infrastructure/utils/gradientUtils";
13
+ import { useOnboardingScreenState } from "../hooks/useOnboardingScreenState";
21
14
  import { OnboardingScreenContent } from "../components/OnboardingScreenContent";
22
15
 
23
16
  export interface OnboardingScreenProps extends OnboardingOptions {
@@ -99,116 +92,31 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
99
92
  useGradient: globalUseGradient = false,
100
93
  SliderComponent,
101
94
  }) => {
102
- const insets = useSafeAreaInsets();
103
- const tokens = useAppDesignTokens();
104
- const onboardingStore = useOnboardingStore();
105
-
106
- // Filter slides using service
107
- const filteredSlides = useMemo(() => {
108
- if (!slides || !Array.isArray(slides) || slides.length === 0) {
109
- return [];
110
- }
111
- const userData = onboardingStore.getUserData();
112
- return OnboardingSlideService.filterSlides(slides, userData);
113
- }, [slides, onboardingStore]);
114
-
115
- // Navigation hook
116
95
  const {
96
+ filteredSlides,
97
+ currentSlide,
117
98
  currentIndex,
118
- goToNext,
119
- goToPrevious,
120
- complete: completeOnboarding,
121
- skip: skipOnboarding,
122
- isLastSlide,
123
99
  isFirstSlide,
124
- } = useOnboardingNavigation(
125
- filteredSlides.length,
126
- async () => {
127
- await onboardingStore.complete(storageKey);
128
- if (onComplete) {
129
- await onComplete();
130
- }
131
- },
132
- async () => {
133
- await onboardingStore.skip(storageKey);
134
- if (onSkip) {
135
- await onSkip();
136
- }
137
- },
138
- );
139
-
140
- // Get current slide
141
- const currentSlide = useMemo(
142
- () => OnboardingSlideService.getSlideAtIndex(filteredSlides, currentIndex),
143
- [filteredSlides, currentIndex],
144
- );
145
-
146
- // Answer management hook
147
- const {
100
+ isLastSlide,
148
101
  currentAnswer,
102
+ isAnswerValid,
103
+ useGradient,
104
+ containerStyle,
105
+ handleNext,
106
+ handlePrevious,
107
+ handleSkip,
149
108
  setCurrentAnswer,
150
- loadAnswerForSlide,
151
- saveCurrentAnswer,
152
- } = useOnboardingAnswers(currentSlide);
153
-
154
- // Handle next slide
155
- const handleNext = async () => {
156
- await saveCurrentAnswer(currentSlide);
157
- if (isLastSlide) {
158
- await completeOnboarding();
159
- } else {
160
- goToNext();
161
- const nextSlide = OnboardingSlideService.getSlideAtIndex(
162
- filteredSlides,
163
- currentIndex + 1,
164
- );
165
- loadAnswerForSlide(nextSlide);
166
- }
167
- };
168
-
169
- // Handle previous slide
170
- const handlePrevious = () => {
171
- goToPrevious();
172
- const prevSlide = OnboardingSlideService.getSlideAtIndex(
173
- filteredSlides,
174
- currentIndex - 1,
175
- );
176
- loadAnswerForSlide(prevSlide);
177
- };
178
-
179
- // Handle skip
180
- const handleSkip = async () => {
181
- await skipOnboarding();
182
- };
183
-
184
- // Check if gradient should be used
185
- const useGradient = shouldUseGradient(currentSlide, globalUseGradient);
186
-
187
- // Validate answer using service
188
- const isAnswerValid = useMemo(() => {
189
- if (!currentSlide?.question) {
190
- return true;
191
- }
192
- return OnboardingValidationService.validateAnswer(
193
- currentSlide.question,
194
- currentAnswer,
195
- );
196
- }, [currentSlide, currentAnswer]);
197
-
198
- const containerStyle = useMemo(
199
- () => [
200
- styles.container,
201
- {
202
- paddingTop: insets.top,
203
- backgroundColor: useGradient ? "transparent" : tokens.colors.backgroundPrimary,
204
- },
205
- ],
206
- [insets.top, useGradient, tokens.colors.backgroundPrimary],
207
- );
109
+ } = useOnboardingScreenState({
110
+ slides,
111
+ storageKey,
112
+ onComplete,
113
+ onSkip,
114
+ globalUseGradient,
115
+ });
208
116
 
209
117
  return (
210
118
  <OnboardingScreenContent
211
- containerStyle={containerStyle}
119
+ containerStyle={[styles.container, containerStyle]}
212
120
  useGradient={useGradient}
213
121
  currentSlide={currentSlide}
214
122
  isFirstSlide={isFirstSlide}