@umituz/react-native-onboarding 3.6.25 → 3.6.27
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 +10 -10
- package/src/domain/entities/OnboardingSlide.ts +1 -1
- package/src/index.ts +2 -2
- package/src/infrastructure/utils/layouts/gridLayouts.ts +21 -11
- package/src/infrastructure/utils/layouts/layoutTypes.ts +8 -0
- package/src/infrastructure/utils/layouts/screenDimensions.ts +3 -3
- package/src/presentation/components/BackgroundImageCollage.tsx +11 -3
- package/src/presentation/components/BaseSlide.tsx +21 -30
- package/src/presentation/hooks/__tests__/useOnboardingContainerStyle.test.ts +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-onboarding",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.27",
|
|
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",
|
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@expo/vector-icons": ">=15.0.0",
|
|
32
|
-
"@umituz/react-native-design-system": "
|
|
33
|
-
"@umituz/react-native-localization": "
|
|
34
|
-
"@umituz/react-native-storage": "
|
|
35
|
-
"expo-image": ">=
|
|
36
|
-
"expo-video": ">=
|
|
37
|
-
"react": ">=
|
|
38
|
-
"react-native": ">=0.
|
|
39
|
-
"react-native-safe-area-context": ">=
|
|
40
|
-
"zustand": ">=
|
|
32
|
+
"@umituz/react-native-design-system": "latest",
|
|
33
|
+
"@umituz/react-native-localization": "latest",
|
|
34
|
+
"@umituz/react-native-storage": "latest",
|
|
35
|
+
"expo-image": ">=3.0.0",
|
|
36
|
+
"expo-video": ">=3.0.0",
|
|
37
|
+
"react": ">=19.0.0",
|
|
38
|
+
"react-native": ">=0.81.0",
|
|
39
|
+
"react-native-safe-area-context": ">=5.0.0",
|
|
40
|
+
"zustand": ">=5.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@expo/vector-icons": "^15.0.0",
|
|
@@ -127,7 +127,7 @@ export interface OnboardingSlide {
|
|
|
127
127
|
backgroundVideo?: any;
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
* Opacity of the overlay
|
|
130
|
+
* Opacity of the overlay color on top of background media
|
|
131
131
|
* Range: 0.0 to 1.0 (Default: 0.5)
|
|
132
132
|
*/
|
|
133
133
|
overlayOpacity?: number;
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* React Native Onboarding - Public API
|
|
3
3
|
*
|
|
4
|
-
* Generic onboarding flow for React Native apps with
|
|
4
|
+
* Generic onboarding flow for React Native apps with custom backgrounds,
|
|
5
5
|
* animations, and customizable slides. Follows SOLID, DRY, KISS principles.
|
|
6
6
|
*
|
|
7
7
|
* Architecture:
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* title: 'Welcome',
|
|
20
20
|
* description: 'Welcome to the app',
|
|
21
21
|
* icon: '🎉',
|
|
22
|
-
*
|
|
22
|
+
* backgroundColor: '#3B82F6',
|
|
23
23
|
* },
|
|
24
24
|
* ]}
|
|
25
25
|
* onComplete={() => console.log('Completed')}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Grid Layout Generators
|
|
3
|
-
* Standard and dense grid layouts
|
|
3
|
+
* Standard and dense grid layouts with safe area support
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { SCREEN_WIDTH, SCREEN_HEIGHT } from "./screenDimensions";
|
|
@@ -10,14 +10,19 @@ export const generateGridLayout = (
|
|
|
10
10
|
images: ImageSourceType[],
|
|
11
11
|
config: LayoutConfig = {},
|
|
12
12
|
): ImageLayoutItem[] => {
|
|
13
|
-
const { columns, gap = 0, borderRadius = 0 } = config;
|
|
13
|
+
const { columns, gap = 0, borderRadius = 0, safeAreaInsets } = config;
|
|
14
14
|
const count = images.length;
|
|
15
15
|
const cols = columns ?? Math.ceil(Math.sqrt(count));
|
|
16
16
|
const rows = Math.ceil(count / cols);
|
|
17
|
+
|
|
18
|
+
const insets = safeAreaInsets ?? { top: 0, bottom: 0, left: 0, right: 0 };
|
|
19
|
+
const availableWidth = SCREEN_WIDTH - insets.left - insets.right;
|
|
20
|
+
const availableHeight = SCREEN_HEIGHT - insets.top - insets.bottom;
|
|
21
|
+
|
|
17
22
|
const totalGapX = gap * (cols - 1);
|
|
18
23
|
const totalGapY = gap * (rows - 1);
|
|
19
|
-
const cellWidth = (
|
|
20
|
-
const cellHeight = (
|
|
24
|
+
const cellWidth = (availableWidth - totalGapX) / cols;
|
|
25
|
+
const cellHeight = (availableHeight - totalGapY) / rows;
|
|
21
26
|
|
|
22
27
|
return images.map((source, index) => {
|
|
23
28
|
const col = index % cols;
|
|
@@ -27,8 +32,8 @@ export const generateGridLayout = (
|
|
|
27
32
|
source,
|
|
28
33
|
style: {
|
|
29
34
|
position: "absolute" as const,
|
|
30
|
-
left: col * (cellWidth + gap),
|
|
31
|
-
top: row * (cellHeight + gap),
|
|
35
|
+
left: insets.left + col * (cellWidth + gap),
|
|
36
|
+
top: insets.top + row * (cellHeight + gap),
|
|
32
37
|
width: cellWidth,
|
|
33
38
|
height: cellHeight,
|
|
34
39
|
borderRadius,
|
|
@@ -41,13 +46,18 @@ export const generateDenseGridLayout = (
|
|
|
41
46
|
images: ImageSourceType[],
|
|
42
47
|
config: LayoutConfig = {},
|
|
43
48
|
): ImageLayoutItem[] => {
|
|
44
|
-
const { columns = 6, gap = 2, borderRadius = 4 } = config;
|
|
49
|
+
const { columns = 6, gap = 2, borderRadius = 4, safeAreaInsets } = config;
|
|
45
50
|
const count = images.length;
|
|
46
51
|
const rows = Math.ceil(count / columns);
|
|
52
|
+
|
|
53
|
+
const insets = safeAreaInsets ?? { top: 0, bottom: 0, left: 0, right: 0 };
|
|
54
|
+
const availableWidth = SCREEN_WIDTH - insets.left - insets.right;
|
|
55
|
+
const availableHeight = SCREEN_HEIGHT - insets.top - insets.bottom;
|
|
56
|
+
|
|
47
57
|
const totalGapX = gap * (columns - 1);
|
|
48
58
|
const totalGapY = gap * (rows - 1);
|
|
49
|
-
const cellWidth = (
|
|
50
|
-
const cellHeight = (
|
|
59
|
+
const cellWidth = (availableWidth - totalGapX) / columns;
|
|
60
|
+
const cellHeight = (availableHeight - totalGapY) / rows;
|
|
51
61
|
|
|
52
62
|
return images.map((source, index) => {
|
|
53
63
|
const col = index % columns;
|
|
@@ -57,8 +67,8 @@ export const generateDenseGridLayout = (
|
|
|
57
67
|
source,
|
|
58
68
|
style: {
|
|
59
69
|
position: "absolute" as const,
|
|
60
|
-
left: col * (cellWidth + gap),
|
|
61
|
-
top: row * (cellHeight + gap),
|
|
70
|
+
left: insets.left + col * (cellWidth + gap),
|
|
71
|
+
top: insets.top + row * (cellHeight + gap),
|
|
62
72
|
width: cellWidth,
|
|
63
73
|
height: cellHeight,
|
|
64
74
|
borderRadius,
|
|
@@ -7,6 +7,13 @@ import type { ImageSource } from "expo-image";
|
|
|
7
7
|
|
|
8
8
|
export type ImageSourceType = ImageSource | number | string;
|
|
9
9
|
|
|
10
|
+
export interface SafeAreaInsets {
|
|
11
|
+
top: number;
|
|
12
|
+
bottom: number;
|
|
13
|
+
left: number;
|
|
14
|
+
right: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
export interface ImageLayoutStyle {
|
|
11
18
|
position: "absolute";
|
|
12
19
|
top: number;
|
|
@@ -26,4 +33,5 @@ export interface LayoutConfig {
|
|
|
26
33
|
gap?: number;
|
|
27
34
|
borderRadius?: number;
|
|
28
35
|
randomizeSize?: boolean;
|
|
36
|
+
safeAreaInsets?: SafeAreaInsets;
|
|
29
37
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Screen Dimensions
|
|
3
|
-
* Centralized screen dimension values
|
|
3
|
+
* Centralized screen dimension values using design system utilities
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { getScreenDimensions } from "@umituz/react-native-design-system";
|
|
7
7
|
|
|
8
|
-
const dimensions =
|
|
8
|
+
const dimensions = getScreenDimensions();
|
|
9
9
|
|
|
10
10
|
export const SCREEN_WIDTH = dimensions.width;
|
|
11
11
|
export const SCREEN_HEIGHT = dimensions.height;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Background Image Collage Component
|
|
3
|
-
* Displays multiple images in various layout patterns
|
|
3
|
+
* Displays multiple images in various layout patterns with safe area support
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { Image } from "expo-image";
|
|
9
|
+
import { useSafeAreaInsets } from "@umituz/react-native-design-system";
|
|
9
10
|
import {
|
|
10
11
|
generateGridLayout,
|
|
11
12
|
generateDenseGridLayout,
|
|
@@ -57,12 +58,19 @@ export const BackgroundImageCollage: React.FC<BackgroundImageCollageProps> = ({
|
|
|
57
58
|
borderRadius,
|
|
58
59
|
opacity = 1,
|
|
59
60
|
}) => {
|
|
61
|
+
const insets = useSafeAreaInsets();
|
|
62
|
+
|
|
60
63
|
const imageLayouts = useMemo(() => {
|
|
61
64
|
if (!images || images.length === 0) return [];
|
|
62
65
|
|
|
63
66
|
const generator = LAYOUT_GENERATORS[layout] ?? generateGridLayout;
|
|
64
|
-
return generator(images, {
|
|
65
|
-
|
|
67
|
+
return generator(images, {
|
|
68
|
+
columns,
|
|
69
|
+
gap,
|
|
70
|
+
borderRadius,
|
|
71
|
+
safeAreaInsets: insets
|
|
72
|
+
});
|
|
73
|
+
}, [images, layout, columns, gap, borderRadius, insets]);
|
|
66
74
|
|
|
67
75
|
if (imageLayouts.length === 0) return null;
|
|
68
76
|
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* Single Responsibility: Provide a base layout for all onboarding slides
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { View,
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { View, ScrollView } from "react-native";
|
|
8
|
+
import { useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
|
|
8
9
|
import type { ContentPosition } from "../../domain/entities/OnboardingSlide";
|
|
9
10
|
|
|
10
11
|
export interface BaseSlideProps {
|
|
@@ -16,41 +17,31 @@ export const BaseSlide = ({
|
|
|
16
17
|
children,
|
|
17
18
|
contentPosition = "center",
|
|
18
19
|
}: BaseSlideProps) => {
|
|
20
|
+
const tokens = useAppDesignTokens();
|
|
21
|
+
const { verticalPadding, horizontalPadding } = useResponsive();
|
|
19
22
|
const isBottom = contentPosition === "bottom";
|
|
20
23
|
|
|
24
|
+
const contentContainerStyle = useMemo(() => ({
|
|
25
|
+
flexGrow: 1,
|
|
26
|
+
paddingVertical: verticalPadding,
|
|
27
|
+
justifyContent: isBottom ? "flex-end" as const : "center" as const,
|
|
28
|
+
paddingBottom: isBottom ? verticalPadding : undefined,
|
|
29
|
+
}), [verticalPadding, isBottom]);
|
|
30
|
+
|
|
31
|
+
const slideContainerStyle = useMemo(() => ({
|
|
32
|
+
width: "100%" as const,
|
|
33
|
+
paddingHorizontal: horizontalPadding,
|
|
34
|
+
alignItems: "center" as const,
|
|
35
|
+
}), [horizontalPadding]);
|
|
36
|
+
|
|
21
37
|
return (
|
|
22
38
|
<ScrollView
|
|
23
|
-
style={
|
|
24
|
-
contentContainerStyle={
|
|
25
|
-
styles.content,
|
|
26
|
-
isBottom ? styles.contentBottom : styles.contentCenter,
|
|
27
|
-
]}
|
|
39
|
+
style={{ flex: 1 }}
|
|
40
|
+
contentContainerStyle={contentContainerStyle}
|
|
28
41
|
showsVerticalScrollIndicator={false}
|
|
29
42
|
bounces={false}
|
|
30
43
|
>
|
|
31
|
-
<View style={
|
|
44
|
+
<View style={slideContainerStyle}>{children}</View>
|
|
32
45
|
</ScrollView>
|
|
33
46
|
);
|
|
34
47
|
};
|
|
35
|
-
|
|
36
|
-
const styles = StyleSheet.create({
|
|
37
|
-
container: {
|
|
38
|
-
flex: 1,
|
|
39
|
-
},
|
|
40
|
-
content: {
|
|
41
|
-
flexGrow: 1,
|
|
42
|
-
paddingVertical: 40,
|
|
43
|
-
},
|
|
44
|
-
contentCenter: {
|
|
45
|
-
justifyContent: "center",
|
|
46
|
-
},
|
|
47
|
-
contentBottom: {
|
|
48
|
-
justifyContent: "flex-end",
|
|
49
|
-
paddingBottom: 40, // Reduced from 140 as footer handles its own padding
|
|
50
|
-
},
|
|
51
|
-
slideContainer: {
|
|
52
|
-
width: "100%",
|
|
53
|
-
paddingHorizontal: 24,
|
|
54
|
-
alignItems: "center",
|
|
55
|
-
},
|
|
56
|
-
});
|
|
@@ -39,9 +39,9 @@ describe('useOnboardingContainerStyle', () => {
|
|
|
39
39
|
} as any);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it('should return container style with
|
|
42
|
+
it('should return container style with custom background disabled', () => {
|
|
43
43
|
const { result } = renderHook(() =>
|
|
44
|
-
useOnboardingContainerStyle({
|
|
44
|
+
useOnboardingContainerStyle({ useCustomBackground: false })
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
expect(result.current.containerStyle).toEqual([
|
|
@@ -52,9 +52,9 @@ describe('useOnboardingContainerStyle', () => {
|
|
|
52
52
|
]);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it('should return container style with
|
|
55
|
+
it('should return container style with custom background enabled', () => {
|
|
56
56
|
const { result } = renderHook(() =>
|
|
57
|
-
useOnboardingContainerStyle({
|
|
57
|
+
useOnboardingContainerStyle({ useCustomBackground: true })
|
|
58
58
|
);
|
|
59
59
|
|
|
60
60
|
expect(result.current.containerStyle).toEqual([
|
|
@@ -74,7 +74,7 @@ describe('useOnboardingContainerStyle', () => {
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
const { result } = renderHook(() =>
|
|
77
|
-
useOnboardingContainerStyle({
|
|
77
|
+
useOnboardingContainerStyle({ useCustomBackground: false })
|
|
78
78
|
);
|
|
79
79
|
|
|
80
80
|
expect(result.current.containerStyle[0].paddingTop).toBe(50);
|
|
@@ -88,7 +88,7 @@ describe('useOnboardingContainerStyle', () => {
|
|
|
88
88
|
} as any);
|
|
89
89
|
|
|
90
90
|
const { result } = renderHook(() =>
|
|
91
|
-
useOnboardingContainerStyle({
|
|
91
|
+
useOnboardingContainerStyle({ useCustomBackground: false })
|
|
92
92
|
);
|
|
93
93
|
|
|
94
94
|
expect(result.current.containerStyle[0].backgroundColor).toBe('#f0f0f0');
|