@umituz/react-native-settings 1.4.1 → 1.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/README.md +133 -17
- package/package.json +2 -1
- package/src/index.ts +11 -0
- package/src/presentation/components/DisclaimerSetting.tsx +1 -1
- package/src/presentation/components/SettingItem.tsx +135 -227
- package/src/presentation/components/SettingsFooter.tsx +46 -0
- package/src/presentation/components/SettingsSection.tsx +59 -0
- package/src/presentation/components/UserProfileHeader.tsx +129 -0
- package/src/presentation/screens/AppearanceScreen.tsx +25 -16
- package/src/presentation/screens/LanguageSelectionScreen.tsx +3 -2
- package/src/presentation/screens/SettingsScreen.tsx +182 -285
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Section Component
|
|
3
|
+
* Single Responsibility: Render a settings section with title and container
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
|
|
10
|
+
export interface SettingsSectionProps {
|
|
11
|
+
/** Section title */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Section content */
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const SettingsSection: React.FC<SettingsSectionProps> = ({
|
|
18
|
+
title,
|
|
19
|
+
children,
|
|
20
|
+
}) => {
|
|
21
|
+
const tokens = useAppDesignTokens();
|
|
22
|
+
const colors = tokens.colors;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<View style={styles.container}>
|
|
26
|
+
<Text style={[styles.title, { color: colors.textSecondary }]}>
|
|
27
|
+
{title}
|
|
28
|
+
</Text>
|
|
29
|
+
<View
|
|
30
|
+
style={[
|
|
31
|
+
styles.content,
|
|
32
|
+
{ backgroundColor: `${colors.textSecondary}10` },
|
|
33
|
+
]}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</View>
|
|
37
|
+
</View>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const styles = StyleSheet.create({
|
|
42
|
+
container: {
|
|
43
|
+
marginBottom: 24,
|
|
44
|
+
},
|
|
45
|
+
title: {
|
|
46
|
+
fontSize: 12,
|
|
47
|
+
fontWeight: "700",
|
|
48
|
+
textTransform: "uppercase",
|
|
49
|
+
letterSpacing: 1,
|
|
50
|
+
paddingHorizontal: 16,
|
|
51
|
+
paddingBottom: 8,
|
|
52
|
+
},
|
|
53
|
+
content: {
|
|
54
|
+
borderRadius: 12,
|
|
55
|
+
marginHorizontal: 16,
|
|
56
|
+
overflow: "hidden",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Profile Header Component
|
|
3
|
+
* Displays user avatar, name, and ID
|
|
4
|
+
* Works for both guest and authenticated users
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { View, Text, TouchableOpacity, StyleSheet, Image } from "react-native";
|
|
9
|
+
import { ChevronRight } from "lucide-react-native";
|
|
10
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
11
|
+
import { useNavigation } from "@react-navigation/native";
|
|
12
|
+
|
|
13
|
+
export interface UserProfileHeaderProps {
|
|
14
|
+
/** User display name */
|
|
15
|
+
displayName?: string;
|
|
16
|
+
/** User ID */
|
|
17
|
+
userId?: string;
|
|
18
|
+
/** Whether user is guest */
|
|
19
|
+
isGuest?: boolean;
|
|
20
|
+
/** Avatar URL (optional) */
|
|
21
|
+
avatarUrl?: string;
|
|
22
|
+
/** Navigation route for account settings */
|
|
23
|
+
accountSettingsRoute?: string;
|
|
24
|
+
/** Custom onPress handler */
|
|
25
|
+
onPress?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const UserProfileHeader: React.FC<UserProfileHeaderProps> = ({
|
|
29
|
+
displayName,
|
|
30
|
+
userId,
|
|
31
|
+
isGuest = false,
|
|
32
|
+
avatarUrl,
|
|
33
|
+
accountSettingsRoute = "AccountSettings",
|
|
34
|
+
onPress,
|
|
35
|
+
}) => {
|
|
36
|
+
const tokens = useAppDesignTokens();
|
|
37
|
+
const navigation = useNavigation();
|
|
38
|
+
const colors = tokens.colors;
|
|
39
|
+
|
|
40
|
+
const finalDisplayName = displayName || (isGuest ? "Guest" : "User");
|
|
41
|
+
const finalUserId = userId || "Unknown";
|
|
42
|
+
const avatarName = isGuest ? "Guest" : finalDisplayName;
|
|
43
|
+
const finalAvatarUrl =
|
|
44
|
+
avatarUrl ||
|
|
45
|
+
`https://ui-avatars.com/api/?name=${encodeURIComponent(avatarName)}&background=${colors.primary.replace("#", "")}&color=fff&size=64`;
|
|
46
|
+
|
|
47
|
+
const handlePress = () => {
|
|
48
|
+
if (onPress) {
|
|
49
|
+
onPress();
|
|
50
|
+
} else if (accountSettingsRoute) {
|
|
51
|
+
navigation.navigate(accountSettingsRoute as never);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<TouchableOpacity
|
|
57
|
+
style={[
|
|
58
|
+
styles.container,
|
|
59
|
+
{
|
|
60
|
+
backgroundColor: colors.backgroundPrimary,
|
|
61
|
+
borderColor: colors.borderLight,
|
|
62
|
+
},
|
|
63
|
+
]}
|
|
64
|
+
onPress={handlePress}
|
|
65
|
+
activeOpacity={0.7}
|
|
66
|
+
>
|
|
67
|
+
<View style={styles.content}>
|
|
68
|
+
<Image
|
|
69
|
+
source={{ uri: finalAvatarUrl }}
|
|
70
|
+
style={[styles.avatar, { borderColor: `${colors.primary}40` }]}
|
|
71
|
+
/>
|
|
72
|
+
<View style={styles.textContainer}>
|
|
73
|
+
<Text
|
|
74
|
+
style={[styles.name, { color: colors.textPrimary }]}
|
|
75
|
+
numberOfLines={1}
|
|
76
|
+
>
|
|
77
|
+
{finalDisplayName}
|
|
78
|
+
</Text>
|
|
79
|
+
<Text
|
|
80
|
+
style={[styles.id, { color: colors.textSecondary }]}
|
|
81
|
+
numberOfLines={1}
|
|
82
|
+
>
|
|
83
|
+
ID: {finalUserId.substring(0, 8)}...
|
|
84
|
+
</Text>
|
|
85
|
+
</View>
|
|
86
|
+
</View>
|
|
87
|
+
<ChevronRight size={20} color={colors.textSecondary} />
|
|
88
|
+
</TouchableOpacity>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const styles = StyleSheet.create({
|
|
93
|
+
container: {
|
|
94
|
+
flexDirection: "row",
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
justifyContent: "space-between",
|
|
97
|
+
paddingHorizontal: 16,
|
|
98
|
+
paddingVertical: 16,
|
|
99
|
+
marginHorizontal: 16,
|
|
100
|
+
marginTop: 0,
|
|
101
|
+
borderRadius: 12,
|
|
102
|
+
borderWidth: 1,
|
|
103
|
+
},
|
|
104
|
+
content: {
|
|
105
|
+
flexDirection: "row",
|
|
106
|
+
alignItems: "center",
|
|
107
|
+
flex: 1,
|
|
108
|
+
},
|
|
109
|
+
avatar: {
|
|
110
|
+
width: 48,
|
|
111
|
+
height: 48,
|
|
112
|
+
borderRadius: 24,
|
|
113
|
+
borderWidth: 2,
|
|
114
|
+
},
|
|
115
|
+
textContainer: {
|
|
116
|
+
marginLeft: 12,
|
|
117
|
+
flex: 1,
|
|
118
|
+
},
|
|
119
|
+
name: {
|
|
120
|
+
fontSize: 16,
|
|
121
|
+
fontWeight: "600",
|
|
122
|
+
marginBottom: 4,
|
|
123
|
+
},
|
|
124
|
+
id: {
|
|
125
|
+
fontSize: 12,
|
|
126
|
+
fontWeight: "400",
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
@@ -14,15 +14,15 @@ import { View, StyleSheet } from 'react-native';
|
|
|
14
14
|
|
|
15
15
|
import { useNavigation } from '@react-navigation/native';
|
|
16
16
|
import { useDesignSystemTheme, useAppDesignTokens, type DesignTokens } from '@umituz/react-native-design-system-theme';
|
|
17
|
-
import { AtomicText
|
|
17
|
+
import { AtomicText } from '@umituz/react-native-design-system-atoms';
|
|
18
|
+
import { ScreenLayout } from '@umituz/react-native-design-system-organisms';
|
|
18
19
|
import { useLocalization, getLanguageByCode } from '@umituz/react-native-localization';
|
|
19
20
|
import { SettingItem } from '../components/SettingItem';
|
|
20
21
|
|
|
21
22
|
export const AppearanceScreen: React.FC = () => {
|
|
22
23
|
const { t, currentLanguage } = useLocalization();
|
|
23
24
|
const navigation = useNavigation();
|
|
24
|
-
|
|
25
|
-
const { themeMode } = useDesignSystemTheme();
|
|
25
|
+
const { themeMode, setThemeMode } = useDesignSystemTheme();
|
|
26
26
|
const tokens = useAppDesignTokens();
|
|
27
27
|
const styles = getStyles(tokens);
|
|
28
28
|
|
|
@@ -35,13 +35,8 @@ export const AppearanceScreen: React.FC = () => {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const handleThemeToggle = () => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// App should provide toggle functionality via navigation params or event emitter
|
|
41
|
-
// For now, just navigate back - app can handle toggle in its own theme management
|
|
42
|
-
if (navigation.canGoBack()) {
|
|
43
|
-
navigation.goBack();
|
|
44
|
-
}
|
|
38
|
+
const newMode = themeMode === 'dark' ? 'light' : 'dark';
|
|
39
|
+
setThemeMode(newMode);
|
|
45
40
|
};
|
|
46
41
|
|
|
47
42
|
return (
|
|
@@ -57,8 +52,10 @@ export const AppearanceScreen: React.FC = () => {
|
|
|
57
52
|
</View>
|
|
58
53
|
|
|
59
54
|
{/* Language Section */}
|
|
60
|
-
<
|
|
61
|
-
<
|
|
55
|
+
<View style={{ marginBottom: tokens.spacing.md }}>
|
|
56
|
+
<AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
|
|
57
|
+
{t('settings.language')}
|
|
58
|
+
</AtomicText>
|
|
62
59
|
<SettingItem
|
|
63
60
|
icon="Languages"
|
|
64
61
|
iconGradient={((tokens.colors as any).settingGradients?.language as unknown as string[]) || [tokens.colors.primary, tokens.colors.secondary]}
|
|
@@ -67,11 +64,13 @@ export const AppearanceScreen: React.FC = () => {
|
|
|
67
64
|
onPress={handleLanguagePress}
|
|
68
65
|
testID="language-button"
|
|
69
66
|
/>
|
|
70
|
-
</
|
|
67
|
+
</View>
|
|
71
68
|
|
|
72
69
|
{/* Theme Section */}
|
|
73
|
-
<
|
|
74
|
-
<
|
|
70
|
+
<View style={{ marginBottom: tokens.spacing.md }}>
|
|
71
|
+
<AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
|
|
72
|
+
{t('settings.appearance.darkMode')}
|
|
73
|
+
</AtomicText>
|
|
75
74
|
<SettingItem
|
|
76
75
|
icon={themeMode === 'dark' ? 'Moon' : 'Sun'}
|
|
77
76
|
iconGradient={
|
|
@@ -84,7 +83,7 @@ export const AppearanceScreen: React.FC = () => {
|
|
|
84
83
|
onPress={handleThemeToggle}
|
|
85
84
|
testID="theme-button"
|
|
86
85
|
/>
|
|
87
|
-
</
|
|
86
|
+
</View>
|
|
88
87
|
</ScreenLayout>
|
|
89
88
|
);
|
|
90
89
|
};
|
|
@@ -94,11 +93,21 @@ const getStyles = (tokens: DesignTokens) =>
|
|
|
94
93
|
header: {
|
|
95
94
|
paddingBottom: tokens.spacing.lg,
|
|
96
95
|
paddingTop: tokens.spacing.md,
|
|
96
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
97
97
|
},
|
|
98
98
|
headerSubtitle: {
|
|
99
99
|
marginTop: tokens.spacing.sm,
|
|
100
100
|
lineHeight: 20,
|
|
101
101
|
opacity: 0.8,
|
|
102
102
|
},
|
|
103
|
+
sectionHeader: {
|
|
104
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
105
|
+
paddingTop: tokens.spacing.lg,
|
|
106
|
+
paddingBottom: tokens.spacing.md,
|
|
107
|
+
textTransform: 'uppercase',
|
|
108
|
+
letterSpacing: 1,
|
|
109
|
+
fontWeight: '600',
|
|
110
|
+
fontSize: 12,
|
|
111
|
+
},
|
|
103
112
|
});
|
|
104
113
|
|
|
@@ -15,8 +15,9 @@ import {
|
|
|
15
15
|
TextInput,
|
|
16
16
|
} from 'react-native';
|
|
17
17
|
import { useNavigation } from '@react-navigation/native';
|
|
18
|
-
import {
|
|
19
|
-
import { AtomicIcon, AtomicText
|
|
18
|
+
import { useAppDesignTokens, withAlpha, STATIC_TOKENS, type DesignTokens } from '@umituz/react-native-design-system-theme';
|
|
19
|
+
import { AtomicIcon, AtomicText } from '@umituz/react-native-design-system-atoms';
|
|
20
|
+
import { ScreenLayout } from '@umituz/react-native-design-system-organisms';
|
|
20
21
|
import { useLocalization, searchLanguages, Language, LANGUAGES } from '@umituz/react-native-localization';
|
|
21
22
|
|
|
22
23
|
/**
|