@umituz/react-native-onboarding 2.6.8 → 2.6.9

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.8",
3
+ "version": "2.6.9",
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,173 @@
1
+ /**
2
+ * Onboarding Screen Content Component
3
+ * Single Responsibility: Render onboarding screen content (header, slide, footer)
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet, StatusBar } from "react-native";
8
+ import { LinearGradient } from "expo-linear-gradient";
9
+ import { useTheme } from "@umituz/react-native-design-system-theme";
10
+ import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
11
+ import { OnboardingHeader } from "./OnboardingHeader";
12
+ import { OnboardingSlide as OnboardingSlideComponent } from "./OnboardingSlide";
13
+ import { QuestionSlide } from "./QuestionSlide";
14
+ import { OnboardingFooter } from "./OnboardingFooter";
15
+
16
+ export interface OnboardingScreenContentProps {
17
+ containerStyle?: any;
18
+ useGradient: boolean;
19
+ currentSlide: OnboardingSlide | undefined;
20
+ isFirstSlide: boolean;
21
+ isLastSlide: boolean;
22
+ currentIndex: number;
23
+ totalSlides: number;
24
+ currentAnswer: any;
25
+ isAnswerValid: boolean;
26
+ showBackButton: boolean;
27
+ showSkipButton: boolean;
28
+ showProgressBar: boolean;
29
+ showDots: boolean;
30
+ showProgressText: boolean;
31
+ skipButtonText?: string;
32
+ nextButtonText?: string;
33
+ getStartedButtonText?: string;
34
+ onBack: () => void;
35
+ onSkip: () => void;
36
+ onNext: () => void;
37
+ onAnswerChange: (value: any) => void;
38
+ renderHeader?: (props: {
39
+ isFirstSlide: boolean;
40
+ onBack: () => void;
41
+ onSkip: () => void;
42
+ }) => React.ReactNode;
43
+ renderFooter?: (props: {
44
+ currentIndex: number;
45
+ totalSlides: number;
46
+ isLastSlide: boolean;
47
+ onNext: () => void;
48
+ onUpgrade?: () => void;
49
+ showPaywallOnComplete?: boolean;
50
+ }) => React.ReactNode;
51
+ renderSlide?: (slide: OnboardingSlide) => React.ReactNode;
52
+ onUpgrade?: () => void;
53
+ showPaywallOnComplete?: boolean;
54
+ SliderComponent?: React.ComponentType<{
55
+ style?: any;
56
+ minimumValue: number;
57
+ maximumValue: number;
58
+ value: number;
59
+ onValueChange: (value: number) => void;
60
+ minimumTrackTintColor?: string;
61
+ maximumTrackTintColor?: string;
62
+ thumbTintColor?: string;
63
+ step?: number;
64
+ }>;
65
+ }
66
+
67
+ export const OnboardingScreenContent: React.FC<OnboardingScreenContentProps> = ({
68
+ containerStyle,
69
+ useGradient,
70
+ currentSlide,
71
+ isFirstSlide,
72
+ isLastSlide,
73
+ currentIndex,
74
+ totalSlides,
75
+ currentAnswer,
76
+ isAnswerValid,
77
+ showBackButton,
78
+ showSkipButton,
79
+ showProgressBar,
80
+ showDots,
81
+ showProgressText,
82
+ skipButtonText,
83
+ nextButtonText,
84
+ getStartedButtonText,
85
+ onBack,
86
+ onSkip,
87
+ onNext,
88
+ onAnswerChange,
89
+ renderHeader,
90
+ renderFooter,
91
+ renderSlide,
92
+ onUpgrade,
93
+ showPaywallOnComplete,
94
+ SliderComponent,
95
+ }) => {
96
+ const { themeMode } = useTheme();
97
+
98
+ return (
99
+ <View style={[styles.container, containerStyle]}>
100
+ <StatusBar barStyle={themeMode === "dark" ? "light-content" : "dark-content"} />
101
+ {useGradient && currentSlide && (
102
+ <LinearGradient
103
+ colors={currentSlide.gradient as [string, string, ...string[]]}
104
+ start={{ x: 0, y: 0 }}
105
+ end={{ x: 1, y: 1 }}
106
+ style={StyleSheet.absoluteFill}
107
+ />
108
+ )}
109
+ {renderHeader ? (
110
+ renderHeader({
111
+ isFirstSlide,
112
+ onBack,
113
+ onSkip,
114
+ })
115
+ ) : (
116
+ <OnboardingHeader
117
+ isFirstSlide={isFirstSlide}
118
+ onBack={onBack}
119
+ onSkip={onSkip}
120
+ showBackButton={showBackButton}
121
+ showSkipButton={showSkipButton}
122
+ skipButtonText={skipButtonText}
123
+ useGradient={useGradient}
124
+ />
125
+ )}
126
+ {currentSlide &&
127
+ (renderSlide ? (
128
+ renderSlide(currentSlide)
129
+ ) : currentSlide.type === "question" && currentSlide.question ? (
130
+ <QuestionSlide
131
+ slide={currentSlide}
132
+ value={currentAnswer}
133
+ onChange={onAnswerChange}
134
+ useGradient={useGradient}
135
+ SliderComponent={SliderComponent}
136
+ />
137
+ ) : (
138
+ <OnboardingSlideComponent slide={currentSlide} useGradient={useGradient} />
139
+ ))}
140
+ {renderFooter ? (
141
+ renderFooter({
142
+ currentIndex,
143
+ totalSlides,
144
+ isLastSlide,
145
+ onNext,
146
+ onUpgrade,
147
+ showPaywallOnComplete,
148
+ })
149
+ ) : (
150
+ <OnboardingFooter
151
+ currentIndex={currentIndex}
152
+ totalSlides={totalSlides}
153
+ isLastSlide={isLastSlide}
154
+ onNext={onNext}
155
+ showProgressBar={showProgressBar}
156
+ showDots={showDots}
157
+ showProgressText={showProgressText}
158
+ nextButtonText={nextButtonText}
159
+ getStartedButtonText={getStartedButtonText}
160
+ disabled={!isAnswerValid}
161
+ useGradient={useGradient}
162
+ />
163
+ )}
164
+ </View>
165
+ );
166
+ };
167
+
168
+ const styles = StyleSheet.create({
169
+ container: {
170
+ flex: 1,
171
+ },
172
+ });
173
+
@@ -3,15 +3,14 @@
3
3
  *
4
4
  * Main onboarding screen component with theme-aware colors
5
5
  * Generic and reusable across hundreds of apps
6
- *
6
+ *
7
7
  * This component only handles UI coordination - no business logic
8
8
  */
9
9
 
10
10
  import React, { useMemo } from "react";
11
- import { View, StyleSheet, StatusBar } from "react-native";
12
- import { LinearGradient } from "expo-linear-gradient";
11
+ import { StyleSheet } from "react-native";
13
12
  import { useSafeAreaInsets } from "react-native-safe-area-context";
14
- import { useAppDesignTokens, useTheme } from "@umituz/react-native-design-system-theme";
13
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
15
14
  import type { OnboardingOptions } from "../../domain/entities/OnboardingOptions";
16
15
  import { useOnboardingNavigation } from "../../infrastructure/hooks/useOnboardingNavigation";
17
16
  import { useOnboardingAnswers } from "../../infrastructure/hooks/useOnboardingAnswers";
@@ -19,10 +18,7 @@ import { useOnboardingStore } from "../../infrastructure/storage/OnboardingStore
19
18
  import { OnboardingSlideService } from "../../infrastructure/services/OnboardingSlideService";
20
19
  import { OnboardingValidationService } from "../../infrastructure/services/OnboardingValidationService";
21
20
  import { shouldUseGradient } from "../../infrastructure/utils/gradientUtils";
22
- import { OnboardingHeader } from "../components/OnboardingHeader";
23
- import { OnboardingSlide as OnboardingSlideComponent } from "../components/OnboardingSlide";
24
- import { QuestionSlide } from "../components/QuestionSlide";
25
- import { OnboardingFooter } from "../components/OnboardingFooter";
21
+ import { OnboardingScreenContent } from "../components/OnboardingScreenContent";
26
22
 
27
23
  export interface OnboardingScreenProps extends OnboardingOptions {
28
24
  /**
@@ -81,11 +77,6 @@ export interface OnboardingScreenProps extends OnboardingOptions {
81
77
  }>;
82
78
  }
83
79
 
84
- /**
85
- * Onboarding Screen Component
86
- *
87
- * Displays onboarding flow with theme-aware colors, animations, and navigation
88
- */
89
80
  export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
90
81
  slides,
91
82
  onComplete,
@@ -110,12 +101,10 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
110
101
  }) => {
111
102
  const insets = useSafeAreaInsets();
112
103
  const tokens = useAppDesignTokens();
113
- const { themeMode } = useTheme();
114
104
  const onboardingStore = useOnboardingStore();
115
105
 
116
106
  // Filter slides using service
117
107
  const filteredSlides = useMemo(() => {
118
- // Safety check: if slides is undefined or empty, return empty array
119
108
  if (!slides || !Array.isArray(slides) || slides.length === 0) {
120
109
  return [];
121
110
  }
@@ -165,7 +154,6 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
165
154
  // Handle next slide
166
155
  const handleNext = async () => {
167
156
  await saveCurrentAnswer(currentSlide);
168
-
169
157
  if (isLastSlide) {
170
158
  await completeOnboarding();
171
159
  } else {
@@ -207,93 +195,52 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
207
195
  );
208
196
  }, [currentSlide, currentAnswer]);
209
197
 
210
- const styles = useMemo(
211
- () => getStyles(insets, tokens, useGradient),
212
- [insets, tokens, useGradient],
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],
213
207
  );
214
208
 
215
209
  return (
216
- <View style={styles.container}>
217
- <StatusBar barStyle={themeMode === "dark" ? "light-content" : "dark-content"} />
218
- {useGradient && currentSlide && (
219
- <LinearGradient
220
- colors={currentSlide.gradient as [string, string, ...string[]]}
221
- start={{ x: 0, y: 0 }}
222
- end={{ x: 1, y: 1 }}
223
- style={StyleSheet.absoluteFill}
224
- />
225
- )}
226
- {renderHeader ? (
227
- renderHeader({
228
- isFirstSlide,
229
- onBack: handlePrevious,
230
- onSkip: handleSkip,
231
- })
232
- ) : (
233
- <OnboardingHeader
234
- isFirstSlide={isFirstSlide}
235
- onBack={handlePrevious}
236
- onSkip={handleSkip}
237
- showBackButton={showBackButton}
238
- showSkipButton={showSkipButton}
239
- skipButtonText={skipButtonText}
240
- useGradient={useGradient}
241
- />
242
- )}
243
- {currentSlide && (
244
- renderSlide ? (
245
- renderSlide(currentSlide)
246
- ) : currentSlide.type === "question" && currentSlide.question ? (
247
- <QuestionSlide
248
- slide={currentSlide}
249
- value={currentAnswer}
250
- onChange={setCurrentAnswer}
251
- useGradient={useGradient}
252
- SliderComponent={SliderComponent}
253
- />
254
- ) : (
255
- <OnboardingSlideComponent slide={currentSlide} useGradient={useGradient} />
256
- )
257
- )}
258
- {renderFooter ? (
259
- renderFooter({
260
- currentIndex,
261
- totalSlides: filteredSlides.length,
262
- isLastSlide,
263
- onNext: handleNext,
264
- onUpgrade,
265
- showPaywallOnComplete,
266
- })
267
- ) : (
268
- <OnboardingFooter
269
- currentIndex={currentIndex}
270
- totalSlides={filteredSlides.length}
271
- isLastSlide={isLastSlide}
272
- onNext={handleNext}
273
- showProgressBar={showProgressBar}
274
- showDots={showDots}
275
- showProgressText={showProgressText}
276
- nextButtonText={nextButtonText}
277
- getStartedButtonText={getStartedButtonText}
278
- disabled={!isAnswerValid}
279
- useGradient={useGradient}
280
- />
281
- )}
282
- </View>
210
+ <OnboardingScreenContent
211
+ containerStyle={containerStyle}
212
+ useGradient={useGradient}
213
+ currentSlide={currentSlide}
214
+ isFirstSlide={isFirstSlide}
215
+ isLastSlide={isLastSlide}
216
+ currentIndex={currentIndex}
217
+ totalSlides={filteredSlides.length}
218
+ currentAnswer={currentAnswer}
219
+ isAnswerValid={isAnswerValid}
220
+ showBackButton={showBackButton}
221
+ showSkipButton={showSkipButton}
222
+ showProgressBar={showProgressBar}
223
+ showDots={showDots}
224
+ showProgressText={showProgressText}
225
+ skipButtonText={skipButtonText}
226
+ nextButtonText={nextButtonText}
227
+ getStartedButtonText={getStartedButtonText}
228
+ onBack={handlePrevious}
229
+ onSkip={handleSkip}
230
+ onNext={handleNext}
231
+ onAnswerChange={setCurrentAnswer}
232
+ renderHeader={renderHeader}
233
+ renderFooter={renderFooter}
234
+ renderSlide={renderSlide}
235
+ onUpgrade={onUpgrade}
236
+ showPaywallOnComplete={showPaywallOnComplete}
237
+ SliderComponent={SliderComponent}
238
+ />
283
239
  );
284
240
  };
285
241
 
286
- const getStyles = (
287
- insets: { top: number },
288
- tokens: ReturnType<typeof useAppDesignTokens>,
289
- useGradient: boolean,
290
- ) =>
291
- StyleSheet.create({
292
- container: {
293
- flex: 1,
294
- paddingTop: insets.top,
295
- // Use transparent background when gradient is used, otherwise use theme background
296
- backgroundColor: useGradient ? 'transparent' : tokens.colors.backgroundPrimary,
297
- },
298
- });
299
-
242
+ const styles = StyleSheet.create({
243
+ container: {
244
+ flex: 1,
245
+ },
246
+ });