@umituz/react-native-design-system 2.6.128 → 2.7.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 (64) 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/onboarding/domain/entities/OnboardingOptions.ts +104 -0
  5. package/src/onboarding/domain/entities/OnboardingQuestion.ts +165 -0
  6. package/src/onboarding/domain/entities/OnboardingSlide.ts +152 -0
  7. package/src/onboarding/domain/entities/OnboardingUserData.ts +43 -0
  8. package/src/onboarding/hooks/useOnboardingFlow.ts +50 -0
  9. package/src/onboarding/index.ts +108 -0
  10. package/src/onboarding/infrastructure/hooks/__tests__/useOnboardingAnswers.test.ts +163 -0
  11. package/src/onboarding/infrastructure/hooks/__tests__/useOnboardingNavigation.test.ts +121 -0
  12. package/src/onboarding/infrastructure/hooks/useOnboardingAnswers.ts +69 -0
  13. package/src/onboarding/infrastructure/hooks/useOnboardingNavigation.ts +75 -0
  14. package/src/onboarding/infrastructure/services/SlideManager.ts +53 -0
  15. package/src/onboarding/infrastructure/services/ValidationManager.ts +127 -0
  16. package/src/onboarding/infrastructure/storage/OnboardingStore.ts +99 -0
  17. package/src/onboarding/infrastructure/storage/OnboardingStoreActions.ts +50 -0
  18. package/src/onboarding/infrastructure/storage/OnboardingStoreSelectors.ts +25 -0
  19. package/src/onboarding/infrastructure/storage/OnboardingStoreState.ts +22 -0
  20. package/src/onboarding/infrastructure/storage/__tests__/OnboardingStore.test.ts +85 -0
  21. package/src/onboarding/infrastructure/storage/actions/answerActions.ts +47 -0
  22. package/src/onboarding/infrastructure/storage/actions/completeAction.ts +45 -0
  23. package/src/onboarding/infrastructure/storage/actions/index.ts +22 -0
  24. package/src/onboarding/infrastructure/storage/actions/initializeAction.ts +40 -0
  25. package/src/onboarding/infrastructure/storage/actions/resetAction.ts +37 -0
  26. package/src/onboarding/infrastructure/storage/actions/skipAction.ts +46 -0
  27. package/src/onboarding/infrastructure/storage/actions/storageHelpers.ts +60 -0
  28. package/src/onboarding/infrastructure/utils/arrayUtils.ts +28 -0
  29. package/src/onboarding/infrastructure/utils/backgroundUtils.ts +38 -0
  30. package/src/onboarding/infrastructure/utils/layouts/collageLayout.ts +81 -0
  31. package/src/onboarding/infrastructure/utils/layouts/gridLayouts.ts +78 -0
  32. package/src/onboarding/infrastructure/utils/layouts/honeycombLayout.ts +36 -0
  33. package/src/onboarding/infrastructure/utils/layouts/index.ts +12 -0
  34. package/src/onboarding/infrastructure/utils/layouts/layoutTypes.ts +37 -0
  35. package/src/onboarding/infrastructure/utils/layouts/masonryLayout.ts +37 -0
  36. package/src/onboarding/infrastructure/utils/layouts/scatteredLayout.ts +34 -0
  37. package/src/onboarding/infrastructure/utils/layouts/screenDimensions.ts +11 -0
  38. package/src/onboarding/infrastructure/utils/layouts/tilesLayout.ts +34 -0
  39. package/src/onboarding/presentation/components/BackgroundImageCollage.tsx +90 -0
  40. package/src/onboarding/presentation/components/BackgroundVideo.tsx +24 -0
  41. package/src/onboarding/presentation/components/BaseSlide.tsx +47 -0
  42. package/src/onboarding/presentation/components/OnboardingBackground.tsx +91 -0
  43. package/src/onboarding/presentation/components/OnboardingFooter.tsx +151 -0
  44. package/src/onboarding/presentation/components/OnboardingHeader.tsx +92 -0
  45. package/src/onboarding/presentation/components/OnboardingResetSetting.tsx +70 -0
  46. package/src/onboarding/presentation/components/OnboardingScreenContent.tsx +146 -0
  47. package/src/onboarding/presentation/components/OnboardingSlide.tsx +124 -0
  48. package/src/onboarding/presentation/components/QuestionRenderer.tsx +60 -0
  49. package/src/onboarding/presentation/components/QuestionSlide.tsx +67 -0
  50. package/src/onboarding/presentation/components/QuestionSlideHeader.tsx +75 -0
  51. package/src/onboarding/presentation/components/questions/MultipleChoiceQuestion.tsx +74 -0
  52. package/src/onboarding/presentation/components/questions/QuestionOptionItem.tsx +115 -0
  53. package/src/onboarding/presentation/components/questions/RatingQuestion.tsx +66 -0
  54. package/src/onboarding/presentation/components/questions/SingleChoiceQuestion.tsx +117 -0
  55. package/src/onboarding/presentation/components/questions/TextInputQuestion.tsx +71 -0
  56. package/src/onboarding/presentation/hooks/__tests__/useOnboardingContainerStyle.test.ts +96 -0
  57. package/src/onboarding/presentation/hooks/useOnboardingContainerStyle.ts +37 -0
  58. package/src/onboarding/presentation/hooks/useOnboardingGestures.ts +45 -0
  59. package/src/onboarding/presentation/hooks/useOnboardingScreenHandlers.ts +114 -0
  60. package/src/onboarding/presentation/hooks/useOnboardingScreenState.ts +146 -0
  61. package/src/onboarding/presentation/providers/OnboardingProvider.tsx +51 -0
  62. package/src/onboarding/presentation/screens/OnboardingScreen.tsx +189 -0
  63. package/src/onboarding/presentation/types/OnboardingProps.ts +46 -0
  64. package/src/onboarding/presentation/types/OnboardingTheme.ts +27 -0
@@ -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
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Onboarding Background Component
3
+ * Handles rendering of video, image or solid backgrounds
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+
9
+ import { Image } from "expo-image";
10
+ import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
11
+ import { BackgroundVideo } from "./BackgroundVideo";
12
+ import { BackgroundImageCollage } from "./BackgroundImageCollage";
13
+
14
+ interface OnboardingBackgroundProps {
15
+ currentSlide: OnboardingSlide | undefined;
16
+ useCustomBackground: boolean;
17
+ showOverlay: boolean;
18
+ overlayOpacity: number;
19
+ }
20
+
21
+ export const OnboardingBackground: React.FC<OnboardingBackgroundProps> = ({
22
+ currentSlide,
23
+ useCustomBackground,
24
+ showOverlay,
25
+ overlayOpacity,
26
+ }) => {
27
+ if (!currentSlide) return null;
28
+
29
+ const renderContent = () => {
30
+ if (currentSlide.backgroundVideo) {
31
+ return (
32
+ <BackgroundVideo
33
+ source={currentSlide.backgroundVideo}
34
+ overlayOpacity={overlayOpacity}
35
+ />
36
+ );
37
+ }
38
+
39
+ if (currentSlide.backgroundImages && currentSlide.backgroundImages.length > 0) {
40
+ return (
41
+ <BackgroundImageCollage
42
+ images={currentSlide.backgroundImages}
43
+ layout={currentSlide.backgroundImagesLayout || "grid"}
44
+ columns={currentSlide.backgroundImagesColumns}
45
+ gap={currentSlide.backgroundImagesGap}
46
+ borderRadius={currentSlide.backgroundImagesBorderRadius}
47
+ />
48
+ );
49
+ }
50
+
51
+ if (currentSlide.backgroundImage) {
52
+ return (
53
+ <Image
54
+ source={currentSlide.backgroundImage}
55
+ style={StyleSheet.absoluteFill}
56
+ contentFit="cover"
57
+ transition={500}
58
+ />
59
+ );
60
+ }
61
+
62
+ if (useCustomBackground && currentSlide.backgroundColor) {
63
+ return (
64
+ <View
65
+ style={[
66
+ StyleSheet.absoluteFill,
67
+ { backgroundColor: currentSlide.backgroundColor }
68
+ ]}
69
+ />
70
+ );
71
+ }
72
+
73
+ return null;
74
+ };
75
+
76
+ return (
77
+ <View style={StyleSheet.absoluteFill} pointerEvents="none">
78
+ {renderContent()}
79
+ <View
80
+ style={[
81
+ StyleSheet.absoluteFill,
82
+ {
83
+ backgroundColor: showOverlay
84
+ ? `rgba(0,0,0,${overlayOpacity})`
85
+ : "transparent",
86
+ },
87
+ ]}
88
+ />
89
+ </View>
90
+ );
91
+ };
@@ -0,0 +1,151 @@
1
+ import React from "react";
2
+ import { View, StyleSheet, TouchableOpacity } from "react-native";
3
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
+ import { useLocalization } from "@umituz/react-native-localization";
5
+ import { AtomicText } from "@umituz/react-native-design-system";
6
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
7
+
8
+ export interface OnboardingFooterProps {
9
+ currentIndex: number;
10
+ totalSlides: number;
11
+ isLastSlide: boolean;
12
+ onNext: () => void;
13
+ showProgressBar?: boolean;
14
+ showDots?: boolean;
15
+ showProgressText?: boolean;
16
+ nextButtonText?: string;
17
+ getStartedButtonText?: string;
18
+ disabled?: boolean;
19
+ }
20
+
21
+ export const OnboardingFooter = ({
22
+ currentIndex,
23
+ totalSlides,
24
+ isLastSlide,
25
+ onNext,
26
+ showProgressBar = true,
27
+ showDots = true,
28
+ showProgressText = true,
29
+ nextButtonText,
30
+ getStartedButtonText,
31
+ disabled = false,
32
+ }: OnboardingFooterProps) => {
33
+ const insets = useSafeAreaInsets();
34
+ const { t } = useLocalization();
35
+ const {
36
+ theme: { colors },
37
+ } = useOnboardingProvider();
38
+
39
+ const buttonText = isLastSlide
40
+ ? getStartedButtonText || t("onboarding.getStarted")
41
+ : nextButtonText || t("general.continue");
42
+
43
+ const progressPercent = ((currentIndex + 1) / totalSlides) * 100;
44
+
45
+ return (
46
+ <View style={[styles.footer, { paddingBottom: insets.bottom + 24 }]}>
47
+ {showProgressBar && (
48
+ <View style={styles.progressContainer}>
49
+ <View style={[styles.progressBar, { backgroundColor: colors.progressBarBg }]}>
50
+ <View style={[styles.progressFill, { width: `${progressPercent}%`, backgroundColor: colors.progressFillColor }]} />
51
+ </View>
52
+ </View>
53
+ )}
54
+
55
+ {showDots && (
56
+ <View style={styles.dots}>
57
+ {Array.from({ length: totalSlides }).map((_, index) => (
58
+ <View
59
+ key={index}
60
+ style={[
61
+ styles.dot,
62
+ { backgroundColor: colors.dotColor },
63
+ index === currentIndex && {
64
+ width: 12,
65
+ backgroundColor: colors.activeDotColor
66
+ }
67
+ ]}
68
+ />
69
+ ))}
70
+ </View>
71
+ )}
72
+
73
+ <TouchableOpacity
74
+ onPress={onNext}
75
+ disabled={disabled}
76
+ activeOpacity={0.7}
77
+ style={[
78
+ styles.button,
79
+ {
80
+ backgroundColor: colors.buttonBg,
81
+ opacity: disabled ? 0.5 : 1,
82
+ },
83
+ ]}
84
+ >
85
+ <AtomicText
86
+ type="labelLarge"
87
+ style={[styles.buttonText, { color: colors.buttonTextColor }]}
88
+ >
89
+ {buttonText}
90
+ </AtomicText>
91
+ </TouchableOpacity>
92
+
93
+ {showProgressText && (
94
+ <AtomicText
95
+ type="labelSmall"
96
+ style={[styles.progressText, { color: colors.progressTextColor }]}
97
+ >
98
+ {currentIndex + 1} {t("general.of")} {totalSlides}
99
+ </AtomicText>
100
+ )}
101
+ </View>
102
+ );
103
+ };
104
+
105
+ const styles = StyleSheet.create({
106
+ footer: {
107
+ paddingHorizontal: 24,
108
+ paddingTop: 24,
109
+ },
110
+ progressContainer: {
111
+ marginBottom: 20,
112
+ },
113
+ progressBar: {
114
+ height: 4,
115
+ borderRadius: 2,
116
+ overflow: "hidden",
117
+ },
118
+ progressFill: {
119
+ height: "100%",
120
+ borderRadius: 2,
121
+ },
122
+ dots: {
123
+ flexDirection: "row",
124
+ justifyContent: "center",
125
+ marginBottom: 24,
126
+ gap: 8,
127
+ },
128
+ dot: {
129
+ width: 6,
130
+ height: 6,
131
+ borderRadius: 3,
132
+ },
133
+ progressText: {
134
+ marginTop: 12,
135
+ textAlign: "center",
136
+ },
137
+ button: {
138
+ width: "100%",
139
+ minHeight: 52,
140
+ borderRadius: 16,
141
+ alignItems: "center",
142
+ justifyContent: "center",
143
+ paddingVertical: 16,
144
+ paddingHorizontal: 24,
145
+ },
146
+ buttonText: {
147
+ fontWeight: "700",
148
+ },
149
+ });
150
+
151
+