@umituz/react-native-onboarding 3.5.6 → 3.6.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.5.6",
3
+ "version": "3.6.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",
@@ -72,4 +72,4 @@
72
72
  "README.md",
73
73
  "LICENSE"
74
74
  ]
75
- }
75
+ }
@@ -5,6 +5,7 @@
5
5
  * Uses @umituz/react-native-storage for persistence
6
6
  */
7
7
 
8
+ import { useMemo } from "react";
8
9
  import { create, StoreApi } from "zustand";
9
10
  import type { OnboardingStoreState } from "./OnboardingStoreState";
10
11
  import { initialOnboardingState } from "./OnboardingStoreState";
@@ -51,15 +52,17 @@ export const useOnboardingStore = create<OnboardingStore>((set: StoreApi<Onboard
51
52
 
52
53
  /**
53
54
  * Hook for accessing onboarding state
55
+ * Memoized to prevent unnecessary re-renders in consumer components
54
56
  */
55
57
  export const useOnboarding = () => {
56
58
  const store = useOnboardingStore();
57
59
  const setState = store.setState;
58
- const getState = () => store;
59
- const actions = createOnboardingStoreActions(setState, getState);
60
- const selectors = createOnboardingStoreSelectors(getState);
60
+ const getState = store.getState;
61
61
 
62
- return {
62
+ const actions = useMemo(() => createOnboardingStoreActions(setState, getState), [setState, getState]);
63
+ const selectors = useMemo(() => createOnboardingStoreSelectors(getState), [getState]);
64
+
65
+ return useMemo(() => ({
63
66
  // State
64
67
  isOnboardingComplete: store.isOnboardingComplete,
65
68
  currentStep: store.currentStep,
@@ -81,6 +84,6 @@ export const useOnboarding = () => {
81
84
  // Selectors
82
85
  getAnswer: selectors.getAnswer,
83
86
  getUserData: selectors.getUserData,
84
- };
87
+ }), [store, actions, selectors]);
85
88
  };
86
89
 
@@ -11,6 +11,7 @@ import { QuestionSlideHeader } from "./QuestionSlideHeader";
11
11
  import { QuestionRenderer } from "./QuestionRenderer";
12
12
  import { BaseSlide } from "./BaseSlide";
13
13
  import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
14
+ import { useLocalization } from "@umituz/react-native-localization";
14
15
 
15
16
  export interface QuestionSlideProps {
16
17
  slide: OnboardingSlide;
@@ -26,6 +27,7 @@ export const QuestionSlide = ({
26
27
  variant: _variant = "default",
27
28
  }: QuestionSlideProps) => {
28
29
  const { colors } = useOnboardingTheme();
30
+ const { t } = useLocalization();
29
31
  const { question } = slide;
30
32
 
31
33
  if (!question) return null;
@@ -43,7 +45,7 @@ export const QuestionSlide = ({
43
45
  type="labelSmall"
44
46
  style={[styles.requiredHint, { color: colors.errorColor }]}
45
47
  >
46
- * This field is required
48
+ {t("onboarding.fieldRequired")}
47
49
  </AtomicText>
48
50
  )}
49
51
  </BaseSlide>
@@ -29,7 +29,6 @@ export const MultipleChoiceQuestion = ({
29
29
  };
30
30
  const renderOption = (option: QuestionOption) => {
31
31
  const isSelected = value.includes(option.id);
32
- const textColor = isSelected ? colors.textColor : colors.subTextColor;
33
32
  const isEmoji = option.iconType === 'emoji';
34
33
 
35
34
  return (
@@ -40,21 +39,29 @@ export const MultipleChoiceQuestion = ({
40
39
  {
41
40
  backgroundColor: isSelected ? colors.iconBg : colors.featureItemBg,
42
41
  borderColor: isSelected ? colors.iconColor : colors.headerButtonBorder,
42
+ borderWidth: isSelected ? 2 : 1,
43
43
  }
44
44
  ]}
45
45
  onPress={() => handleToggle(option.id)}
46
- activeOpacity={0.7}
46
+ activeOpacity={0.8}
47
47
  >
48
48
  {option.icon && (
49
- <View style={styles.optionIcon}>
49
+ <View style={[
50
+ styles.optionIcon,
51
+ { backgroundColor: isSelected ? colors.iconColor : colors.featureItemBg }
52
+ ]}>
50
53
  {isEmoji ? (
51
54
  <AtomicText style={{ fontSize: 24 }}>{option.icon}</AtomicText>
52
55
  ) : (
53
- <AtomicIcon name={option.icon as any} customSize={24} customColor={textColor} />
56
+ <AtomicIcon
57
+ name={option.icon as any}
58
+ customSize={20}
59
+ customColor={isSelected ? colors.buttonTextColor : colors.subTextColor}
60
+ />
54
61
  )}
55
62
  </View>
56
63
  )}
57
- <AtomicText type="bodyLarge" style={[styles.optionLabel, { color: textColor, fontWeight: isSelected ? '700' : '500' }]}>
64
+ <AtomicText type="bodyLarge" style={[styles.optionLabel, { color: isSelected ? colors.textColor : colors.subTextColor, fontWeight: isSelected ? '700' : '500' }]}>
58
65
  {option.label}
59
66
  </AtomicText>
60
67
  <View style={[
@@ -93,15 +100,21 @@ const styles = StyleSheet.create({
93
100
  option: {
94
101
  flexDirection: "row",
95
102
  alignItems: "center",
96
- borderRadius: 16,
103
+ borderRadius: 20,
97
104
  padding: 16,
98
- borderWidth: 2,
105
+ marginBottom: 8,
99
106
  },
100
107
  optionIcon: {
101
- marginRight: 12,
108
+ width: 40,
109
+ height: 40,
110
+ borderRadius: 20,
111
+ alignItems: 'center',
112
+ justifyContent: 'center',
113
+ marginRight: 16,
102
114
  },
103
115
  optionLabel: {
104
116
  flex: 1,
117
+ fontSize: 16,
105
118
  },
106
119
  checkbox: {
107
120
  width: 24,
@@ -24,19 +24,19 @@ export const RatingQuestion = ({
24
24
  {Array.from({ length: max }).map((_, i) => {
25
25
  const isFilled = i < value;
26
26
  return (
27
- <TouchableOpacity key={i} onPress={() => onChange(i + 1)} activeOpacity={0.7} style={styles.star}>
27
+ <TouchableOpacity key={i} onPress={() => onChange(i + 1)} activeOpacity={0.8} style={styles.star}>
28
28
  <AtomicIcon
29
29
  name={isFilled ? "star" : "star-outline"}
30
30
  customSize={48}
31
- customColor={isFilled ? "#FFD700" : colors.subTextColor}
31
+ customColor={isFilled ? colors.iconColor : colors.headerButtonBorder}
32
32
  />
33
33
  </TouchableOpacity>
34
34
  );
35
35
  })}
36
36
  </View>
37
37
  {value > 0 && (
38
- <AtomicText type="headlineSmall" style={[styles.valueText, { color: colors.textColor }]}>
39
- {value} / {max}
38
+ <AtomicText type="headlineSmall" style={[styles.valueText, { color: colors.textColor, marginTop: 12 }]}>
39
+ {value} <AtomicText type="bodyMedium" color="textSecondary">/ {max}</AtomicText>
40
40
  </AtomicText>
41
41
  )}
42
42
  </View>
@@ -19,7 +19,6 @@ export const SingleChoiceQuestion = ({
19
19
 
20
20
  const renderOption = (option: QuestionOption) => {
21
21
  const isSelected = value === option.id;
22
- const textColor = isSelected ? colors.textColor : colors.subTextColor;
23
22
  const isEmoji = option.iconType === 'emoji';
24
23
 
25
24
  return (
@@ -30,34 +29,39 @@ export const SingleChoiceQuestion = ({
30
29
  {
31
30
  backgroundColor: isSelected ? colors.iconBg : colors.featureItemBg,
32
31
  borderColor: isSelected ? colors.iconColor : colors.headerButtonBorder,
32
+ borderWidth: isSelected ? 2 : 1,
33
33
  }
34
34
  ]}
35
35
  onPress={() => onChange(option.id)}
36
- activeOpacity={0.7}
36
+ activeOpacity={0.8}
37
37
  >
38
38
  {option.icon && (
39
- <View style={styles.optionIcon}>
39
+ <View style={[
40
+ styles.optionIcon,
41
+ { backgroundColor: isSelected ? colors.iconColor : colors.featureItemBg }
42
+ ]}>
40
43
  {isEmoji ? (
41
44
  <AtomicText style={{ fontSize: 24 }}>{option.icon}</AtomicText>
42
45
  ) : (
43
46
  <AtomicIcon
44
47
  name={option.icon as any}
45
- customSize={24}
46
- customColor={textColor}
48
+ customSize={20}
49
+ customColor={isSelected ? colors.buttonTextColor : colors.subTextColor}
47
50
  />
48
51
  )}
49
52
  </View>
50
53
  )}
51
- <AtomicText type="bodyLarge" style={[styles.optionLabel, { color: textColor, fontWeight: isSelected ? '700' : '500' }]}>
54
+ <AtomicText type="bodyLarge" style={[styles.optionLabel, { color: isSelected ? colors.textColor : colors.subTextColor, fontWeight: isSelected ? '700' : '500' }]}>
52
55
  {option.label}
53
56
  </AtomicText>
54
57
  <View style={[
55
- styles.radio,
56
- {
57
- borderColor: isSelected ? colors.iconColor : colors.headerButtonBorder,
58
- borderWidth: isSelected ? 6 : 2,
59
- }
60
- ]} />
58
+ styles.radioOuter,
59
+ { borderColor: isSelected ? colors.iconColor : colors.headerButtonBorder }
60
+ ]}>
61
+ {isSelected && (
62
+ <View style={[styles.radioInner, { backgroundColor: colors.iconColor }]} />
63
+ )}
64
+ </View>
61
65
  </TouchableOpacity>
62
66
  );
63
67
  };
@@ -77,20 +81,34 @@ const styles = StyleSheet.create({
77
81
  option: {
78
82
  flexDirection: "row",
79
83
  alignItems: "center",
80
- borderRadius: 16,
84
+ borderRadius: 20,
81
85
  padding: 16,
82
- borderWidth: 2,
86
+ marginBottom: 8,
83
87
  },
84
88
  optionIcon: {
85
- marginRight: 12,
89
+ width: 40,
90
+ height: 40,
91
+ borderRadius: 20,
92
+ alignItems: 'center',
93
+ justifyContent: 'center',
94
+ marginRight: 16,
86
95
  },
87
96
  optionLabel: {
88
97
  flex: 1,
98
+ fontSize: 16,
89
99
  },
90
- radio: {
100
+ radioOuter: {
91
101
  width: 24,
92
102
  height: 24,
93
103
  borderRadius: 12,
104
+ borderWidth: 2,
105
+ alignItems: 'center',
106
+ justifyContent: 'center',
107
+ },
108
+ radioInner: {
109
+ width: 12,
110
+ height: 12,
111
+ borderRadius: 6,
94
112
  },
95
113
  });
96
114
 
@@ -25,7 +25,7 @@ export const TextInputQuestion = ({
25
25
  styles.input,
26
26
  {
27
27
  backgroundColor: colors.featureItemBg,
28
- borderColor: colors.headerButtonBorder,
28
+ borderColor: value ? colors.iconColor : colors.headerButtonBorder,
29
29
  color: colors.textColor,
30
30
  }
31
31
  ]}
@@ -38,6 +38,7 @@ export const TextInputQuestion = ({
38
38
  numberOfLines={(validation?.maxLength ?? 0) > 100 ? 5 : 1}
39
39
  autoCapitalize="sentences"
40
40
  autoCorrect={true}
41
+ textAlignVertical="top"
41
42
  />
42
43
  {validation?.maxLength && (
43
44
  <AtomicText type="labelSmall" style={[styles.charCount, { color: colors.subTextColor }]}>