@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-onboarding",
3
- "version": "3.6.25",
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": ">=2.0.0",
33
- "@umituz/react-native-localization": ">=1.0.0",
34
- "@umituz/react-native-storage": ">=2.6.0",
35
- "expo-image": ">=2.0.0",
36
- "expo-video": ">=1.0.0",
37
- "react": ">=18.2.0",
38
- "react-native": ">=0.74.0",
39
- "react-native-safe-area-context": ">=4.0.0",
40
- "zustand": ">=4.5.2"
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 gradient/color on top of background media
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 gradient backgrounds,
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
- * gradient: ['#3B82F6', '#8B5CF6'],
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 = (SCREEN_WIDTH - totalGapX) / cols;
20
- const cellHeight = (SCREEN_HEIGHT - totalGapY) / rows;
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 = (SCREEN_WIDTH - totalGapX) / columns;
50
- const cellHeight = (SCREEN_HEIGHT - totalGapY) / rows;
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 { Dimensions } from "react-native";
6
+ import { getScreenDimensions } from "@umituz/react-native-design-system";
7
7
 
8
- const dimensions = Dimensions.get("window");
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, { columns, gap, borderRadius });
65
- }, [images, layout, columns, gap, borderRadius]);
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, StyleSheet, ScrollView } from "react-native";
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={styles.container}
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={styles.slideContainer}>{children}</View>
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 gradient disabled', () => {
42
+ it('should return container style with custom background disabled', () => {
43
43
  const { result } = renderHook(() =>
44
- useOnboardingContainerStyle({ useGradient: false })
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 gradient enabled', () => {
55
+ it('should return container style with custom background enabled', () => {
56
56
  const { result } = renderHook(() =>
57
- useOnboardingContainerStyle({ useGradient: true })
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({ useGradient: false })
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({ useGradient: false })
91
+ useOnboardingContainerStyle({ useCustomBackground: false })
92
92
  );
93
93
 
94
94
  expect(result.current.containerStyle[0].backgroundColor).toBe('#f0f0f0');