@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.
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
211
|
-
() =>
|
|
212
|
-
|
|
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
|
-
<
|
|
217
|
-
|
|
218
|
-
{useGradient
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
{
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
{
|
|
244
|
-
|
|
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
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
+
});
|