@tcbs/react-native-mazic-ui 0.1.2 → 0.1.4

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,3 @@
1
+ import React from 'react';
2
+ declare const ExampleScreen: React.FC;
3
+ export default ExampleScreen;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { View, Text } from 'react-native';
3
+ import { useTcbsColorStore } from '../store/themeStore';
4
+ const ExampleScreen = () => {
5
+ const { colors, mode } = useTcbsColorStore();
6
+ const screenBgColor = colors[mode].screenBgColor;
7
+ return (React.createElement(View, { style: { flex: 1, backgroundColor: screenBgColor } },
8
+ React.createElement(Text, { style: { color: colors[mode].btnTextColor } }, "This screen uses theme screenBgColor!")));
9
+ };
10
+ export default ExampleScreen;
@@ -1,4 +1,5 @@
1
1
  import React, { useMemo } from 'react';
2
+ import { Appearance } from 'react-native';
2
3
  import { TouchableOpacity, Text, View } from 'react-native';
3
4
  import AntDesign from 'react-native-vector-icons/AntDesign';
4
5
  import Feather from 'react-native-vector-icons/Feather';
@@ -9,6 +10,7 @@ import MaterialDesignIcons from 'react-native-vector-icons/MaterialCommunityIcon
9
10
  import Octicons from 'react-native-vector-icons/Octicons';
10
11
  // import Lucide from 'react-native-vector-icons/Lucide';
11
12
  import { BUTTON_SIZE, BUTTON_VARIANT, BORDER_RADIUS, } from './TcbsButton.types';
13
+ import { useTcbsColorStore } from '../store/themeStore';
12
14
  const HEIGHTS = {
13
15
  [BUTTON_SIZE.LARGE]: 56,
14
16
  [BUTTON_SIZE.MEDIUM]: 40,
@@ -19,6 +21,7 @@ const FONT_SIZES = {
19
21
  [BUTTON_SIZE.MEDIUM]: 16,
20
22
  [BUTTON_SIZE.SMALL]: 14,
21
23
  };
24
+ // Support for BORDER_RADIUS.NONE and BORDER_RADIUS.FULL (50%)
22
25
  const BORDER_RADIUSES = {
23
26
  [BUTTON_SIZE.LARGE]: BORDER_RADIUS.MEDIUM,
24
27
  [BUTTON_SIZE.MEDIUM]: BORDER_RADIUS.SMALL,
@@ -39,37 +42,70 @@ const BORDER_RADIUSES = {
39
42
  * />
40
43
  * ```
41
44
  */
42
- export const TcbsButton = ({ title, onPress, size = BUTTON_SIZE.LARGE, variant = BUTTON_VARIANT.PRIMARY, borderRadius, disabled = false, style, textStyle, iconName, iconGroup = 'MaterialDesignIcons', iconColor, iconSize, iconPosition = 'top', accessibilityLabel, accessibilityHint, accessibilityRole = 'button', accessibilityState, themeColor = {
43
- btnColor: '#007AFF',
44
- themeColor: '#007AFF',
45
- btnTextColor: '#FFFFFF',
46
- btnTxtColor: '#FFFFFF',
47
- }, }) => {
48
- const colors = themeColor;
45
+ export const TcbsButton = ({ title, onPress, size = BUTTON_SIZE.LARGE, variant = BUTTON_VARIANT.PRIMARY, borderRadius, disabled = false, style, textStyle, iconName, iconGroup = 'MaterialDesignIcons', iconColor, iconSize, iconPosition = 'top', accessibilityLabel, accessibilityHint, accessibilityRole = 'button', accessibilityState, }) => {
46
+ var _a, _b, _c, _d, _e, _f, _g;
47
+ // Use themeColors from store if not provided as prop
48
+ const { themeColors, tcbsTheme } = useTcbsColorStore();
49
+ const effectiveThemeColor = themeColors;
50
+ // Normalize colors: if only one color is set, use it for all
51
+ const normalizedColors = {
52
+ btnColor: (_b = (_a = effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.btnColor) !== null && _a !== void 0 ? _a : effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.themeColor) !== null && _b !== void 0 ? _b : '#007AFF',
53
+ btnBorderColor: (_d = (_c = effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.btnBorderColor) !== null && _c !== void 0 ? _c : effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.btnColor) !== null && _d !== void 0 ? _d : '#007AFF',
54
+ btnIconColor: effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.btnIconColor,
55
+ btnTextColor: (_e = effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.btnTextColor) !== null && _e !== void 0 ? _e : effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.btnTxtColor,
56
+ themeColor: (_g = (_f = effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.themeColor) !== null && _f !== void 0 ? _f : effectiveThemeColor === null || effectiveThemeColor === void 0 ? void 0 : effectiveThemeColor.btnColor) !== null && _g !== void 0 ? _g : '#007AFF',
57
+ };
49
58
  const buttonStyle = useMemo(() => {
59
+ const height = HEIGHTS[size];
60
+ let computedBorderRadius;
61
+ if (borderRadius === BORDER_RADIUS.NONE) {
62
+ computedBorderRadius = 0;
63
+ }
64
+ else if (borderRadius === BORDER_RADIUS.FULL) {
65
+ computedBorderRadius = height / 2;
66
+ }
67
+ else if (borderRadius !== undefined) {
68
+ computedBorderRadius = borderRadius;
69
+ }
70
+ else {
71
+ computedBorderRadius = BORDER_RADIUSES[size];
72
+ }
50
73
  const baseStyle = {
51
- height: HEIGHTS[size],
52
- borderRadius: borderRadius !== null && borderRadius !== void 0 ? borderRadius : BORDER_RADIUSES[size],
74
+ height,
75
+ borderRadius: computedBorderRadius,
53
76
  alignItems: 'center',
54
77
  justifyContent: 'center',
55
78
  opacity: disabled ? 0.6 : 1,
56
79
  paddingHorizontal: 24,
57
80
  };
58
81
  if (variant === BUTTON_VARIANT.SECONDARY) {
59
- return Object.assign(Object.assign(Object.assign({}, baseStyle), { backgroundColor: '#fff', borderWidth: 2, borderColor: colors.btnColor || colors.themeColor }), style);
82
+ return Object.assign(Object.assign(Object.assign({}, baseStyle), { backgroundColor: '#fff', borderWidth: 2, borderColor: normalizedColors.btnBorderColor }), style);
60
83
  }
61
84
  if (variant === BUTTON_VARIANT.NO_BORDER) {
62
- return Object.assign(Object.assign(Object.assign({}, baseStyle), { backgroundColor: '#fff' }), style);
85
+ return Object.assign(Object.assign(Object.assign({}, baseStyle), { backgroundColor: 'transparent' }), style);
63
86
  }
64
87
  // Primary variant (default)
65
- return Object.assign(Object.assign(Object.assign({}, baseStyle), { backgroundColor: colors.btnColor || colors.themeColor, shadowColor: colors.btnColor, shadowOpacity: 0.15, shadowRadius: 6, shadowOffset: { width: 0, height: 2 }, elevation: 2 }), style);
66
- }, [size, variant, colors, style, disabled, borderRadius]);
88
+ return Object.assign(Object.assign(Object.assign({}, baseStyle), { backgroundColor: normalizedColors.btnColor, shadowColor: normalizedColors.btnColor, shadowOpacity: 0.15, shadowRadius: 6, shadowOffset: { width: 0, height: 2 }, elevation: 2 }), style);
89
+ }, [size, variant, normalizedColors, style, disabled, borderRadius]);
67
90
  const themedTextStyle = useMemo(() => {
68
- const baseTextColor = variant === BUTTON_VARIANT.PRIMARY
69
- ? colors.btnTextColor || colors.btnTxtColor || '#fff'
70
- : colors.btnColor || colors.themeColor;
91
+ let baseTextColor;
92
+ if (variant === BUTTON_VARIANT.PRIMARY) {
93
+ baseTextColor = normalizedColors.btnTextColor || '#FFFFFF';
94
+ }
95
+ else if (variant === BUTTON_VARIANT.NO_BORDER) {
96
+ let colorScheme = tcbsTheme;
97
+ if (tcbsTheme === 'system') {
98
+ colorScheme = Appearance.getColorScheme() || 'light';
99
+ }
100
+ baseTextColor = colorScheme === 'dark'
101
+ ? normalizedColors.btnTextColor || '#FFFFFF'
102
+ : (normalizedColors === null || normalizedColors === void 0 ? void 0 : normalizedColors.btnColor) || '#007AFF';
103
+ }
104
+ else {
105
+ baseTextColor = (normalizedColors === null || normalizedColors === void 0 ? void 0 : normalizedColors.btnColor) || '#FFFFFF';
106
+ }
71
107
  return Object.assign({ color: baseTextColor, fontSize: FONT_SIZES[size], fontWeight: '700' }, textStyle);
72
- }, [size, variant, colors, textStyle]);
108
+ }, [size, variant, normalizedColors, textStyle, tcbsTheme]);
73
109
  const renderIcon = (IconComponent) => (React.createElement(IconComponent, { name: iconName, size: iconSize || FONT_SIZES[size] * 2, color: iconColor || themedTextStyle.color, style: iconPosition === 'top'
74
110
  ? { marginBottom: 2 }
75
111
  : iconPosition === 'left'
@@ -113,5 +149,4 @@ export const TcbsButton = ({ title, onPress, size = BUTTON_SIZE.LARGE, variant =
113
149
  };
114
150
  return (React.createElement(TouchableOpacity, { onPress: onPress, disabled: disabled, style: buttonStyle, accessibilityLabel: accessibilityLabel || title, accessibilityHint: accessibilityHint, accessibilityRole: accessibilityRole, accessibilityState: accessibilityState || { disabled } }, renderContent()));
115
151
  };
116
- // Export constants for use in consuming applications
117
152
  export { BUTTON_SIZE, BUTTON_VARIANT, BORDER_RADIUS };
@@ -20,6 +20,8 @@ export declare const BORDER_RADIUS: {
20
20
  readonly SMALL: 8;
21
21
  readonly MEDIUM: 12;
22
22
  readonly LARGE: 16;
23
+ readonly NONE: 0;
24
+ readonly FULL: "50%";
23
25
  };
24
26
  export type ButtonSize = (typeof BUTTON_SIZE)[keyof typeof BUTTON_SIZE];
25
27
  export type ButtonVariant = (typeof BUTTON_VARIANT)[keyof typeof BUTTON_VARIANT];
@@ -45,8 +47,11 @@ export interface TcbsButtonProps {
45
47
  accessibilityState?: AccessibilityState;
46
48
  themeColor?: {
47
49
  btnColor?: string;
50
+ btnBorderColor?: string;
51
+ btnIconColor?: string;
48
52
  themeColor?: string;
49
53
  btnTextColor?: string;
50
54
  btnTxtColor?: string;
51
55
  };
56
+ screenBgColor?: string;
52
57
  }
@@ -12,4 +12,6 @@ export const BORDER_RADIUS = {
12
12
  SMALL: 8,
13
13
  MEDIUM: 12,
14
14
  LARGE: 16,
15
+ NONE: 0,
16
+ FULL: '50%',
15
17
  };
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export interface ThemeModalProps {
3
+ visible: boolean;
4
+ onClose: () => void;
5
+ }
6
+ export declare const ThemeModal: React.FC<ThemeModalProps>;
7
+ export default ThemeModal;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { Modal, Pressable, View, Text, StyleSheet } from 'react-native';
3
+ import { useTcbsColorStore } from '../store/themeStore';
4
+ import { BUTTON_VARIANT, TcbsButton } from './TcbsButton';
5
+ export const ThemeModal = ({ visible, onClose }) => {
6
+ const { tcbsTheme, setTcbsTheme, themeColors } = useTcbsColorStore();
7
+ // You can customize these colors or get them from your theme
8
+ const colors = {
9
+ menuCardBkgColor: themeColors.screenBgColor || '#fff',
10
+ textDark: themeColors.modalTitleColor || '#222',
11
+ textGray: '#888',
12
+ };
13
+ return (React.createElement(Modal, { transparent: true, animationType: "fade", visible: visible, onRequestClose: onClose },
14
+ React.createElement(Pressable, { style: styles.modalOverlay, onPress: onClose },
15
+ React.createElement(Pressable, { style: [styles.modalCard, { backgroundColor: themeColors.modalBgColor || "#00000080" }], onPress: () => { } },
16
+ React.createElement(View, { style: styles.modalClose },
17
+ React.createElement(TcbsButton, { onPress: onClose, iconName: "close", iconColor: colors.textDark, iconPosition: "left", variant: BUTTON_VARIANT.NO_BORDER, iconSize: 22, accessibilityLabel: "Close", style: { padding: 8, marginRight: 0, minWidth: 0, minHeight: 0, alignSelf: 'flex-end' } })),
18
+ React.createElement(Text, { style: [styles.modalTitle, { color: colors.textDark }] }, "Theme"),
19
+ React.createElement(Text, { style: [styles.modalSubtitle, { color: colors.textDark }] }, "Choose how the app looks on this device."),
20
+ React.createElement(View, { style: { marginTop: 18 } },
21
+ React.createElement(TcbsButton, { title: "Light", onPress: () => setTcbsTheme('light'), style: { marginBottom: 8 }, variant: tcbsTheme === 'light' ? 'primary' : 'secondary', iconGroup: "Ionicons", iconName: "sunny", iconPosition: "left", textStyle: { flex: 1, textAlign: 'center' } }),
22
+ React.createElement(TcbsButton, { title: "Dark", onPress: () => setTcbsTheme('dark'), style: { marginBottom: 8 }, variant: tcbsTheme === 'dark' ? 'primary' : 'secondary', iconGroup: "Ionicons", iconName: "moon", iconPosition: "left", textStyle: { flex: 1, textAlign: 'center' } }),
23
+ React.createElement(TcbsButton, { title: "System (default)", onPress: () => setTcbsTheme('system'), variant: tcbsTheme === 'system' ? 'primary' : 'secondary', iconGroup: "Ionicons", iconName: "settings", iconPosition: "left", textStyle: { flex: 1, textAlign: 'center' } }))))));
24
+ };
25
+ const styles = StyleSheet.create({
26
+ modalOverlay: {
27
+ flex: 1,
28
+ backgroundColor: 'rgba(0,0,0,0.3)',
29
+ justifyContent: 'center',
30
+ alignItems: 'center',
31
+ },
32
+ modalCard: {
33
+ minWidth: 300,
34
+ borderRadius: 16,
35
+ padding: 24,
36
+ alignItems: 'stretch',
37
+ // shadowColor: '#000',
38
+ // shadowOpacity: 0.15,
39
+ shadowRadius: 12,
40
+ // shadowOffset: { width: 0, height: 4 },
41
+ // elevation: 4,
42
+ },
43
+ modalClose: {
44
+ position: 'absolute',
45
+ top: 8,
46
+ right: 8,
47
+ zIndex: 2,
48
+ },
49
+ modalTitle: {
50
+ fontSize: 20,
51
+ fontWeight: '700',
52
+ marginTop: 8,
53
+ marginBottom: 2,
54
+ textAlign: 'center',
55
+ },
56
+ modalSubtitle: {
57
+ fontSize: 14,
58
+ fontWeight: '400',
59
+ marginBottom: 8,
60
+ textAlign: 'center',
61
+ },
62
+ });
63
+ export default ThemeModal;
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ export interface AppErrorBoundaryProps {
3
+ children?: React.ReactNode;
4
+ }
5
+ export interface AppErrorBoundaryState {
6
+ hasError: boolean;
7
+ error: any;
8
+ reactError: {
9
+ name?: string;
10
+ message?: string;
11
+ stack?: string;
12
+ } | null;
13
+ errorInfo: {
14
+ componentStack?: string;
15
+ } | null;
16
+ }
17
+ export declare class AppErrorBoundary extends React.Component<AppErrorBoundaryProps, AppErrorBoundaryState> {
18
+ state: AppErrorBoundaryState;
19
+ static getDerivedStateFromError(error: any): {
20
+ hasError: boolean;
21
+ error: any;
22
+ };
23
+ componentDidCatch(error: any, errorInfo: any): void;
24
+ renderDevError(): React.JSX.Element | null;
25
+ renderProdError(): React.JSX.Element;
26
+ render(): React.ReactNode;
27
+ }
@@ -0,0 +1,117 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, ScrollView, } from 'react-native';
3
+ import { SafeAreaView } from 'react-native-safe-area-context';
4
+ import { setupGlobalExceptionHandlers } from './setupGlobalExceptionHandlers';
5
+ setupGlobalExceptionHandlers();
6
+ export class AppErrorBoundary extends React.Component {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.state = {
10
+ hasError: false,
11
+ error: null,
12
+ reactError: null,
13
+ errorInfo: null,
14
+ };
15
+ }
16
+ static getDerivedStateFromError(error) {
17
+ return { hasError: true, error };
18
+ }
19
+ componentDidCatch(error, errorInfo) {
20
+ // Log exactly what React gives
21
+ console.log('React Render Error:', error);
22
+ // Store everything needed for UI
23
+ this.setState({
24
+ error,
25
+ reactError: {
26
+ name: error === null || error === void 0 ? void 0 : error.name,
27
+ message: error === null || error === void 0 ? void 0 : error.message,
28
+ stack: error === null || error === void 0 ? void 0 : error.stack,
29
+ },
30
+ errorInfo,
31
+ });
32
+ }
33
+ renderDevError() {
34
+ const { error, reactError, errorInfo } = this.state;
35
+ if (!error)
36
+ return null;
37
+ return (React.createElement(SafeAreaView, { style: { flex: 1 } },
38
+ React.createElement(ScrollView, { style: styles.container },
39
+ React.createElement(Text, { style: styles.title }, "\uD83D\uDEA8 Application Error (DEV)"),
40
+ React.createElement(Section, { title: "\u274C What happened" },
41
+ React.createElement(Text, { style: styles.errorText },
42
+ error.name,
43
+ ": ",
44
+ error.message)),
45
+ React.createElement(Section, { title: "\u269B\uFE0F React render error" },
46
+ React.createElement(Text, { style: styles.mono }, reactError === null || reactError === void 0 ? void 0 : reactError.stack)),
47
+ React.createElement(Section, { title: "\uD83D\uDCDC Component stack (Advanced)" },
48
+ React.createElement(Text, { style: styles.stack }, errorInfo === null || errorInfo === void 0 ? void 0 : errorInfo.componentStack)))));
49
+ }
50
+ renderProdError() {
51
+ return (React.createElement(View, { style: styles.prodContainer },
52
+ React.createElement(Text, { style: styles.prodTitle }, "Something went wrong"),
53
+ React.createElement(Text, { style: styles.prodSubtitle }, "Please restart the application.")));
54
+ }
55
+ render() {
56
+ if (this.state.hasError) {
57
+ return __DEV__ ? this.renderDevError() : this.renderProdError();
58
+ }
59
+ return this.props.children;
60
+ }
61
+ }
62
+ const Section = ({ title, children }) => (React.createElement(View, { style: styles.section },
63
+ React.createElement(Text, { style: styles.sectionTitle }, title),
64
+ children));
65
+ /* ---------- Styles ---------- */
66
+ const styles = StyleSheet.create({
67
+ container: {
68
+ flex: 1,
69
+ backgroundColor: '#0f172a',
70
+ padding: 16,
71
+ },
72
+ title: {
73
+ fontSize: 18,
74
+ fontWeight: '700',
75
+ color: '#f87171',
76
+ marginBottom: 12,
77
+ },
78
+ section: {
79
+ marginBottom: 16,
80
+ },
81
+ sectionTitle: {
82
+ fontSize: 14,
83
+ fontWeight: '600',
84
+ color: '#38bdf8',
85
+ marginBottom: 6,
86
+ },
87
+ errorText: {
88
+ color: '#fecaca',
89
+ fontSize: 14,
90
+ },
91
+ mono: {
92
+ fontSize: 12,
93
+ color: '#e5e7eb',
94
+ lineHeight: 16,
95
+ },
96
+ stack: {
97
+ fontSize: 11,
98
+ color: '#94a3b8',
99
+ lineHeight: 16,
100
+ },
101
+ prodContainer: {
102
+ flex: 1,
103
+ alignItems: 'center',
104
+ justifyContent: 'center',
105
+ padding: 24,
106
+ },
107
+ prodTitle: {
108
+ fontSize: 18,
109
+ fontWeight: '600',
110
+ marginBottom: 8,
111
+ },
112
+ prodSubtitle: {
113
+ fontSize: 14,
114
+ color: '#666',
115
+ textAlign: 'center',
116
+ },
117
+ });
package/lib/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { Button } from './components/Button';
2
1
  export { TcbsButton } from './components/TcbsButton';
3
2
  export { BUTTON_SIZE, BUTTON_VARIANT, BORDER_RADIUS, ButtonSize, ButtonVariant, IconGroupType, IconPosition, TcbsButtonProps, IconComponentType, } from './components/TcbsButton.types';
3
+ export { useTcbsColorStore } from './store/themeStore';
4
+ export { ThemeModal } from './components/ThemeModal';
5
+ export { AppErrorBoundary } from './components/error/AppErrorBoundary';
package/lib/index.js CHANGED
@@ -1,3 +1,5 @@
1
- export { Button } from './components/Button';
2
1
  export { TcbsButton } from './components/TcbsButton';
3
2
  export { BUTTON_SIZE, BUTTON_VARIANT, BORDER_RADIUS, } from './components/TcbsButton.types';
3
+ export { useTcbsColorStore } from './store/themeStore';
4
+ export { ThemeModal } from './components/ThemeModal';
5
+ export { AppErrorBoundary } from './components/error/AppErrorBoundary';
@@ -0,0 +1,52 @@
1
+ export type ThemeColor = {
2
+ btnColor: string;
3
+ btnBorderColor?: string;
4
+ btnIconColor?: string;
5
+ themeColor: string;
6
+ btnTextColor: string;
7
+ tabBarIconActiveColor?: string;
8
+ tabBarIconInactiveColor?: string;
9
+ modalBgColor?: string;
10
+ primaryColor?: string;
11
+ secondaryColor?: string;
12
+ tertiaryColor?: string;
13
+ screenBgColor?: string;
14
+ modalHeaderBgColor?: string;
15
+ modalCardBgColor?: string;
16
+ modalTitleColor?: string;
17
+ textPrimary?: string;
18
+ textSecondary?: string;
19
+ borderColor?: string;
20
+ dividerColor?: string;
21
+ inputBgColor?: string;
22
+ inputBorderColor?: string;
23
+ cardBgColor?: string;
24
+ cardBorderColor?: string;
25
+ accentColor?: string;
26
+ errorColor?: string;
27
+ successColor?: string;
28
+ warningColor?: string;
29
+ };
30
+ export type ThemeMode = 'light' | 'dark' | 'system';
31
+ export type ThemeColors = {
32
+ light: ThemeColor;
33
+ dark: ThemeColor;
34
+ };
35
+ export interface ThemeStore {
36
+ colors: ThemeColors;
37
+ tcbsTheme: ThemeMode;
38
+ themeColors: ThemeColor;
39
+ /**
40
+ * Returns the current theme as 'light' or 'dark' (never 'system').
41
+ * If tcbsTheme is 'system', resolves to the current system color scheme.
42
+ */
43
+ currentThemeMode: 'light' | 'dark';
44
+ setTcbsColor: (colors: Partial<ThemeColor> & {
45
+ light?: Partial<ThemeColor>;
46
+ dark?: Partial<ThemeColor>;
47
+ }) => void;
48
+ setTcbsTheme: (mode: ThemeMode) => void;
49
+ toggleTcbsTheme: () => void;
50
+ setMazicColor: (baseColor: string) => void;
51
+ }
52
+ export declare const useTcbsColorStore: any;
@@ -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,7 +13,7 @@
13
13
  "react-native-vector-icons"
14
14
  ],
15
15
  "author": "TechCraft By Subrata <subraatakumar@gmail.com>",
16
- "version": "0.1.2",
16
+ "version": "0.1.4",
17
17
  "publishConfig": {
18
18
  "access": "public"
19
19
  },
@@ -32,9 +32,13 @@
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": ">=17.0.0",
35
- "react-native": ">=0.68.0",
36
- "react-native-vector-icons": ">=10.2.0"
35
+ "react-native": ">=0.68.0"
37
36
  },
37
+ "dependencies": {
38
+ "react-native-vector-icons": ">=10.2.0",
39
+ "react-error-boundary": ">=5.0.0",
40
+ "@tcbs/react-native-exception-handler": "1.0.0"
41
+ },
38
42
  "devDependencies": {
39
43
  "@types/react": "^17.0.0",
40
44
  "@types/react-native": "^0.68.0",
@@ -16,7 +16,7 @@ export const ThemeModal: React.FC<ThemeModalProps> = ({ visible, onClose }) => {
16
16
  // You can customize these colors or get them from your theme
17
17
  const colors = {
18
18
  menuCardBkgColor: themeColors.screenBgColor || '#fff',
19
- textDark: themeColors.btnTextColor || '#222',
19
+ textDark: themeColors.modalTitleColor || '#222',
20
20
  textGray: '#888',
21
21
  };
22
22
 
@@ -28,13 +28,14 @@ export const ThemeModal: React.FC<ThemeModalProps> = ({ visible, onClose }) => {
28
28
  onRequestClose={onClose}
29
29
  >
30
30
  <Pressable style={styles.modalOverlay} onPress={onClose}>
31
- <Pressable style={[styles.modalCard, { backgroundColor: colors.menuCardBkgColor }]}
31
+ <Pressable style={[styles.modalCard, { backgroundColor: themeColors.modalBgColor || "#00000080" }]}
32
32
  onPress={() => {}} // Prevent closing when pressing inside card
33
33
  >
34
34
  <View style={styles.modalClose}>
35
35
  <TcbsButton
36
36
  onPress={onClose}
37
37
  iconName="close"
38
+ iconColor={colors.textDark}
38
39
  iconPosition="left"
39
40
  variant={BUTTON_VARIANT.NO_BORDER}
40
41
  iconSize={22}
@@ -43,7 +44,7 @@ export const ThemeModal: React.FC<ThemeModalProps> = ({ visible, onClose }) => {
43
44
  />
44
45
  </View>
45
46
  <Text style={[styles.modalTitle, { color: colors.textDark }]}>Theme</Text>
46
- <Text style={[styles.modalSubtitle, { color: colors.textGray }]}>Choose how the app looks on this device.</Text>
47
+ <Text style={[styles.modalSubtitle, { color: colors.textDark }]}>Choose how the app looks on this device.</Text>
47
48
  <View style={{ marginTop: 18 }}>
48
49
  <TcbsButton
49
50
  title="Light"
@@ -93,11 +94,11 @@ const styles = StyleSheet.create({
93
94
  borderRadius: 16,
94
95
  padding: 24,
95
96
  alignItems: 'stretch',
96
- shadowColor: '#000',
97
- shadowOpacity: 0.15,
97
+ // shadowColor: '#000',
98
+ // shadowOpacity: 0.15,
98
99
  shadowRadius: 12,
99
- shadowOffset: { width: 0, height: 4 },
100
- elevation: 4,
100
+ // shadowOffset: { width: 0, height: 4 },
101
+ // elevation: 4,
101
102
  },
102
103
  modalClose: {
103
104
  position: 'absolute',
@@ -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> {}
@@ -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,4 +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';
15
+ export {ThemeModal} from './components/ThemeModal';
16
+ export {AppErrorBoundary} from './components/error/AppErrorBoundary';
@@ -17,13 +17,14 @@ export type ThemeColor = {
17
17
  btnTextColor: string;
18
18
  tabBarIconActiveColor?: string;
19
19
  tabBarIconInactiveColor?: string;
20
+ modalBgColor?: string;
20
21
  primaryColor?: string;
21
22
  secondaryColor?: string;
22
23
  tertiaryColor?: string;
23
24
  screenBgColor?: string;
24
- modalBgColor?: string;
25
25
  modalHeaderBgColor?: string;
26
26
  modalCardBgColor?: string;
27
+ modalTitleColor?: string;
27
28
  textPrimary?: string;
28
29
  textSecondary?: string;
29
30
  borderColor?: string;
@@ -101,8 +102,14 @@ const rgbToHex = (r: number, g: number, b: number): string => {
101
102
  const adjustBrightness = (hex: string, percent: number): string => {
102
103
  const rgb = hexToRgb(hex);
103
104
  if (!rgb) return hex;
104
-
105
- const adjust = (value: number) => Math.max(0, Math.min(255, value + (255 * percent / 100)));
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
+ };
106
113
  return rgbToHex(adjust(rgb.r), adjust(rgb.g), adjust(rgb.b));
107
114
  };
108
115
 
@@ -237,13 +244,15 @@ export const useTcbsColorStore = create<ThemeStore>((set: (fn: (state: ThemeStor
237
244
  tabBarIconActiveColor: buttonTextColor,
238
245
  tabBarIconInactiveColor: addAlpha("#000000",0.4),
239
246
 
247
+ // modalBgColor: 80% lighter than baseColor
240
248
  primaryColor: addAlpha(baseColor,1),
241
249
  secondaryColor: addAlpha(baseColor,0.7),
242
250
  tertiaryColor: addAlpha(baseColor,0.1),
243
251
 
244
252
  // Backgrounds (Clean white/near-white neutrals)
245
253
  screenBgColor: addAlpha(baseColor,0.1), // Pure white
246
- modalBgColor: addAlpha('#000000', 0.5), // Standard dark overlay
254
+ modalBgColor: adjustBrightness(baseColor, 10), // 80% lighter tone
255
+ modalTitleColor: adjustBrightness(baseColor, 90),
247
256
  modalHeaderBgColor: '#F0F0F0', // Light gray
248
257
  modalCardBgColor: '#FAFAFA', // Off-white for cards/modals
249
258
 
@@ -279,13 +288,14 @@ export const useTcbsColorStore = create<ThemeStore>((set: (fn: (state: ThemeStor
279
288
 
280
289
  tabBarIconActiveColor: buttonTextColor,
281
290
  tabBarIconInactiveColor: addAlpha("#000000",0.4),
291
+
282
292
  primaryColor: addAlpha(baseColor,1),
283
293
  secondaryColor: addAlpha(baseColor,0.7),
284
294
  tertiaryColor: addAlpha(baseColor,0.2),
285
295
 
286
296
  // Backgrounds (Clean dark/near-black neutrals)
287
297
  screenBgColor: addAlpha(baseColor,0.8), // Very dark gray
288
- modalBgColor: addAlpha('#000000', 0.8), // Darker overlay
298
+ modalBgColor: adjustBrightness(baseColor, 80), // Darker overlay
289
299
  modalHeaderBgColor: '#1F1F1F', // Slightly lighter dark gray
290
300
  modalCardBgColor: '#2C2C2C', // Medium dark gray for cards/modals
291
301
 
@@ -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
- });