@umituz/react-native-onboarding 3.3.7 → 3.3.9

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.
@@ -1,17 +1,8 @@
1
- /**
2
- * Onboarding Slide Component
3
- *
4
- * Displays a single onboarding slide with icon, title, and description
5
- * Supports multiple variants (default, card, minimal) and dark mode
6
- */
7
-
8
- import React, { useMemo } from "react";
9
- import { View, Text, ScrollView } from "react-native";
10
- import { AtomicIcon } from "@umituz/react-native-design-system";
11
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
1
+ import React from "react";
2
+ import { View, ScrollView, StyleSheet } from "react-native";
3
+ import { AtomicIcon, AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
12
4
  import type { OnboardingSlide as OnboardingSlideType } from "../../domain/entities/OnboardingSlide";
13
5
  import type { OnboardingThemeVariant } from "../../domain/entities/OnboardingTheme";
14
- import { createOnboardingStyles } from "../styles/OnboardingSlideStyles";
15
6
 
16
7
  export interface OnboardingSlideProps {
17
8
  slide: OnboardingSlideType;
@@ -21,76 +12,101 @@ export interface OnboardingSlideProps {
21
12
 
22
13
  const EMOJI_REGEX = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u;
23
14
 
24
- export const OnboardingSlide: React.FC<OnboardingSlideProps> = ({
15
+ export const OnboardingSlide = ({
25
16
  slide,
26
17
  useGradient = false,
27
18
  variant = "default"
28
- }) => {
19
+ }: OnboardingSlideProps) => {
29
20
  const tokens = useAppDesignTokens();
30
- const styles = useMemo(
31
- () => createOnboardingStyles(tokens, useGradient, variant),
32
- [tokens, useGradient, variant]
33
- );
34
21
 
35
22
  const isEmoji = EMOJI_REGEX.test(slide.icon);
36
- // Simple check for valid icon name - assuming string and not emoji
37
23
  const isValidIconName = !isEmoji && typeof slide.icon === "string" && slide.icon.length > 0;
38
-
39
- const iconColor = useGradient ? tokens.colors.surface : tokens.colors.primary;
40
- const iconSize = variant === "minimal" ? 80 : 64;
41
-
42
- const renderIcon = () => {
43
- if (isEmoji) {
44
- return <Text style={{ fontSize: iconSize }}>{slide.icon}</Text>;
45
- }
46
- if (isValidIconName) {
47
- return <AtomicIcon name={slide.icon} customSize={iconSize} customColor={iconColor} />;
48
- }
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;
55
- };
56
-
57
- const renderFeatures = () => {
58
- if (!slide.features || slide.features.length === 0) return null;
59
- return (
60
- <View style={styles.featuresContainer}>
61
- {slide.features.map((feature, index) => (
62
- <View key={index} style={styles.featureItem}>
63
- <Text style={styles.featureBullet}>•</Text>
64
- <Text style={styles.featureText}>{feature}</Text>
65
- </View>
66
- ))}
67
- </View>
68
- );
69
- };
24
+ const iconColor = useGradient ? "#FFFFFF" : tokens.colors.primary;
25
+ const textColor = useGradient ? "#FFFFFF" : tokens.colors.textPrimary;
26
+ const subTextColor = useGradient ? "rgba(255, 255, 255, 0.8)" : tokens.colors.textSecondary;
27
+ const iconSize = variant === "minimal" ? 80 : 72;
70
28
 
71
29
  return (
72
- <ScrollView
73
- contentContainerStyle={styles.content}
74
- showsVerticalScrollIndicator={false}
75
- bounces={false}
76
- >
30
+ <ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false} bounces={false}>
77
31
  <View style={styles.slideContainer}>
78
- {/* Icon Section */}
79
32
  {(isEmoji || isValidIconName) && (
80
33
  <View style={styles.iconBox}>
81
- {renderIcon()}
34
+ {isEmoji ? (
35
+ <AtomicText style={{ fontSize: iconSize }}>{slide.icon}</AtomicText>
36
+ ) : (
37
+ <AtomicIcon name={slide.icon} customSize={iconSize} customColor={iconColor} />
38
+ )}
82
39
  </View>
83
40
  )}
84
41
 
85
- {/* Text Section */}
86
- <Text style={styles.title}>{slide.title}</Text>
87
- <Text style={styles.description}>{slide.description}</Text>
42
+ <AtomicText type="headlineMedium" style={[styles.title, { color: textColor }]}>
43
+ {slide.title}
44
+ </AtomicText>
45
+
46
+ <AtomicText type="bodyLarge" style={[styles.description, { color: subTextColor }]}>
47
+ {slide.description}
48
+ </AtomicText>
88
49
 
89
- {/* Features Section */}
90
- {renderFeatures()}
50
+ {slide.features && slide.features.length > 0 && (
51
+ <View style={styles.featuresContainer}>
52
+ {slide.features.map((feature, index) => (
53
+ <View key={index} style={styles.featureItem}>
54
+ <AtomicIcon name="checkmark-circle" size="sm" customColor={iconColor} />
55
+ <AtomicText type="bodyMedium" style={[styles.featureText, { color: textColor }]}>
56
+ {feature}
57
+ </AtomicText>
58
+ </View>
59
+ ))}
60
+ </View>
61
+ )}
91
62
  </View>
92
63
  </ScrollView>
93
64
  );
94
65
  };
95
66
 
67
+ const styles = StyleSheet.create({
68
+ content: {
69
+ flexGrow: 1,
70
+ paddingTop: 40,
71
+ },
72
+ slideContainer: {
73
+ paddingHorizontal: 30,
74
+ alignItems: "center",
75
+ },
76
+ iconBox: {
77
+ marginBottom: 40,
78
+ height: 120,
79
+ justifyContent: "center",
80
+ alignItems: "center",
81
+ },
82
+ title: {
83
+ textAlign: "center",
84
+ marginBottom: 16,
85
+ fontWeight: "800",
86
+ },
87
+ description: {
88
+ textAlign: "center",
89
+ lineHeight: 24,
90
+ marginBottom: 32,
91
+ opacity: 0.9,
92
+ },
93
+ featuresContainer: {
94
+ width: "100%",
95
+ gap: 12,
96
+ marginTop: 8,
97
+ },
98
+ featureItem: {
99
+ flexDirection: "row",
100
+ alignItems: "center",
101
+ gap: 12,
102
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
103
+ padding: 12,
104
+ borderRadius: 12,
105
+ },
106
+ featureText: {
107
+ flex: 1,
108
+ },
109
+ });
110
+
111
+
96
112
 
@@ -16,11 +16,11 @@ export interface QuestionRendererProps {
16
16
  onChange: (value: any) => void;
17
17
  }
18
18
 
19
- export const QuestionRenderer: React.FC<QuestionRendererProps> = ({
19
+ export const QuestionRenderer = ({
20
20
  question,
21
21
  value,
22
22
  onChange,
23
- }) => {
23
+ }: QuestionRendererProps) => {
24
24
  switch (question.type) {
25
25
  case "single_choice":
26
26
  return (
@@ -1,16 +1,10 @@
1
- /**
2
- * Question Slide Component
3
- * Single Responsibility: Display question slide with header and question
4
- */
5
-
6
- import React, { useMemo } from "react";
7
- import { View, Text, ScrollView } from "react-native";
8
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
1
+ import React from "react";
2
+ import { View, ScrollView, StyleSheet } from "react-native";
3
+ import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
9
4
  import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
10
5
  import type { OnboardingThemeVariant } from "../../domain/entities/OnboardingTheme";
11
6
  import { QuestionSlideHeader } from "./QuestionSlideHeader";
12
7
  import { QuestionRenderer } from "./QuestionRenderer";
13
- import { createQuestionStyles } from "../styles/QuestionSlideStyles";
14
8
 
15
9
  export interface QuestionSlideProps {
16
10
  slide: OnboardingSlide;
@@ -20,52 +14,55 @@ export interface QuestionSlideProps {
20
14
  variant?: OnboardingThemeVariant;
21
15
  }
22
16
 
23
- export const QuestionSlide: React.FC<QuestionSlideProps> = ({
17
+ export const QuestionSlide = ({
24
18
  slide,
25
19
  value,
26
20
  onChange,
27
21
  useGradient = false,
28
- variant = "default",
29
- }) => {
22
+ }: QuestionSlideProps) => {
30
23
  const tokens = useAppDesignTokens();
31
- const styles = useMemo(
32
- () => createQuestionStyles(tokens, useGradient, variant),
33
- [tokens, useGradient, variant]
34
- );
35
24
  const { question } = slide;
36
25
 
37
- if (!question) {
38
- return null;
39
- }
40
-
41
- const content = (
42
- <>
43
- <QuestionSlideHeader slide={slide} useGradient={useGradient} />
44
-
45
- <View style={styles.questionContainer}>
46
- <QuestionRenderer
47
- question={question}
48
- value={value}
49
- onChange={onChange}
50
- />
51
- </View>
52
-
53
- {question.validation?.required && !value && (
54
- <Text style={styles.requiredHint}>* This field is required</Text>
55
- )}
56
- </>
57
- );
26
+ if (!question) return null;
58
27
 
59
28
  return (
60
- <ScrollView
61
- contentContainerStyle={styles.content}
62
- showsVerticalScrollIndicator={false}
63
- bounces={false}
64
- >
29
+ <ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false} bounces={false}>
65
30
  <View style={styles.slideContainer}>
66
- {content}
31
+ <QuestionSlideHeader slide={slide} useGradient={useGradient} />
32
+
33
+ <View style={styles.questionContainer}>
34
+ <QuestionRenderer question={question} value={value} onChange={onChange} />
35
+ </View>
36
+
37
+ {question.validation?.required && !value && (
38
+ <AtomicText
39
+ type="labelSmall"
40
+ style={[styles.requiredHint, { color: useGradient ? '#FFFFFF' : tokens.colors.error }]}
41
+ >
42
+ * This field is required
43
+ </AtomicText>
44
+ )}
67
45
  </View>
68
46
  </ScrollView>
69
47
  );
70
48
  };
71
49
 
50
+ const styles = StyleSheet.create({
51
+ content: {
52
+ flexGrow: 1,
53
+ paddingTop: 40,
54
+ },
55
+ slideContainer: {
56
+ paddingHorizontal: 24,
57
+ },
58
+ questionContainer: {
59
+ marginTop: 24,
60
+ },
61
+ requiredHint: {
62
+ marginTop: 12,
63
+ textAlign: "center",
64
+ fontWeight: "600",
65
+ },
66
+ });
67
+
68
+
@@ -1,12 +1,6 @@
1
- /**
2
- * Question Slide Header Component
3
- * Single Responsibility: Display slide header (icon, title, description)
4
- */
5
-
6
1
  import React from "react";
7
- import { View, Text, StyleSheet } from "react-native";
8
- import { AtomicIcon } from "@umituz/react-native-design-system";
9
- import { useAppDesignTokens, withAlpha } from "@umituz/react-native-design-system";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { AtomicIcon, AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
10
4
  import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
11
5
 
12
6
  export interface QuestionSlideHeaderProps {
@@ -17,72 +11,68 @@ export interface QuestionSlideHeaderProps {
17
11
  const isEmoji = (icon: string): boolean =>
18
12
  /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u.test(icon);
19
13
 
20
- export const QuestionSlideHeader: React.FC<QuestionSlideHeaderProps> = ({
14
+ export const QuestionSlideHeader = ({
21
15
  slide,
22
16
  useGradient,
23
- }) => {
17
+ }: QuestionSlideHeaderProps) => {
24
18
  const tokens = useAppDesignTokens();
25
- const styles = getStyles(tokens, useGradient);
19
+ const textColor = useGradient ? "#FFFFFF" : tokens.colors.textPrimary;
20
+ const subTextColor = useGradient ? "rgba(255, 255, 255, 0.9)" : tokens.colors.textSecondary;
26
21
 
27
22
  return (
28
- <>
29
- <View style={styles.iconContainer}>
23
+ <View style={styles.container}>
24
+ <View style={[
25
+ styles.iconContainer,
26
+ {
27
+ backgroundColor: useGradient ? "rgba(255, 255, 255, 0.2)" : tokens.colors.primary + '15',
28
+ borderColor: useGradient ? "rgba(255, 255, 255, 0.4)" : tokens.colors.primary + '30',
29
+ }
30
+ ]}>
30
31
  {isEmoji(slide.icon) ? (
31
- <Text style={styles.icon}>{slide.icon}</Text>
32
+ <AtomicText style={{ fontSize: 48 }}>{slide.icon}</AtomicText>
32
33
  ) : (
33
- <AtomicIcon
34
- name={slide.icon as any}
35
- customSize={48}
36
- customColor={useGradient ? "#FFFFFF" : tokens.colors.textPrimary}
37
- />
34
+ <AtomicIcon name={slide.icon as any} customSize={48} customColor={textColor} />
38
35
  )}
39
36
  </View>
40
37
 
41
- <Text style={styles.title}>{slide.title}</Text>
38
+ <AtomicText type="headlineMedium" style={[styles.title, { color: textColor }]}>
39
+ {slide.title}
40
+ </AtomicText>
42
41
 
43
- {slide.description && <Text style={styles.description}>{slide.description}</Text>}
44
- </>
42
+ {slide.description && (
43
+ <AtomicText type="bodyMedium" style={[styles.description, { color: subTextColor }]}>
44
+ {slide.description}
45
+ </AtomicText>
46
+ )}
47
+ </View>
45
48
  );
46
49
  };
47
50
 
48
- const getStyles = (
49
- tokens: ReturnType<typeof useAppDesignTokens>,
50
- useGradient: boolean,
51
- ) =>
52
- StyleSheet.create({
53
- iconContainer: {
54
- width: 96,
55
- height: 96,
56
- borderRadius: 48,
57
- backgroundColor: useGradient
58
- ? "rgba(255, 255, 255, 0.25)"
59
- : withAlpha(tokens.colors.primary, 0.2),
60
- alignItems: "center",
61
- justifyContent: "center",
62
- marginBottom: 24,
63
- borderWidth: 2,
64
- borderColor: useGradient
65
- ? "rgba(255, 255, 255, 0.4)"
66
- : withAlpha(tokens.colors.primary, 0.4),
67
- },
68
- icon: {
69
- fontSize: 48,
70
- },
71
- title: {
72
- fontSize: 24,
73
- fontWeight: "bold",
74
- color: useGradient ? "#FFFFFF" : tokens.colors.textPrimary,
75
- textAlign: "center",
76
- marginBottom: 12,
77
- },
78
- description: {
79
- fontSize: 15,
80
- color: useGradient ? "rgba(255, 255, 255, 0.9)" : tokens.colors.textSecondary,
81
- textAlign: "center",
82
- lineHeight: 22,
83
- marginBottom: 24,
84
- },
85
- });
51
+ const styles = StyleSheet.create({
52
+ container: {
53
+ alignItems: "center",
54
+ },
55
+ iconContainer: {
56
+ width: 96,
57
+ height: 96,
58
+ borderRadius: 48,
59
+ alignItems: "center",
60
+ justifyContent: "center",
61
+ marginBottom: 24,
62
+ borderWidth: 2,
63
+ },
64
+ title: {
65
+ fontWeight: "800",
66
+ textAlign: "center",
67
+ marginBottom: 12,
68
+ },
69
+ description: {
70
+ textAlign: "center",
71
+ lineHeight: 22,
72
+ marginBottom: 24,
73
+ },
74
+ });
75
+
86
76
 
87
77
 
88
78
 
@@ -1,13 +1,6 @@
1
- /**
2
- * Multiple Choice Question Component
3
- *
4
- * Checkbox style question for multiple selections
5
- */
6
-
7
1
  import React from "react";
8
- import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
9
- import { AtomicIcon } from "@umituz/react-native-design-system";
10
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
2
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
3
+ import { AtomicIcon, AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
11
4
  import type { OnboardingQuestion, QuestionOption } from "../../../domain/entities/OnboardingQuestion";
12
5
 
13
6
  export interface MultipleChoiceQuestionProps {
@@ -16,17 +9,13 @@ export interface MultipleChoiceQuestionProps {
16
9
  onChange: (value: string[]) => void;
17
10
  }
18
11
 
19
- export const MultipleChoiceQuestion: React.FC<MultipleChoiceQuestionProps> = ({
12
+ export const MultipleChoiceQuestion = ({
20
13
  question,
21
14
  value = [],
22
15
  onChange,
23
- }) => {
16
+ }: MultipleChoiceQuestionProps) => {
24
17
  const tokens = useAppDesignTokens();
25
18
 
26
- if (!tokens) {
27
- return null;
28
- }
29
-
30
19
  const isEmoji = (icon: string) =>
31
20
  /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u.test(icon);
32
21
 
@@ -35,57 +24,51 @@ export const MultipleChoiceQuestion: React.FC<MultipleChoiceQuestionProps> = ({
35
24
  ? value.filter((id) => id !== optionId)
36
25
  : [...value, optionId];
37
26
 
38
- // Check max selections
39
- if (
40
- question.validation?.maxSelections &&
41
- newValue.length > question.validation.maxSelections
42
- ) {
27
+ if (question.validation?.maxSelections && newValue.length > question.validation.maxSelections) {
43
28
  return;
44
29
  }
45
-
46
30
  onChange(newValue);
47
31
  };
48
32
 
49
33
  const renderOption = (option: QuestionOption) => {
50
34
  const isSelected = value.includes(option.id);
35
+ const textColor = isSelected ? tokens.colors.textPrimary : tokens.colors.textSecondary;
51
36
 
52
37
  return (
53
38
  <TouchableOpacity
54
39
  key={option.id}
55
- style={[styles.option, isSelected && styles.optionSelected]}
40
+ style={[
41
+ styles.option,
42
+ {
43
+ backgroundColor: isSelected ? tokens.colors.primary + '10' : tokens.colors.surface,
44
+ borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
45
+ }
46
+ ]}
56
47
  onPress={() => handleToggle(option.id)}
57
48
  activeOpacity={0.7}
58
49
  >
59
50
  {option.icon && (
60
51
  <View style={styles.optionIcon}>
61
52
  {isEmoji(option.icon) ? (
62
- <Text style={styles.emoji}>{option.icon}</Text>
53
+ <AtomicText style={{ fontSize: 24 }}>{option.icon}</AtomicText>
63
54
  ) : (
64
- <AtomicIcon
65
- name={option.icon as any}
66
- customSize={24}
67
- customColor={isSelected ? tokens.colors.textPrimary : tokens.colors.textSecondary}
68
- />
55
+ <AtomicIcon name={option.icon as any} customSize={24} customColor={textColor} />
69
56
  )}
70
57
  </View>
71
58
  )}
72
- <Text style={[
73
- styles.optionLabel,
74
- isSelected && styles.optionLabelSelected,
75
- { color: isSelected ? tokens.colors.textPrimary : tokens.colors.textSecondary }
76
- ]}>
59
+ <AtomicText type="bodyLarge" style={[styles.optionLabel, { color: textColor, fontWeight: isSelected ? '700' : '500' }]}>
77
60
  {option.label}
78
- </Text>
61
+ </AtomicText>
79
62
  <View style={[
80
63
  styles.checkbox,
81
- isSelected && { borderWidth: 3 },
82
64
  {
83
65
  borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
84
- backgroundColor: isSelected ? tokens.colors.backgroundSecondary : 'transparent'
66
+ backgroundColor: isSelected ? tokens.colors.primary : 'transparent',
67
+ borderWidth: isSelected ? 0 : 2,
85
68
  }
86
69
  ]}>
87
70
  {isSelected && (
88
- <AtomicIcon name="Check" customSize={16} customColor={tokens.colors.primary} />
71
+ <AtomicIcon name="checkmark" customSize={16} customColor="#FFFFFF" />
89
72
  )}
90
73
  </View>
91
74
  </TouchableOpacity>
@@ -96,9 +79,9 @@ export const MultipleChoiceQuestion: React.FC<MultipleChoiceQuestionProps> = ({
96
79
  <View style={styles.container}>
97
80
  {question.options?.map(renderOption)}
98
81
  {question.validation?.maxSelections && (
99
- <Text style={[styles.hint, { color: tokens.colors.textSecondary }]}>
82
+ <AtomicText type="labelSmall" style={[styles.hint, { color: tokens.colors.textSecondary }]}>
100
83
  Select up to {question.validation.maxSelections} options
101
- </Text>
84
+ </AtomicText>
102
85
  )}
103
86
  </View>
104
87
  );
@@ -112,39 +95,27 @@ const styles = StyleSheet.create({
112
95
  option: {
113
96
  flexDirection: "row",
114
97
  alignItems: "center",
115
- borderRadius: 12,
98
+ borderRadius: 16,
116
99
  padding: 16,
117
100
  borderWidth: 2,
118
101
  },
119
- optionSelected: {
120
- borderWidth: 3,
121
- },
122
102
  optionIcon: {
123
103
  marginRight: 12,
124
104
  },
125
- emoji: {
126
- fontSize: 24,
127
- },
128
105
  optionLabel: {
129
106
  flex: 1,
130
- fontSize: 16,
131
- fontWeight: "500",
132
- },
133
- optionLabelSelected: {
134
- fontWeight: "600",
135
107
  },
136
108
  checkbox: {
137
109
  width: 24,
138
110
  height: 24,
139
- borderRadius: 6,
140
- borderWidth: 2,
111
+ borderRadius: 8,
141
112
  alignItems: "center",
142
113
  justifyContent: "center",
143
114
  },
144
115
  hint: {
145
- fontSize: 13,
146
116
  textAlign: "center",
147
- marginTop: 4,
117
+ marginTop: 8,
148
118
  },
149
119
  });
150
120
 
121
+