@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,13 @@
1
+ export interface SubscriptionContextValue {
2
+ isSubscribed: boolean;
3
+ subscriptionPlan: string | null;
4
+ expirationDate: string | null;
5
+ isLoading: boolean;
6
+ }
7
+ export declare const STUB_SUBSCRIPTION_VALUE: SubscriptionContextValue;
8
+ export declare const SafeSubscriptionContext: import("react").Context<SubscriptionContextValue>;
9
+ /**
10
+ * Hook to safely access subscription context.
11
+ * Returns stub value when no provider is available.
12
+ */
13
+ export declare function useSafeSubscription(): SubscriptionContextValue;
@@ -0,0 +1,15 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const STUB_SUBSCRIPTION_VALUE = {
3
+ isSubscribed: false,
4
+ subscriptionPlan: null,
5
+ expirationDate: null,
6
+ isLoading: false,
7
+ };
8
+ export const SafeSubscriptionContext = createContext(STUB_SUBSCRIPTION_VALUE);
9
+ /**
10
+ * Hook to safely access subscription context.
11
+ * Returns stub value when no provider is available.
12
+ */
13
+ export function useSafeSubscription() {
14
+ return useContext(SafeSubscriptionContext);
15
+ }
@@ -0,0 +1,36 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ import type { SubscriptionPageLabels, SubscriptionPageFormatters, AnalyticsTrackingParams } from '../../types';
3
+ export interface SubscriptionPackage {
4
+ id: string;
5
+ title: string;
6
+ description?: string;
7
+ price: number;
8
+ currency: string;
9
+ period: 'monthly' | 'yearly' | 'lifetime';
10
+ features?: string[];
11
+ isMostPopular?: boolean;
12
+ isCurrent?: boolean;
13
+ }
14
+ export interface SubscriptionScreenProps {
15
+ /** Available subscription packages */
16
+ packages: SubscriptionPackage[];
17
+ /** Labels for UI text */
18
+ labels?: SubscriptionPageLabels;
19
+ /** Formatters for prices and periods */
20
+ formatters?: SubscriptionPageFormatters;
21
+ /** Called when a package is purchased */
22
+ onPurchase: (packageId: string) => Promise<boolean>;
23
+ /** Called when restore purchases is pressed */
24
+ onRestore: () => Promise<boolean>;
25
+ /** Called on successful purchase */
26
+ onPurchaseSuccess?: () => void;
27
+ /** Called on successful restore */
28
+ onRestoreSuccess?: () => void;
29
+ /** Called on error */
30
+ onError?: (title: string, message: string) => void;
31
+ /** Custom style */
32
+ style?: StyleProp<ViewStyle>;
33
+ /** Analytics tracking */
34
+ onTrack?: (params: AnalyticsTrackingParams) => void;
35
+ }
36
+ export declare function SubscriptionScreen({ packages, labels, formatters, onPurchase, onRestore, onPurchaseSuccess, onRestoreSuccess, onError, style, onTrack, }: SubscriptionScreenProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,188 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback } from 'react';
3
+ import { View, Text, Pressable, ScrollView, ActivityIndicator, } from 'react-native';
4
+ import { createThemedStyles } from '../../utils/styles';
5
+ export function SubscriptionScreen({ packages, labels = {}, formatters = {}, onPurchase, onRestore, onPurchaseSuccess, onRestoreSuccess, onError, style, onTrack, }) {
6
+ const styles = useStyles();
7
+ const [loading, setLoading] = useState(null);
8
+ const formatPrice = formatters.formatPrice ??
9
+ ((price, currency) => `${currency} ${price.toFixed(2)}`);
10
+ const handlePurchase = useCallback(async (pkg) => {
11
+ if (loading)
12
+ return;
13
+ setLoading(pkg.id);
14
+ onTrack?.({
15
+ eventType: 'subscription_action',
16
+ componentName: 'SubscriptionScreen',
17
+ label: 'purchase_tapped',
18
+ params: { package_id: pkg.id },
19
+ });
20
+ try {
21
+ const success = await onPurchase(pkg.id);
22
+ if (success) {
23
+ onPurchaseSuccess?.();
24
+ }
25
+ }
26
+ catch (e) {
27
+ onError?.('Purchase Failed', e instanceof Error ? e.message : 'An error occurred.');
28
+ }
29
+ finally {
30
+ setLoading(null);
31
+ }
32
+ }, [loading, onPurchase, onPurchaseSuccess, onError, onTrack]);
33
+ const handleRestore = useCallback(async () => {
34
+ if (loading)
35
+ return;
36
+ setLoading('restore');
37
+ onTrack?.({
38
+ eventType: 'subscription_action',
39
+ componentName: 'SubscriptionScreen',
40
+ label: 'restore_tapped',
41
+ });
42
+ try {
43
+ const success = await onRestore();
44
+ if (success) {
45
+ onRestoreSuccess?.();
46
+ }
47
+ }
48
+ catch (e) {
49
+ onError?.('Restore Failed', e instanceof Error ? e.message : 'An error occurred.');
50
+ }
51
+ finally {
52
+ setLoading(null);
53
+ }
54
+ }, [loading, onRestore, onRestoreSuccess, onError, onTrack]);
55
+ return (_jsxs(ScrollView, { style: [styles.container, style], contentContainerStyle: styles.content, children: [_jsx(Text, { style: styles.title, children: labels.title ?? 'Subscription' }), labels.subtitle && (_jsx(Text, { style: styles.subtitle, children: labels.subtitle })), _jsx(View, { style: styles.packageList, children: packages.map(pkg => (_jsxs(View, { style: [
56
+ styles.packageCard,
57
+ pkg.isMostPopular && styles.packageCardPopular,
58
+ ], children: [pkg.isMostPopular && (_jsx(View, { style: styles.popularBadge, children: _jsx(Text, { style: styles.popularBadgeText, children: labels.mostPopular ?? 'Most Popular' }) })), _jsx(Text, { style: styles.packageTitle, children: pkg.title }), pkg.description && (_jsx(Text, { style: styles.packageDescription, children: pkg.description })), _jsx(Text, { style: styles.packagePrice, children: formatPrice(pkg.price, pkg.currency) }), pkg.features && pkg.features.length > 0 && (_jsx(View, { style: styles.featureList, children: pkg.features.map((feature, i) => (_jsxs(Text, { style: styles.featureItem, children: ['\u2713', " ", feature] }, i))) })), pkg.isCurrent ? (_jsx(View, { style: styles.currentBadge, children: _jsx(Text, { style: styles.currentBadgeText, children: labels.currentlyActive ?? 'Current Plan' }) })) : (_jsx(Pressable, { style: [
59
+ styles.purchaseButton,
60
+ loading === pkg.id && styles.purchaseButtonDisabled,
61
+ ], onPress: () => handlePurchase(pkg), disabled: loading !== null, children: loading === pkg.id ? (_jsx(ActivityIndicator, { color: '#ffffff', size: 'small' })) : (_jsx(Text, { style: styles.purchaseButtonText, children: labels.purchase ?? 'Subscribe' })) }))] }, pkg.id))) }), _jsx(Pressable, { style: [
62
+ styles.restoreButton,
63
+ loading === 'restore' && styles.restoreButtonDisabled,
64
+ ], onPress: handleRestore, disabled: loading !== null, children: loading === 'restore' ? (_jsx(ActivityIndicator, { color: styles.restoreText.color, size: 'small' })) : (_jsx(Text, { style: styles.restoreText, children: labels.restore ?? 'Restore Purchases' })) }), labels.restoreDescription && (_jsx(Text, { style: styles.restoreDescription, children: labels.restoreDescription }))] }));
65
+ }
66
+ const useStyles = createThemedStyles(colors => ({
67
+ container: {
68
+ flex: 1,
69
+ backgroundColor: colors.background,
70
+ },
71
+ content: {
72
+ padding: 16,
73
+ paddingBottom: 32,
74
+ },
75
+ title: {
76
+ fontSize: 28,
77
+ fontWeight: '700',
78
+ color: colors.text,
79
+ marginBottom: 4,
80
+ },
81
+ subtitle: {
82
+ fontSize: 15,
83
+ color: colors.textSecondary,
84
+ marginBottom: 20,
85
+ },
86
+ packageList: {
87
+ gap: 16,
88
+ marginTop: 16,
89
+ },
90
+ packageCard: {
91
+ backgroundColor: colors.card,
92
+ borderRadius: 16,
93
+ padding: 20,
94
+ borderWidth: 1,
95
+ borderColor: colors.border,
96
+ },
97
+ packageCardPopular: {
98
+ borderColor: colors.primary,
99
+ borderWidth: 2,
100
+ },
101
+ popularBadge: {
102
+ backgroundColor: colors.primary,
103
+ borderRadius: 12,
104
+ paddingHorizontal: 10,
105
+ paddingVertical: 4,
106
+ alignSelf: 'flex-start',
107
+ marginBottom: 8,
108
+ },
109
+ popularBadgeText: {
110
+ color: '#ffffff',
111
+ fontSize: 12,
112
+ fontWeight: '600',
113
+ },
114
+ packageTitle: {
115
+ fontSize: 20,
116
+ fontWeight: '600',
117
+ color: colors.text,
118
+ },
119
+ packageDescription: {
120
+ fontSize: 14,
121
+ color: colors.textSecondary,
122
+ marginTop: 4,
123
+ },
124
+ packagePrice: {
125
+ fontSize: 28,
126
+ fontWeight: '700',
127
+ color: colors.text,
128
+ marginTop: 8,
129
+ },
130
+ featureList: {
131
+ marginTop: 12,
132
+ gap: 6,
133
+ },
134
+ featureItem: {
135
+ fontSize: 14,
136
+ color: colors.textSecondary,
137
+ },
138
+ purchaseButton: {
139
+ backgroundColor: colors.primary,
140
+ borderRadius: 10,
141
+ paddingVertical: 14,
142
+ alignItems: 'center',
143
+ marginTop: 16,
144
+ minHeight: 48,
145
+ justifyContent: 'center',
146
+ },
147
+ purchaseButtonDisabled: {
148
+ opacity: 0.6,
149
+ },
150
+ purchaseButtonText: {
151
+ color: '#ffffff',
152
+ fontSize: 16,
153
+ fontWeight: '600',
154
+ },
155
+ currentBadge: {
156
+ borderRadius: 10,
157
+ paddingVertical: 14,
158
+ alignItems: 'center',
159
+ marginTop: 16,
160
+ backgroundColor: colors.surfaceSecondary,
161
+ },
162
+ currentBadgeText: {
163
+ color: colors.textSecondary,
164
+ fontSize: 16,
165
+ fontWeight: '500',
166
+ },
167
+ restoreButton: {
168
+ marginTop: 24,
169
+ paddingVertical: 12,
170
+ alignItems: 'center',
171
+ minHeight: 44,
172
+ justifyContent: 'center',
173
+ },
174
+ restoreButtonDisabled: {
175
+ opacity: 0.6,
176
+ },
177
+ restoreText: {
178
+ color: colors.primary,
179
+ fontSize: 15,
180
+ fontWeight: '500',
181
+ },
182
+ restoreDescription: {
183
+ fontSize: 13,
184
+ color: colors.textMuted,
185
+ textAlign: 'center',
186
+ marginTop: 4,
187
+ },
188
+ }));
@@ -0,0 +1,4 @@
1
+ export { SafeSubscriptionContext, STUB_SUBSCRIPTION_VALUE, useSafeSubscription, } from './SafeSubscriptionContext';
2
+ export type { SubscriptionContextValue } from './SafeSubscriptionContext';
3
+ export { SubscriptionScreen } from './SubscriptionScreen';
4
+ export type { SubscriptionScreenProps, SubscriptionPackage, } from './SubscriptionScreen';
@@ -0,0 +1,2 @@
1
+ export { SafeSubscriptionContext, STUB_SUBSCRIPTION_VALUE, useSafeSubscription, } from './SafeSubscriptionContext';
2
+ export { SubscriptionScreen } from './SubscriptionScreen';
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ export type ToastType = 'success' | 'error' | 'warning' | 'info';
3
+ export interface Toast {
4
+ id: string;
5
+ message: string;
6
+ type: ToastType;
7
+ duration?: number;
8
+ }
9
+ interface ToastContextValue {
10
+ addToast: (message: string, type?: ToastType, duration?: number) => void;
11
+ removeToast: (id: string) => void;
12
+ }
13
+ export declare function ToastProvider({ children }: {
14
+ children: React.ReactNode;
15
+ }): import("react/jsx-runtime").JSX.Element;
16
+ /**
17
+ * Hook to access toast functionality.
18
+ */
19
+ export declare function useToast(): ToastContextValue;
20
+ export {};
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { createContext, useCallback, useContext, useRef, useState, } from 'react';
3
+ import { View, Text, Animated, Pressable, StyleSheet } from 'react-native';
4
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
+ const ToastContext = createContext(null);
6
+ let toastCounter = 0;
7
+ export function ToastProvider({ children }) {
8
+ const [toasts, setToasts] = useState([]);
9
+ const insets = useSafeAreaInsets();
10
+ const removeToast = useCallback((id) => {
11
+ setToasts(prev => prev.filter(t => t.id !== id));
12
+ }, []);
13
+ const addToast = useCallback((message, type = 'info', duration = 3000) => {
14
+ const id = `toast-${++toastCounter}`;
15
+ setToasts(prev => [...prev, { id, message, type, duration }]);
16
+ if (duration > 0) {
17
+ setTimeout(() => removeToast(id), duration);
18
+ }
19
+ }, [removeToast]);
20
+ return (_jsxs(ToastContext.Provider, { value: { addToast, removeToast }, children: [children, _jsx(View, { style: [styles.toastContainer, { top: insets.top + 8 }], pointerEvents: 'box-none', children: toasts.map(toast => (_jsx(ToastItem, { toast: toast, onDismiss: () => removeToast(toast.id) }, toast.id))) })] }));
21
+ }
22
+ function ToastItem({ toast, onDismiss, }) {
23
+ const opacity = useRef(new Animated.Value(0)).current;
24
+ const translateY = useRef(new Animated.Value(-20)).current;
25
+ React.useEffect(() => {
26
+ Animated.parallel([
27
+ Animated.timing(opacity, {
28
+ toValue: 1,
29
+ duration: 200,
30
+ useNativeDriver: true,
31
+ }),
32
+ Animated.timing(translateY, {
33
+ toValue: 0,
34
+ duration: 200,
35
+ useNativeDriver: true,
36
+ }),
37
+ ]).start();
38
+ }, [opacity, translateY]);
39
+ const bgColor = {
40
+ success: '#22c55e',
41
+ error: '#ef4444',
42
+ warning: '#f59e0b',
43
+ info: '#3b82f6',
44
+ }[toast.type];
45
+ return (_jsx(Animated.View, { style: [
46
+ styles.toast,
47
+ { backgroundColor: bgColor, opacity, transform: [{ translateY }] },
48
+ ], children: _jsx(Pressable, { style: styles.toastContent, onPress: onDismiss, children: _jsx(Text, { style: styles.toastText, children: toast.message }) }) }));
49
+ }
50
+ /**
51
+ * Hook to access toast functionality.
52
+ */
53
+ export function useToast() {
54
+ const context = useContext(ToastContext);
55
+ if (!context) {
56
+ throw new Error('useToast must be used within a ToastProvider');
57
+ }
58
+ return context;
59
+ }
60
+ const styles = StyleSheet.create({
61
+ toastContainer: {
62
+ position: 'absolute',
63
+ left: 16,
64
+ right: 16,
65
+ zIndex: 9999,
66
+ gap: 8,
67
+ },
68
+ toast: {
69
+ borderRadius: 10,
70
+ shadowColor: '#000',
71
+ shadowOffset: { width: 0, height: 2 },
72
+ shadowOpacity: 0.15,
73
+ shadowRadius: 6,
74
+ elevation: 6,
75
+ },
76
+ toastContent: {
77
+ paddingHorizontal: 16,
78
+ paddingVertical: 12,
79
+ minHeight: 44,
80
+ justifyContent: 'center',
81
+ },
82
+ toastText: {
83
+ color: '#ffffff',
84
+ fontSize: 15,
85
+ fontWeight: '500',
86
+ },
87
+ });
@@ -0,0 +1,2 @@
1
+ export { ToastProvider, useToast } from './ToastProvider';
2
+ export type { Toast, ToastType } from './ToastProvider';
@@ -0,0 +1 @@
1
+ export { ToastProvider, useToast } from './ToastProvider';
@@ -0,0 +1 @@
1
+ export * from './languages';
@@ -0,0 +1 @@
1
+ export * from './languages';
@@ -0,0 +1,18 @@
1
+ export interface LanguageConfig {
2
+ code: string;
3
+ name: string;
4
+ flag: string;
5
+ }
6
+ /**
7
+ * Default set of 16 supported languages with their flags.
8
+ * Apps can override this list by passing their own languages prop.
9
+ */
10
+ export declare const DEFAULT_LANGUAGES: LanguageConfig[];
11
+ /**
12
+ * Languages that use right-to-left text direction.
13
+ */
14
+ export declare const RTL_LANGUAGES: string[];
15
+ /**
16
+ * Check if a language code is RTL.
17
+ */
18
+ export declare function isRTL(languageCode: string): boolean;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Default set of 16 supported languages with their flags.
3
+ * Apps can override this list by passing their own languages prop.
4
+ */
5
+ export const DEFAULT_LANGUAGES = [
6
+ { code: 'en', name: 'English', flag: '\u{1F1FA}\u{1F1F8}' },
7
+ {
8
+ code: 'ar',
9
+ name: '\u0627\u0644\u0639\u0631\u0628\u064A\u0629',
10
+ flag: '\u{1F1F8}\u{1F1E6}',
11
+ },
12
+ { code: 'de', name: 'Deutsch', flag: '\u{1F1E9}\u{1F1EA}' },
13
+ { code: 'es', name: 'Espa\u00F1ol', flag: '\u{1F1EA}\u{1F1F8}' },
14
+ { code: 'fr', name: 'Fran\u00E7ais', flag: '\u{1F1EB}\u{1F1F7}' },
15
+ { code: 'it', name: 'Italiano', flag: '\u{1F1EE}\u{1F1F9}' },
16
+ { code: 'ja', name: '\u65E5\u672C\u8A9E', flag: '\u{1F1EF}\u{1F1F5}' },
17
+ { code: 'ko', name: '\uD55C\uAD6D\uC5B4', flag: '\u{1F1F0}\u{1F1F7}' },
18
+ { code: 'pt', name: 'Portugu\u00EAs', flag: '\u{1F1F5}\u{1F1F9}' },
19
+ {
20
+ code: 'ru',
21
+ name: '\u0420\u0443\u0441\u0441\u043A\u0438\u0439',
22
+ flag: '\u{1F1F7}\u{1F1FA}',
23
+ },
24
+ { code: 'sv', name: 'Svenska', flag: '\u{1F1F8}\u{1F1EA}' },
25
+ { code: 'th', name: '\u0E44\u0E17\u0E22', flag: '\u{1F1F9}\u{1F1ED}' },
26
+ {
27
+ code: 'uk',
28
+ name: '\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430',
29
+ flag: '\u{1F1FA}\u{1F1E6}',
30
+ },
31
+ { code: 'vi', name: 'Ti\u1EBFng Vi\u1EC7t', flag: '\u{1F1FB}\u{1F1F3}' },
32
+ { code: 'zh', name: '\u7B80\u4F53\u4E2D\u6587', flag: '\u{1F1E8}\u{1F1F3}' },
33
+ {
34
+ code: 'zh-hant',
35
+ name: '\u7E41\u9AD4\u4E2D\u6587',
36
+ flag: '\u{1F1F9}\u{1F1FC}',
37
+ },
38
+ ];
39
+ /**
40
+ * Languages that use right-to-left text direction.
41
+ */
42
+ export const RTL_LANGUAGES = ['ar'];
43
+ /**
44
+ * Check if a language code is RTL.
45
+ */
46
+ export function isRTL(languageCode) {
47
+ return RTL_LANGUAGES.includes(languageCode);
48
+ }
@@ -0,0 +1,2 @@
1
+ export { useResponsive } from './useResponsive';
2
+ export type { ResponsiveInfo } from './useResponsive';
@@ -0,0 +1 @@
1
+ export { useResponsive } from './useResponsive';
@@ -0,0 +1,11 @@
1
+ export interface ResponsiveInfo {
2
+ width: number;
3
+ height: number;
4
+ isSmall: boolean;
5
+ isMedium: boolean;
6
+ isLarge: boolean;
7
+ }
8
+ /**
9
+ * Hook for responsive breakpoint detection.
10
+ */
11
+ export declare function useResponsive(): ResponsiveInfo;
@@ -0,0 +1,14 @@
1
+ import { useWindowDimensions } from 'react-native';
2
+ /**
3
+ * Hook for responsive breakpoint detection.
4
+ */
5
+ export function useResponsive() {
6
+ const { width, height } = useWindowDimensions();
7
+ return {
8
+ width,
9
+ height,
10
+ isSmall: width < 380,
11
+ isMedium: width >= 380 && width <= 768,
12
+ isLarge: width > 768,
13
+ };
14
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * React Native i18n initialization.
3
+ *
4
+ * Uses react-native-localize for device language detection
5
+ * and bundled translations (no HTTP backend).
6
+ */
7
+ import i18n from 'i18next';
8
+ export interface I18nConfig {
9
+ /** Supported language codes. Defaults to ["en"]. */
10
+ supportedLanguages?: string[];
11
+ /** Translation namespaces. */
12
+ namespaces?: string[];
13
+ /** Default namespace. Defaults to "common". */
14
+ defaultNamespace?: string;
15
+ /** Bundled translation resources. */
16
+ resources?: Record<string, Record<string, Record<string, string>>>;
17
+ /** Enable debug logging. Defaults to false. */
18
+ debug?: boolean;
19
+ }
20
+ /**
21
+ * Initialize the i18n instance for React Native.
22
+ * Safe to call multiple times - only initializes once.
23
+ */
24
+ export declare function initializeI18nRN(config?: I18nConfig): typeof i18n;
25
+ /**
26
+ * Get the i18n instance.
27
+ * Initializes with defaults if not already initialized.
28
+ */
29
+ export declare function getI18n(): typeof i18n;
30
+ export { i18n };
31
+ export default i18n;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * React Native i18n initialization.
3
+ *
4
+ * Uses react-native-localize for device language detection
5
+ * and bundled translations (no HTTP backend).
6
+ */
7
+ import i18n from 'i18next';
8
+ import { initReactI18next } from 'react-i18next';
9
+ const DEFAULT_SUPPORTED_LANGUAGES = ['en'];
10
+ const DEFAULT_NAMESPACES = [
11
+ 'common',
12
+ 'home',
13
+ 'settings',
14
+ 'auth',
15
+ 'privacy',
16
+ 'terms',
17
+ ];
18
+ /**
19
+ * Detect device language using react-native-localize (if available).
20
+ */
21
+ function detectDeviceLanguage(supportedLanguages) {
22
+ try {
23
+ // Try react-native-localize if available
24
+ const RNLocalize = require('react-native-localize');
25
+ const locales = RNLocalize.getLocales();
26
+ if (locales && locales.length > 0) {
27
+ const deviceLang = locales[0].languageCode;
28
+ if (supportedLanguages.includes(deviceLang)) {
29
+ return deviceLang;
30
+ }
31
+ }
32
+ }
33
+ catch {
34
+ // react-native-localize not installed, fall back to 'en'
35
+ }
36
+ return 'en';
37
+ }
38
+ let initialized = false;
39
+ /**
40
+ * Initialize the i18n instance for React Native.
41
+ * Safe to call multiple times - only initializes once.
42
+ */
43
+ export function initializeI18nRN(config = {}) {
44
+ if (initialized) {
45
+ return i18n;
46
+ }
47
+ initialized = true;
48
+ const { supportedLanguages = DEFAULT_SUPPORTED_LANGUAGES, namespaces = DEFAULT_NAMESPACES, defaultNamespace = 'common', resources, debug = false, } = config;
49
+ i18n.use(initReactI18next).init({
50
+ lng: detectDeviceLanguage(supportedLanguages),
51
+ fallbackLng: {
52
+ zh: ['zh', 'en'],
53
+ 'zh-hant': ['zh-hant', 'zh', 'en'],
54
+ default: ['en'],
55
+ },
56
+ supportedLngs: supportedLanguages,
57
+ debug,
58
+ interpolation: {
59
+ escapeValue: false,
60
+ },
61
+ resources,
62
+ defaultNS: defaultNamespace,
63
+ ns: namespaces,
64
+ });
65
+ return i18n;
66
+ }
67
+ /**
68
+ * Get the i18n instance.
69
+ * Initializes with defaults if not already initialized.
70
+ */
71
+ export function getI18n() {
72
+ if (!initialized) {
73
+ initializeI18nRN();
74
+ }
75
+ return i18n;
76
+ }
77
+ export { i18n };
78
+ export default i18n;
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { Theme } from '../types';
3
+ import { type ThemeColors } from './colors';
4
+ export interface ThemeContextValue {
5
+ /** User's theme preference */
6
+ theme: Theme;
7
+ /** Resolved theme (light or dark) based on preference + system */
8
+ resolvedTheme: 'light' | 'dark';
9
+ /** Resolved color palette */
10
+ colors: ThemeColors;
11
+ /** Whether the resolved theme is dark */
12
+ isDark: boolean;
13
+ /** Update the theme preference */
14
+ setTheme: (theme: Theme) => void;
15
+ }
16
+ interface ThemeProviderProps {
17
+ children: React.ReactNode;
18
+ /** Initial theme override (default: system) */
19
+ initialTheme?: Theme;
20
+ /** Custom storage key prefix */
21
+ storageKeyPrefix?: string;
22
+ }
23
+ export declare function ThemeProvider({ children, initialTheme, storageKeyPrefix, }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element | null;
24
+ /**
25
+ * Hook to access theme context.
26
+ * Must be used within a ThemeProvider.
27
+ */
28
+ export declare function useTheme(): ThemeContextValue;
29
+ /**
30
+ * Safely access theme context.
31
+ * Returns null if not within a ThemeProvider.
32
+ */
33
+ export declare function useThemeSafe(): ThemeContextValue | null;
34
+ export {};