@umituz/react-native-design-system 2.6.128 → 2.8.0

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.
Files changed (66) hide show
  1. package/package.json +4 -2
  2. package/src/exports/onboarding.ts +6 -0
  3. package/src/index.ts +5 -0
  4. package/src/molecules/navigation/utils/AppNavigation.ts +53 -28
  5. package/src/molecules/splash/hooks/useSplashFlow.ts +33 -7
  6. package/src/onboarding/domain/entities/OnboardingOptions.ts +104 -0
  7. package/src/onboarding/domain/entities/OnboardingQuestion.ts +165 -0
  8. package/src/onboarding/domain/entities/OnboardingSlide.ts +152 -0
  9. package/src/onboarding/domain/entities/OnboardingUserData.ts +43 -0
  10. package/src/onboarding/hooks/useOnboardingFlow.ts +50 -0
  11. package/src/onboarding/index.ts +108 -0
  12. package/src/onboarding/infrastructure/hooks/__tests__/useOnboardingAnswers.test.ts +163 -0
  13. package/src/onboarding/infrastructure/hooks/__tests__/useOnboardingNavigation.test.ts +121 -0
  14. package/src/onboarding/infrastructure/hooks/useOnboardingAnswers.ts +69 -0
  15. package/src/onboarding/infrastructure/hooks/useOnboardingNavigation.ts +75 -0
  16. package/src/onboarding/infrastructure/services/SlideManager.ts +53 -0
  17. package/src/onboarding/infrastructure/services/ValidationManager.ts +127 -0
  18. package/src/onboarding/infrastructure/storage/OnboardingStore.ts +99 -0
  19. package/src/onboarding/infrastructure/storage/OnboardingStoreActions.ts +50 -0
  20. package/src/onboarding/infrastructure/storage/OnboardingStoreSelectors.ts +25 -0
  21. package/src/onboarding/infrastructure/storage/OnboardingStoreState.ts +22 -0
  22. package/src/onboarding/infrastructure/storage/__tests__/OnboardingStore.test.ts +85 -0
  23. package/src/onboarding/infrastructure/storage/actions/answerActions.ts +47 -0
  24. package/src/onboarding/infrastructure/storage/actions/completeAction.ts +45 -0
  25. package/src/onboarding/infrastructure/storage/actions/index.ts +22 -0
  26. package/src/onboarding/infrastructure/storage/actions/initializeAction.ts +40 -0
  27. package/src/onboarding/infrastructure/storage/actions/resetAction.ts +37 -0
  28. package/src/onboarding/infrastructure/storage/actions/skipAction.ts +46 -0
  29. package/src/onboarding/infrastructure/storage/actions/storageHelpers.ts +60 -0
  30. package/src/onboarding/infrastructure/utils/arrayUtils.ts +28 -0
  31. package/src/onboarding/infrastructure/utils/backgroundUtils.ts +38 -0
  32. package/src/onboarding/infrastructure/utils/layouts/collageLayout.ts +81 -0
  33. package/src/onboarding/infrastructure/utils/layouts/gridLayouts.ts +78 -0
  34. package/src/onboarding/infrastructure/utils/layouts/honeycombLayout.ts +36 -0
  35. package/src/onboarding/infrastructure/utils/layouts/index.ts +12 -0
  36. package/src/onboarding/infrastructure/utils/layouts/layoutTypes.ts +37 -0
  37. package/src/onboarding/infrastructure/utils/layouts/masonryLayout.ts +37 -0
  38. package/src/onboarding/infrastructure/utils/layouts/scatteredLayout.ts +34 -0
  39. package/src/onboarding/infrastructure/utils/layouts/screenDimensions.ts +11 -0
  40. package/src/onboarding/infrastructure/utils/layouts/tilesLayout.ts +34 -0
  41. package/src/onboarding/presentation/components/BackgroundImageCollage.tsx +90 -0
  42. package/src/onboarding/presentation/components/BackgroundVideo.tsx +24 -0
  43. package/src/onboarding/presentation/components/BaseSlide.tsx +47 -0
  44. package/src/onboarding/presentation/components/OnboardingBackground.tsx +91 -0
  45. package/src/onboarding/presentation/components/OnboardingFooter.tsx +151 -0
  46. package/src/onboarding/presentation/components/OnboardingHeader.tsx +92 -0
  47. package/src/onboarding/presentation/components/OnboardingResetSetting.tsx +70 -0
  48. package/src/onboarding/presentation/components/OnboardingScreenContent.tsx +146 -0
  49. package/src/onboarding/presentation/components/OnboardingSlide.tsx +124 -0
  50. package/src/onboarding/presentation/components/QuestionRenderer.tsx +60 -0
  51. package/src/onboarding/presentation/components/QuestionSlide.tsx +67 -0
  52. package/src/onboarding/presentation/components/QuestionSlideHeader.tsx +75 -0
  53. package/src/onboarding/presentation/components/questions/MultipleChoiceQuestion.tsx +74 -0
  54. package/src/onboarding/presentation/components/questions/QuestionOptionItem.tsx +115 -0
  55. package/src/onboarding/presentation/components/questions/RatingQuestion.tsx +66 -0
  56. package/src/onboarding/presentation/components/questions/SingleChoiceQuestion.tsx +117 -0
  57. package/src/onboarding/presentation/components/questions/TextInputQuestion.tsx +71 -0
  58. package/src/onboarding/presentation/hooks/__tests__/useOnboardingContainerStyle.test.ts +96 -0
  59. package/src/onboarding/presentation/hooks/useOnboardingContainerStyle.ts +37 -0
  60. package/src/onboarding/presentation/hooks/useOnboardingGestures.ts +45 -0
  61. package/src/onboarding/presentation/hooks/useOnboardingScreenHandlers.ts +114 -0
  62. package/src/onboarding/presentation/hooks/useOnboardingScreenState.ts +146 -0
  63. package/src/onboarding/presentation/providers/OnboardingProvider.tsx +51 -0
  64. package/src/onboarding/presentation/screens/OnboardingScreen.tsx +189 -0
  65. package/src/onboarding/presentation/types/OnboardingProps.ts +46 -0
  66. package/src/onboarding/presentation/types/OnboardingTheme.ts +27 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Storage Helpers
3
+ * Single Responsibility: Common storage operations and error handling
4
+ */
5
+
6
+ import { storageRepository, unwrap } from "@umituz/react-native-storage";
7
+ import type { OnboardingUserData } from "../../../domain/entities/OnboardingUserData";
8
+
9
+ export const DEFAULT_STORAGE_KEY = "@onboarding:completed";
10
+ export const USER_DATA_STORAGE_KEY = "@onboarding:user_data";
11
+
12
+ export async function loadCompletionStatus(storageKey: string): Promise<boolean> {
13
+ const result = await storageRepository.getString(storageKey, "false");
14
+ return unwrap(result, "false") === "true";
15
+ }
16
+
17
+ export async function loadUserData(
18
+ defaultData: OnboardingUserData
19
+ ): Promise<OnboardingUserData> {
20
+ const result = await storageRepository.getItem<OnboardingUserData>(
21
+ USER_DATA_STORAGE_KEY,
22
+ defaultData
23
+ );
24
+ return unwrap(result, defaultData);
25
+ }
26
+
27
+ export async function saveCompletionStatus(storageKey: string): Promise<void> {
28
+ const result = await storageRepository.setString(storageKey, "true");
29
+ if (!result.success) {
30
+ throw new Error("Failed to save completion status to storage");
31
+ }
32
+ }
33
+
34
+ export async function saveUserData(data: OnboardingUserData): Promise<void> {
35
+ const result = await storageRepository.setItem(USER_DATA_STORAGE_KEY, data);
36
+ if (!result.success) {
37
+ throw new Error("Failed to save user data to storage");
38
+ }
39
+ }
40
+
41
+ export async function removeStorageKeys(storageKey: string): Promise<void> {
42
+ await storageRepository.removeItem(storageKey);
43
+ await storageRepository.removeItem(USER_DATA_STORAGE_KEY);
44
+ }
45
+
46
+ export function handleError(error: unknown, context: string): string {
47
+ const message = error instanceof Error ? error.message : `Failed to ${context}`;
48
+
49
+ if (__DEV__) {
50
+ console.error(`[OnboardingStore] ${context} error:`, error);
51
+ }
52
+
53
+ return message;
54
+ }
55
+
56
+ export function logSuccess(message: string): void {
57
+ if (__DEV__) {
58
+ console.log(`[OnboardingStore] ${message}`);
59
+ }
60
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Array Utilities
3
+ * Safe array operations for onboarding components
4
+ */
5
+
6
+ /**
7
+ * Ensures the value is a valid array, returns empty array if not
8
+ */
9
+ export const ensureArray = <T>(value: T[] | undefined | null): T[] => {
10
+ return Array.isArray(value) ? value : [];
11
+ };
12
+
13
+ /**
14
+ * Safe includes check that handles undefined/null values
15
+ */
16
+ export const safeIncludes = <T>(array: T[] | undefined | null, item: T): boolean => {
17
+ return ensureArray(array).includes(item);
18
+ };
19
+
20
+ /**
21
+ * Safe filter that handles undefined/null values
22
+ */
23
+ export const safeFilter = <T>(
24
+ array: T[] | undefined | null,
25
+ predicate: (item: T) => boolean,
26
+ ): T[] => {
27
+ return ensureArray(array).filter(predicate);
28
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Background Utilities
3
+ *
4
+ * Utility functions for background-related operations
5
+ * Follows Single Responsibility Principle
6
+ */
7
+
8
+ import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
9
+
10
+ /**
11
+ * Check if slide should use custom background or overlay
12
+ * Indicates that we are not using the default theme background.
13
+ *
14
+ * @param slide - The slide to check
15
+ * @param globalUseCustomBackground - Global option from OnboardingOptions
16
+ * @returns true if custom background/media should be used, false otherwise
17
+ */
18
+ export function shouldUseCustomBackground(
19
+ slide: OnboardingSlide | undefined,
20
+ globalUseCustomBackground?: boolean
21
+ ): boolean {
22
+ if (!slide) {
23
+ return false;
24
+ }
25
+
26
+ // If there is background media, we always treat it as custom (needs overlay for readability)
27
+ if (slide.backgroundImage || slide.backgroundVideo || (slide.backgroundImages && slide.backgroundImages.length > 0)) {
28
+ return true;
29
+ }
30
+
31
+ // If global custom background is enabled, use it if slide has color defined
32
+ if (globalUseCustomBackground === true) {
33
+ return !!slide.backgroundColor;
34
+ }
35
+
36
+ // Otherwise, check slide's own useCustomBackground prop
37
+ return slide.useCustomBackground === true && !!slide.backgroundColor;
38
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Collage Layout Generator
3
+ * Random collage with varying sizes
4
+ */
5
+
6
+ import { SCREEN_WIDTH, SCREEN_HEIGHT } from "./screenDimensions";
7
+ import type { ImageLayoutItem, LayoutConfig, ImageSourceType } from "./layoutTypes";
8
+
9
+ export const generateCollageLayout = (
10
+ images: ImageSourceType[],
11
+ config: LayoutConfig = {},
12
+ ): ImageLayoutItem[] => {
13
+ const { borderRadius = 8 } = config;
14
+ const layouts: ImageLayoutItem[] = [];
15
+ const minSize = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 0.15;
16
+ const maxSize = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 0.35;
17
+
18
+ images.forEach((source) => {
19
+ const size = minSize + Math.random() * (maxSize - minSize);
20
+ const position = findNonOverlappingPosition(layouts, size);
21
+
22
+ layouts.push({
23
+ source,
24
+ style: {
25
+ position: "absolute" as const,
26
+ left: position.left,
27
+ top: position.top,
28
+ width: size,
29
+ height: size,
30
+ borderRadius,
31
+ },
32
+ });
33
+ });
34
+
35
+ return layouts;
36
+ };
37
+
38
+ const findNonOverlappingPosition = (
39
+ existing: ImageLayoutItem[],
40
+ size: number,
41
+ ): { left: number; top: number } => {
42
+ const maxX = SCREEN_WIDTH - size;
43
+ const maxY = SCREEN_HEIGHT - size;
44
+ let attempts = 0;
45
+ let left = 0;
46
+ let top = 0;
47
+
48
+ while (attempts < 50) {
49
+ left = Math.random() * maxX;
50
+ top = Math.random() * maxY;
51
+
52
+ if (!hasOverlap(existing, left, top, size)) {
53
+ break;
54
+ }
55
+ attempts++;
56
+ }
57
+
58
+ return { left, top };
59
+ };
60
+
61
+ const hasOverlap = (
62
+ existing: ImageLayoutItem[],
63
+ left: number,
64
+ top: number,
65
+ size: number,
66
+ ): boolean => {
67
+ const threshold = 20;
68
+
69
+ for (const item of existing) {
70
+ const { style } = item;
71
+ if (
72
+ left < style.left + style.width - threshold &&
73
+ left + size > style.left + threshold &&
74
+ top < style.top + style.height - threshold &&
75
+ top + size > style.top + threshold
76
+ ) {
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Grid Layout Generators
3
+ * Standard and dense grid layouts with safe area support
4
+ */
5
+
6
+ import { SCREEN_WIDTH, SCREEN_HEIGHT } from "./screenDimensions";
7
+ import type { ImageLayoutItem, LayoutConfig, ImageSourceType } from "./layoutTypes";
8
+
9
+ export const generateGridLayout = (
10
+ images: ImageSourceType[],
11
+ config: LayoutConfig = {},
12
+ ): ImageLayoutItem[] => {
13
+ const { columns, gap = 0, borderRadius = 0, safeAreaInsets } = config;
14
+ const count = images.length;
15
+ const cols = columns ?? Math.ceil(Math.sqrt(count));
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
+
22
+ const totalGapX = gap * (cols - 1);
23
+ const totalGapY = gap * (rows - 1);
24
+ const cellWidth = (availableWidth - totalGapX) / cols;
25
+ const cellHeight = (availableHeight - totalGapY) / rows;
26
+
27
+ return images.map((source, index) => {
28
+ const col = index % cols;
29
+ const row = Math.floor(index / cols);
30
+
31
+ return {
32
+ source,
33
+ style: {
34
+ position: "absolute" as const,
35
+ left: insets.left + col * (cellWidth + gap),
36
+ top: insets.top + row * (cellHeight + gap),
37
+ width: cellWidth,
38
+ height: cellHeight,
39
+ borderRadius,
40
+ },
41
+ };
42
+ });
43
+ };
44
+
45
+ export const generateDenseGridLayout = (
46
+ images: ImageSourceType[],
47
+ config: LayoutConfig = {},
48
+ ): ImageLayoutItem[] => {
49
+ const { columns = 6, gap = 2, borderRadius = 4, safeAreaInsets } = config;
50
+ const count = images.length;
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
+
57
+ const totalGapX = gap * (columns - 1);
58
+ const totalGapY = gap * (rows - 1);
59
+ const cellWidth = (availableWidth - totalGapX) / columns;
60
+ const cellHeight = (availableHeight - totalGapY) / rows;
61
+
62
+ return images.map((source, index) => {
63
+ const col = index % columns;
64
+ const row = Math.floor(index / columns);
65
+
66
+ return {
67
+ source,
68
+ style: {
69
+ position: "absolute" as const,
70
+ left: insets.left + col * (cellWidth + gap),
71
+ top: insets.top + row * (cellHeight + gap),
72
+ width: cellWidth,
73
+ height: cellHeight,
74
+ borderRadius,
75
+ },
76
+ };
77
+ });
78
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Honeycomb Layout Generator
3
+ * Hexagonal pattern layout
4
+ */
5
+
6
+ import { SCREEN_WIDTH } from "./screenDimensions";
7
+ import type { ImageLayoutItem, LayoutConfig, ImageSourceType } from "./layoutTypes";
8
+
9
+ export const generateHoneycombLayout = (
10
+ images: ImageSourceType[],
11
+ config: LayoutConfig = {},
12
+ ): ImageLayoutItem[] => {
13
+ const { borderRadius = 50 } = config;
14
+ const size = 80;
15
+ const horizontalSpacing = size * 0.85;
16
+ const verticalSpacing = size * 0.75;
17
+ const columns = Math.floor(SCREEN_WIDTH / horizontalSpacing);
18
+
19
+ return images.map((source, index) => {
20
+ const row = Math.floor(index / columns);
21
+ const col = index % columns;
22
+ const offsetX = row % 2 === 1 ? horizontalSpacing / 2 : 0;
23
+
24
+ return {
25
+ source,
26
+ style: {
27
+ position: "absolute" as const,
28
+ left: col * horizontalSpacing + offsetX,
29
+ top: row * verticalSpacing,
30
+ width: size,
31
+ height: size,
32
+ borderRadius,
33
+ },
34
+ };
35
+ });
36
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Layout Generators - Barrel Export
3
+ */
4
+
5
+ export type { ImageLayoutItem, ImageLayoutStyle, LayoutConfig, ImageSourceType } from "./layoutTypes";
6
+
7
+ export { generateGridLayout, generateDenseGridLayout } from "./gridLayouts";
8
+ export { generateMasonryLayout } from "./masonryLayout";
9
+ export { generateCollageLayout } from "./collageLayout";
10
+ export { generateScatteredLayout } from "./scatteredLayout";
11
+ export { generateTilesLayout } from "./tilesLayout";
12
+ export { generateHoneycombLayout } from "./honeycombLayout";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Layout Types
3
+ * Type definitions for image layout generators
4
+ */
5
+
6
+ import type { ImageSource } from "expo-image";
7
+
8
+ export type ImageSourceType = ImageSource | number | string;
9
+
10
+ export interface SafeAreaInsets {
11
+ top: number;
12
+ bottom: number;
13
+ left: number;
14
+ right: number;
15
+ }
16
+
17
+ export interface ImageLayoutStyle {
18
+ position: "absolute";
19
+ top: number;
20
+ left: number;
21
+ width: number;
22
+ height: number;
23
+ borderRadius?: number;
24
+ }
25
+
26
+ export interface ImageLayoutItem {
27
+ source: ImageSourceType;
28
+ style: ImageLayoutStyle;
29
+ }
30
+
31
+ export interface LayoutConfig {
32
+ columns?: number;
33
+ gap?: number;
34
+ borderRadius?: number;
35
+ randomizeSize?: boolean;
36
+ safeAreaInsets?: SafeAreaInsets;
37
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Masonry Layout Generator
3
+ * Pinterest-style masonry layout
4
+ */
5
+
6
+ import { SCREEN_WIDTH } from "./screenDimensions";
7
+ import type { ImageLayoutItem, LayoutConfig, ImageSourceType } from "./layoutTypes";
8
+
9
+ export const generateMasonryLayout = (
10
+ images: ImageSourceType[],
11
+ config: LayoutConfig = {},
12
+ ): ImageLayoutItem[] => {
13
+ const { columns = 3, gap = 2, borderRadius = 4 } = config;
14
+ const colWidth = (SCREEN_WIDTH - gap * (columns - 1)) / columns;
15
+ const columnHeights = new Array(columns).fill(0);
16
+
17
+ return images.map((source) => {
18
+ const shortestCol = columnHeights.indexOf(Math.min(...columnHeights));
19
+ const aspectRatio = 0.7 + Math.random() * 0.6;
20
+ const height = colWidth * aspectRatio;
21
+
22
+ const layout: ImageLayoutItem = {
23
+ source,
24
+ style: {
25
+ position: "absolute" as const,
26
+ left: shortestCol * (colWidth + gap),
27
+ top: columnHeights[shortestCol],
28
+ width: colWidth,
29
+ height,
30
+ borderRadius,
31
+ },
32
+ };
33
+
34
+ columnHeights[shortestCol] += height + gap;
35
+ return layout;
36
+ });
37
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Scattered Layout Generator
3
+ * Random small images scattered across screen
4
+ */
5
+
6
+ import { SCREEN_WIDTH, SCREEN_HEIGHT } from "./screenDimensions";
7
+ import type { ImageLayoutItem, LayoutConfig, ImageSourceType } from "./layoutTypes";
8
+
9
+ export const generateScatteredLayout = (
10
+ images: ImageSourceType[],
11
+ config: LayoutConfig = {},
12
+ ): ImageLayoutItem[] => {
13
+ const { borderRadius = 6 } = config;
14
+ const minSize = 60;
15
+ const maxSize = 100;
16
+
17
+ return images.map((source) => {
18
+ const size = minSize + Math.random() * (maxSize - minSize);
19
+ const left = Math.random() * (SCREEN_WIDTH - size);
20
+ const top = Math.random() * (SCREEN_HEIGHT - size);
21
+
22
+ return {
23
+ source,
24
+ style: {
25
+ position: "absolute" as const,
26
+ left,
27
+ top,
28
+ width: size,
29
+ height: size,
30
+ borderRadius,
31
+ },
32
+ };
33
+ });
34
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Screen Dimensions
3
+ * Centralized screen dimension values using design system utilities
4
+ */
5
+
6
+ import { getScreenDimensions } from "@umituz/react-native-design-system";
7
+
8
+ const dimensions = getScreenDimensions();
9
+
10
+ export const SCREEN_WIDTH = dimensions.width;
11
+ export const SCREEN_HEIGHT = dimensions.height;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Tiles Layout Generator
3
+ * Fixed size tiles centered on screen
4
+ */
5
+
6
+ import { SCREEN_WIDTH, SCREEN_HEIGHT } from "./screenDimensions";
7
+ import type { ImageLayoutItem, LayoutConfig, ImageSourceType } from "./layoutTypes";
8
+
9
+ export const generateTilesLayout = (
10
+ images: ImageSourceType[],
11
+ config: LayoutConfig = {},
12
+ ): ImageLayoutItem[] => {
13
+ const { columns = 5, gap = 4, borderRadius = 8 } = config;
14
+ const tileSize = (SCREEN_WIDTH - gap * (columns + 1)) / columns;
15
+ const rows = Math.ceil(images.length / columns);
16
+ const startY = (SCREEN_HEIGHT - rows * (tileSize + gap)) / 2;
17
+
18
+ return images.map((source, index) => {
19
+ const col = index % columns;
20
+ const row = Math.floor(index / columns);
21
+
22
+ return {
23
+ source,
24
+ style: {
25
+ position: "absolute" as const,
26
+ left: gap + col * (tileSize + gap),
27
+ top: startY + row * (tileSize + gap),
28
+ width: tileSize,
29
+ height: tileSize,
30
+ borderRadius,
31
+ },
32
+ };
33
+ });
34
+ };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Background Image Collage Component
3
+ * Displays multiple images in various layout patterns with safe area support
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { Image } from "expo-image";
9
+ import { useSafeAreaInsets } from "@umituz/react-native-design-system";
10
+ import {
11
+ generateGridLayout,
12
+ generateDenseGridLayout,
13
+ generateMasonryLayout,
14
+ generateCollageLayout,
15
+ generateScatteredLayout,
16
+ generateTilesLayout,
17
+ generateHoneycombLayout,
18
+ type ImageLayoutItem,
19
+ type LayoutConfig,
20
+ type ImageSourceType,
21
+ } from "../../infrastructure/utils/layouts";
22
+
23
+ export type CollageLayout =
24
+ | "grid"
25
+ | "dense"
26
+ | "masonry"
27
+ | "collage"
28
+ | "scattered"
29
+ | "tiles"
30
+ | "honeycomb";
31
+
32
+ export interface BackgroundImageCollageProps {
33
+ images: ImageSourceType[];
34
+ layout?: CollageLayout;
35
+ columns?: number;
36
+ gap?: number;
37
+ borderRadius?: number;
38
+ opacity?: number;
39
+ }
40
+
41
+ type LayoutGenerator = (images: ImageSourceType[], config: LayoutConfig) => ImageLayoutItem[];
42
+
43
+ const LAYOUT_GENERATORS: Record<CollageLayout, LayoutGenerator> = {
44
+ grid: generateGridLayout,
45
+ dense: generateDenseGridLayout,
46
+ masonry: generateMasonryLayout,
47
+ collage: generateCollageLayout,
48
+ scattered: generateScatteredLayout,
49
+ tiles: generateTilesLayout,
50
+ honeycomb: generateHoneycombLayout,
51
+ };
52
+
53
+ export const BackgroundImageCollage: React.FC<BackgroundImageCollageProps> = ({
54
+ images,
55
+ layout = "grid",
56
+ columns,
57
+ gap,
58
+ borderRadius,
59
+ opacity = 1,
60
+ }) => {
61
+ const insets = useSafeAreaInsets();
62
+
63
+ const imageLayouts = useMemo(() => {
64
+ if (!images || images.length === 0) return [];
65
+
66
+ const generator = LAYOUT_GENERATORS[layout] ?? generateGridLayout;
67
+ return generator(images, {
68
+ columns,
69
+ gap,
70
+ borderRadius,
71
+ safeAreaInsets: insets
72
+ });
73
+ }, [images, layout, columns, gap, borderRadius, insets]);
74
+
75
+ if (imageLayouts.length === 0) return null;
76
+
77
+ return (
78
+ <View style={[StyleSheet.absoluteFill, { opacity }]} pointerEvents="none">
79
+ {imageLayouts.map((item, index) => (
80
+ <Image
81
+ key={index}
82
+ source={item.source}
83
+ style={item.style}
84
+ contentFit="cover"
85
+ transition={300}
86
+ />
87
+ ))}
88
+ </View>
89
+ );
90
+ };
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { StyleSheet, View } from 'react-native';
3
+ import { useVideoPlayer, VideoView } from 'expo-video';
4
+
5
+ interface BackgroundVideoProps {
6
+ source: any;
7
+ overlayOpacity?: number;
8
+ }
9
+
10
+ export const BackgroundVideo = ({ source, overlayOpacity = 0.5 }: BackgroundVideoProps) => {
11
+ const player = useVideoPlayer(source, (p: any) => {
12
+ p.loop = true;
13
+ p.play();
14
+ p.muted = true;
15
+ });
16
+
17
+ return (
18
+ <View style={StyleSheet.absoluteFill}>
19
+ <VideoView player={player} style={StyleSheet.absoluteFill} contentFit="cover" nativeControls={false} />
20
+ <View style={[StyleSheet.absoluteFill, { backgroundColor: `rgba(0,0,0,${overlayOpacity})` }]} />
21
+ </View>
22
+ );
23
+ };
24
+
@@ -0,0 +1,47 @@
1
+ /**
2
+ * BaseSlide Component
3
+ * Single Responsibility: Provide a base layout for all onboarding slides
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, ScrollView } from "react-native";
8
+ import { useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
9
+ import type { ContentPosition } from "../../domain/entities/OnboardingSlide";
10
+
11
+ export interface BaseSlideProps {
12
+ children: React.ReactNode;
13
+ contentPosition?: ContentPosition;
14
+ }
15
+
16
+ export const BaseSlide = ({
17
+ children,
18
+ contentPosition = "center",
19
+ }: BaseSlideProps) => {
20
+ const tokens = useAppDesignTokens();
21
+ const { verticalPadding, horizontalPadding } = useResponsive();
22
+ const isBottom = contentPosition === "bottom";
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
+
37
+ return (
38
+ <ScrollView
39
+ style={{ flex: 1 }}
40
+ contentContainerStyle={contentContainerStyle}
41
+ showsVerticalScrollIndicator={false}
42
+ bounces={false}
43
+ >
44
+ <View style={slideContainerStyle}>{children}</View>
45
+ </ScrollView>
46
+ );
47
+ };