@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-onboarding",
3
- "version": "3.6.10",
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 || !!currentSlide?.backgroundVideo;
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