@umituz/react-native-onboarding 3.6.10 → 3.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/package.json +1 -1
- package/src/domain/entities/OnboardingSlide.ts +16 -0
- package/src/index.ts +1 -0
- package/src/presentation/components/BackgroundImageCollage.tsx +159 -0
- package/src/presentation/components/OnboardingBackground.tsx +10 -0
- package/src/presentation/components/OnboardingScreenContent.tsx +3 -1
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.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",
|
|
@@ -86,6 +86,22 @@ export interface OnboardingSlide {
|
|
|
86
86
|
*/
|
|
87
87
|
backgroundImage?: any;
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Optional multiple background images (URLs or require paths)
|
|
91
|
+
* Displayed in a collage/grid pattern behind content
|
|
92
|
+
* If provided, takes precedence over single backgroundImage
|
|
93
|
+
*/
|
|
94
|
+
backgroundImages?: any[];
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Layout pattern for multiple background images
|
|
98
|
+
* 'grid' - Equal sized grid
|
|
99
|
+
* 'masonry' - Pinterest-style masonry layout
|
|
100
|
+
* 'collage' - Random sizes and positions
|
|
101
|
+
* Default: 'grid'
|
|
102
|
+
*/
|
|
103
|
+
backgroundImagesLayout?: 'grid' | 'masonry' | 'collage';
|
|
104
|
+
|
|
89
105
|
/**
|
|
90
106
|
* Optional background video (URL or require path)
|
|
91
107
|
* Plays in loop behind content
|
package/src/index.ts
CHANGED
|
@@ -69,6 +69,7 @@ export { OnboardingScreen, type OnboardingScreenProps } from "./presentation/scr
|
|
|
69
69
|
export { OnboardingHeader, type OnboardingHeaderProps } from "./presentation/components/OnboardingHeader";
|
|
70
70
|
export { OnboardingFooter, type OnboardingFooterProps } from "./presentation/components/OnboardingFooter";
|
|
71
71
|
export { OnboardingProvider, type OnboardingProviderProps, useOnboardingProvider } from "./presentation/providers/OnboardingProvider";
|
|
72
|
+
export { BackgroundImageCollage } from "./presentation/components/BackgroundImageCollage";
|
|
72
73
|
export type { OnboardingTheme, OnboardingColors } from "./presentation/types/OnboardingTheme";
|
|
73
74
|
|
|
74
75
|
// Export OnboardingSlide component
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background Image Collage Component
|
|
3
|
+
* Displays multiple images in various layout patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { View, StyleSheet, Dimensions } from "react-native";
|
|
8
|
+
import { Image } from "expo-image";
|
|
9
|
+
|
|
10
|
+
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
|
|
11
|
+
|
|
12
|
+
interface BackgroundImageCollageProps {
|
|
13
|
+
images: any[];
|
|
14
|
+
layout?: 'grid' | 'masonry' | 'collage';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ImageLayout {
|
|
18
|
+
source: any;
|
|
19
|
+
style: {
|
|
20
|
+
position: 'absolute';
|
|
21
|
+
top: number;
|
|
22
|
+
left: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const BackgroundImageCollage: React.FC<BackgroundImageCollageProps> = ({
|
|
29
|
+
images,
|
|
30
|
+
layout = 'grid',
|
|
31
|
+
}) => {
|
|
32
|
+
const imageLayouts = useMemo(() => {
|
|
33
|
+
if (images.length === 0) return [];
|
|
34
|
+
|
|
35
|
+
switch (layout) {
|
|
36
|
+
case 'grid':
|
|
37
|
+
return generateGridLayout(images);
|
|
38
|
+
case 'masonry':
|
|
39
|
+
return generateMasonryLayout(images);
|
|
40
|
+
case 'collage':
|
|
41
|
+
return generateCollageLayout(images);
|
|
42
|
+
default:
|
|
43
|
+
return generateGridLayout(images);
|
|
44
|
+
}
|
|
45
|
+
}, [images, layout]);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<View style={StyleSheet.absoluteFill} pointerEvents="none">
|
|
49
|
+
{imageLayouts.map((item, index) => (
|
|
50
|
+
<Image
|
|
51
|
+
key={index}
|
|
52
|
+
source={item.source}
|
|
53
|
+
style={item.style}
|
|
54
|
+
contentFit="cover"
|
|
55
|
+
transition={300}
|
|
56
|
+
/>
|
|
57
|
+
))}
|
|
58
|
+
</View>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const generateGridLayout = (images: any[]): ImageLayout[] => {
|
|
63
|
+
const count = images.length;
|
|
64
|
+
const cols = Math.ceil(Math.sqrt(count));
|
|
65
|
+
const rows = Math.ceil(count / cols);
|
|
66
|
+
const cellWidth = SCREEN_WIDTH / cols;
|
|
67
|
+
const cellHeight = SCREEN_HEIGHT / rows;
|
|
68
|
+
|
|
69
|
+
return images.map((source, index) => {
|
|
70
|
+
const col = index % cols;
|
|
71
|
+
const row = Math.floor(index / cols);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
source,
|
|
75
|
+
style: {
|
|
76
|
+
position: 'absolute' as const,
|
|
77
|
+
left: col * cellWidth,
|
|
78
|
+
top: row * cellHeight,
|
|
79
|
+
width: cellWidth,
|
|
80
|
+
height: cellHeight,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const generateMasonryLayout = (images: any[]): ImageLayout[] => {
|
|
87
|
+
const cols = 3;
|
|
88
|
+
const colWidth = SCREEN_WIDTH / cols;
|
|
89
|
+
const columnHeights = new Array(cols).fill(0);
|
|
90
|
+
|
|
91
|
+
return images.map((source, _index) => {
|
|
92
|
+
const shortestCol = columnHeights.indexOf(Math.min(...columnHeights));
|
|
93
|
+
const height = colWidth * (0.7 + Math.random() * 0.6);
|
|
94
|
+
|
|
95
|
+
const layout: ImageLayout = {
|
|
96
|
+
source,
|
|
97
|
+
style: {
|
|
98
|
+
position: 'absolute' as const,
|
|
99
|
+
left: shortestCol * colWidth,
|
|
100
|
+
top: columnHeights[shortestCol],
|
|
101
|
+
width: colWidth,
|
|
102
|
+
height,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
columnHeights[shortestCol] += height;
|
|
107
|
+
return layout;
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const generateCollageLayout = (images: any[]): ImageLayout[] => {
|
|
112
|
+
const layouts: ImageLayout[] = [];
|
|
113
|
+
const minSize = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 0.25;
|
|
114
|
+
const maxSize = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 0.45;
|
|
115
|
+
|
|
116
|
+
images.forEach((source, _index) => {
|
|
117
|
+
const size = minSize + Math.random() * (maxSize - minSize);
|
|
118
|
+
const maxX = SCREEN_WIDTH - size;
|
|
119
|
+
const maxY = SCREEN_HEIGHT - size;
|
|
120
|
+
|
|
121
|
+
let attempts = 0;
|
|
122
|
+
let overlaps = true;
|
|
123
|
+
let left = 0;
|
|
124
|
+
let top = 0;
|
|
125
|
+
|
|
126
|
+
while (overlaps && attempts < 50) {
|
|
127
|
+
left = Math.random() * maxX;
|
|
128
|
+
top = Math.random() * maxY;
|
|
129
|
+
overlaps = false;
|
|
130
|
+
|
|
131
|
+
for (const existing of layouts) {
|
|
132
|
+
if (
|
|
133
|
+
left < existing.style.left + existing.style.width &&
|
|
134
|
+
left + size > existing.style.left &&
|
|
135
|
+
top < existing.style.top + existing.style.height &&
|
|
136
|
+
top + size > existing.style.top
|
|
137
|
+
) {
|
|
138
|
+
overlaps = true;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
attempts++;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
layouts.push({
|
|
147
|
+
source,
|
|
148
|
+
style: {
|
|
149
|
+
position: 'absolute' as const,
|
|
150
|
+
left,
|
|
151
|
+
top,
|
|
152
|
+
width: size,
|
|
153
|
+
height: size,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return layouts;
|
|
159
|
+
};
|
|
@@ -9,6 +9,7 @@ import { LinearGradient } from "expo-linear-gradient";
|
|
|
9
9
|
import { Image } from "expo-image";
|
|
10
10
|
import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
|
|
11
11
|
import { BackgroundVideo } from "./BackgroundVideo";
|
|
12
|
+
import { BackgroundImageCollage } from "./BackgroundImageCollage";
|
|
12
13
|
|
|
13
14
|
interface OnboardingBackgroundProps {
|
|
14
15
|
currentSlide: OnboardingSlide | undefined;
|
|
@@ -35,6 +36,15 @@ export const OnboardingBackground: React.FC<OnboardingBackgroundProps> = ({
|
|
|
35
36
|
);
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
if (currentSlide.backgroundImages && currentSlide.backgroundImages.length > 0) {
|
|
40
|
+
return (
|
|
41
|
+
<BackgroundImageCollage
|
|
42
|
+
images={currentSlide.backgroundImages}
|
|
43
|
+
layout={currentSlide.backgroundImagesLayout || 'grid'}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
if (currentSlide.backgroundImage) {
|
|
39
49
|
return (
|
|
40
50
|
<Image
|
|
@@ -53,7 +53,9 @@ export const OnboardingScreenContent = ({
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
const hasMedia =
|
|
56
|
-
!!currentSlide?.backgroundImage ||
|
|
56
|
+
!!currentSlide?.backgroundImage ||
|
|
57
|
+
!!currentSlide?.backgroundVideo ||
|
|
58
|
+
(!!currentSlide?.backgroundImages && currentSlide.backgroundImages.length > 0);
|
|
57
59
|
const overlayOpacity = currentSlide?.overlayOpacity ?? 0.5;
|
|
58
60
|
const effectivelyUseGradient = useGradient || hasMedia;
|
|
59
61
|
|