@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,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
+
@@ -0,0 +1,92 @@
1
+ import React from "react";
2
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
3
+ import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
+ import { useLocalization } from "@umituz/react-native-localization";
5
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
6
+
7
+ export interface OnboardingHeaderProps {
8
+ isFirstSlide: boolean;
9
+ onBack: () => void;
10
+ onSkip: () => void;
11
+ showBackButton?: boolean;
12
+ showSkipButton?: boolean;
13
+ skipButtonText?: string;
14
+ }
15
+
16
+ export const OnboardingHeader = ({
17
+ isFirstSlide,
18
+ onBack,
19
+ onSkip,
20
+ showBackButton = true,
21
+ showSkipButton = true,
22
+ skipButtonText,
23
+ }: OnboardingHeaderProps) => {
24
+ const { t } = useLocalization();
25
+ const {
26
+ theme: { colors },
27
+ } = useOnboardingProvider();
28
+
29
+ const skipText = skipButtonText || t("onboarding.skip");
30
+
31
+ return (
32
+ <View style={styles.header}>
33
+ {showBackButton ? (
34
+ <TouchableOpacity
35
+ onPress={() => !isFirstSlide && onBack?.()}
36
+ disabled={isFirstSlide}
37
+ style={[
38
+ styles.headerButton,
39
+ {
40
+ backgroundColor: colors.headerButtonBg,
41
+ borderColor: colors.headerButtonBorder,
42
+ },
43
+ isFirstSlide && styles.headerButtonDisabled,
44
+ ]}
45
+ activeOpacity={0.7}
46
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
47
+ >
48
+ <AtomicIcon name="chevron-back" customSize={20} customColor={colors.iconColor} />
49
+ </TouchableOpacity>
50
+ ) : (
51
+ <View style={styles.headerButton} />
52
+ )}
53
+ {showSkipButton ? (
54
+ <TouchableOpacity onPress={onSkip} activeOpacity={0.7}>
55
+ <AtomicText
56
+ type="labelLarge"
57
+ style={[styles.skipText, { color: colors.textColor }]}
58
+ >
59
+ {skipText}
60
+ </AtomicText>
61
+ </TouchableOpacity>
62
+ ) : <View />}
63
+ </View>
64
+ );
65
+ };
66
+
67
+ const styles = StyleSheet.create({
68
+ header: {
69
+ flexDirection: "row",
70
+ justifyContent: "space-between",
71
+ alignItems: "center",
72
+ paddingHorizontal: 20,
73
+ paddingTop: 10,
74
+ paddingBottom: 20,
75
+ },
76
+ headerButton: {
77
+ width: 40,
78
+ height: 40,
79
+ borderRadius: 20,
80
+ alignItems: "center",
81
+ justifyContent: "center",
82
+ borderWidth: 1,
83
+ },
84
+ headerButtonDisabled: {
85
+ opacity: 0,
86
+ },
87
+ skipText: {
88
+ fontWeight: "700",
89
+ },
90
+ });
91
+
92
+
@@ -0,0 +1,70 @@
1
+ import React, { useCallback } from "react";
2
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
3
+ import { AtomicIcon, AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
4
+
5
+ export interface OnboardingResetSettingProps {
6
+ onReset: () => void | Promise<void>;
7
+ title?: string;
8
+ description?: string;
9
+ iconName?: string;
10
+ iconColor?: string;
11
+ titleColor?: string;
12
+ visible?: boolean;
13
+ isLast?: boolean;
14
+ }
15
+
16
+ export const OnboardingResetSetting = ({
17
+ onReset,
18
+ title,
19
+ description,
20
+ iconName = "extension-puzzle",
21
+ iconColor,
22
+ titleColor,
23
+ visible = __DEV__,
24
+ isLast = false,
25
+ }: OnboardingResetSettingProps) => {
26
+ const tokens = useAppDesignTokens();
27
+
28
+ const handlePress = useCallback(async () => {
29
+ await onReset();
30
+ }, [onReset]);
31
+
32
+ if (!visible) return null;
33
+
34
+ const defaultIconColor = iconColor || tokens.colors.error;
35
+ const defaultTitleColor = titleColor || tokens.colors.error;
36
+
37
+ return (
38
+ <TouchableOpacity onPress={handlePress} activeOpacity={0.7}>
39
+ <View style={[styles.container, !isLast && { borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: tokens.colors.border }]}>
40
+ <View style={styles.iconContainer}>
41
+ <AtomicIcon name={iconName} size="md" customColor={defaultIconColor} />
42
+ </View>
43
+ <View style={styles.content}>
44
+ <AtomicText type="bodyLarge" style={{ color: defaultTitleColor, fontWeight: '600' }} numberOfLines={1}>
45
+ {title}
46
+ </AtomicText>
47
+ <AtomicText type="bodyMedium" style={{ color: tokens.colors.textSecondary }} numberOfLines={1}>
48
+ {description}
49
+ </AtomicText>
50
+ </View>
51
+ </View>
52
+ </TouchableOpacity>
53
+ );
54
+ };
55
+
56
+ const styles = StyleSheet.create({
57
+ container: {
58
+ flexDirection: "row",
59
+ alignItems: "center",
60
+ paddingVertical: 16,
61
+ paddingHorizontal: 20,
62
+ },
63
+ iconContainer: {
64
+ marginRight: 16,
65
+ },
66
+ content: {
67
+ flex: 1,
68
+ }
69
+ });
70
+
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Onboarding Screen Content Component
3
+ * Single Responsibility: Render onboarding screen content (header, slide, footer)
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet, StatusBar } from "react-native";
8
+ import { useTheme } from "@umituz/react-native-design-system";
9
+ import { OnboardingHeader } from "./OnboardingHeader";
10
+ import { OnboardingSlide as OnboardingSlideComponent } from "./OnboardingSlide";
11
+ import { QuestionSlide } from "./QuestionSlide";
12
+ import { OnboardingFooter } from "./OnboardingFooter";
13
+ import { OnboardingBackground } from "./OnboardingBackground";
14
+ import { useOnboardingGestures } from "../hooks/useOnboardingGestures";
15
+ import type { OnboardingScreenContentProps } from "../types/OnboardingProps";
16
+
17
+ export const OnboardingScreenContent = ({
18
+ containerStyle,
19
+ useCustomBackground,
20
+ currentSlide,
21
+ isFirstSlide,
22
+ isLastSlide,
23
+ currentIndex,
24
+ totalSlides,
25
+ currentAnswer,
26
+ isAnswerValid,
27
+ showBackButton,
28
+ showSkipButton,
29
+ showProgressBar,
30
+ showDots,
31
+ showProgressText,
32
+ skipButtonText,
33
+ nextButtonText,
34
+ getStartedButtonText,
35
+ onBack,
36
+ onSkip,
37
+ onNext,
38
+ onAnswerChange,
39
+ renderHeader,
40
+ renderFooter,
41
+ renderSlide,
42
+ onUpgrade,
43
+ showPaywallOnComplete,
44
+ variant = "default",
45
+ }: OnboardingScreenContentProps) => {
46
+ const { themeMode } = useTheme();
47
+
48
+ const panResponder = useOnboardingGestures({
49
+ isFirstSlide,
50
+ isAnswerValid,
51
+ onNext,
52
+ onBack,
53
+ });
54
+
55
+ const hasMedia =
56
+ !!currentSlide?.backgroundImage ||
57
+ !!currentSlide?.backgroundVideo ||
58
+ (!!currentSlide?.backgroundImages && currentSlide.backgroundImages.length > 0);
59
+ const overlayOpacity = currentSlide?.overlayOpacity ?? 0.5;
60
+ const showOverlay = useCustomBackground || hasMedia;
61
+
62
+ return (
63
+ <View
64
+ style={[styles.container, containerStyle]}
65
+ {...panResponder.panHandlers}
66
+ >
67
+ <StatusBar
68
+ barStyle={
69
+ themeMode === "dark" || showOverlay
70
+ ? "light-content"
71
+ : "dark-content"
72
+ }
73
+ />
74
+
75
+ <OnboardingBackground
76
+ currentSlide={currentSlide}
77
+ useCustomBackground={useCustomBackground}
78
+ showOverlay={showOverlay}
79
+ overlayOpacity={overlayOpacity}
80
+ />
81
+
82
+ {renderHeader ? (
83
+ renderHeader({ isFirstSlide, onBack, onSkip })
84
+ ) : (
85
+ <OnboardingHeader
86
+ isFirstSlide={isFirstSlide}
87
+ onBack={onBack}
88
+ onSkip={onSkip}
89
+ showBackButton={showBackButton}
90
+ showSkipButton={showSkipButton}
91
+ skipButtonText={skipButtonText}
92
+ />
93
+ )}
94
+
95
+ {currentSlide && (
96
+ <View style={styles.content}>
97
+ {renderSlide ? (
98
+ renderSlide(currentSlide)
99
+ ) : currentSlide.type === "question" && currentSlide.question ? (
100
+ <QuestionSlide
101
+ slide={currentSlide}
102
+ value={currentAnswer}
103
+ onChange={onAnswerChange}
104
+ variant={variant}
105
+ />
106
+ ) : (
107
+ <OnboardingSlideComponent slide={currentSlide} variant={variant} />
108
+ )}
109
+ </View>
110
+ )}
111
+
112
+ {renderFooter ? (
113
+ renderFooter({
114
+ currentIndex,
115
+ totalSlides,
116
+ isLastSlide,
117
+ onNext,
118
+ onUpgrade,
119
+ showPaywallOnComplete,
120
+ })
121
+ ) : (
122
+ <OnboardingFooter
123
+ currentIndex={currentIndex}
124
+ totalSlides={totalSlides}
125
+ isLastSlide={isLastSlide}
126
+ onNext={onNext}
127
+ showProgressBar={showProgressBar}
128
+ showDots={showDots}
129
+ showProgressText={showProgressText}
130
+ nextButtonText={nextButtonText}
131
+ getStartedButtonText={getStartedButtonText}
132
+ disabled={!isAnswerValid}
133
+ />
134
+ )}
135
+ </View>
136
+ );
137
+ };
138
+
139
+ const styles = StyleSheet.create({
140
+ container: {
141
+ flex: 1,
142
+ },
143
+ content: {
144
+ flex: 1,
145
+ },
146
+ });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * OnboardingSlide Component
3
+ * Single Responsibility: Render a single onboarding slide
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
9
+ import type { OnboardingSlide as OnboardingSlideType } from "../../domain/entities/OnboardingSlide";
10
+ import { BaseSlide } from "./BaseSlide";
11
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
12
+
13
+ export interface OnboardingSlideProps {
14
+ slide: OnboardingSlideType;
15
+ variant?: "default" | "card" | "minimal" | "fullscreen";
16
+ }
17
+
18
+ export const OnboardingSlide = ({
19
+ slide,
20
+ variant = "default",
21
+ }: OnboardingSlideProps) => {
22
+ const {
23
+ theme: { colors },
24
+ } = useOnboardingProvider();
25
+
26
+ const shouldShowIcon = slide.icon && !slide.hideIcon;
27
+ const isEmoji = slide.iconType === 'emoji';
28
+ const iconSize = variant === "minimal" ? 80 : 72;
29
+ const contentPosition = slide.contentPosition || "center";
30
+
31
+ return (
32
+ <BaseSlide contentPosition={contentPosition}>
33
+ {shouldShowIcon && slide.icon && (
34
+ <View style={styles.iconBox}>
35
+ {isEmoji ? (
36
+ <AtomicText style={{ fontSize: iconSize }}>{slide.icon}</AtomicText>
37
+ ) : (
38
+ <AtomicIcon
39
+ name={slide.icon as any}
40
+ customSize={iconSize}
41
+ customColor={colors.iconColor}
42
+ />
43
+ )}
44
+ </View>
45
+ )}
46
+
47
+ <AtomicText
48
+ type="displaySmall"
49
+ style={[styles.title, { color: colors.textColor }]}
50
+ >
51
+ {slide.title}
52
+ </AtomicText>
53
+
54
+ {slide.description && (
55
+ <AtomicText
56
+ type="bodyLarge"
57
+ style={[styles.description, { color: colors.subTextColor }]}
58
+ >
59
+ {slide.description}
60
+ </AtomicText>
61
+ )}
62
+
63
+ {slide.features && slide.features.length > 0 && (
64
+ <View style={styles.features}>
65
+ {slide.features.map((feature, index) => (
66
+ <View
67
+ key={index}
68
+ style={[
69
+ styles.featureItem,
70
+ { backgroundColor: colors.featureItemBg },
71
+ ]}
72
+ >
73
+ <AtomicIcon
74
+ name="checkmark-circle"
75
+ size="sm"
76
+ customColor={colors.iconColor}
77
+ />
78
+ <AtomicText
79
+ type="bodyMedium"
80
+ style={[styles.featureText, { color: colors.textColor }]}
81
+ >
82
+ {feature}
83
+ </AtomicText>
84
+ </View>
85
+ ))}
86
+ </View>
87
+ )}
88
+ </BaseSlide>
89
+ );
90
+ };
91
+
92
+ const styles = StyleSheet.create({
93
+ iconBox: {
94
+ marginBottom: 32,
95
+ height: 100,
96
+ justifyContent: "center",
97
+ alignItems: "center",
98
+ },
99
+ title: {
100
+ fontWeight: "800",
101
+ textAlign: "center",
102
+ marginBottom: 16,
103
+ },
104
+ description: {
105
+ textAlign: "center",
106
+ lineHeight: 24,
107
+ marginBottom: 32,
108
+ },
109
+ features: {
110
+ width: "100%",
111
+ gap: 12,
112
+ },
113
+ featureItem: {
114
+ flexDirection: "row",
115
+ alignItems: "center",
116
+ padding: 16,
117
+ borderRadius: 16,
118
+ gap: 12,
119
+ },
120
+ featureText: {
121
+ fontWeight: "600",
122
+ flex: 1,
123
+ },
124
+ });