@tcbs/react-native-mazic-ui 0.1.1 → 0.1.3

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.
@@ -0,0 +1,258 @@
1
+ import { create } from 'zustand';
2
+ import { Appearance } from 'react-native';
3
+ import { MMKV } from 'react-native-mmkv';
4
+ // MMKV instance for theme persistence
5
+ const storage = new MMKV();
6
+ const THEME_KEY = 'tcbsTheme';
7
+ // Store the listener subscription so we can remove it
8
+ let appearanceListener = null;
9
+ const defaultColors = {
10
+ light: {
11
+ // You can set initial defaults here if needed, or leave them empty
12
+ btnColor: '#007AFF',
13
+ btnBorderColor: '#007AFF',
14
+ btnIconColor: '#FFFFFF',
15
+ themeColor: '#007AFF',
16
+ btnTextColor: '#FFFFFF',
17
+ },
18
+ dark: {
19
+ btnColor: '#222222',
20
+ btnBorderColor: '#222222',
21
+ btnIconColor: '#FFFFFF',
22
+ themeColor: '#222222',
23
+ btnTextColor: '#FFFFFF',
24
+ },
25
+ };
26
+ // Try to load persisted theme, fallback to 'light'. If 'system', use current system color scheme.
27
+ const defaultTheme = storage.getString(THEME_KEY) || 'light';
28
+ // Helper functions for color manipulation
29
+ const hexToRgb = (hex) => {
30
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
31
+ return result ? {
32
+ r: parseInt(result[1], 16),
33
+ g: parseInt(result[2], 16),
34
+ b: parseInt(result[3], 16)
35
+ } : null;
36
+ };
37
+ const rgbToHex = (r, g, b) => {
38
+ return '#' + [r, g, b].map(x => {
39
+ const hex = Math.round(x).toString(16);
40
+ return hex.length === 1 ? '0' + hex : hex;
41
+ }).join('');
42
+ };
43
+ const adjustBrightness = (hex, percent) => {
44
+ const rgb = hexToRgb(hex);
45
+ if (!rgb)
46
+ return hex;
47
+ // percent > 0: lighten, percent < 0: darken
48
+ const adjust = (value) => {
49
+ if (percent > 0) {
50
+ return Math.round(value + (255 - value) * (percent / 100));
51
+ }
52
+ else {
53
+ return Math.round(value * (1 + percent / 100));
54
+ }
55
+ };
56
+ return rgbToHex(adjust(rgb.r), adjust(rgb.g), adjust(rgb.b));
57
+ };
58
+ // NEW HELPER: Determines if a color is dark enough to require white text
59
+ const isColorDark = (hex) => {
60
+ const rgb = hexToRgb(hex);
61
+ if (!rgb)
62
+ return true; // Default to white text if color is invalid
63
+ // Calculate Luma (YIQ method is good for perceived brightness)
64
+ // Formula: (R * 299 + G * 587 + B * 114) / 1000
65
+ const luma = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
66
+ // Threshold 0.45 is slightly lower than standard 0.5, favoring white text
67
+ return luma < 0.45;
68
+ };
69
+ const addAlpha = (hex, alpha) => {
70
+ const alphaHex = Math.round(alpha * 255).toString(16).padStart(2, '0');
71
+ return hex + alphaHex;
72
+ };
73
+ export const useTcbsColorStore = create((set, get) => {
74
+ var _a;
75
+ // Helper to get the current theme color
76
+ const getThemeColors = (theme, colors) => {
77
+ var _a;
78
+ if (theme === 'light' || theme === 'dark') {
79
+ return colors[theme];
80
+ }
81
+ else {
82
+ // system: use Appearance API
83
+ const colorScheme = ((_a = Appearance.getColorScheme) === null || _a === void 0 ? void 0 : _a.call(Appearance)) || 'light';
84
+ return colors[colorScheme] || colors.light;
85
+ }
86
+ };
87
+ // Initial state
88
+ const initialColors = defaultColors;
89
+ const initialTheme = defaultTheme;
90
+ const initialThemeColors = getThemeColors(initialTheme, initialColors);
91
+ // Listen to system theme changes if needed
92
+ if (initialTheme === 'system' && !appearanceListener) {
93
+ appearanceListener = (_a = Appearance.addChangeListener) === null || _a === void 0 ? void 0 : _a.call(Appearance, ({ colorScheme }) => {
94
+ set((state) => ({
95
+ themeColors: state.colors[colorScheme || 'light']
96
+ }));
97
+ });
98
+ }
99
+ return {
100
+ colors: initialColors,
101
+ tcbsTheme: initialTheme,
102
+ themeColors: initialThemeColors,
103
+ get currentThemeMode() {
104
+ var _a;
105
+ const state = get();
106
+ if (state.tcbsTheme === 'light' || state.tcbsTheme === 'dark') {
107
+ return state.tcbsTheme;
108
+ }
109
+ // system: use Appearance API
110
+ const colorScheme = ((_a = Appearance.getColorScheme) === null || _a === void 0 ? void 0 : _a.call(Appearance)) || 'light';
111
+ return (colorScheme === 'dark' ? 'dark' : 'light');
112
+ },
113
+ setTcbsColor: (colors) => {
114
+ set((state) => {
115
+ let newColors = Object.assign({}, state.colors);
116
+ // If colors for both themes are provided
117
+ if (colors.light || colors.dark) {
118
+ if (colors.light) {
119
+ newColors.light = Object.assign(Object.assign({}, newColors.light), colors.light);
120
+ }
121
+ if (colors.dark) {
122
+ newColors.dark = Object.assign(Object.assign({}, newColors.dark), colors.dark);
123
+ }
124
+ }
125
+ else {
126
+ // If only one set, update both themes
127
+ newColors.light = Object.assign(Object.assign({}, newColors.light), colors);
128
+ newColors.dark = Object.assign(Object.assign({}, newColors.dark), colors);
129
+ }
130
+ // Update themeColors as well
131
+ const themeColors = getThemeColors(state.tcbsTheme, newColors);
132
+ return { colors: newColors, themeColors };
133
+ });
134
+ },
135
+ setTcbsTheme: (newTheme) => {
136
+ var _a;
137
+ // Persist user selection
138
+ storage.set(THEME_KEY, newTheme);
139
+ // Remove previous listener if exists
140
+ if (appearanceListener) {
141
+ appearanceListener.remove();
142
+ appearanceListener = null;
143
+ }
144
+ // If new theme is system, add listener
145
+ if (newTheme === 'system') {
146
+ appearanceListener = (_a = Appearance.addChangeListener) === null || _a === void 0 ? void 0 : _a.call(Appearance, ({ colorScheme }) => {
147
+ set((state) => ({
148
+ themeColors: state.colors[colorScheme || 'light']
149
+ }));
150
+ });
151
+ }
152
+ // Update themeColors as well
153
+ set((state) => ({
154
+ tcbsTheme: newTheme,
155
+ themeColors: getThemeColors(newTheme, state.colors)
156
+ }));
157
+ },
158
+ toggleTcbsTheme: () => {
159
+ set((state) => {
160
+ const themes = ['light', 'dark', 'system'];
161
+ const currentIdx = themes.indexOf(state.tcbsTheme);
162
+ const nextTheme = themes[(currentIdx + 1) % themes.length];
163
+ // Note: setTcbsTheme is called outside the state update, but here it's called internally
164
+ // which is a common pattern in zustand actions.
165
+ // @ts-ignore
166
+ state.setTcbsTheme(nextTheme);
167
+ return {};
168
+ });
169
+ },
170
+ // REWRITTEN FUNCTION using hardcoded neutrals for better UI contrast
171
+ setMazicColor: (baseColor) => {
172
+ // Determine the best text color for the button based on the base color's brightness
173
+ // const buttonTextColor = isColorDark(baseColor) ? '#FFFFFF' : '#000000';
174
+ const buttonTextColor = '#FFFFFF';
175
+ const secondaryBaseColor = adjustBrightness(baseColor, -10); // A slightly darker shade for accents
176
+ // --- Light Theme Palette ---
177
+ const lightColors = {
178
+ // Primary & Button Colors (baseColor is the accent)
179
+ btnColor: addAlpha(baseColor, 1),
180
+ btnBorderColor: baseColor,
181
+ btnIconColor: buttonTextColor,
182
+ themeColor: baseColor,
183
+ btnTextColor: buttonTextColor,
184
+ tabBarIconActiveColor: buttonTextColor,
185
+ tabBarIconInactiveColor: addAlpha("#000000", 0.4),
186
+ // modalBgColor: 80% lighter than baseColor
187
+ primaryColor: addAlpha(baseColor, 1),
188
+ secondaryColor: addAlpha(baseColor, 0.7),
189
+ tertiaryColor: addAlpha(baseColor, 0.1),
190
+ // Backgrounds (Clean white/near-white neutrals)
191
+ screenBgColor: addAlpha(baseColor, 0.1),
192
+ modalBgColor: adjustBrightness(baseColor, 10),
193
+ modalTitleColor: adjustBrightness(baseColor, 90),
194
+ modalHeaderBgColor: '#F0F0F0',
195
+ modalCardBgColor: '#FAFAFA',
196
+ // Text Colors (High contrast black/dark gray)
197
+ textPrimary: '#1F1F1F',
198
+ textSecondary: '#6B7280',
199
+ // Borders & Dividers (Very subtle grays)
200
+ borderColor: '#E5E7EB',
201
+ dividerColor: '#F3F4F6',
202
+ // Inputs & Cards
203
+ inputBgColor: '#FFFFFF',
204
+ inputBorderColor: '#D1D5DB',
205
+ cardBgColor: '#FFFFFF',
206
+ cardBorderColor: '#E5E7EB',
207
+ // Status Colors (Standard, high-contrast semantic colors)
208
+ accentColor: secondaryBaseColor,
209
+ errorColor: '#DC2626',
210
+ successColor: '#16A34A',
211
+ warningColor: '#F59E0B',
212
+ };
213
+ // --- Dark Theme Palette ---
214
+ const darkColors = {
215
+ // Primary & Button Colors
216
+ btnColor: addAlpha(baseColor, 1),
217
+ btnBorderColor: baseColor,
218
+ btnIconColor: buttonTextColor,
219
+ themeColor: baseColor,
220
+ btnTextColor: buttonTextColor,
221
+ tabBarIconActiveColor: buttonTextColor,
222
+ tabBarIconInactiveColor: addAlpha("#000000", 0.4),
223
+ primaryColor: addAlpha(baseColor, 1),
224
+ secondaryColor: addAlpha(baseColor, 0.7),
225
+ tertiaryColor: addAlpha(baseColor, 0.2),
226
+ // Backgrounds (Clean dark/near-black neutrals)
227
+ screenBgColor: addAlpha(baseColor, 0.8),
228
+ modalBgColor: adjustBrightness(baseColor, 80),
229
+ modalHeaderBgColor: '#1F1F1F',
230
+ modalCardBgColor: '#2C2C2C',
231
+ // Text Colors (High contrast white/light gray)
232
+ textPrimary: '#FFFFFF',
233
+ textSecondary: '#A0A0A0',
234
+ // Borders & Dividers (Subtle dark grays)
235
+ borderColor: '#374151',
236
+ dividerColor: '#2C2C2C',
237
+ // Inputs & Cards
238
+ inputBgColor: '#1F1F1F',
239
+ inputBorderColor: '#374151',
240
+ cardBgColor: '#1F1F1F',
241
+ cardBorderColor: '#374151',
242
+ // Status Colors (Brighter semantic colors for dark background)
243
+ accentColor: secondaryBaseColor,
244
+ errorColor: '#EF4444',
245
+ successColor: '#22C55E',
246
+ warningColor: '#FBBF24',
247
+ };
248
+ set((state) => {
249
+ const newColors = {
250
+ light: lightColors,
251
+ dark: darkColors,
252
+ };
253
+ const themeColors = getThemeColors(state.tcbsTheme, newColors);
254
+ return { colors: newColors, themeColors };
255
+ });
256
+ }
257
+ };
258
+ });
package/package.json CHANGED
@@ -13,13 +13,13 @@
13
13
  "react-native-vector-icons"
14
14
  ],
15
15
  "author": "TechCraft By Subrata <subraatakumar@gmail.com>",
16
- "version": "0.1.1",
17
- "publishConfig": {
16
+ "version": "0.1.3",
17
+ "publishConfig": {
18
18
  "access": "public"
19
19
  },
20
20
  "main": "lib/commonjs/index.js",
21
21
  "module": "lib/module/index.js",
22
- "types": "lib/types/index.d.ts",
22
+ "types": "lib/index.d.ts",
23
23
  "react-native": "src/index.ts",
24
24
  "files": [
25
25
  "lib/",
@@ -33,7 +33,9 @@
33
33
  "peerDependencies": {
34
34
  "react": ">=17.0.0",
35
35
  "react-native": ">=0.68.0",
36
- "react-native-vector-icons": ">=10.2.0"
36
+ "react-native-vector-icons": ">=10.2.0",
37
+ "react-error-boundary": ">=5.0.0",
38
+ "@tcbs/react-native-exception-handler": "1.0.0"
37
39
  },
38
40
  "devDependencies": {
39
41
  "@types/react": "^17.0.0",
@@ -1,4 +1,5 @@
1
1
  import React, { useMemo } from 'react';
2
+ import { Appearance } from 'react-native';
2
3
  import { TouchableOpacity, Text, View , StyleProp, ViewStyle, TextStyle } from 'react-native';
3
4
  import AntDesign from 'react-native-vector-icons/AntDesign';
4
5
  import Feather from 'react-native-vector-icons/Feather';
@@ -33,7 +34,8 @@ const FONT_SIZES: Record<ButtonSize, number> = {
33
34
  [BUTTON_SIZE.SMALL]: 14,
34
35
  };
35
36
 
36
- const BORDER_RADIUSES: Record<ButtonSize, number> = {
37
+ // Support for BORDER_RADIUS.NONE and BORDER_RADIUS.FULL (50%)
38
+ const BORDER_RADIUSES: Record<ButtonSize, number | string> = {
37
39
  [BUTTON_SIZE.LARGE]: BORDER_RADIUS.MEDIUM,
38
40
  [BUTTON_SIZE.MEDIUM]: BORDER_RADIUS.SMALL,
39
41
  [BUTTON_SIZE.SMALL]: BORDER_RADIUS.SMALL,
@@ -72,25 +74,35 @@ export const TcbsButton: React.FC<TcbsButtonProps> = ({
72
74
  accessibilityHint,
73
75
  accessibilityRole = 'button',
74
76
  accessibilityState,
75
- themeColor,
76
- screenBgColor,
77
77
  }) => {
78
- // Use theme from store if not provided as prop
79
- const { colors, mode } = useTcbsColorStore();
80
- const effectiveThemeColor = themeColor ?? colors[mode];
78
+ // Use themeColors from store if not provided as prop
79
+ const { themeColors, tcbsTheme } = useTcbsColorStore();
80
+ const effectiveThemeColor = themeColors;
81
81
  // Normalize colors: if only one color is set, use it for all
82
82
  const normalizedColors = {
83
83
  btnColor: effectiveThemeColor?.btnColor ?? effectiveThemeColor?.themeColor ?? '#007AFF',
84
84
  btnBorderColor: effectiveThemeColor?.btnBorderColor ?? effectiveThemeColor?.btnColor ?? '#007AFF',
85
85
  btnIconColor: effectiveThemeColor?.btnIconColor,
86
- btnTextColor: effectiveThemeColor?.btnTextColor ?? effectiveThemeColor?.btnTxtColor ,
86
+ btnTextColor: effectiveThemeColor?.btnTextColor ?? effectiveThemeColor?.btnTxtColor,
87
87
  themeColor: effectiveThemeColor?.themeColor ?? effectiveThemeColor?.btnColor ?? '#007AFF',
88
88
  };
89
89
 
90
90
  const buttonStyle = useMemo<StyleProp<ViewStyle>>(() => {
91
+ const height = HEIGHTS[size];
92
+ let computedBorderRadius: number | string;
93
+ if (borderRadius === BORDER_RADIUS.NONE) {
94
+ computedBorderRadius = 0;
95
+ } else if (borderRadius === BORDER_RADIUS.FULL) {
96
+ computedBorderRadius = height / 2;
97
+ } else if (borderRadius !== undefined) {
98
+ computedBorderRadius = borderRadius;
99
+ } else {
100
+ computedBorderRadius = BORDER_RADIUSES[size];
101
+ }
102
+
91
103
  const baseStyle: ViewStyle = {
92
- height: HEIGHTS[size],
93
- borderRadius: borderRadius ?? BORDER_RADIUSES[size],
104
+ height,
105
+ borderRadius: computedBorderRadius,
94
106
  alignItems: 'center',
95
107
  justifyContent: 'center',
96
108
  opacity: disabled ? 0.6 : 1,
@@ -129,10 +141,20 @@ export const TcbsButton: React.FC<TcbsButtonProps> = ({
129
141
  }, [size, variant, normalizedColors, style, disabled, borderRadius]);
130
142
 
131
143
  const themedTextStyle = useMemo<TextStyle>(() => {
132
- const baseTextColor =
133
- variant === BUTTON_VARIANT.PRIMARY
144
+ let baseTextColor;
145
+ if (variant === BUTTON_VARIANT.PRIMARY) {
146
+ baseTextColor = normalizedColors.btnTextColor || '#FFFFFF';
147
+ } else if (variant === BUTTON_VARIANT.NO_BORDER) {
148
+ let colorScheme = tcbsTheme;
149
+ if (tcbsTheme === 'system') {
150
+ colorScheme = Appearance.getColorScheme() || 'light';
151
+ }
152
+ baseTextColor = colorScheme === 'dark'
134
153
  ? normalizedColors.btnTextColor || '#FFFFFF'
135
- : variant === BUTTON_VARIANT.NO_BORDER && mode === 'dark' ? normalizedColors.btnTextColor || "#FFFFFF" : normalizedColors?.btnColor || normalizedColors?.themeColor || "#FFFFFF";
154
+ : normalizedColors?.btnColor || '#007AFF';
155
+ } else {
156
+ baseTextColor = normalizedColors?.btnColor || '#FFFFFF';
157
+ }
136
158
 
137
159
  return {
138
160
  color: baseTextColor,
@@ -140,13 +162,13 @@ export const TcbsButton: React.FC<TcbsButtonProps> = ({
140
162
  fontWeight: '700',
141
163
  ...(textStyle as TextStyle),
142
164
  };
143
- }, [size, variant, normalizedColors, textStyle]);
165
+ }, [size, variant, normalizedColors, textStyle, tcbsTheme]);
144
166
 
145
167
  const renderIcon = (IconComponent: IconComponentType) => (
146
168
  <IconComponent
147
169
  name={iconName!}
148
170
  size={iconSize || FONT_SIZES[size] * 2}
149
- color={iconColor || normalizedColors.btnIconColor || themedTextStyle.color}
171
+ color={iconColor || themedTextStyle.color}
150
172
  style={
151
173
  iconPosition === 'top'
152
174
  ? { marginBottom: 2 }
@@ -230,6 +252,5 @@ export const TcbsButton: React.FC<TcbsButtonProps> = ({
230
252
  );
231
253
  };
232
254
 
233
- // Export constants for use in consuming applications
234
255
  export { BUTTON_SIZE, BUTTON_VARIANT, BORDER_RADIUS };
235
256
  export type { ButtonSize, ButtonVariant, IconGroupType, IconPosition };
@@ -17,12 +17,15 @@ export const BUTTON_VARIANT = {
17
17
  PRIMARY: 'primary',
18
18
  SECONDARY: 'secondary',
19
19
  NO_BORDER: 'no_border',
20
+
20
21
  } as const;
21
22
 
22
23
  export const BORDER_RADIUS = {
23
24
  SMALL: 8,
24
25
  MEDIUM: 12,
25
26
  LARGE: 16,
27
+ NONE: 0,
28
+ FULL: '50%',
26
29
  } as const;
27
30
 
28
31
  export type ButtonSize = (typeof BUTTON_SIZE)[keyof typeof BUTTON_SIZE];
@@ -0,0 +1,124 @@
1
+ import React from 'react';
2
+ import { Modal, Pressable, TouchableOpacity, View, Text, StyleSheet } from 'react-native';
3
+ import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
4
+ import Ionicons from 'react-native-vector-icons/Ionicons';
5
+ import Entypo from 'react-native-vector-icons/Entypo';
6
+ import { useTcbsColorStore } from '../store/themeStore';
7
+ import { BUTTON_VARIANT, TcbsButton } from './TcbsButton';
8
+
9
+ export interface ThemeModalProps {
10
+ visible: boolean;
11
+ onClose: () => void;
12
+ }
13
+
14
+ export const ThemeModal: React.FC<ThemeModalProps> = ({ visible, onClose }) => {
15
+ const { tcbsTheme, setTcbsTheme, themeColors } = useTcbsColorStore();
16
+ // You can customize these colors or get them from your theme
17
+ const colors = {
18
+ menuCardBkgColor: themeColors.screenBgColor || '#fff',
19
+ textDark: themeColors.modalTitleColor || '#222',
20
+ textGray: '#888',
21
+ };
22
+
23
+ return (
24
+ <Modal
25
+ transparent={true}
26
+ animationType="fade"
27
+ visible={visible}
28
+ onRequestClose={onClose}
29
+ >
30
+ <Pressable style={styles.modalOverlay} onPress={onClose}>
31
+ <Pressable style={[styles.modalCard, { backgroundColor: themeColors.modalBgColor || "#00000080" }]}
32
+ onPress={() => {}} // Prevent closing when pressing inside card
33
+ >
34
+ <View style={styles.modalClose}>
35
+ <TcbsButton
36
+ onPress={onClose}
37
+ iconName="close"
38
+ iconColor={colors.textDark}
39
+ iconPosition="left"
40
+ variant={BUTTON_VARIANT.NO_BORDER}
41
+ iconSize={22}
42
+ accessibilityLabel="Close"
43
+ style={{ padding: 8, marginRight: 0, minWidth: 0, minHeight: 0, alignSelf: 'flex-end' }}
44
+ />
45
+ </View>
46
+ <Text style={[styles.modalTitle, { color: colors.textDark }]}>Theme</Text>
47
+ <Text style={[styles.modalSubtitle, { color: colors.textDark }]}>Choose how the app looks on this device.</Text>
48
+ <View style={{ marginTop: 18 }}>
49
+ <TcbsButton
50
+ title="Light"
51
+ onPress={() => setTcbsTheme('light')}
52
+ style={{ marginBottom: 8 }}
53
+ variant={tcbsTheme === 'light' ? 'primary' : 'secondary'}
54
+ iconGroup="Ionicons"
55
+ iconName="sunny"
56
+ iconPosition="left"
57
+ textStyle={{ flex: 1, textAlign: 'center' }}
58
+ />
59
+ <TcbsButton
60
+ title="Dark"
61
+ onPress={() => setTcbsTheme('dark')}
62
+ style={{ marginBottom: 8 }}
63
+ variant={tcbsTheme === 'dark' ? 'primary' : 'secondary'}
64
+ iconGroup="Ionicons"
65
+ iconName="moon"
66
+ iconPosition="left"
67
+ textStyle={{ flex: 1, textAlign: 'center' }}
68
+ />
69
+ <TcbsButton
70
+ title="System (default)"
71
+ onPress={() => setTcbsTheme('system')}
72
+ variant={tcbsTheme === 'system' ? 'primary' : 'secondary'}
73
+ iconGroup="Ionicons"
74
+ iconName="settings"
75
+ iconPosition="left"
76
+ textStyle={{ flex: 1, textAlign: 'center' }}
77
+ />
78
+ </View>
79
+ </Pressable>
80
+ </Pressable>
81
+ </Modal>
82
+ );
83
+ };
84
+
85
+ const styles = StyleSheet.create({
86
+ modalOverlay: {
87
+ flex: 1,
88
+ backgroundColor: 'rgba(0,0,0,0.3)',
89
+ justifyContent: 'center',
90
+ alignItems: 'center',
91
+ },
92
+ modalCard: {
93
+ minWidth: 300,
94
+ borderRadius: 16,
95
+ padding: 24,
96
+ alignItems: 'stretch',
97
+ // shadowColor: '#000',
98
+ // shadowOpacity: 0.15,
99
+ shadowRadius: 12,
100
+ // shadowOffset: { width: 0, height: 4 },
101
+ // elevation: 4,
102
+ },
103
+ modalClose: {
104
+ position: 'absolute',
105
+ top: 8,
106
+ right: 8,
107
+ zIndex: 2,
108
+ },
109
+ modalTitle: {
110
+ fontSize: 20,
111
+ fontWeight: '700',
112
+ marginTop: 8,
113
+ marginBottom: 2,
114
+ textAlign: 'center',
115
+ },
116
+ modalSubtitle: {
117
+ fontSize: 14,
118
+ fontWeight: '400',
119
+ marginBottom: 8,
120
+ textAlign: 'center',
121
+ },
122
+ });
123
+
124
+ export default ThemeModal;
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+
3
+ export interface AppErrorBoundaryProps {
4
+ children?: React.ReactNode;
5
+ }
6
+
7
+ export interface AppErrorBoundaryState {
8
+ hasError: boolean;
9
+ error: any;
10
+ reactError: {
11
+ name?: string;
12
+ message?: string;
13
+ stack?: string;
14
+ } | null;
15
+ errorInfo: {
16
+ componentStack?: string;
17
+ } | null;
18
+ }
19
+
20
+ export class AppErrorBoundary extends React.Component<AppErrorBoundaryProps, AppErrorBoundaryState> {}