@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 +1 -1
- package/src/domains/faqs/presentation/components/FAQSection.tsx +5 -2
- package/src/domains/faqs/presentation/screens/FAQScreen.tsx +2 -2
- package/src/presentation/components/SettingItem.tsx +1 -1
- package/src/presentation/components/SettingsItemCard.tsx +55 -37
- package/src/presentation/screens/SettingsScreen.tsx +5 -11
- package/src/presentation/screens/components/SettingsContent.tsx +7 -18
- package/src/presentation/screens/components/SettingsHeader.tsx +28 -37
- package/src/presentation/screens/components/sections/ProfileSectionLoader.tsx +11 -1
- package/src/presentation/screens/components/sections/SubscriptionSettingsSection.tsx +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.20.
|
|
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
|
-
|
|
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
|
|
124
|
+
{renderHeader({ onBack })}
|
|
125
125
|
</View>
|
|
126
126
|
{renderContent()}
|
|
127
127
|
</View>
|
|
@@ -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
|
|
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
|
-
{
|
|
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="
|
|
98
|
+
<AtomicText type="labelMedium" color="textSecondary" style={{ textTransform: 'uppercase' }}>
|
|
65
99
|
{sectionTitle}
|
|
66
100
|
</AtomicText>
|
|
67
101
|
</View>
|
|
68
102
|
)}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
84
|
-
<
|
|
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
|
-
</
|
|
96
|
+
</ScreenLayout>
|
|
103
97
|
);
|
|
104
98
|
};
|
|
105
99
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
|
-
import { View,
|
|
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
|
-
<
|
|
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
|
-
</
|
|
110
|
+
</View>
|
|
124
111
|
);
|
|
125
112
|
};
|
|
126
113
|
|
|
127
114
|
const styles = StyleSheet.create({
|
|
128
|
-
|
|
129
|
-
|
|
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,
|
|
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 {
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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={
|
|
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
|
-
|
|
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
|
);
|