@umituz/react-native-onboarding 3.2.0 → 3.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-onboarding",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
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",
@@ -38,6 +38,8 @@
38
38
  "@umituz/react-native-design-system": "latest",
39
39
  "@umituz/react-native-design-system-atoms": "latest",
40
40
  "@expo/vector-icons": ">=14.0.0",
41
+ "expo-av": "~15.0.0",
42
+ "expo-image": "~2.0.0",
41
43
  "expo-linear-gradient": "^15.0.0",
42
44
  "react": ">=18.2.0",
43
45
  "react-native": ">=0.74.0",
@@ -53,6 +55,8 @@
53
55
  "@umituz/react-native-design-system-theme": "latest",
54
56
  "@umituz/react-native-design-system-atoms": "latest",
55
57
  "@expo/vector-icons": ">=14.0.0",
58
+ "expo-av": "~15.0.0",
59
+ "expo-image": "~2.0.0",
56
60
  "expo-linear-gradient": "^15.0.7",
57
61
  "react": "^18.2.0",
58
62
  "react-native": "^0.74.0",
@@ -68,4 +72,4 @@
68
72
  "README.md",
69
73
  "LICENSE"
70
74
  ]
71
- }
75
+ }
@@ -91,5 +91,14 @@ export interface OnboardingOptions {
91
91
  * When true, all slides will use gradient backgrounds if available
92
92
  */
93
93
  useGradient?: boolean;
94
+
95
+ /**
96
+ * Visual theme variant (default: "default")
97
+ * - default: Standard layout
98
+ * - card: Content in a card with shadow
99
+ * - minimal: Clean, text-focused layout
100
+ * - fullscreen: Immersive fullscreen layout
101
+ */
102
+ themeVariant?: "default" | "card" | "minimal" | "fullscreen";
94
103
  }
95
104
 
@@ -58,7 +58,25 @@ export interface OnboardingSlide {
58
58
  /**
59
59
  * Optional image URL (alternative to icon)
60
60
  */
61
- image?: string;
61
+ image?: any;
62
+
63
+ /**
64
+ * Optional background image (URL or require path)
65
+ * Stretches to fill the screen behind content
66
+ */
67
+ backgroundImage?: any;
68
+
69
+ /**
70
+ * Optional background video (URL or require path)
71
+ * Plays in loop behind content
72
+ */
73
+ backgroundVideo?: any;
74
+
75
+ /**
76
+ * Opacity of the overlay gradient/color on top of background media
77
+ * Range: 0.0 to 1.0 (Default: 0.5)
78
+ */
79
+ overlayOpacity?: number;
62
80
 
63
81
  /**
64
82
  * Optional features list to display
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Onboarding Theme Variant
3
+ *
4
+ * Defines the visual style of the onboarding flow
5
+ */
6
+ export type OnboardingThemeVariant = "default" | "card" | "minimal" | "fullscreen";
@@ -6,6 +6,8 @@
6
6
  import React from "react";
7
7
  import { View, StyleSheet, StatusBar } from "react-native";
8
8
  import { LinearGradient } from "expo-linear-gradient";
9
+ import { Image } from "expo-image";
10
+ import { Video, ResizeMode } from "expo-av";
9
11
  import { useTheme } from "@umituz/react-native-design-system-theme";
10
12
  import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
11
13
  import { OnboardingHeader } from "./OnboardingHeader";
@@ -51,6 +53,7 @@ export interface OnboardingScreenContentProps {
51
53
  renderSlide?: (slide: OnboardingSlide) => React.ReactNode;
52
54
  onUpgrade?: () => void;
53
55
  showPaywallOnComplete?: boolean;
56
+ variant?: "default" | "card" | "minimal" | "fullscreen";
54
57
  }
55
58
 
56
59
  export const OnboardingScreenContent: React.FC<OnboardingScreenContentProps> = ({
@@ -80,20 +83,78 @@ export const OnboardingScreenContent: React.FC<OnboardingScreenContentProps> = (
80
83
  renderSlide,
81
84
  onUpgrade,
82
85
  showPaywallOnComplete,
86
+ variant = "default",
83
87
  }) => {
84
88
  const { themeMode } = useTheme();
85
89
 
86
- return (
87
- <View style={[styles.container, containerStyle]}>
88
- <StatusBar barStyle={themeMode === "dark" ? "light-content" : "dark-content"} />
89
- {useGradient && currentSlide && (
90
+ const hasMedia = !!currentSlide?.backgroundImage || !!currentSlide?.backgroundVideo;
91
+ const overlayOpacity = currentSlide?.overlayOpacity ?? 0.5;
92
+ // If media is present, valid gradient mode is enforced (white text)
93
+ const effectivelyUseGradient = useGradient || hasMedia;
94
+
95
+ const renderBackground = () => {
96
+ if (!currentSlide) return null;
97
+
98
+ if (currentSlide.backgroundVideo) {
99
+ return (
100
+ <View style={StyleSheet.absoluteFill}>
101
+ <Video
102
+ source={currentSlide.backgroundVideo}
103
+ style={StyleSheet.absoluteFill}
104
+ resizeMode={ResizeMode.COVER}
105
+ isLooping
106
+ shouldPlay
107
+ isMuted
108
+ />
109
+ <View
110
+ style={[
111
+ StyleSheet.absoluteFill,
112
+ { backgroundColor: `rgba(0,0,0,${overlayOpacity})` }
113
+ ]}
114
+ />
115
+ </View>
116
+ );
117
+ }
118
+
119
+ if (currentSlide.backgroundImage) {
120
+ return (
121
+ <View style={StyleSheet.absoluteFill}>
122
+ <Image
123
+ source={currentSlide.backgroundImage}
124
+ style={StyleSheet.absoluteFill}
125
+ contentFit="cover"
126
+ transition={500}
127
+ />
128
+ <View
129
+ style={[
130
+ StyleSheet.absoluteFill,
131
+ { backgroundColor: `rgba(0,0,0,${overlayOpacity})` }
132
+ ]}
133
+ />
134
+ </View>
135
+ );
136
+ }
137
+
138
+ if (useGradient && currentSlide.gradient) {
139
+ return (
90
140
  <LinearGradient
91
141
  colors={currentSlide.gradient as [string, string, ...string[]]}
92
142
  start={{ x: 0, y: 0 }}
93
143
  end={{ x: 1, y: 1 }}
94
144
  style={StyleSheet.absoluteFill}
95
145
  />
96
- )}
146
+ );
147
+ }
148
+
149
+ return null;
150
+ };
151
+
152
+ return (
153
+ <View style={[styles.container, containerStyle]}>
154
+ <StatusBar barStyle={themeMode === "dark" || effectivelyUseGradient ? "light-content" : "dark-content"} />
155
+
156
+ {renderBackground()}
157
+
97
158
  {renderHeader ? (
98
159
  renderHeader({
99
160
  isFirstSlide,
@@ -108,7 +169,7 @@ export const OnboardingScreenContent: React.FC<OnboardingScreenContentProps> = (
108
169
  showBackButton={showBackButton}
109
170
  showSkipButton={showSkipButton}
110
171
  skipButtonText={skipButtonText}
111
- useGradient={useGradient}
172
+ useGradient={effectivelyUseGradient}
112
173
  />
113
174
  )}
114
175
  {currentSlide &&
@@ -119,10 +180,15 @@ export const OnboardingScreenContent: React.FC<OnboardingScreenContentProps> = (
119
180
  slide={currentSlide}
120
181
  value={currentAnswer}
121
182
  onChange={onAnswerChange}
122
- useGradient={useGradient}
183
+ useGradient={effectivelyUseGradient}
184
+ variant={variant}
123
185
  />
124
186
  ) : (
125
- <OnboardingSlideComponent slide={currentSlide} useGradient={useGradient} />
187
+ <OnboardingSlideComponent
188
+ slide={currentSlide}
189
+ useGradient={effectivelyUseGradient}
190
+ variant={variant}
191
+ />
126
192
  ))}
127
193
  {renderFooter ? (
128
194
  renderFooter({
@@ -145,7 +211,7 @@ export const OnboardingScreenContent: React.FC<OnboardingScreenContentProps> = (
145
211
  nextButtonText={nextButtonText}
146
212
  getStartedButtonText={getStartedButtonText}
147
213
  disabled={!isAnswerValid}
148
- useGradient={useGradient}
214
+ useGradient={effectivelyUseGradient}
149
215
  />
150
216
  )}
151
217
  </View>
@@ -158,3 +224,4 @@ const styles = StyleSheet.create({
158
224
  },
159
225
  });
160
226
 
227
+
@@ -2,37 +2,56 @@
2
2
  * Onboarding Slide Component
3
3
  *
4
4
  * Displays a single onboarding slide with icon, title, and description
5
- * Uses AtomicIcon from design system for consistent icon rendering
5
+ * Supports multiple variants (default, card, minimal) and dark mode
6
6
  */
7
7
 
8
8
  import React, { useMemo } from "react";
9
- import { View, Text, StyleSheet, ScrollView } from "react-native";
9
+ import { View, Text, ScrollView } from "react-native";
10
10
  import { AtomicIcon } from "@umituz/react-native-design-system-atoms";
11
- import { useAppDesignTokens, withAlpha } from "@umituz/react-native-design-system-theme";
11
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
12
12
  import type { OnboardingSlide as OnboardingSlideType } from "../../domain/entities/OnboardingSlide";
13
+ import type { OnboardingThemeVariant } from "../../domain/entities/OnboardingTheme";
14
+ import { createOnboardingStyles } from "../styles/OnboardingSlideStyles";
13
15
 
14
16
  export interface OnboardingSlideProps {
15
17
  slide: OnboardingSlideType;
16
18
  useGradient?: boolean;
19
+ variant?: OnboardingThemeVariant;
17
20
  }
18
21
 
19
22
  const EMOJI_REGEX = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u;
20
23
 
21
- export const OnboardingSlide: React.FC<OnboardingSlideProps> = ({ slide, useGradient = false }) => {
24
+ export const OnboardingSlide: React.FC<OnboardingSlideProps> = ({
25
+ slide,
26
+ useGradient = false,
27
+ variant = "default"
28
+ }) => {
22
29
  const tokens = useAppDesignTokens();
23
- const styles = useMemo(() => getStyles(tokens, useGradient), [tokens, useGradient]);
30
+ const styles = useMemo(
31
+ () => createOnboardingStyles(tokens, useGradient, variant),
32
+ [tokens, useGradient, variant]
33
+ );
24
34
 
25
35
  const isEmoji = EMOJI_REGEX.test(slide.icon);
26
- const isValidIconName = !isEmoji && typeof slide.icon === "string";
36
+ // Simple check for valid icon name - assuming string and not emoji
37
+ const isValidIconName = !isEmoji && typeof slide.icon === "string" && slide.icon.length > 0;
38
+
39
+ const iconColor = useGradient ? "#FFFFFF" : tokens.colors.primary;
40
+ const iconSize = variant === "minimal" ? 80 : 64;
27
41
 
28
- const renderIcon = (color: string) => {
42
+ const renderIcon = () => {
29
43
  if (isEmoji) {
30
- return <Text style={styles.icon}>{slide.icon}</Text>;
44
+ return <Text style={{ fontSize: iconSize }}>{slide.icon}</Text>;
31
45
  }
32
46
  if (isValidIconName) {
33
- return <AtomicIcon name={slide.icon} customSize={60} customColor={color} />;
47
+ return <AtomicIcon name={slide.icon} customSize={iconSize} customColor={iconColor} />;
34
48
  }
35
- return <Text style={styles.icon}>📱</Text>;
49
+ // Fallback if no icon
50
+ if (slide.image) {
51
+ // TODO: Implement image support if needed, for now just placeholder
52
+ return <AtomicIcon name="image" customSize={iconSize} customColor={iconColor} />;
53
+ }
54
+ return null;
36
55
  };
37
56
 
38
57
  const renderFeatures = () => {
@@ -49,111 +68,29 @@ export const OnboardingSlide: React.FC<OnboardingSlideProps> = ({ slide, useGrad
49
68
  );
50
69
  };
51
70
 
52
- const iconColor = useGradient ? "#FFFFFF" : tokens.colors.textPrimary;
53
-
54
71
  return (
55
72
  <ScrollView
56
73
  contentContainerStyle={styles.content}
57
74
  showsVerticalScrollIndicator={false}
75
+ bounces={false}
58
76
  >
59
- {useGradient ? (
60
- <>
61
- <View style={styles.iconContainer}>{renderIcon(iconColor)}</View>
62
- <Text style={styles.title}>{slide.title}</Text>
63
- <Text style={styles.description}>{slide.description}</Text>
64
- {renderFeatures()}
65
- </>
66
- ) : (
67
- <View style={styles.slideContent}>
68
- <View style={styles.iconContainer}>{renderIcon(iconColor)}</View>
69
- <Text style={styles.title}>{slide.title}</Text>
70
- <Text style={styles.description}>{slide.description}</Text>
71
- {renderFeatures()}
72
- </View>
73
- )}
77
+ <View style={styles.slideContainer}>
78
+ {/* Icon Section */}
79
+ {(isEmoji || isValidIconName) && (
80
+ <View style={styles.iconBox}>
81
+ {renderIcon()}
82
+ </View>
83
+ )}
84
+
85
+ {/* Text Section */}
86
+ <Text style={styles.title}>{slide.title}</Text>
87
+ <Text style={styles.description}>{slide.description}</Text>
88
+
89
+ {/* Features Section */}
90
+ {renderFeatures()}
91
+ </View>
74
92
  </ScrollView>
75
93
  );
76
94
  };
77
95
 
78
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>, useGradient: boolean) =>
79
- StyleSheet.create({
80
- content: {
81
- flexGrow: 1,
82
- justifyContent: "center",
83
- alignItems: "center",
84
- paddingHorizontal: 30,
85
- paddingVertical: 40,
86
- },
87
- slideContent: {
88
- alignItems: "center",
89
- maxWidth: 400,
90
- width: "100%",
91
- backgroundColor: tokens.colors.surface,
92
- padding: 30,
93
- borderRadius: 24,
94
- borderWidth: 1,
95
- borderColor: tokens.colors.borderLight,
96
- shadowColor: tokens.colors.textPrimary,
97
- shadowOffset: {
98
- width: 0,
99
- height: 4,
100
- },
101
- shadowOpacity: 0.1,
102
- shadowRadius: 8,
103
- elevation: 4,
104
- },
105
- iconContainer: {
106
- width: 120,
107
- height: 120,
108
- borderRadius: 60,
109
- backgroundColor: useGradient
110
- ? "rgba(255, 255, 255, 0.25)"
111
- : withAlpha(tokens.colors.primary, 0.2),
112
- alignItems: "center",
113
- justifyContent: "center",
114
- marginBottom: 40,
115
- borderWidth: 2,
116
- borderColor: useGradient
117
- ? "rgba(255, 255, 255, 0.4)"
118
- : withAlpha(tokens.colors.primary, 0.4),
119
- },
120
- icon: {
121
- fontSize: 60,
122
- },
123
- title: {
124
- fontSize: 28,
125
- fontWeight: "bold",
126
- color: useGradient ? "#FFFFFF" : tokens.colors.textPrimary,
127
- textAlign: "center",
128
- marginBottom: 16,
129
- },
130
- description: {
131
- fontSize: 16,
132
- color: useGradient ? "rgba(255, 255, 255, 0.9)" : tokens.colors.textSecondary,
133
- textAlign: "center",
134
- lineHeight: 24,
135
- marginBottom: 20,
136
- },
137
- featuresContainer: {
138
- width: "100%",
139
- marginTop: 10,
140
- },
141
- featureItem: {
142
- flexDirection: "row",
143
- alignItems: "flex-start",
144
- marginBottom: 12,
145
- },
146
- featureBullet: {
147
- color: useGradient ? "#FFFFFF" : tokens.colors.primary,
148
- fontSize: 20,
149
- marginRight: 12,
150
- marginTop: 2,
151
- },
152
- featureText: {
153
- flex: 1,
154
- fontSize: 15,
155
- color: useGradient ? "rgba(255, 255, 255, 0.9)" : tokens.colors.textSecondary,
156
- lineHeight: 22,
157
- },
158
- });
159
96
 
@@ -4,17 +4,20 @@
4
4
  */
5
5
 
6
6
  import React, { useMemo } from "react";
7
- import { View, Text, StyleSheet, ScrollView } from "react-native";
7
+ import { View, Text, ScrollView } from "react-native";
8
8
  import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
9
  import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
10
+ import type { OnboardingThemeVariant } from "../../domain/entities/OnboardingTheme";
10
11
  import { QuestionSlideHeader } from "./QuestionSlideHeader";
11
12
  import { QuestionRenderer } from "./QuestionRenderer";
13
+ import { createQuestionStyles } from "../styles/QuestionSlideStyles";
12
14
 
13
15
  export interface QuestionSlideProps {
14
16
  slide: OnboardingSlide;
15
17
  value: any;
16
18
  onChange: (value: any) => void;
17
19
  useGradient?: boolean;
20
+ variant?: OnboardingThemeVariant;
18
21
  }
19
22
 
20
23
  export const QuestionSlide: React.FC<QuestionSlideProps> = ({
@@ -22,9 +25,13 @@ export const QuestionSlide: React.FC<QuestionSlideProps> = ({
22
25
  value,
23
26
  onChange,
24
27
  useGradient = false,
28
+ variant = "default",
25
29
  }) => {
26
30
  const tokens = useAppDesignTokens();
27
- const styles = useMemo(() => getStyles(tokens, useGradient), [tokens, useGradient]);
31
+ const styles = useMemo(
32
+ () => createQuestionStyles(tokens, useGradient, variant),
33
+ [tokens, useGradient, variant]
34
+ );
28
35
  const { question } = slide;
29
36
 
30
37
  if (!question) {
@@ -53,54 +60,12 @@ export const QuestionSlide: React.FC<QuestionSlideProps> = ({
53
60
  <ScrollView
54
61
  contentContainerStyle={styles.content}
55
62
  showsVerticalScrollIndicator={false}
63
+ bounces={false}
56
64
  >
57
- {useGradient ? (
58
- content
59
- ) : (
60
- <View style={styles.slideContent}>{content}</View>
61
- )}
65
+ <View style={styles.slideContainer}>
66
+ {content}
67
+ </View>
62
68
  </ScrollView>
63
69
  );
64
70
  };
65
71
 
66
- const getStyles = (
67
- tokens: ReturnType<typeof useAppDesignTokens>,
68
- useGradient: boolean,
69
- ) =>
70
- StyleSheet.create({
71
- content: {
72
- flexGrow: 1,
73
- justifyContent: "center",
74
- alignItems: "center",
75
- paddingHorizontal: 30,
76
- paddingVertical: 20,
77
- },
78
- slideContent: {
79
- alignItems: "center",
80
- maxWidth: 500,
81
- width: "100%",
82
- backgroundColor: tokens.colors.surface,
83
- padding: 30,
84
- borderRadius: 24,
85
- borderWidth: 1,
86
- borderColor: tokens.colors.borderLight,
87
- shadowColor: tokens.colors.textPrimary,
88
- shadowOffset: {
89
- width: 0,
90
- height: 4,
91
- },
92
- shadowOpacity: 0.1,
93
- shadowRadius: 8,
94
- elevation: 4,
95
- },
96
- questionContainer: {
97
- width: "100%",
98
- marginTop: 8,
99
- },
100
- requiredHint: {
101
- fontSize: 13,
102
- color: useGradient ? "rgba(255, 255, 255, 0.8)" : tokens.colors.textSecondary,
103
- fontStyle: "italic",
104
- marginTop: 12,
105
- },
106
- });
@@ -73,6 +73,7 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
73
73
  onUpgrade,
74
74
  showPaywallOnComplete = false,
75
75
  useGradient: globalUseGradient = false,
76
+ themeVariant = "default",
76
77
  }) => {
77
78
  if (__DEV__) {
78
79
  console.log("[OnboardingScreen] Rendering with slides:", slides?.length);
@@ -140,6 +141,7 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
140
141
  renderSlide={renderSlide}
141
142
  onUpgrade={onUpgrade}
142
143
  showPaywallOnComplete={showPaywallOnComplete}
144
+ variant={themeVariant}
143
145
  />
144
146
  );
145
147
  };
@@ -0,0 +1,144 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { withAlpha } from "@umituz/react-native-design-system-theme";
3
+
4
+ export const createOnboardingStyles = (
5
+ tokens: any,
6
+ useGradient: boolean,
7
+ variant: "default" | "card" | "minimal" | "fullscreen" = "default"
8
+ ) => {
9
+ const isDark = tokens.isDark;
10
+
11
+ // Base styles
12
+ const baseStyles = StyleSheet.create({
13
+ content: {
14
+ flexGrow: 1,
15
+ justifyContent: "center",
16
+ alignItems: "center",
17
+ paddingHorizontal: 24,
18
+ paddingVertical: 40,
19
+ },
20
+ title: {
21
+ ...tokens.typography.headingLarge,
22
+ textAlign: "center",
23
+ marginBottom: 16,
24
+ color: useGradient ? "#FFFFFF" : tokens.colors.textPrimary,
25
+ },
26
+ description: {
27
+ ...tokens.typography.bodyLarge,
28
+ textAlign: "center",
29
+ color: useGradient
30
+ ? "rgba(255, 255, 255, 0.9)"
31
+ : tokens.colors.textSecondary,
32
+ lineHeight: 24,
33
+ marginBottom: 24,
34
+ maxWidth: 320,
35
+ },
36
+ iconContainer: {
37
+ marginBottom: 40,
38
+ alignItems: "center",
39
+ justifyContent: "center",
40
+ },
41
+ featuresContainer: {
42
+ width: "100%",
43
+ marginTop: 24,
44
+ paddingHorizontal: 16,
45
+ },
46
+ featureItem: {
47
+ flexDirection: "row",
48
+ alignItems: "flex-start",
49
+ marginBottom: 16,
50
+ backgroundColor: useGradient
51
+ ? "rgba(255, 255, 255, 0.1)"
52
+ : tokens.colors.surfaceSecondary,
53
+ padding: 12,
54
+ borderRadius: 12,
55
+ },
56
+ featureBullet: {
57
+ color: useGradient ? "#FFFFFF" : tokens.colors.primary,
58
+ fontSize: 18,
59
+ marginRight: 12,
60
+ marginTop: 2,
61
+ },
62
+ featureText: {
63
+ flex: 1,
64
+ ...tokens.typography.bodyMedium,
65
+ color: useGradient ? "#FFFFFF" : tokens.colors.textPrimary,
66
+ },
67
+ });
68
+
69
+ // Variant specific styles
70
+ if (variant === "card") {
71
+ return StyleSheet.create({
72
+ ...baseStyles,
73
+ slideContainer: {
74
+ width: "100%",
75
+ maxWidth: 400,
76
+ backgroundColor: useGradient ? "transparent" : tokens.colors.surface,
77
+ borderRadius: 24,
78
+ padding: 32,
79
+ alignItems: "center",
80
+ // Shadow only for non-gradient or light mode
81
+ ...(!useGradient && {
82
+ shadowColor: tokens.colors.shadow,
83
+ shadowOffset: { width: 0, height: 4 },
84
+ shadowOpacity: 0.1,
85
+ shadowRadius: 12,
86
+ elevation: 5,
87
+ borderWidth: 1,
88
+ borderColor: tokens.colors.borderLight,
89
+ }),
90
+ },
91
+ iconBox: {
92
+ width: 100,
93
+ height: 100,
94
+ borderRadius: 50,
95
+ backgroundColor: useGradient
96
+ ? "rgba(255, 255, 255, 0.2)"
97
+ : withAlpha(tokens.colors.primary, 0.1),
98
+ alignItems: "center",
99
+ justifyContent: "center",
100
+ marginBottom: 24,
101
+ }
102
+ });
103
+ }
104
+
105
+ if (variant === "minimal") {
106
+ return StyleSheet.create({
107
+ ...baseStyles,
108
+ slideContainer: {
109
+ width: "100%",
110
+ alignItems: "center",
111
+ padding: 0,
112
+ },
113
+ title: {
114
+ ...baseStyles.title,
115
+ fontSize: 32,
116
+ marginBottom: 24,
117
+ },
118
+ iconBox: {
119
+ marginBottom: 48,
120
+ }
121
+ });
122
+ }
123
+
124
+ // Default / Fullscreen
125
+ return StyleSheet.create({
126
+ ...baseStyles,
127
+ slideContainer: {
128
+ width: "100%",
129
+ alignItems: "center",
130
+ padding: 16,
131
+ },
132
+ iconBox: {
133
+ width: 120,
134
+ height: 120,
135
+ borderRadius: 60,
136
+ backgroundColor: useGradient
137
+ ? "rgba(255, 255, 255, 0.15)"
138
+ : tokens.colors.surfaceSecondary,
139
+ alignItems: "center",
140
+ justifyContent: "center",
141
+ marginBottom: 32,
142
+ }
143
+ });
144
+ };
@@ -0,0 +1,78 @@
1
+ import { StyleSheet } from "react-native";
2
+
3
+ export const createQuestionStyles = (
4
+ tokens: any,
5
+ useGradient: boolean,
6
+ variant: "default" | "card" | "minimal" | "fullscreen" = "default"
7
+ ) => {
8
+ // Base styles
9
+ const baseStyles = StyleSheet.create({
10
+ content: {
11
+ flexGrow: 1,
12
+ justifyContent: "center",
13
+ alignItems: "center",
14
+ paddingHorizontal: 24,
15
+ paddingVertical: 20,
16
+ },
17
+ questionContainer: {
18
+ width: "100%",
19
+ marginTop: 24,
20
+ },
21
+ requiredHint: {
22
+ fontSize: 13,
23
+ color: useGradient ? "rgba(255, 255, 255, 0.8)" : tokens.colors.textSecondary,
24
+ fontStyle: "italic",
25
+ marginTop: 16,
26
+ textAlign: "left",
27
+ alignSelf: "flex-start",
28
+ marginLeft: 4,
29
+ },
30
+ });
31
+
32
+ if (variant === "card") {
33
+ return StyleSheet.create({
34
+ ...baseStyles,
35
+ slideContainer: {
36
+ width: "100%",
37
+ maxWidth: 500,
38
+ backgroundColor: useGradient ? "transparent" : tokens.colors.surface,
39
+ borderRadius: 24,
40
+ padding: 32,
41
+ alignItems: "center",
42
+ ...(!useGradient && {
43
+ shadowColor: tokens.colors.shadow,
44
+ shadowOffset: { width: 0, height: 4 },
45
+ shadowOpacity: 0.1,
46
+ shadowRadius: 12,
47
+ elevation: 5,
48
+ borderWidth: 1,
49
+ borderColor: tokens.colors.borderLight,
50
+ }),
51
+ },
52
+ });
53
+ }
54
+
55
+ if (variant === "minimal") {
56
+ return StyleSheet.create({
57
+ ...baseStyles,
58
+ slideContainer: {
59
+ width: "100%",
60
+ alignItems: "stretch",
61
+ padding: 0,
62
+ },
63
+ });
64
+ }
65
+
66
+ // Default
67
+ return StyleSheet.create({
68
+ ...baseStyles,
69
+ slideContainer: {
70
+ width: "100%",
71
+ maxWidth: 500,
72
+ alignItems: "center",
73
+ padding: 16,
74
+ backgroundColor: useGradient ? "rgba(255, 255, 255, 0.1)" : tokens.colors.surfaceSecondary,
75
+ borderRadius: 16,
76
+ },
77
+ });
78
+ };