@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,179 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ ScrollView,
7
+ } from 'react-native';
8
+ import { SafeAreaView } from 'react-native-safe-area-context';
9
+ import { setupGlobalExceptionHandlers } from './setupGlobalExceptionHandlers';
10
+
11
+ setupGlobalExceptionHandlers();
12
+
13
+ export interface AppErrorBoundaryProps {
14
+ children?: React.ReactNode;
15
+ }
16
+
17
+ export interface AppErrorBoundaryState {
18
+ hasError: boolean;
19
+ error: any;
20
+ reactError: {
21
+ name?: string;
22
+ message?: string;
23
+ stack?: string;
24
+ } | null;
25
+ errorInfo: {
26
+ componentStack?: string;
27
+ } | null;
28
+ }
29
+
30
+ export class AppErrorBoundary extends React.Component<AppErrorBoundaryProps, AppErrorBoundaryState> {
31
+ state: AppErrorBoundaryState = {
32
+ hasError: false,
33
+ error: null,
34
+ reactError: null,
35
+ errorInfo: null,
36
+ };
37
+
38
+ static getDerivedStateFromError(error: any) {
39
+ return { hasError: true, error };
40
+ }
41
+
42
+ componentDidCatch(error: any, errorInfo: any) {
43
+ // Log exactly what React gives
44
+ console.log('React Render Error:', error);
45
+
46
+ // Store everything needed for UI
47
+ this.setState({
48
+ error,
49
+ reactError: {
50
+ name: error?.name,
51
+ message: error?.message,
52
+ stack: error?.stack,
53
+ },
54
+ errorInfo,
55
+ });
56
+ }
57
+
58
+ renderDevError() {
59
+ const { error, reactError, errorInfo } = this.state;
60
+
61
+ if (!error) return null;
62
+
63
+ return (
64
+ <SafeAreaView style={{ flex: 1 }}>
65
+ <ScrollView style={styles.container}>
66
+ <Text style={styles.title}>🚨 Application Error (DEV)</Text>
67
+
68
+ {/* 1️⃣ What happened */}
69
+ <Section title="❌ What happened">
70
+ <Text style={styles.errorText}>
71
+ {error.name}: {error.message}
72
+ </Text>
73
+ </Section>
74
+
75
+ {/* 2️⃣ React render error (same as console.log(error)) */}
76
+ <Section title="⚛️ React render error">
77
+ <Text style={styles.mono}>
78
+ {reactError?.stack}
79
+ </Text>
80
+ </Section>
81
+
82
+ {/* 3️⃣ Component stack (advanced) */}
83
+ <Section title="📜 Component stack (Advanced)">
84
+ <Text style={styles.stack}>
85
+ {errorInfo?.componentStack}
86
+ </Text>
87
+ </Section>
88
+ </ScrollView>
89
+ </SafeAreaView>
90
+ );
91
+ }
92
+
93
+ renderProdError() {
94
+ return (
95
+ <View style={styles.prodContainer}>
96
+ <Text style={styles.prodTitle}>Something went wrong</Text>
97
+ <Text style={styles.prodSubtitle}>
98
+ Please restart the application.
99
+ </Text>
100
+ </View>
101
+ );
102
+ }
103
+
104
+ render() {
105
+ if (this.state.hasError) {
106
+ return __DEV__ ? this.renderDevError() : this.renderProdError();
107
+ }
108
+ return this.props.children;
109
+ }
110
+ }
111
+
112
+ /* ---------- Helper Components ---------- */
113
+
114
+ interface SectionProps {
115
+ title: string;
116
+ children: React.ReactNode;
117
+ }
118
+
119
+ const Section = ({ title, children }: SectionProps) => (
120
+ <View style={styles.section}>
121
+ <Text style={styles.sectionTitle}>{title}</Text>
122
+ {children}
123
+ </View>
124
+ );
125
+
126
+ /* ---------- Styles ---------- */
127
+
128
+ const styles = StyleSheet.create({
129
+ container: {
130
+ flex: 1,
131
+ backgroundColor: '#0f172a',
132
+ padding: 16,
133
+ },
134
+ title: {
135
+ fontSize: 18,
136
+ fontWeight: '700',
137
+ color: '#f87171',
138
+ marginBottom: 12,
139
+ },
140
+ section: {
141
+ marginBottom: 16,
142
+ },
143
+ sectionTitle: {
144
+ fontSize: 14,
145
+ fontWeight: '600',
146
+ color: '#38bdf8',
147
+ marginBottom: 6,
148
+ },
149
+ errorText: {
150
+ color: '#fecaca',
151
+ fontSize: 14,
152
+ },
153
+ mono: {
154
+ fontSize: 12,
155
+ color: '#e5e7eb',
156
+ lineHeight: 16,
157
+ },
158
+ stack: {
159
+ fontSize: 11,
160
+ color: '#94a3b8',
161
+ lineHeight: 16,
162
+ },
163
+ prodContainer: {
164
+ flex: 1,
165
+ alignItems: 'center',
166
+ justifyContent: 'center',
167
+ padding: 24,
168
+ },
169
+ prodTitle: {
170
+ fontSize: 18,
171
+ fontWeight: '600',
172
+ marginBottom: 8,
173
+ },
174
+ prodSubtitle: {
175
+ fontSize: 14,
176
+ color: '#666',
177
+ textAlign: 'center',
178
+ },
179
+ });
@@ -0,0 +1,41 @@
1
+ import { Alert, Platform } from 'react-native';
2
+ import {
3
+ setJSExceptionHandler,
4
+ setNativeExceptionHandler,
5
+ } from '@tcbs/react-native-exception-handler';
6
+
7
+ /**
8
+ * JS Exception Handler
9
+ * This is a LAST-RESORT handler
10
+ */
11
+ const jsExceptionHandler = (error, isFatal) => {
12
+ console.log('Global JS Exception:', error);
13
+
14
+ if (isFatal) {
15
+ Alert.alert(
16
+ 'Unexpected error occurred',
17
+ `${error?.name ? error.name + ': ' : ''}${error?.message || error?.toString?.() || 'Unknown error'}\n\n${error?.stack || ''}`,
18
+ [{ text: 'OK' }],
19
+ );
20
+ }
21
+ };
22
+
23
+ /**
24
+ * Native Exception Handler
25
+ * UI via JS WILL NOT WORK here
26
+ */
27
+ const nativeExceptionHandler = (exceptionString) => {
28
+ console.log('Global Native Exception:', exceptionString);
29
+
30
+ // Send logs to server / analytics if needed
31
+ };
32
+
33
+ export const setupGlobalExceptionHandlers = () => {
34
+ setJSExceptionHandler(jsExceptionHandler, true);
35
+
36
+ setNativeExceptionHandler(
37
+ nativeExceptionHandler,
38
+ true, // forceAppQuit (Android)
39
+ true // executeDefaultHandler
40
+ );
41
+ };
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { Button } from './components/Button';
1
+
2
2
  export { TcbsButton } from './components/TcbsButton';
3
3
  export {
4
4
  BUTTON_SIZE,
@@ -12,3 +12,5 @@ export {
12
12
  IconComponentType,
13
13
  } from './components/TcbsButton.types';
14
14
  export { useTcbsColorStore } from './store/themeStore';
15
+ export {ThemeModal} from './components/ThemeModal';
16
+ export {AppErrorBoundary} from './components/error/AppErrorBoundary';
@@ -1,4 +1,13 @@
1
1
  import { create } from 'zustand';
2
+ import { Appearance } from 'react-native';
3
+ import { MMKV } from 'react-native-mmkv';
4
+
5
+ // MMKV instance for theme persistence
6
+ const storage = new MMKV();
7
+ const THEME_KEY = 'tcbsTheme';
8
+
9
+ // Store the listener subscription so we can remove it
10
+ let appearanceListener: { remove: () => void } | null = null;
2
11
 
3
12
  export type ThemeColor = {
4
13
  btnColor: string;
@@ -6,10 +15,31 @@ export type ThemeColor = {
6
15
  btnIconColor?: string;
7
16
  themeColor: string;
8
17
  btnTextColor: string;
18
+ tabBarIconActiveColor?: string;
19
+ tabBarIconInactiveColor?: string;
20
+ modalBgColor?: string;
21
+ primaryColor?: string;
22
+ secondaryColor?: string;
23
+ tertiaryColor?: string;
9
24
  screenBgColor?: string;
25
+ modalHeaderBgColor?: string;
26
+ modalCardBgColor?: string;
27
+ modalTitleColor?: string;
28
+ textPrimary?: string;
29
+ textSecondary?: string;
30
+ borderColor?: string;
31
+ dividerColor?: string;
32
+ inputBgColor?: string;
33
+ inputBorderColor?: string;
34
+ cardBgColor?: string;
35
+ cardBorderColor?: string;
36
+ accentColor?: string;
37
+ errorColor?: string;
38
+ successColor?: string;
39
+ warningColor?: string;
10
40
  };
11
41
 
12
- export type ThemeMode = 'light' | 'dark';
42
+ export type ThemeMode = 'light' | 'dark' | 'system';
13
43
 
14
44
  export type ThemeColors = {
15
45
  light: ThemeColor;
@@ -18,49 +48,286 @@ export type ThemeColors = {
18
48
 
19
49
  export interface ThemeStore {
20
50
  colors: ThemeColors;
21
- mode: ThemeMode;
51
+ tcbsTheme: ThemeMode;
52
+ themeColors: ThemeColor;
53
+ /**
54
+ * Returns the current theme as 'light' or 'dark' (never 'system').
55
+ * If tcbsTheme is 'system', resolves to the current system color scheme.
56
+ */
57
+ currentThemeMode: 'light' | 'dark';
22
58
  setTcbsColor: (colors: Partial<ThemeColor> & { light?: Partial<ThemeColor>; dark?: Partial<ThemeColor> }) => void;
23
59
  setTcbsTheme: (mode: ThemeMode) => void;
60
+ toggleTcbsTheme: () => void;
61
+ setMazicColor: (baseColor: string) => void;
24
62
  }
25
63
 
26
64
  const defaultColors: ThemeColors = {
27
65
  light: {
28
- // btnColor: '#007AFF',
29
- // btnBorderColor: '#007AFF',
30
- // btnIconColor: '#16a62bff',
31
- // themeColor: '#007AFF',
32
- // btnTextColor: '#FFFFFF',
66
+ // You can set initial defaults here if needed, or leave them empty
67
+ btnColor: '#007AFF',
68
+ btnBorderColor: '#007AFF',
69
+ btnIconColor: '#FFFFFF',
70
+ themeColor: '#007AFF',
71
+ btnTextColor: '#FFFFFF',
33
72
  },
34
73
  dark: {
35
- // btnColor: '#222222',
36
- // btnBorderColor: '#222222',
37
- // btnIconColor: '#FFFFFF',
38
- // themeColor: '#222222',
39
- // btnTextColor: '#FFFFFF',
74
+ btnColor: '#222222',
75
+ btnBorderColor: '#222222',
76
+ btnIconColor: '#FFFFFF',
77
+ themeColor: '#222222',
78
+ btnTextColor: '#FFFFFF',
40
79
  },
41
80
  };
42
81
 
43
- export const useTcbsColorStore = create<ThemeStore>((set: (fn: (state: ThemeStore) => Partial<ThemeStore>) => void) => ({
44
- colors: defaultColors,
45
- mode: 'light',
46
- setTcbsColor: (colors: Partial<ThemeColor> & { light?: Partial<ThemeColor>; dark?: Partial<ThemeColor> }) => {
47
- set((state: ThemeStore) => {
48
- let newColors = { ...state.colors };
49
- // If colors for both themes are provided
50
- if (colors.light || colors.dark) {
51
- if (colors.light) {
52
- newColors.light = { ...newColors.light, ...colors.light };
53
- }
54
- if (colors.dark) {
55
- newColors.dark = { ...newColors.dark, ...colors.dark };
82
+ // Try to load persisted theme, fallback to 'light'. If 'system', use current system color scheme.
83
+ const defaultTheme = storage.getString(THEME_KEY) as ThemeMode | undefined || 'light';
84
+
85
+ // Helper functions for color manipulation
86
+ const hexToRgb = (hex: string): { r: number; g: number; b: number } | null => {
87
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
88
+ return result ? {
89
+ r: parseInt(result[1], 16),
90
+ g: parseInt(result[2], 16),
91
+ b: parseInt(result[3], 16)
92
+ } : null;
93
+ };
94
+
95
+ const rgbToHex = (r: number, g: number, b: number): string => {
96
+ return '#' + [r, g, b].map(x => {
97
+ const hex = Math.round(x).toString(16);
98
+ return hex.length === 1 ? '0' + hex : hex;
99
+ }).join('');
100
+ };
101
+
102
+ const adjustBrightness = (hex: string, percent: number): string => {
103
+ const rgb = hexToRgb(hex);
104
+ if (!rgb) return hex;
105
+ // percent > 0: lighten, percent < 0: darken
106
+ const adjust = (value: number) => {
107
+ if (percent > 0) {
108
+ return Math.round(value + (255 - value) * (percent / 100));
109
+ } else {
110
+ return Math.round(value * (1 + percent / 100));
111
+ }
112
+ };
113
+ return rgbToHex(adjust(rgb.r), adjust(rgb.g), adjust(rgb.b));
114
+ };
115
+
116
+ // NEW HELPER: Determines if a color is dark enough to require white text
117
+ const isColorDark = (hex: string): boolean => {
118
+ const rgb = hexToRgb(hex);
119
+ if (!rgb) return true; // Default to white text if color is invalid
120
+ // Calculate Luma (YIQ method is good for perceived brightness)
121
+ // Formula: (R * 299 + G * 587 + B * 114) / 1000
122
+ const luma = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
123
+ // Threshold 0.45 is slightly lower than standard 0.5, favoring white text
124
+ return luma < 0.45;
125
+ };
126
+
127
+ const addAlpha = (hex: string, alpha: number): string => {
128
+ const alphaHex = Math.round(alpha * 255).toString(16).padStart(2, '0');
129
+ return hex + alphaHex;
130
+ };
131
+
132
+
133
+ export const useTcbsColorStore = create<ThemeStore>((set: (fn: (state: ThemeStore) => Partial<ThemeStore>) => void, get) => {
134
+ // Helper to get the current theme color
135
+ const getThemeColors = (theme: ThemeMode, colors: ThemeColors): ThemeColor => {
136
+ if (theme === 'light' || theme === 'dark') {
137
+ return colors[theme];
138
+ } else {
139
+ // system: use Appearance API
140
+ const colorScheme = Appearance.getColorScheme?.() || 'light';
141
+ return colors[colorScheme as 'light' | 'dark'] || colors.light;
142
+ }
143
+ };
144
+
145
+ // Initial state
146
+ const initialColors = defaultColors;
147
+ const initialTheme = defaultTheme;
148
+ const initialThemeColors = getThemeColors(initialTheme, initialColors);
149
+
150
+ // Listen to system theme changes if needed
151
+ if (initialTheme === 'system' && !appearanceListener) {
152
+ appearanceListener = Appearance.addChangeListener?.(({ colorScheme }) => {
153
+ set((state: ThemeStore) => ({
154
+ themeColors: state.colors[(colorScheme as 'light' | 'dark') || 'light']
155
+ }));
156
+ });
157
+ }
158
+
159
+ return {
160
+ colors: initialColors,
161
+ tcbsTheme: initialTheme,
162
+ themeColors: initialThemeColors,
163
+ get currentThemeMode() {
164
+ const state = get();
165
+ if (state.tcbsTheme === 'light' || state.tcbsTheme === 'dark') {
166
+ return state.tcbsTheme;
167
+ }
168
+ // system: use Appearance API
169
+ const colorScheme = Appearance.getColorScheme?.() || 'light';
170
+ return (colorScheme === 'dark' ? 'dark' : 'light');
171
+ },
172
+ setTcbsColor: (colors: Partial<ThemeColor> & { light?: Partial<ThemeColor>; dark?: Partial<ThemeColor> }) => {
173
+ set((state: ThemeStore) => {
174
+ let newColors = { ...state.colors };
175
+ // If colors for both themes are provided
176
+ if (colors.light || colors.dark) {
177
+ if (colors.light) {
178
+ newColors.light = { ...newColors.light, ...colors.light };
179
+ }
180
+ if (colors.dark) {
181
+ newColors.dark = { ...newColors.dark, ...colors.dark };
182
+ }
183
+ } else {
184
+ // If only one set, update both themes
185
+ newColors.light = { ...newColors.light, ...colors };
186
+ newColors.dark = { ...newColors.dark, ...colors };
56
187
  }
57
- } else {
58
- // If only one set, update both themes
59
- newColors.light = { ...newColors.light, ...colors };
60
- newColors.dark = { ...newColors.dark, ...colors };
188
+ // Update themeColors as well
189
+ const themeColors = getThemeColors(state.tcbsTheme, newColors);
190
+ return { colors: newColors, themeColors };
191
+ });
192
+ },
193
+ setTcbsTheme: (newTheme: ThemeMode) => {
194
+ // Persist user selection
195
+ storage.set(THEME_KEY, newTheme);
196
+ // Remove previous listener if exists
197
+ if (appearanceListener) {
198
+ appearanceListener.remove();
199
+ appearanceListener = null;
61
200
  }
62
- return { colors: newColors };
63
- });
64
- },
65
- setTcbsTheme: (mode: ThemeMode) => set((state) => ({ ...state, mode })),
66
- }));
201
+ // If new theme is system, add listener
202
+ if (newTheme === 'system') {
203
+ appearanceListener = Appearance.addChangeListener?.(({ colorScheme }) => {
204
+ set((state: ThemeStore) => ({
205
+ themeColors: state.colors[(colorScheme as 'light' | 'dark') || 'light']
206
+ }));
207
+ });
208
+ }
209
+ // Update themeColors as well
210
+ set((state: ThemeStore) => ({
211
+ tcbsTheme: newTheme,
212
+ themeColors: getThemeColors(newTheme, state.colors)
213
+ }));
214
+ },
215
+ toggleTcbsTheme: () => {
216
+ set((state: ThemeStore) => {
217
+ const themes: ThemeMode[] = ['light', 'dark', 'system'];
218
+ const currentIdx = themes.indexOf(state.tcbsTheme);
219
+ const nextTheme = themes[(currentIdx + 1) % themes.length];
220
+ // Note: setTcbsTheme is called outside the state update, but here it's called internally
221
+ // which is a common pattern in zustand actions.
222
+ // @ts-ignore
223
+ state.setTcbsTheme(nextTheme);
224
+ return {};
225
+ });
226
+ },
227
+
228
+ // REWRITTEN FUNCTION using hardcoded neutrals for better UI contrast
229
+ setMazicColor: (baseColor: string) => {
230
+ // Determine the best text color for the button based on the base color's brightness
231
+ // const buttonTextColor = isColorDark(baseColor) ? '#FFFFFF' : '#000000';
232
+ const buttonTextColor = '#FFFFFF' ;
233
+ const secondaryBaseColor = adjustBrightness(baseColor, -10); // A slightly darker shade for accents
234
+
235
+ // --- Light Theme Palette ---
236
+ const lightColors: ThemeColor = {
237
+ // Primary & Button Colors (baseColor is the accent)
238
+ btnColor: addAlpha(baseColor,1),
239
+ btnBorderColor: baseColor,
240
+ btnIconColor: buttonTextColor,
241
+ themeColor: baseColor,
242
+ btnTextColor: buttonTextColor,
243
+
244
+ tabBarIconActiveColor: buttonTextColor,
245
+ tabBarIconInactiveColor: addAlpha("#000000",0.4),
246
+
247
+ // modalBgColor: 80% lighter than baseColor
248
+ primaryColor: addAlpha(baseColor,1),
249
+ secondaryColor: addAlpha(baseColor,0.7),
250
+ tertiaryColor: addAlpha(baseColor,0.1),
251
+
252
+ // Backgrounds (Clean white/near-white neutrals)
253
+ screenBgColor: addAlpha(baseColor,0.1), // Pure white
254
+ modalBgColor: adjustBrightness(baseColor, 10), // 80% lighter tone
255
+ modalTitleColor: adjustBrightness(baseColor, 90),
256
+ modalHeaderBgColor: '#F0F0F0', // Light gray
257
+ modalCardBgColor: '#FAFAFA', // Off-white for cards/modals
258
+
259
+ // Text Colors (High contrast black/dark gray)
260
+ textPrimary: '#1F1F1F', // Very dark gray
261
+ textSecondary: '#6B7280', // Medium gray
262
+
263
+ // Borders & Dividers (Very subtle grays)
264
+ borderColor: '#E5E7EB',
265
+ dividerColor: '#F3F4F6',
266
+
267
+ // Inputs & Cards
268
+ inputBgColor: '#FFFFFF',
269
+ inputBorderColor: '#D1D5DB',
270
+ cardBgColor: '#FFFFFF',
271
+ cardBorderColor: '#E5E7EB',
272
+
273
+ // Status Colors (Standard, high-contrast semantic colors)
274
+ accentColor: secondaryBaseColor,
275
+ errorColor: '#DC2626',
276
+ successColor: '#16A34A',
277
+ warningColor: '#F59E0B',
278
+ };
279
+
280
+ // --- Dark Theme Palette ---
281
+ const darkColors: ThemeColor = {
282
+ // Primary & Button Colors
283
+ btnColor: addAlpha(baseColor,1),
284
+ btnBorderColor: baseColor,
285
+ btnIconColor: buttonTextColor,
286
+ themeColor: baseColor,
287
+ btnTextColor: buttonTextColor,
288
+
289
+ tabBarIconActiveColor: buttonTextColor,
290
+ tabBarIconInactiveColor: addAlpha("#000000",0.4),
291
+
292
+ primaryColor: addAlpha(baseColor,1),
293
+ secondaryColor: addAlpha(baseColor,0.7),
294
+ tertiaryColor: addAlpha(baseColor,0.2),
295
+
296
+ // Backgrounds (Clean dark/near-black neutrals)
297
+ screenBgColor: addAlpha(baseColor,0.8), // Very dark gray
298
+ modalBgColor: adjustBrightness(baseColor, 80), // Darker overlay
299
+ modalHeaderBgColor: '#1F1F1F', // Slightly lighter dark gray
300
+ modalCardBgColor: '#2C2C2C', // Medium dark gray for cards/modals
301
+
302
+ // Text Colors (High contrast white/light gray)
303
+ textPrimary: '#FFFFFF', // Pure white
304
+ textSecondary: '#A0A0A0', // Light gray
305
+
306
+ // Borders & Dividers (Subtle dark grays)
307
+ borderColor: '#374151',
308
+ dividerColor: '#2C2C2C',
309
+
310
+ // Inputs & Cards
311
+ inputBgColor: '#1F1F1F',
312
+ inputBorderColor: '#374151',
313
+ cardBgColor: '#1F1F1F',
314
+ cardBorderColor: '#374151',
315
+
316
+ // Status Colors (Brighter semantic colors for dark background)
317
+ accentColor: secondaryBaseColor,
318
+ errorColor: '#EF4444',
319
+ successColor: '#22C55E',
320
+ warningColor: '#FBBF24',
321
+ };
322
+
323
+ set((state: ThemeStore) => {
324
+ const newColors = {
325
+ light: lightColors,
326
+ dark: darkColors,
327
+ };
328
+ const themeColors = getThemeColors(state.tcbsTheme, newColors);
329
+ return { colors: newColors, themeColors };
330
+ });
331
+ }
332
+ };
333
+ });
@@ -1,26 +0,0 @@
1
- import React from 'react';
2
- import { TouchableOpacity, Text, StyleSheet, GestureResponderEvent } from 'react-native';
3
-
4
- type ButtonProps = {
5
- title: string;
6
- onPress: (event: GestureResponderEvent) => void;
7
- };
8
-
9
- export const Button: React.FC<ButtonProps> = ({ title, onPress }) => (
10
- <TouchableOpacity style={styles.button} onPress={onPress}>
11
- <Text style={styles.text}>TCBS 1: {title}</Text>
12
- </TouchableOpacity>
13
- );
14
-
15
- const styles = StyleSheet.create({
16
- button: {
17
- backgroundColor: '#007AFF',
18
- padding: 12,
19
- borderRadius: 8,
20
- alignItems: 'center'
21
- },
22
- text: {
23
- color: '#fff',
24
- fontWeight: 'bold'
25
- }
26
- });