@sudobility/building_blocks_rn 0.0.2
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/dist/firebase.d.ts +10 -0
- package/dist/firebase.js +10 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +25 -0
- package/dist/src/api/ApiContext.d.ts +52 -0
- package/dist/src/api/ApiContext.js +67 -0
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/app/SudobilityAppRN.d.ts +29 -0
- package/dist/src/app/SudobilityAppRN.js +37 -0
- package/dist/src/app/SudobilityAppRNWithFirebaseAuth.d.ts +13 -0
- package/dist/src/app/SudobilityAppRNWithFirebaseAuth.js +12 -0
- package/dist/src/app/index.d.ts +4 -0
- package/dist/src/app/index.js +2 -0
- package/dist/src/components/footer/AppFooter.d.ts +21 -0
- package/dist/src/components/footer/AppFooter.js +72 -0
- package/dist/src/components/footer/index.d.ts +2 -0
- package/dist/src/components/footer/index.js +1 -0
- package/dist/src/components/header/AppHeader.d.ts +28 -0
- package/dist/src/components/header/AppHeader.js +79 -0
- package/dist/src/components/header/index.d.ts +2 -0
- package/dist/src/components/header/index.js +1 -0
- package/dist/src/components/layout/AppScreenLayout.d.ts +20 -0
- package/dist/src/components/layout/AppScreenLayout.js +41 -0
- package/dist/src/components/layout/index.d.ts +2 -0
- package/dist/src/components/layout/index.js +1 -0
- package/dist/src/components/pages/AppTextScreen.d.ts +16 -0
- package/dist/src/components/pages/AppTextScreen.js +96 -0
- package/dist/src/components/pages/LoginScreen.d.ts +26 -0
- package/dist/src/components/pages/LoginScreen.js +183 -0
- package/dist/src/components/pages/index.d.ts +4 -0
- package/dist/src/components/pages/index.js +2 -0
- package/dist/src/components/settings/AppearanceSettings.d.ts +13 -0
- package/dist/src/components/settings/AppearanceSettings.js +106 -0
- package/dist/src/components/settings/LanguagePicker.d.ts +15 -0
- package/dist/src/components/settings/LanguagePicker.js +100 -0
- package/dist/src/components/settings/SettingsListScreen.d.ts +15 -0
- package/dist/src/components/settings/SettingsListScreen.js +82 -0
- package/dist/src/components/settings/index.d.ts +6 -0
- package/dist/src/components/settings/index.js +3 -0
- package/dist/src/components/subscription/SafeSubscriptionContext.d.ts +13 -0
- package/dist/src/components/subscription/SafeSubscriptionContext.js +15 -0
- package/dist/src/components/subscription/SubscriptionScreen.d.ts +36 -0
- package/dist/src/components/subscription/SubscriptionScreen.js +188 -0
- package/dist/src/components/subscription/index.d.ts +4 -0
- package/dist/src/components/subscription/index.js +2 -0
- package/dist/src/components/toast/ToastProvider.d.ts +20 -0
- package/dist/src/components/toast/ToastProvider.js +87 -0
- package/dist/src/components/toast/index.d.ts +2 -0
- package/dist/src/components/toast/index.js +1 -0
- package/dist/src/constants/index.d.ts +1 -0
- package/dist/src/constants/index.js +1 -0
- package/dist/src/constants/languages.d.ts +18 -0
- package/dist/src/constants/languages.js +48 -0
- package/dist/src/hooks/index.d.ts +2 -0
- package/dist/src/hooks/index.js +1 -0
- package/dist/src/hooks/useResponsive.d.ts +11 -0
- package/dist/src/hooks/useResponsive.js +14 -0
- package/dist/src/i18n/index.d.ts +31 -0
- package/dist/src/i18n/index.js +78 -0
- package/dist/src/theme/ThemeContext.d.ts +34 -0
- package/dist/src/theme/ThemeContext.js +66 -0
- package/dist/src/theme/colors.d.ts +55 -0
- package/dist/src/theme/colors.js +69 -0
- package/dist/src/theme/index.d.ts +7 -0
- package/dist/src/theme/index.js +4 -0
- package/dist/src/theme/spacing.d.ts +16 -0
- package/dist/src/theme/spacing.js +15 -0
- package/dist/src/theme/typography.d.ts +24 -0
- package/dist/src/theme/typography.js +34 -0
- package/dist/src/types.d.ts +152 -0
- package/dist/src/types.js +27 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.js +1 -0
- package/dist/src/utils/styles.d.ts +23 -0
- package/dist/src/utils/styles.js +25 -0
- package/package.json +94 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, ScrollView } from 'react-native';
|
|
3
|
+
import { createThemedStyles } from '../../utils/styles';
|
|
4
|
+
function TextSectionView({ section, level = 2, }) {
|
|
5
|
+
const styles = useStyles();
|
|
6
|
+
return (_jsxs(View, { style: styles.section, children: [_jsx(Text, { style: level === 2 ? styles.sectionTitle : styles.subsectionTitle, children: section.title }), section.content && (_jsx(Text, { style: styles.sectionContent, children: section.content })), section.items && section.items.length > 0 && (_jsx(View, { style: styles.list, children: section.items.map((item, index) => (_jsxs(View, { style: styles.listItem, children: [_jsx(Text, { style: styles.bullet, children: '\u2022' }), _jsx(Text, { style: styles.listItemText, children: item })] }, index))) })), section.subsections?.map((sub, index) => (_jsx(TextSectionView, { section: sub, level: 3 }, index)))] }));
|
|
7
|
+
}
|
|
8
|
+
export function AppTextScreen({ text, lastUpdatedDate, ScreenWrapper, style, }) {
|
|
9
|
+
const styles = useStyles();
|
|
10
|
+
const lastUpdated = text.lastUpdated
|
|
11
|
+
? text.lastUpdated.replace('{{date}}', lastUpdatedDate ?? '')
|
|
12
|
+
: lastUpdatedDate
|
|
13
|
+
? `Last updated: ${lastUpdatedDate}`
|
|
14
|
+
: undefined;
|
|
15
|
+
const content = (_jsxs(View, { style: [styles.container, style], children: [_jsx(Text, { style: styles.title, children: text.title }), lastUpdated && _jsx(Text, { style: styles.lastUpdated, children: lastUpdated }), text.sections.map((section, index) => (_jsx(TextSectionView, { section: section }, index))), text.contact && (_jsxs(View, { style: styles.contactSection, children: [_jsx(Text, { style: styles.sectionTitle, children: text.contact.title }), _jsx(Text, { style: styles.sectionContent, children: text.contact.description }), _jsx(Text, { style: styles.contactInfo, children: text.contact.info }), text.contact.gdprNotice && (_jsx(Text, { style: styles.gdprNotice, children: text.contact.gdprNotice }))] }))] }));
|
|
16
|
+
if (ScreenWrapper) {
|
|
17
|
+
return _jsx(ScreenWrapper, { children: content });
|
|
18
|
+
}
|
|
19
|
+
return (_jsx(ScrollView, { contentContainerStyle: styles.scrollContent, children: content }));
|
|
20
|
+
}
|
|
21
|
+
const useStyles = createThemedStyles(colors => ({
|
|
22
|
+
scrollContent: {
|
|
23
|
+
flexGrow: 1,
|
|
24
|
+
},
|
|
25
|
+
container: {
|
|
26
|
+
padding: 16,
|
|
27
|
+
},
|
|
28
|
+
title: {
|
|
29
|
+
fontSize: 28,
|
|
30
|
+
fontWeight: '700',
|
|
31
|
+
color: colors.text,
|
|
32
|
+
marginBottom: 8,
|
|
33
|
+
},
|
|
34
|
+
lastUpdated: {
|
|
35
|
+
fontSize: 14,
|
|
36
|
+
color: colors.textMuted,
|
|
37
|
+
marginBottom: 24,
|
|
38
|
+
},
|
|
39
|
+
section: {
|
|
40
|
+
marginBottom: 24,
|
|
41
|
+
},
|
|
42
|
+
sectionTitle: {
|
|
43
|
+
fontSize: 20,
|
|
44
|
+
fontWeight: '600',
|
|
45
|
+
color: colors.text,
|
|
46
|
+
marginBottom: 8,
|
|
47
|
+
},
|
|
48
|
+
subsectionTitle: {
|
|
49
|
+
fontSize: 17,
|
|
50
|
+
fontWeight: '600',
|
|
51
|
+
color: colors.text,
|
|
52
|
+
marginBottom: 6,
|
|
53
|
+
},
|
|
54
|
+
sectionContent: {
|
|
55
|
+
fontSize: 15,
|
|
56
|
+
lineHeight: 22,
|
|
57
|
+
color: colors.textSecondary,
|
|
58
|
+
},
|
|
59
|
+
list: {
|
|
60
|
+
marginTop: 8,
|
|
61
|
+
gap: 6,
|
|
62
|
+
},
|
|
63
|
+
listItem: {
|
|
64
|
+
flexDirection: 'row',
|
|
65
|
+
paddingLeft: 8,
|
|
66
|
+
},
|
|
67
|
+
bullet: {
|
|
68
|
+
fontSize: 15,
|
|
69
|
+
color: colors.textSecondary,
|
|
70
|
+
marginRight: 8,
|
|
71
|
+
lineHeight: 22,
|
|
72
|
+
},
|
|
73
|
+
listItemText: {
|
|
74
|
+
fontSize: 15,
|
|
75
|
+
lineHeight: 22,
|
|
76
|
+
color: colors.textSecondary,
|
|
77
|
+
flex: 1,
|
|
78
|
+
},
|
|
79
|
+
contactSection: {
|
|
80
|
+
marginTop: 16,
|
|
81
|
+
padding: 16,
|
|
82
|
+
backgroundColor: colors.surfaceSecondary,
|
|
83
|
+
borderRadius: 12,
|
|
84
|
+
},
|
|
85
|
+
contactInfo: {
|
|
86
|
+
fontSize: 15,
|
|
87
|
+
color: colors.primary,
|
|
88
|
+
marginTop: 8,
|
|
89
|
+
},
|
|
90
|
+
gdprNotice: {
|
|
91
|
+
fontSize: 13,
|
|
92
|
+
color: colors.textMuted,
|
|
93
|
+
marginTop: 12,
|
|
94
|
+
fontStyle: 'italic',
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
export interface LoginScreenProps {
|
|
3
|
+
/** App name displayed in the header */
|
|
4
|
+
appName: string;
|
|
5
|
+
/** Logo image source */
|
|
6
|
+
logo?: number | {
|
|
7
|
+
uri: string;
|
|
8
|
+
};
|
|
9
|
+
/** Called on successful login with email and password */
|
|
10
|
+
onLogin: (email: string, password: string) => Promise<void>;
|
|
11
|
+
/** Called on successful sign up with email and password */
|
|
12
|
+
onSignUp?: (email: string, password: string) => Promise<void>;
|
|
13
|
+
/** Called when Google sign-in is pressed */
|
|
14
|
+
onGoogleSignIn?: () => Promise<void>;
|
|
15
|
+
/** Called when Apple sign-in is pressed */
|
|
16
|
+
onAppleSignIn?: () => Promise<void>;
|
|
17
|
+
/** Whether to show Google sign-in button */
|
|
18
|
+
showGoogleSignIn?: boolean;
|
|
19
|
+
/** Whether to show Apple sign-in button */
|
|
20
|
+
showAppleSignIn?: boolean;
|
|
21
|
+
/** Whether to show sign-up option */
|
|
22
|
+
showSignUp?: boolean;
|
|
23
|
+
/** Custom style */
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
}
|
|
26
|
+
export declare function LoginScreen({ appName, logo, onLogin, onSignUp, onGoogleSignIn, onAppleSignIn, showGoogleSignIn, showAppleSignIn, showSignUp, style, }: LoginScreenProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { View, Text, TextInput, Pressable, ActivityIndicator, Image, KeyboardAvoidingView, Platform, } from 'react-native';
|
|
4
|
+
import { createThemedStyles } from '../../utils/styles';
|
|
5
|
+
export function LoginScreen({ appName, logo, onLogin, onSignUp, onGoogleSignIn, onAppleSignIn, showGoogleSignIn = false, showAppleSignIn = false, showSignUp = true, style, }) {
|
|
6
|
+
const styles = useStyles();
|
|
7
|
+
const [email, setEmail] = useState('');
|
|
8
|
+
const [password, setPassword] = useState('');
|
|
9
|
+
const [isSignUp, setIsSignUp] = useState(false);
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const handleSubmit = useCallback(async () => {
|
|
13
|
+
if (!email.trim() || !password.trim()) {
|
|
14
|
+
setError('Please enter email and password.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
setError(null);
|
|
18
|
+
setLoading(true);
|
|
19
|
+
try {
|
|
20
|
+
if (isSignUp && onSignUp) {
|
|
21
|
+
await onSignUp(email.trim(), password);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
await onLogin(email.trim(), password);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
setError(e instanceof Error ? e.message : 'An error occurred.');
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
}, [email, password, isSignUp, onLogin, onSignUp]);
|
|
34
|
+
const handleSocialSignIn = useCallback(async (signIn) => {
|
|
35
|
+
setError(null);
|
|
36
|
+
setLoading(true);
|
|
37
|
+
try {
|
|
38
|
+
await signIn();
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
setError(e instanceof Error ? e.message : 'An error occurred.');
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
return (_jsx(KeyboardAvoidingView, { style: [styles.container, style], behavior: Platform.OS === 'ios' ? 'padding' : 'height', children: _jsxs(View, { style: styles.card, children: [logo && (_jsx(Image, { source: logo, style: styles.logo, resizeMode: 'contain' })), _jsx(Text, { style: styles.title, children: isSignUp ? `Sign up for ${appName}` : `Sign in to ${appName}` }), error && (_jsx(View, { style: styles.errorBox, children: _jsx(Text, { style: styles.errorText, children: error }) })), _jsxs(View, { style: styles.form, children: [_jsx(Text, { style: styles.label, children: "Email" }), _jsx(TextInput, { style: styles.input, value: email, onChangeText: setEmail, placeholder: 'you@example.com', autoCapitalize: 'none', autoCorrect: false, keyboardType: 'email-address', textContentType: 'emailAddress', editable: !loading }), _jsx(Text, { style: styles.label, children: "Password" }), _jsx(TextInput, { style: styles.input, value: password, onChangeText: setPassword, placeholder: 'Password', secureTextEntry: true, textContentType: isSignUp ? 'newPassword' : 'password', editable: !loading, onSubmitEditing: handleSubmit }), _jsx(Pressable, { style: [
|
|
48
|
+
styles.submitButton,
|
|
49
|
+
loading && styles.submitButtonDisabled,
|
|
50
|
+
], onPress: handleSubmit, disabled: loading, children: loading ? (_jsx(ActivityIndicator, { color: '#ffffff', size: 'small' })) : (_jsx(Text, { style: styles.submitButtonText, children: isSignUp ? 'Sign Up' : 'Sign In' })) })] }), (showGoogleSignIn || showAppleSignIn) && (_jsxs(View, { style: styles.socialSection, children: [_jsxs(View, { style: styles.divider, children: [_jsx(View, { style: styles.dividerLine }), _jsx(Text, { style: styles.dividerText, children: "or" }), _jsx(View, { style: styles.dividerLine })] }), showGoogleSignIn && onGoogleSignIn && (_jsx(Pressable, { style: styles.socialButton, onPress: () => handleSocialSignIn(onGoogleSignIn), disabled: loading, children: _jsx(Text, { style: styles.socialButtonText, children: "Continue with Google" }) })), showAppleSignIn && onAppleSignIn && (_jsx(Pressable, { style: [styles.socialButton, styles.appleButton], onPress: () => handleSocialSignIn(onAppleSignIn), disabled: loading, children: _jsx(Text, { style: [styles.socialButtonText, styles.appleButtonText], children: "Continue with Apple" }) }))] })), showSignUp && onSignUp && (_jsx(Pressable, { style: styles.toggleLink, onPress: () => {
|
|
51
|
+
setIsSignUp(!isSignUp);
|
|
52
|
+
setError(null);
|
|
53
|
+
}, children: _jsx(Text, { style: styles.toggleText, children: isSignUp
|
|
54
|
+
? 'Already have an account? Sign in'
|
|
55
|
+
: "Don't have an account? Sign up" }) }))] }) }));
|
|
56
|
+
}
|
|
57
|
+
const useStyles = createThemedStyles(colors => ({
|
|
58
|
+
container: {
|
|
59
|
+
flex: 1,
|
|
60
|
+
justifyContent: 'center',
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
backgroundColor: colors.background,
|
|
63
|
+
paddingHorizontal: 24,
|
|
64
|
+
},
|
|
65
|
+
card: {
|
|
66
|
+
width: '100%',
|
|
67
|
+
maxWidth: 400,
|
|
68
|
+
backgroundColor: colors.card,
|
|
69
|
+
borderRadius: 16,
|
|
70
|
+
padding: 24,
|
|
71
|
+
shadowColor: '#000',
|
|
72
|
+
shadowOffset: { width: 0, height: 2 },
|
|
73
|
+
shadowOpacity: 0.1,
|
|
74
|
+
shadowRadius: 8,
|
|
75
|
+
elevation: 4,
|
|
76
|
+
},
|
|
77
|
+
logo: {
|
|
78
|
+
width: 48,
|
|
79
|
+
height: 48,
|
|
80
|
+
alignSelf: 'center',
|
|
81
|
+
marginBottom: 16,
|
|
82
|
+
},
|
|
83
|
+
title: {
|
|
84
|
+
fontSize: 22,
|
|
85
|
+
fontWeight: '700',
|
|
86
|
+
color: colors.text,
|
|
87
|
+
textAlign: 'center',
|
|
88
|
+
marginBottom: 20,
|
|
89
|
+
},
|
|
90
|
+
errorBox: {
|
|
91
|
+
backgroundColor: '#fef2f2',
|
|
92
|
+
borderRadius: 8,
|
|
93
|
+
padding: 12,
|
|
94
|
+
marginBottom: 16,
|
|
95
|
+
},
|
|
96
|
+
errorText: {
|
|
97
|
+
color: colors.error,
|
|
98
|
+
fontSize: 14,
|
|
99
|
+
},
|
|
100
|
+
form: {
|
|
101
|
+
gap: 8,
|
|
102
|
+
},
|
|
103
|
+
label: {
|
|
104
|
+
fontSize: 14,
|
|
105
|
+
fontWeight: '500',
|
|
106
|
+
color: colors.text,
|
|
107
|
+
marginTop: 4,
|
|
108
|
+
},
|
|
109
|
+
input: {
|
|
110
|
+
borderWidth: 1,
|
|
111
|
+
borderColor: colors.border,
|
|
112
|
+
borderRadius: 8,
|
|
113
|
+
paddingHorizontal: 12,
|
|
114
|
+
paddingVertical: 10,
|
|
115
|
+
fontSize: 16,
|
|
116
|
+
color: colors.text,
|
|
117
|
+
backgroundColor: colors.background,
|
|
118
|
+
},
|
|
119
|
+
submitButton: {
|
|
120
|
+
backgroundColor: colors.primary,
|
|
121
|
+
borderRadius: 8,
|
|
122
|
+
paddingVertical: 12,
|
|
123
|
+
alignItems: 'center',
|
|
124
|
+
marginTop: 8,
|
|
125
|
+
minHeight: 44,
|
|
126
|
+
justifyContent: 'center',
|
|
127
|
+
},
|
|
128
|
+
submitButtonDisabled: {
|
|
129
|
+
opacity: 0.6,
|
|
130
|
+
},
|
|
131
|
+
submitButtonText: {
|
|
132
|
+
color: '#ffffff',
|
|
133
|
+
fontSize: 16,
|
|
134
|
+
fontWeight: '600',
|
|
135
|
+
},
|
|
136
|
+
socialSection: {
|
|
137
|
+
marginTop: 20,
|
|
138
|
+
gap: 12,
|
|
139
|
+
},
|
|
140
|
+
divider: {
|
|
141
|
+
flexDirection: 'row',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
},
|
|
144
|
+
dividerLine: {
|
|
145
|
+
flex: 1,
|
|
146
|
+
height: 1,
|
|
147
|
+
backgroundColor: colors.border,
|
|
148
|
+
},
|
|
149
|
+
dividerText: {
|
|
150
|
+
marginHorizontal: 12,
|
|
151
|
+
fontSize: 14,
|
|
152
|
+
color: colors.textMuted,
|
|
153
|
+
},
|
|
154
|
+
socialButton: {
|
|
155
|
+
borderWidth: 1,
|
|
156
|
+
borderColor: colors.border,
|
|
157
|
+
borderRadius: 8,
|
|
158
|
+
paddingVertical: 12,
|
|
159
|
+
alignItems: 'center',
|
|
160
|
+
minHeight: 44,
|
|
161
|
+
justifyContent: 'center',
|
|
162
|
+
},
|
|
163
|
+
socialButtonText: {
|
|
164
|
+
fontSize: 16,
|
|
165
|
+
fontWeight: '500',
|
|
166
|
+
color: colors.text,
|
|
167
|
+
},
|
|
168
|
+
appleButton: {
|
|
169
|
+
backgroundColor: '#000000',
|
|
170
|
+
borderColor: '#000000',
|
|
171
|
+
},
|
|
172
|
+
appleButtonText: {
|
|
173
|
+
color: '#ffffff',
|
|
174
|
+
},
|
|
175
|
+
toggleLink: {
|
|
176
|
+
marginTop: 16,
|
|
177
|
+
alignItems: 'center',
|
|
178
|
+
},
|
|
179
|
+
toggleText: {
|
|
180
|
+
fontSize: 14,
|
|
181
|
+
color: colors.primary,
|
|
182
|
+
},
|
|
183
|
+
}));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { Theme, FontSize } from '../../types';
|
|
3
|
+
export interface AppearanceSettingsProps {
|
|
4
|
+
theme: Theme | string;
|
|
5
|
+
fontSize: FontSize | string;
|
|
6
|
+
onThemeChange: (theme: Theme) => void;
|
|
7
|
+
onFontSizeChange: (fontSize: FontSize) => void;
|
|
8
|
+
/** Optional translation function */
|
|
9
|
+
t?: (key: string, fallback?: string) => string;
|
|
10
|
+
style?: StyleProp<ViewStyle>;
|
|
11
|
+
showInfoBox?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function AppearanceSettings({ theme, fontSize, onThemeChange, onFontSizeChange, t, style, showInfoBox, }: AppearanceSettingsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, Pressable } from 'react-native';
|
|
3
|
+
import { Theme, FontSize } from '../../types';
|
|
4
|
+
import { createThemedStyles } from '../../utils/styles';
|
|
5
|
+
const THEME_OPTIONS = [
|
|
6
|
+
{ value: Theme.LIGHT, labelKey: 'appearance.theme.light', fallback: 'Light' },
|
|
7
|
+
{ value: Theme.DARK, labelKey: 'appearance.theme.dark', fallback: 'Dark' },
|
|
8
|
+
{
|
|
9
|
+
value: Theme.SYSTEM,
|
|
10
|
+
labelKey: 'appearance.theme.system',
|
|
11
|
+
fallback: 'System',
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
const FONT_SIZE_OPTIONS = [
|
|
15
|
+
{
|
|
16
|
+
value: FontSize.SMALL,
|
|
17
|
+
labelKey: 'appearance.fontSize.small',
|
|
18
|
+
fallback: 'Small',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: FontSize.MEDIUM,
|
|
22
|
+
labelKey: 'appearance.fontSize.medium',
|
|
23
|
+
fallback: 'Medium',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: FontSize.LARGE,
|
|
27
|
+
labelKey: 'appearance.fontSize.large',
|
|
28
|
+
fallback: 'Large',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
export function AppearanceSettings({ theme, fontSize, onThemeChange, onFontSizeChange, t, style, showInfoBox = false, }) {
|
|
32
|
+
const styles = useStyles();
|
|
33
|
+
const translate = (key, fallback) => t?.(key, fallback) ?? fallback;
|
|
34
|
+
return (_jsxs(View, { style: [styles.container, style], children: [_jsx(Text, { style: styles.heading, children: translate('appearance.title', 'Appearance') }), _jsx(Text, { style: styles.label, children: translate('appearance.theme.label', 'Theme') }), _jsx(View, { style: styles.segmentedControl, children: THEME_OPTIONS.map(option => (_jsx(Pressable, { style: [
|
|
35
|
+
styles.segment,
|
|
36
|
+
theme === option.value && styles.segmentActive,
|
|
37
|
+
], onPress: () => onThemeChange(option.value), children: _jsx(Text, { style: [
|
|
38
|
+
styles.segmentText,
|
|
39
|
+
theme === option.value && styles.segmentTextActive,
|
|
40
|
+
], children: translate(option.labelKey, option.fallback) }) }, option.value))) }), _jsx(Text, { style: [styles.label, { marginTop: 20 }], children: translate('appearance.fontSize.label', 'Font Size') }), _jsx(View, { style: styles.segmentedControl, children: FONT_SIZE_OPTIONS.map(option => (_jsx(Pressable, { style: [
|
|
41
|
+
styles.segment,
|
|
42
|
+
fontSize === option.value && styles.segmentActive,
|
|
43
|
+
], onPress: () => onFontSizeChange(option.value), children: _jsx(Text, { style: [
|
|
44
|
+
styles.segmentText,
|
|
45
|
+
fontSize === option.value && styles.segmentTextActive,
|
|
46
|
+
], children: translate(option.labelKey, option.fallback) }) }, option.value))) }), showInfoBox && (_jsx(View, { style: styles.infoBox, children: _jsx(Text, { style: styles.infoText, children: translate('appearance.infoBox', 'Your appearance preferences are stored locally on this device.') }) }))] }));
|
|
47
|
+
}
|
|
48
|
+
const useStyles = createThemedStyles(colors => ({
|
|
49
|
+
container: {
|
|
50
|
+
padding: 16,
|
|
51
|
+
},
|
|
52
|
+
heading: {
|
|
53
|
+
fontSize: 20,
|
|
54
|
+
fontWeight: '600',
|
|
55
|
+
color: colors.text,
|
|
56
|
+
marginBottom: 20,
|
|
57
|
+
},
|
|
58
|
+
label: {
|
|
59
|
+
fontSize: 15,
|
|
60
|
+
fontWeight: '500',
|
|
61
|
+
color: colors.textSecondary,
|
|
62
|
+
marginBottom: 8,
|
|
63
|
+
},
|
|
64
|
+
segmentedControl: {
|
|
65
|
+
flexDirection: 'row',
|
|
66
|
+
backgroundColor: colors.surfaceSecondary,
|
|
67
|
+
borderRadius: 10,
|
|
68
|
+
padding: 3,
|
|
69
|
+
},
|
|
70
|
+
segment: {
|
|
71
|
+
flex: 1,
|
|
72
|
+
paddingVertical: 10,
|
|
73
|
+
borderRadius: 8,
|
|
74
|
+
alignItems: 'center',
|
|
75
|
+
justifyContent: 'center',
|
|
76
|
+
minHeight: 40,
|
|
77
|
+
},
|
|
78
|
+
segmentActive: {
|
|
79
|
+
backgroundColor: colors.card,
|
|
80
|
+
shadowColor: '#000',
|
|
81
|
+
shadowOffset: { width: 0, height: 1 },
|
|
82
|
+
shadowOpacity: 0.1,
|
|
83
|
+
shadowRadius: 2,
|
|
84
|
+
elevation: 2,
|
|
85
|
+
},
|
|
86
|
+
segmentText: {
|
|
87
|
+
fontSize: 14,
|
|
88
|
+
fontWeight: '500',
|
|
89
|
+
color: colors.textSecondary,
|
|
90
|
+
},
|
|
91
|
+
segmentTextActive: {
|
|
92
|
+
color: colors.text,
|
|
93
|
+
fontWeight: '600',
|
|
94
|
+
},
|
|
95
|
+
infoBox: {
|
|
96
|
+
marginTop: 20,
|
|
97
|
+
padding: 12,
|
|
98
|
+
backgroundColor: colors.surfaceSecondary,
|
|
99
|
+
borderRadius: 8,
|
|
100
|
+
},
|
|
101
|
+
infoText: {
|
|
102
|
+
fontSize: 13,
|
|
103
|
+
color: colors.textMuted,
|
|
104
|
+
lineHeight: 18,
|
|
105
|
+
},
|
|
106
|
+
}));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import type { LanguageConfig } from '../../constants/languages';
|
|
3
|
+
export interface LanguagePickerProps {
|
|
4
|
+
/** Available languages (default: DEFAULT_LANGUAGES) */
|
|
5
|
+
languages?: LanguageConfig[];
|
|
6
|
+
/** Currently selected language code */
|
|
7
|
+
currentLanguage?: string;
|
|
8
|
+
/** Called when a language is selected */
|
|
9
|
+
onLanguageChange?: (languageCode: string) => void;
|
|
10
|
+
/** Label displayed above the picker */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Custom style */
|
|
13
|
+
style?: StyleProp<ViewStyle>;
|
|
14
|
+
}
|
|
15
|
+
export declare function LanguagePicker({ languages, currentLanguage, onLanguageChange, label, style, }: LanguagePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { View, Text, Pressable, Modal, FlatList, SafeAreaView, } from 'react-native';
|
|
4
|
+
import { DEFAULT_LANGUAGES } from '../../constants/languages';
|
|
5
|
+
import { createThemedStyles } from '../../utils/styles';
|
|
6
|
+
export function LanguagePicker({ languages = DEFAULT_LANGUAGES, currentLanguage = 'en', onLanguageChange, label, style, }) {
|
|
7
|
+
const styles = useStyles();
|
|
8
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
9
|
+
const currentLang = languages.find(l => l.code === currentLanguage);
|
|
10
|
+
const handleSelect = (code) => {
|
|
11
|
+
onLanguageChange?.(code);
|
|
12
|
+
setModalVisible(false);
|
|
13
|
+
};
|
|
14
|
+
return (_jsxs(View, { style: style, children: [label && _jsx(Text, { style: styles.label, children: label }), _jsxs(Pressable, { style: styles.trigger, onPress: () => setModalVisible(true), children: [_jsx(Text, { style: styles.triggerText, children: currentLang
|
|
15
|
+
? `${currentLang.flag} ${currentLang.name}`
|
|
16
|
+
: currentLanguage }), _jsx(Text, { style: styles.chevron, children: '\u25BC' })] }), _jsx(Modal, { visible: modalVisible, animationType: 'slide', presentationStyle: 'pageSheet', onRequestClose: () => setModalVisible(false), children: _jsxs(SafeAreaView, { style: styles.modal, children: [_jsxs(View, { style: styles.modalHeader, children: [_jsx(Text, { style: styles.modalTitle, children: label ?? 'Select Language' }), _jsx(Pressable, { onPress: () => setModalVisible(false), children: _jsx(Text, { style: styles.closeButton, children: "Done" }) })] }), _jsx(FlatList, { data: languages, keyExtractor: item => item.code, renderItem: ({ item }) => (_jsxs(Pressable, { style: [
|
|
17
|
+
styles.languageRow,
|
|
18
|
+
item.code === currentLanguage && styles.languageRowActive,
|
|
19
|
+
], onPress: () => handleSelect(item.code), children: [_jsx(Text, { style: styles.flag, children: item.flag }), _jsx(Text, { style: styles.languageName, children: item.name }), item.code === currentLanguage && (_jsx(Text, { style: styles.checkmark, children: '\u2713' }))] })), ItemSeparatorComponent: () => _jsx(View, { style: styles.separator }) })] }) })] }));
|
|
20
|
+
}
|
|
21
|
+
const useStyles = createThemedStyles(colors => ({
|
|
22
|
+
label: {
|
|
23
|
+
fontSize: 15,
|
|
24
|
+
fontWeight: '500',
|
|
25
|
+
color: colors.textSecondary,
|
|
26
|
+
marginBottom: 8,
|
|
27
|
+
},
|
|
28
|
+
trigger: {
|
|
29
|
+
flexDirection: 'row',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
justifyContent: 'space-between',
|
|
32
|
+
paddingHorizontal: 12,
|
|
33
|
+
paddingVertical: 12,
|
|
34
|
+
backgroundColor: colors.card,
|
|
35
|
+
borderRadius: 8,
|
|
36
|
+
borderWidth: 1,
|
|
37
|
+
borderColor: colors.border,
|
|
38
|
+
minHeight: 44,
|
|
39
|
+
},
|
|
40
|
+
triggerText: {
|
|
41
|
+
fontSize: 16,
|
|
42
|
+
color: colors.text,
|
|
43
|
+
},
|
|
44
|
+
chevron: {
|
|
45
|
+
fontSize: 10,
|
|
46
|
+
color: colors.textMuted,
|
|
47
|
+
},
|
|
48
|
+
modal: {
|
|
49
|
+
flex: 1,
|
|
50
|
+
backgroundColor: colors.background,
|
|
51
|
+
},
|
|
52
|
+
modalHeader: {
|
|
53
|
+
flexDirection: 'row',
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
justifyContent: 'space-between',
|
|
56
|
+
paddingHorizontal: 16,
|
|
57
|
+
paddingVertical: 12,
|
|
58
|
+
borderBottomWidth: 1,
|
|
59
|
+
borderBottomColor: colors.border,
|
|
60
|
+
},
|
|
61
|
+
modalTitle: {
|
|
62
|
+
fontSize: 18,
|
|
63
|
+
fontWeight: '600',
|
|
64
|
+
color: colors.text,
|
|
65
|
+
},
|
|
66
|
+
closeButton: {
|
|
67
|
+
fontSize: 16,
|
|
68
|
+
fontWeight: '600',
|
|
69
|
+
color: colors.primary,
|
|
70
|
+
},
|
|
71
|
+
languageRow: {
|
|
72
|
+
flexDirection: 'row',
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
paddingHorizontal: 16,
|
|
75
|
+
paddingVertical: 14,
|
|
76
|
+
minHeight: 48,
|
|
77
|
+
},
|
|
78
|
+
languageRowActive: {
|
|
79
|
+
backgroundColor: colors.surfaceSecondary,
|
|
80
|
+
},
|
|
81
|
+
flag: {
|
|
82
|
+
fontSize: 20,
|
|
83
|
+
marginRight: 12,
|
|
84
|
+
},
|
|
85
|
+
languageName: {
|
|
86
|
+
fontSize: 16,
|
|
87
|
+
color: colors.text,
|
|
88
|
+
flex: 1,
|
|
89
|
+
},
|
|
90
|
+
checkmark: {
|
|
91
|
+
fontSize: 18,
|
|
92
|
+
color: colors.primary,
|
|
93
|
+
fontWeight: '600',
|
|
94
|
+
},
|
|
95
|
+
separator: {
|
|
96
|
+
height: 1,
|
|
97
|
+
backgroundColor: colors.border,
|
|
98
|
+
marginLeft: 48,
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import type { SettingsSectionConfig, AnalyticsTrackingParams } from '../../types';
|
|
3
|
+
export interface SettingsListScreenProps {
|
|
4
|
+
/** Settings sections to display */
|
|
5
|
+
sections: SettingsSectionConfig[];
|
|
6
|
+
/** Called when a section is pressed */
|
|
7
|
+
onSectionPress: (sectionId: string) => void;
|
|
8
|
+
/** Title displayed at the top */
|
|
9
|
+
title?: string;
|
|
10
|
+
/** Custom style */
|
|
11
|
+
style?: StyleProp<ViewStyle>;
|
|
12
|
+
/** Analytics tracking */
|
|
13
|
+
onTrack?: (params: AnalyticsTrackingParams) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function SettingsListScreen({ sections, onSectionPress, title, style, onTrack, }: SettingsListScreenProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, Pressable, ScrollView } from 'react-native';
|
|
3
|
+
import { createThemedStyles } from '../../utils/styles';
|
|
4
|
+
export function SettingsListScreen({ sections, onSectionPress, title = 'Settings', style, onTrack, }) {
|
|
5
|
+
const styles = useStyles();
|
|
6
|
+
const handlePress = (section) => {
|
|
7
|
+
onTrack?.({
|
|
8
|
+
eventType: 'navigation',
|
|
9
|
+
componentName: 'SettingsListScreen',
|
|
10
|
+
label: 'settings_section_tapped',
|
|
11
|
+
params: { section_id: section.id },
|
|
12
|
+
});
|
|
13
|
+
onSectionPress(section.id);
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs(ScrollView, { style: [styles.container, style], contentContainerStyle: styles.content, children: [_jsx(Text, { style: styles.title, children: title }), _jsx(View, { style: styles.sectionList, children: sections.map((section, index) => {
|
|
16
|
+
const IconComponent = section.icon;
|
|
17
|
+
return (_jsxs(Pressable, { style: [
|
|
18
|
+
styles.sectionRow,
|
|
19
|
+
index < sections.length - 1 && styles.sectionRowBorder,
|
|
20
|
+
], onPress: () => handlePress(section), children: [IconComponent && (_jsx(View, { style: styles.iconContainer, children: _jsx(IconComponent, { size: 20, color: styles.iconColor.color }) })), _jsxs(View, { style: styles.sectionInfo, children: [_jsx(Text, { style: styles.sectionLabel, children: section.label }), section.description && (_jsx(Text, { style: styles.sectionDescription, children: section.description }))] }), _jsx(Text, { style: styles.chevron, children: '\u203A' })] }, section.id));
|
|
21
|
+
}) })] }));
|
|
22
|
+
}
|
|
23
|
+
const useStyles = createThemedStyles(colors => ({
|
|
24
|
+
container: {
|
|
25
|
+
flex: 1,
|
|
26
|
+
backgroundColor: colors.background,
|
|
27
|
+
},
|
|
28
|
+
content: {
|
|
29
|
+
padding: 16,
|
|
30
|
+
},
|
|
31
|
+
title: {
|
|
32
|
+
fontSize: 28,
|
|
33
|
+
fontWeight: '700',
|
|
34
|
+
color: colors.text,
|
|
35
|
+
marginBottom: 20,
|
|
36
|
+
},
|
|
37
|
+
sectionList: {
|
|
38
|
+
backgroundColor: colors.card,
|
|
39
|
+
borderRadius: 12,
|
|
40
|
+
overflow: 'hidden',
|
|
41
|
+
},
|
|
42
|
+
sectionRow: {
|
|
43
|
+
flexDirection: 'row',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
padding: 16,
|
|
46
|
+
minHeight: 56,
|
|
47
|
+
},
|
|
48
|
+
sectionRowBorder: {
|
|
49
|
+
borderBottomWidth: 1,
|
|
50
|
+
borderBottomColor: colors.border,
|
|
51
|
+
},
|
|
52
|
+
iconContainer: {
|
|
53
|
+
width: 32,
|
|
54
|
+
height: 32,
|
|
55
|
+
borderRadius: 8,
|
|
56
|
+
backgroundColor: colors.surfaceSecondary,
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
justifyContent: 'center',
|
|
59
|
+
marginRight: 12,
|
|
60
|
+
},
|
|
61
|
+
iconColor: {
|
|
62
|
+
color: colors.primary,
|
|
63
|
+
},
|
|
64
|
+
sectionInfo: {
|
|
65
|
+
flex: 1,
|
|
66
|
+
},
|
|
67
|
+
sectionLabel: {
|
|
68
|
+
fontSize: 16,
|
|
69
|
+
fontWeight: '500',
|
|
70
|
+
color: colors.text,
|
|
71
|
+
},
|
|
72
|
+
sectionDescription: {
|
|
73
|
+
fontSize: 13,
|
|
74
|
+
color: colors.textMuted,
|
|
75
|
+
marginTop: 2,
|
|
76
|
+
},
|
|
77
|
+
chevron: {
|
|
78
|
+
fontSize: 22,
|
|
79
|
+
color: colors.textMuted,
|
|
80
|
+
marginLeft: 8,
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { AppearanceSettings } from './AppearanceSettings';
|
|
2
|
+
export type { AppearanceSettingsProps } from './AppearanceSettings';
|
|
3
|
+
export { SettingsListScreen } from './SettingsListScreen';
|
|
4
|
+
export type { SettingsListScreenProps } from './SettingsListScreen';
|
|
5
|
+
export { LanguagePicker } from './LanguagePicker';
|
|
6
|
+
export type { LanguagePickerProps } from './LanguagePicker';
|