@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.
Files changed (77) hide show
  1. package/dist/firebase.d.ts +10 -0
  2. package/dist/firebase.js +10 -0
  3. package/dist/index.d.ts +21 -0
  4. package/dist/index.js +25 -0
  5. package/dist/src/api/ApiContext.d.ts +52 -0
  6. package/dist/src/api/ApiContext.js +67 -0
  7. package/dist/src/api/index.d.ts +2 -0
  8. package/dist/src/api/index.js +1 -0
  9. package/dist/src/app/SudobilityAppRN.d.ts +29 -0
  10. package/dist/src/app/SudobilityAppRN.js +37 -0
  11. package/dist/src/app/SudobilityAppRNWithFirebaseAuth.d.ts +13 -0
  12. package/dist/src/app/SudobilityAppRNWithFirebaseAuth.js +12 -0
  13. package/dist/src/app/index.d.ts +4 -0
  14. package/dist/src/app/index.js +2 -0
  15. package/dist/src/components/footer/AppFooter.d.ts +21 -0
  16. package/dist/src/components/footer/AppFooter.js +72 -0
  17. package/dist/src/components/footer/index.d.ts +2 -0
  18. package/dist/src/components/footer/index.js +1 -0
  19. package/dist/src/components/header/AppHeader.d.ts +28 -0
  20. package/dist/src/components/header/AppHeader.js +79 -0
  21. package/dist/src/components/header/index.d.ts +2 -0
  22. package/dist/src/components/header/index.js +1 -0
  23. package/dist/src/components/layout/AppScreenLayout.d.ts +20 -0
  24. package/dist/src/components/layout/AppScreenLayout.js +41 -0
  25. package/dist/src/components/layout/index.d.ts +2 -0
  26. package/dist/src/components/layout/index.js +1 -0
  27. package/dist/src/components/pages/AppTextScreen.d.ts +16 -0
  28. package/dist/src/components/pages/AppTextScreen.js +96 -0
  29. package/dist/src/components/pages/LoginScreen.d.ts +26 -0
  30. package/dist/src/components/pages/LoginScreen.js +183 -0
  31. package/dist/src/components/pages/index.d.ts +4 -0
  32. package/dist/src/components/pages/index.js +2 -0
  33. package/dist/src/components/settings/AppearanceSettings.d.ts +13 -0
  34. package/dist/src/components/settings/AppearanceSettings.js +106 -0
  35. package/dist/src/components/settings/LanguagePicker.d.ts +15 -0
  36. package/dist/src/components/settings/LanguagePicker.js +100 -0
  37. package/dist/src/components/settings/SettingsListScreen.d.ts +15 -0
  38. package/dist/src/components/settings/SettingsListScreen.js +82 -0
  39. package/dist/src/components/settings/index.d.ts +6 -0
  40. package/dist/src/components/settings/index.js +3 -0
  41. package/dist/src/components/subscription/SafeSubscriptionContext.d.ts +13 -0
  42. package/dist/src/components/subscription/SafeSubscriptionContext.js +15 -0
  43. package/dist/src/components/subscription/SubscriptionScreen.d.ts +36 -0
  44. package/dist/src/components/subscription/SubscriptionScreen.js +188 -0
  45. package/dist/src/components/subscription/index.d.ts +4 -0
  46. package/dist/src/components/subscription/index.js +2 -0
  47. package/dist/src/components/toast/ToastProvider.d.ts +20 -0
  48. package/dist/src/components/toast/ToastProvider.js +87 -0
  49. package/dist/src/components/toast/index.d.ts +2 -0
  50. package/dist/src/components/toast/index.js +1 -0
  51. package/dist/src/constants/index.d.ts +1 -0
  52. package/dist/src/constants/index.js +1 -0
  53. package/dist/src/constants/languages.d.ts +18 -0
  54. package/dist/src/constants/languages.js +48 -0
  55. package/dist/src/hooks/index.d.ts +2 -0
  56. package/dist/src/hooks/index.js +1 -0
  57. package/dist/src/hooks/useResponsive.d.ts +11 -0
  58. package/dist/src/hooks/useResponsive.js +14 -0
  59. package/dist/src/i18n/index.d.ts +31 -0
  60. package/dist/src/i18n/index.js +78 -0
  61. package/dist/src/theme/ThemeContext.d.ts +34 -0
  62. package/dist/src/theme/ThemeContext.js +66 -0
  63. package/dist/src/theme/colors.d.ts +55 -0
  64. package/dist/src/theme/colors.js +69 -0
  65. package/dist/src/theme/index.d.ts +7 -0
  66. package/dist/src/theme/index.js +4 -0
  67. package/dist/src/theme/spacing.d.ts +16 -0
  68. package/dist/src/theme/spacing.js +15 -0
  69. package/dist/src/theme/typography.d.ts +24 -0
  70. package/dist/src/theme/typography.js +34 -0
  71. package/dist/src/types.d.ts +152 -0
  72. package/dist/src/types.js +27 -0
  73. package/dist/src/utils/index.d.ts +1 -0
  74. package/dist/src/utils/index.js +1 -0
  75. package/dist/src/utils/styles.d.ts +23 -0
  76. package/dist/src/utils/styles.js +25 -0
  77. 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,4 @@
1
+ export { AppTextScreen } from './AppTextScreen';
2
+ export type { AppTextScreenProps } from './AppTextScreen';
3
+ export { LoginScreen } from './LoginScreen';
4
+ export type { LoginScreenProps } from './LoginScreen';
@@ -0,0 +1,2 @@
1
+ export { AppTextScreen } from './AppTextScreen';
2
+ export { LoginScreen } from './LoginScreen';
@@ -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';
@@ -0,0 +1,3 @@
1
+ export { AppearanceSettings } from './AppearanceSettings';
2
+ export { SettingsListScreen } from './SettingsListScreen';
3
+ export { LanguagePicker } from './LanguagePicker';