@umituz/react-native-settings 4.20.18 → 4.20.20

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-settings",
3
- "version": "4.20.18",
3
+ "version": "4.20.20",
4
4
  "description": "Complete settings hub for React Native apps - consolidated package with settings, about, legal, appearance, feedback, FAQs, and rating",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -32,7 +32,10 @@ export const FAQSection: React.FC<FAQSectionProps> = ({
32
32
  renderSection,
33
33
  renderItem,
34
34
  }) => {
35
- if (!config.enabled || !config.title || !config.description) return null;
35
+ // onPress is required for FAQ section to be functional
36
+ if (!config.enabled || !config.title || !config.description || !config.onPress) {
37
+ return null;
38
+ }
36
39
 
37
40
  return (
38
41
  <>
@@ -41,7 +44,7 @@ export const FAQSection: React.FC<FAQSectionProps> = ({
41
44
  children: renderItem({
42
45
  title: config.description,
43
46
  icon: 'help-circle',
44
- onPress: config.onPress || (() => {}),
47
+ onPress: config.onPress,
45
48
  isLast: true,
46
49
  }),
47
50
  })}
@@ -116,12 +116,12 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
116
116
  );
117
117
  };
118
118
 
119
- if (renderHeader) {
119
+ if (renderHeader && onBack) {
120
120
  return (
121
121
  <View style={{ flex: 1, backgroundColor: tokens.colors.backgroundPrimary }}>
122
122
  <View style={[styles.container, customStyles?.container]}>
123
123
  <View style={{ alignSelf: 'center', width: '100%', maxWidth: contentMaxWidth }}>
124
- {renderHeader({ onBack: onBack || (() => { }) })}
124
+ {renderHeader({ onBack })}
125
125
  </View>
126
126
  {renderContent()}
127
127
  </View>
@@ -110,7 +110,7 @@ export const SettingItem: React.FC<SettingItemProps> = ({
110
110
  title={title}
111
111
  description={value}
112
112
  icon={icon as any}
113
- onPress={onPress || (() => { })}
113
+ onPress={onPress}
114
114
  iconColor={iconColor}
115
115
  />
116
116
  );
@@ -14,18 +14,20 @@ export interface SettingsItemCardProps {
14
14
  description?: string;
15
15
  /** Icon name from AtomicIcon */
16
16
  icon: IconName;
17
- /** On press handler */
18
- onPress: () => void;
17
+ /** On press handler - if undefined, item is not clickable */
18
+ onPress?: () => void;
19
19
  /** Optional container style */
20
20
  containerStyle?: ViewStyle;
21
21
  /** Optional section title (shows above the card) */
22
22
  sectionTitle?: string;
23
- /** Optional right icon (defaults to chevron-forward) */
23
+ /** Optional right icon (defaults to chevron-forward, hidden if not clickable) */
24
24
  rightIcon?: IconName;
25
25
  /** Optional icon background color (defaults to primary with opacity) */
26
26
  iconBgColor?: string;
27
27
  /** Optional icon color */
28
28
  iconColor?: string;
29
+ /** Show chevron even if not clickable */
30
+ showChevron?: boolean;
29
31
  }
30
32
 
31
33
  /**
@@ -44,59 +46,77 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
44
46
  rightIcon = "chevron-forward",
45
47
  iconBgColor,
46
48
  iconColor,
49
+ showChevron,
47
50
  }) => {
48
51
  const tokens = useAppDesignTokens();
49
52
  const colors = tokens.colors;
50
53
 
51
54
  const defaultIconBg = iconBgColor || `${colors.primary}15`;
52
55
  const defaultIconColor = iconColor || colors.primary;
56
+ const isClickable = !!onPress;
57
+ const shouldShowChevron = showChevron ?? isClickable;
58
+
59
+ const renderContent = () => (
60
+ <View style={styles.content}>
61
+ <View style={[styles.iconContainer, { backgroundColor: defaultIconBg, borderRadius: tokens.borderRadius.md }]}>
62
+ <AtomicIcon name={icon} size="lg" customColor={defaultIconColor} />
63
+ </View>
64
+ <View style={styles.textContainer}>
65
+ <AtomicText
66
+ type="bodyLarge"
67
+ color="onSurface"
68
+ numberOfLines={1}
69
+ style={{ marginBottom: tokens.spacing.xs }}
70
+ >
71
+ {title}
72
+ </AtomicText>
73
+ {!!description && (
74
+ <AtomicText type="bodyMedium" color="secondary" numberOfLines={2}>
75
+ {description}
76
+ </AtomicText>
77
+ )}
78
+ </View>
79
+ {shouldShowChevron && (
80
+ <AtomicIcon name={rightIcon} size="sm" color="secondary" />
81
+ )}
82
+ </View>
83
+ );
53
84
 
54
85
  return (
55
86
  <View
56
87
  style={[
57
88
  styles.sectionContainer,
58
- { backgroundColor: colors.surface },
89
+ {
90
+ backgroundColor: colors.surface,
91
+ borderRadius: tokens.borderRadius.lg,
92
+ },
59
93
  containerStyle,
60
94
  ]}
61
95
  >
62
96
  {!!sectionTitle && (
63
97
  <View style={styles.headerContainer}>
64
- <AtomicText type="titleMedium" color="primary">
98
+ <AtomicText type="labelMedium" color="textSecondary" style={{ textTransform: 'uppercase' }}>
65
99
  {sectionTitle}
66
100
  </AtomicText>
67
101
  </View>
68
102
  )}
69
- <Pressable
70
- style={({ pressed }) => [
71
- styles.itemContainer,
72
- {
73
- backgroundColor: pressed ? `${colors.primary}08` : "transparent",
74
- },
75
- ]}
76
- onPress={onPress}
77
- >
78
- <View style={styles.content}>
79
- <View style={[styles.iconContainer, { backgroundColor: defaultIconBg }]}>
80
- <AtomicIcon name={icon} size="lg" customColor={defaultIconColor} />
81
- </View>
82
- <View style={styles.textContainer}>
83
- <AtomicText
84
- type="bodyLarge"
85
- color="onSurface"
86
- numberOfLines={1}
87
- style={{ marginBottom: 4 }}
88
- >
89
- {title}
90
- </AtomicText>
91
- {!!description && (
92
- <AtomicText type="bodyMedium" color="secondary" numberOfLines={2}>
93
- {description}
94
- </AtomicText>
95
- )}
96
- </View>
97
- <AtomicIcon name={rightIcon} size="sm" color="secondary" />
103
+ {isClickable ? (
104
+ <Pressable
105
+ style={({ pressed }) => [
106
+ styles.itemContainer,
107
+ {
108
+ backgroundColor: pressed ? `${colors.primary}08` : "transparent",
109
+ },
110
+ ]}
111
+ onPress={onPress}
112
+ >
113
+ {renderContent()}
114
+ </Pressable>
115
+ ) : (
116
+ <View style={styles.itemContainer}>
117
+ {renderContent()}
98
118
  </View>
99
- </Pressable>
119
+ )}
100
120
  </View>
101
121
  );
102
122
  };
@@ -104,7 +124,6 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
104
124
  const styles = StyleSheet.create({
105
125
  sectionContainer: {
106
126
  marginBottom: 16,
107
- borderRadius: 12,
108
127
  overflow: "hidden",
109
128
  },
110
129
  headerContainer: {
@@ -127,7 +146,6 @@ const styles = StyleSheet.create({
127
146
  iconContainer: {
128
147
  width: 48,
129
148
  height: 48,
130
- borderRadius: 12,
131
149
  justifyContent: "center",
132
150
  alignItems: "center",
133
151
  marginRight: 16,
@@ -7,8 +7,8 @@ import React from "react";
7
7
  import { View, StatusBar, StyleSheet } from "react-native";
8
8
  import { useNavigation } from "@react-navigation/native";
9
9
  import {
10
- useDesignSystemTheme,
11
10
  useAppDesignTokens,
11
+ ScreenLayout,
12
12
  } from "@umituz/react-native-design-system";
13
13
  import { SettingsHeader } from "./components/SettingsHeader";
14
14
  import { SettingsContent } from "./components/SettingsContent";
@@ -67,12 +67,8 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
67
67
  devSettings,
68
68
  }) => {
69
69
  const navigation = useNavigation();
70
- const { themeMode } = useDesignSystemTheme();
71
70
  const tokens = useAppDesignTokens();
72
71
 
73
- const isDark = themeMode === "dark";
74
- const colors = tokens.colors;
75
-
76
72
  const normalizedConfig = normalizeSettingsConfig(config);
77
73
  const features = useFeatureDetection(normalizedConfig, navigation, featureOptions);
78
74
 
@@ -80,11 +76,9 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
80
76
  const shouldShowUserProfile = showUserProfile ?? features.userProfile;
81
77
 
82
78
  return (
83
- <View style={[styles.container, { backgroundColor: colors.backgroundPrimary }]}>
84
- <StatusBar barStyle={isDark ? "light-content" : "dark-content"} />
85
-
86
- <SettingsHeader showCloseButton={showCloseButton} onClose={onClose} />
87
-
79
+ <ScreenLayout
80
+ header={<SettingsHeader showCloseButton={showCloseButton} onClose={onClose} />}
81
+ >
88
82
  <SettingsErrorBoundary>
89
83
  <SettingsContent
90
84
  normalizedConfig={normalizedConfig}
@@ -99,7 +93,7 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
99
93
  devSettings={devSettings}
100
94
  />
101
95
  </SettingsErrorBoundary>
102
- </View>
96
+ </ScreenLayout>
103
97
  );
104
98
  };
105
99
 
@@ -1,6 +1,5 @@
1
1
  import React, { useMemo } from "react";
2
- import { View, ScrollView, StyleSheet } from "react-native";
3
- import { useSafeAreaInsets } from "react-native-safe-area-context";
2
+ import { View, StyleSheet } from "react-native";
4
3
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
5
4
  import { useLocalization } from "@umituz/react-native-localization";
6
5
  import { SettingsFooter } from "../../components/SettingsFooter";
@@ -56,7 +55,6 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
56
55
  devSettings,
57
56
  }) => {
58
57
  const tokens = useAppDesignTokens();
59
- const insets = useSafeAreaInsets();
60
58
  const { t } = useLocalization();
61
59
 
62
60
  const hasAnyFeatures = useMemo(() =>
@@ -75,18 +73,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
75
73
  );
76
74
 
77
75
  return (
78
- <ScrollView
79
- style={styles.scrollView}
80
- contentContainerStyle={[
81
- styles.scrollContent,
82
- {
83
- paddingTop: showCloseButton ? tokens.spacing.md : insets.top + tokens.spacing.md,
84
- paddingBottom: tokens.spacing.xxxl + tokens.spacing.xl,
85
- paddingHorizontal: tokens.spacing.md,
86
- },
87
- ]}
88
- showsVerticalScrollIndicator={false}
89
- >
76
+ <View style={styles.container}>
90
77
  {showUserProfile && <ProfileSectionLoader userProfile={userProfile} />}
91
78
 
92
79
  <CustomSettingsList customSections={customSections} />
@@ -120,12 +107,14 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
120
107
  versionLabel={t("settings.footer.version")}
121
108
  />
122
109
  )}
123
- </ScrollView>
110
+ </View>
124
111
  );
125
112
  };
126
113
 
127
114
  const styles = StyleSheet.create({
128
- scrollView: { flex: 1 },
129
- scrollContent: { flexGrow: 1 },
115
+ container: {
116
+ flex: 1,
117
+ paddingVertical: 16,
118
+ },
130
119
  emptyContainer: { paddingVertical: 24 },
131
120
  });
@@ -4,11 +4,10 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
- import { View, TouchableOpacity, StyleSheet } from "react-native";
8
- import { useSafeAreaInsets } from "react-native-safe-area-context";
7
+ import { View, Pressable, StyleSheet } from "react-native";
9
8
  import { useNavigation } from "@react-navigation/native";
10
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
11
- import { AtomicIcon } from "@umituz/react-native-design-system";
9
+ import { useAppDesignTokens, AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
10
+ import { useLocalization } from "@umituz/react-native-localization";
12
11
 
13
12
  interface SettingsHeaderProps {
14
13
  showCloseButton?: boolean;
@@ -21,7 +20,7 @@ export const SettingsHeader: React.FC<SettingsHeaderProps> = ({
21
20
  }) => {
22
21
  const navigation = useNavigation();
23
22
  const tokens = useAppDesignTokens();
24
- const insets = useSafeAreaInsets();
23
+ const { t } = useLocalization();
25
24
 
26
25
  const handleClose = () => {
27
26
  if (onClose) {
@@ -31,48 +30,40 @@ export const SettingsHeader: React.FC<SettingsHeaderProps> = ({
31
30
  }
32
31
  };
33
32
 
34
- if (!showCloseButton) {
35
- return null;
36
- }
37
-
38
33
  return (
39
- <View
40
- style={[
41
- styles.closeButtonContainer,
42
- {
43
- paddingTop: insets.top + tokens.spacing.xs,
44
- paddingRight: tokens.spacing.md,
45
- },
46
- ]}
47
- >
48
- <TouchableOpacity
49
- onPress={handleClose}
50
- style={[
51
- styles.closeButton,
52
- {
53
- backgroundColor: tokens.colors.surface,
54
- },
55
- ]}
56
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
57
- >
58
- <AtomicIcon name="x" size="lg" color="primary" />
59
- </TouchableOpacity>
34
+ <View style={[styles.container, { padding: tokens.spacing.lg }]}>
35
+ <AtomicText style={tokens.typography.headingLarge}>
36
+ {t('settings.title')}
37
+ </AtomicText>
38
+
39
+ {showCloseButton && (
40
+ <Pressable
41
+ onPress={handleClose}
42
+ style={({ pressed }) => [
43
+ styles.closeButton,
44
+ {
45
+ backgroundColor: pressed ? tokens.colors.surfaceVariant : tokens.colors.surface,
46
+ borderRadius: tokens.borderRadius.full,
47
+ },
48
+ ]}
49
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
50
+ >
51
+ <AtomicIcon name="close-outline" size="lg" color="textPrimary" />
52
+ </Pressable>
53
+ )}
60
54
  </View>
61
55
  );
62
56
  };
63
57
 
64
58
  const styles = StyleSheet.create({
65
- closeButtonContainer: {
66
- position: "absolute",
67
- top: 0,
68
- right: 0,
69
- zIndex: 10,
70
- alignItems: "flex-end",
59
+ container: {
60
+ flexDirection: 'row',
61
+ justifyContent: 'space-between',
62
+ alignItems: 'center',
71
63
  },
72
64
  closeButton: {
73
65
  width: 44,
74
66
  height: 44,
75
- borderRadius: 22,
76
67
  justifyContent: "center",
77
68
  alignItems: "center",
78
69
  },
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
+ import { useNavigation } from "@react-navigation/native";
3
4
  import { ProfileSection } from "@umituz/react-native-auth";
4
5
  import { useLocalization } from "@umituz/react-native-localization";
5
6
 
@@ -17,9 +18,18 @@ export interface ProfileSectionLoaderProps {
17
18
 
18
19
  export const ProfileSectionLoader: React.FC<ProfileSectionLoaderProps> = ({ userProfile }) => {
19
20
  const { t } = useLocalization();
21
+ const navigation = useNavigation<any>();
20
22
 
21
23
  if (!userProfile) return null;
22
24
 
25
+ const handlePress = () => {
26
+ if (userProfile.onPress) {
27
+ userProfile.onPress();
28
+ } else if (userProfile.accountSettingsRoute) {
29
+ navigation.navigate(userProfile.accountSettingsRoute);
30
+ }
31
+ };
32
+
23
33
  return (
24
34
  <View style={styles.profileContainer}>
25
35
  <ProfileSection
@@ -30,7 +40,7 @@ export const ProfileSectionLoader: React.FC<ProfileSectionLoaderProps> = ({ user
30
40
  avatarUrl: userProfile.avatarUrl,
31
41
  accountSettingsRoute: userProfile.accountSettingsRoute,
32
42
  }}
33
- onPress={userProfile.onPress}
43
+ onPress={handlePress}
34
44
  onSignIn={userProfile.onPress}
35
45
  signInText={t("auth.signIn")}
36
46
  anonymousText={t("settings.profile.anonymousName")}
@@ -13,14 +13,15 @@ export const SubscriptionSettingsSection: React.FC<SubscriptionSettingsSectionPr
13
13
  }) => {
14
14
  const { t } = useLocalization();
15
15
 
16
- if (!config) return null;
16
+ // onPress is required for subscription section to be functional
17
+ if (!config || !config.onPress) return null;
17
18
 
18
19
  return (
19
20
  <SettingsItemCard
20
21
  title={config.title || t("settings.subscription.title")}
21
22
  description={config.description || t("settings.subscription.description")}
22
23
  icon={(config.icon || "star") as IconName}
23
- onPress={config.onPress || (() => {})}
24
+ onPress={config.onPress}
24
25
  sectionTitle={config.sectionTitle || t("settings.sections.subscription")}
25
26
  />
26
27
  );