@umituz/react-native-design-system 2.5.9 → 2.5.10
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/atoms/AtomicButton.tsx +2 -5
- package/src/atoms/AtomicChip.tsx +13 -13
- package/src/atoms/AtomicIcon.tsx +1 -8
- package/src/atoms/AtomicPicker.tsx +6 -5
- package/src/atoms/__tests__/AtomicIcon.test.tsx +0 -6
- package/src/atoms/input/hooks/useInputState.ts +0 -6
- package/src/atoms/picker/components/PickerModal.tsx +15 -6
- package/src/atoms/picker/types/index.ts +17 -4
- package/src/device/detection/deviceDetection.ts +7 -20
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +1 -2
- package/src/molecules/alerts/AlertToast.tsx +1 -1
- package/src/molecules/animation/presentation/hooks/useFireworks.ts +1 -3
- package/src/molecules/animation/presentation/providers/AnimationThemeProvider.tsx +1 -3
- package/src/molecules/avatar/Avatar.tsx +2 -2
- package/src/molecules/bottom-sheet/components/BottomSheetModal.tsx +0 -6
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +2 -2
- package/src/molecules/navigation/StackNavigator.tsx +6 -9
- package/src/molecules/navigation/TabsNavigator.tsx +9 -13
- package/src/molecules/navigation/utils/AppNavigation.ts +0 -2
- package/src/molecules/navigation/utils/LabelProcessor.ts +1 -10
- package/src/molecules/navigation/utils/NavigationCleanup.ts +4 -8
- package/src/molecules/navigation/utils/NavigationValidator.ts +2 -11
- package/src/presentation/utils/variants/compound.ts +0 -4
- package/src/presentation/utils/variants/core.ts +0 -4
- package/src/presentation/utils/variants/helpers.ts +0 -6
- package/src/safe-area/components/SafeAreaProvider.tsx +0 -10
- package/src/safe-area/utils/validation.ts +3 -27
- package/src/theme/core/CustomColors.ts +2 -8
- package/src/theme/core/colors/ColorUtils.ts +0 -6
- package/src/theme/infrastructure/storage/ThemeStorage.ts +2 -16
- package/src/typography/presentation/utils/textColorUtils.ts +0 -3
- package/src/utilities/clipboard/ClipboardUtils.ts +1 -5
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npm install @umituz/react-native-design-system
|
|
|
23
23
|
### Peer Dependencies
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
npm install react@18.3.1 react-native@0.76.3 react-native-reanimated@~3.10.1 react-native-svg@^15.0.0
|
|
26
|
+
npm install react@18.3.1 react-native@0.76.3 react-native-reanimated@~3.10.1 react-native-svg@^15.0.0
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
> **v1.3.0 Breaking Change**: React Native Paper dependency removed! All components now use pure React Native implementation for lighter bundle size and full control over styling.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.10",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -41,9 +41,6 @@ export const AtomicButton: React.FC<AtomicButtonProps> = React.memo(({
|
|
|
41
41
|
|
|
42
42
|
const handlePress = () => {
|
|
43
43
|
if (!disabled) {
|
|
44
|
-
if (__DEV__) {
|
|
45
|
-
console.log('[AtomicButton] Button pressed:', { title, variant, disabled });
|
|
46
|
-
}
|
|
47
44
|
onPress();
|
|
48
45
|
}
|
|
49
46
|
};
|
|
@@ -115,7 +112,7 @@ export const AtomicButton: React.FC<AtomicButtonProps> = React.memo(({
|
|
|
115
112
|
return {
|
|
116
113
|
container: {
|
|
117
114
|
...baseStyle,
|
|
118
|
-
backgroundColor:
|
|
115
|
+
backgroundColor: undefined,
|
|
119
116
|
borderWidth: 1,
|
|
120
117
|
borderColor: tokens.colors.border,
|
|
121
118
|
},
|
|
@@ -129,7 +126,7 @@ export const AtomicButton: React.FC<AtomicButtonProps> = React.memo(({
|
|
|
129
126
|
return {
|
|
130
127
|
container: {
|
|
131
128
|
...baseStyle,
|
|
132
|
-
backgroundColor:
|
|
129
|
+
backgroundColor: undefined,
|
|
133
130
|
},
|
|
134
131
|
text: {
|
|
135
132
|
...baseTextStyle,
|
package/src/atoms/AtomicChip.tsx
CHANGED
|
@@ -108,37 +108,37 @@ export const AtomicChip: React.FC<AtomicChipProps> = React.memo(({
|
|
|
108
108
|
|
|
109
109
|
const sizeConfig = sizeMap[size];
|
|
110
110
|
|
|
111
|
-
// Color mapping
|
|
111
|
+
// Color mapping - using undefined for transparent backgrounds
|
|
112
112
|
const colorMap = {
|
|
113
113
|
primary: {
|
|
114
114
|
filled: { bg: tokens.colors.primary, text: tokens.colors.onPrimary, border: tokens.colors.primary },
|
|
115
|
-
outlined: { bg:
|
|
116
|
-
soft: { bg: tokens.colors.primaryContainer, text: tokens.colors.onPrimaryContainer, border:
|
|
115
|
+
outlined: { bg: undefined, text: tokens.colors.primary, border: tokens.colors.primary },
|
|
116
|
+
soft: { bg: tokens.colors.primaryContainer, text: tokens.colors.onPrimaryContainer, border: undefined },
|
|
117
117
|
},
|
|
118
118
|
secondary: {
|
|
119
119
|
filled: { bg: tokens.colors.secondary, text: tokens.colors.onSecondary, border: tokens.colors.secondary },
|
|
120
|
-
outlined: { bg:
|
|
121
|
-
soft: { bg: tokens.colors.secondaryContainer, text: tokens.colors.onSecondaryContainer, border:
|
|
120
|
+
outlined: { bg: undefined, text: tokens.colors.secondary, border: tokens.colors.secondary },
|
|
121
|
+
soft: { bg: tokens.colors.secondaryContainer, text: tokens.colors.onSecondaryContainer, border: undefined },
|
|
122
122
|
},
|
|
123
123
|
success: {
|
|
124
124
|
filled: { bg: tokens.colors.success, text: tokens.colors.onSuccess, border: tokens.colors.success },
|
|
125
|
-
outlined: { bg:
|
|
126
|
-
soft: { bg: tokens.colors.successContainer, text: tokens.colors.onSuccessContainer, border:
|
|
125
|
+
outlined: { bg: undefined, text: tokens.colors.success, border: tokens.colors.success },
|
|
126
|
+
soft: { bg: tokens.colors.successContainer, text: tokens.colors.onSuccessContainer, border: undefined },
|
|
127
127
|
},
|
|
128
128
|
warning: {
|
|
129
129
|
filled: { bg: tokens.colors.warning, text: tokens.colors.onWarning, border: tokens.colors.warning },
|
|
130
|
-
outlined: { bg:
|
|
131
|
-
soft: { bg: tokens.colors.warningContainer, text: tokens.colors.onWarningContainer, border:
|
|
130
|
+
outlined: { bg: undefined, text: tokens.colors.warning, border: tokens.colors.warning },
|
|
131
|
+
soft: { bg: tokens.colors.warningContainer, text: tokens.colors.onWarningContainer, border: undefined },
|
|
132
132
|
},
|
|
133
133
|
error: {
|
|
134
134
|
filled: { bg: tokens.colors.error, text: tokens.colors.onError, border: tokens.colors.error },
|
|
135
|
-
outlined: { bg:
|
|
136
|
-
soft: { bg: tokens.colors.errorContainer, text: tokens.colors.onErrorContainer, border:
|
|
135
|
+
outlined: { bg: undefined, text: tokens.colors.error, border: tokens.colors.error },
|
|
136
|
+
soft: { bg: tokens.colors.errorContainer, text: tokens.colors.onErrorContainer, border: undefined },
|
|
137
137
|
},
|
|
138
138
|
info: {
|
|
139
139
|
filled: { bg: tokens.colors.info, text: tokens.colors.onInfo, border: tokens.colors.info },
|
|
140
|
-
outlined: { bg:
|
|
141
|
-
soft: { bg: tokens.colors.infoContainer, text: tokens.colors.onInfoContainer, border:
|
|
140
|
+
outlined: { bg: undefined, text: tokens.colors.info, border: tokens.colors.info },
|
|
141
|
+
soft: { bg: tokens.colors.infoContainer, text: tokens.colors.onInfoContainer, border: undefined },
|
|
142
142
|
},
|
|
143
143
|
};
|
|
144
144
|
|
package/src/atoms/AtomicIcon.tsx
CHANGED
|
@@ -122,17 +122,10 @@ export const AtomicIcon: React.FC<AtomicIconProps> = React.memo(({
|
|
|
122
122
|
? getSemanticColor(color, tokens)
|
|
123
123
|
: tokens.colors.textPrimary;
|
|
124
124
|
|
|
125
|
-
// Validate icon
|
|
125
|
+
// Validate icon - use fallback silently if invalid
|
|
126
126
|
const isValidIcon = name in Ionicons.glyphMap;
|
|
127
127
|
const iconName = isValidIcon ? name : FALLBACK_ICON;
|
|
128
128
|
|
|
129
|
-
if (__DEV__ && !isValidIcon) {
|
|
130
|
-
console.warn(
|
|
131
|
-
`[AtomicIcon] Invalid icon name: "${name}". Using fallback icon "${FALLBACK_ICON}". ` +
|
|
132
|
-
`Available icons: https://icons.expo.fyi/`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
129
|
const iconElement = (
|
|
137
130
|
<Ionicons
|
|
138
131
|
name={iconName as keyof typeof Ionicons.glyphMap}
|
|
@@ -73,7 +73,7 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
|
|
|
73
73
|
onChange,
|
|
74
74
|
options,
|
|
75
75
|
label,
|
|
76
|
-
placeholder
|
|
76
|
+
placeholder,
|
|
77
77
|
error,
|
|
78
78
|
disabled = false,
|
|
79
79
|
multiple = false,
|
|
@@ -82,9 +82,10 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
|
|
|
82
82
|
autoClose = true,
|
|
83
83
|
size = 'md',
|
|
84
84
|
modalTitle,
|
|
85
|
-
emptyMessage
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
emptyMessage,
|
|
86
|
+
searchPlaceholder,
|
|
87
|
+
clearAccessibilityLabel,
|
|
88
|
+
closeAccessibilityLabel,
|
|
88
89
|
style,
|
|
89
90
|
labelStyle,
|
|
90
91
|
testID,
|
|
@@ -294,7 +295,7 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
|
|
|
294
295
|
filteredOptions={filteredOptions}
|
|
295
296
|
multiple={multiple}
|
|
296
297
|
emptyMessage={emptyMessage}
|
|
297
|
-
searchPlaceholder=
|
|
298
|
+
searchPlaceholder={searchPlaceholder}
|
|
298
299
|
closeAccessibilityLabel={closeAccessibilityLabel}
|
|
299
300
|
testID={testID}
|
|
300
301
|
/>
|
|
@@ -18,12 +18,6 @@ jest.mock('@umituz/react-native-design-system-theme', () => ({
|
|
|
18
18
|
}),
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
|
-
// Mock Lucide icons
|
|
22
|
-
jest.mock('lucide-react-native', () => ({
|
|
23
|
-
Settings: () => 'Settings-Icon',
|
|
24
|
-
Heart: () => 'Heart-Icon',
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
21
|
describe('AtomicIcon', () => {
|
|
28
22
|
it('renders with default props', () => {
|
|
29
23
|
const { getByTestId } = render(
|
|
@@ -35,17 +35,11 @@ export const useInputState = ({
|
|
|
35
35
|
const [isPasswordVisible, setIsPasswordVisible] = useState(!secureTextEntry);
|
|
36
36
|
|
|
37
37
|
const handleTextChange = useCallback((text: string) => {
|
|
38
|
-
if (__DEV__) {
|
|
39
|
-
console.log('[useInputState] Text changed:', { text, length: text.length });
|
|
40
|
-
}
|
|
41
38
|
setLocalValue(text);
|
|
42
39
|
onChangeText?.(text);
|
|
43
40
|
}, [onChangeText]);
|
|
44
41
|
|
|
45
42
|
const togglePasswordVisibility = useCallback(() => {
|
|
46
|
-
if (__DEV__) {
|
|
47
|
-
console.log('[useInputState] Password visibility toggled');
|
|
48
|
-
}
|
|
49
43
|
setIsPasswordVisible((prev) => !prev);
|
|
50
44
|
}, []);
|
|
51
45
|
|
|
@@ -38,6 +38,12 @@ import {
|
|
|
38
38
|
getEmptyStateTextStyles,
|
|
39
39
|
} from '../styles/pickerStyles';
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* PickerModalProps
|
|
43
|
+
*
|
|
44
|
+
* IMPORTANT: String props are REQUIRED for proper i18n support.
|
|
45
|
+
* Pass translated strings from your app's i18n system.
|
|
46
|
+
*/
|
|
41
47
|
interface PickerModalProps {
|
|
42
48
|
visible: boolean;
|
|
43
49
|
onClose: () => void;
|
|
@@ -50,9 +56,12 @@ interface PickerModalProps {
|
|
|
50
56
|
onSearchChange: (query: string) => void;
|
|
51
57
|
filteredOptions: PickerOption[];
|
|
52
58
|
multiple?: boolean;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
/** Empty state message - REQUIRED for i18n */
|
|
60
|
+
emptyMessage: string;
|
|
61
|
+
/** Search placeholder - REQUIRED for i18n */
|
|
62
|
+
searchPlaceholder: string;
|
|
63
|
+
/** Close accessibility label - REQUIRED for i18n */
|
|
64
|
+
closeAccessibilityLabel: string;
|
|
56
65
|
testID?: string;
|
|
57
66
|
}
|
|
58
67
|
|
|
@@ -66,9 +75,9 @@ export const PickerModal: React.FC<PickerModalProps> = React.memo(({
|
|
|
66
75
|
searchQuery,
|
|
67
76
|
onSearchChange,
|
|
68
77
|
filteredOptions,
|
|
69
|
-
emptyMessage
|
|
70
|
-
searchPlaceholder
|
|
71
|
-
closeAccessibilityLabel
|
|
78
|
+
emptyMessage,
|
|
79
|
+
searchPlaceholder,
|
|
80
|
+
closeAccessibilityLabel,
|
|
72
81
|
testID,
|
|
73
82
|
}) => {
|
|
74
83
|
const tokens = useAppDesignTokens();
|
|
@@ -18,12 +18,20 @@ export interface PickerOption {
|
|
|
18
18
|
|
|
19
19
|
export type PickerSize = 'sm' | 'md' | 'lg';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* AtomicPickerProps
|
|
23
|
+
*
|
|
24
|
+
* IMPORTANT: String props (placeholder, emptyMessage, etc.) are REQUIRED
|
|
25
|
+
* for proper i18n support. Pass translated strings from your app's i18n system.
|
|
26
|
+
* Do NOT rely on default English values.
|
|
27
|
+
*/
|
|
21
28
|
export interface AtomicPickerProps {
|
|
22
29
|
value: string | string[];
|
|
23
30
|
onChange: (value: string | string[]) => void;
|
|
24
31
|
options: PickerOption[];
|
|
25
32
|
label?: string;
|
|
26
|
-
|
|
33
|
+
/** Placeholder text - REQUIRED for i18n, pass translated string */
|
|
34
|
+
placeholder: string;
|
|
27
35
|
error?: string;
|
|
28
36
|
disabled?: boolean;
|
|
29
37
|
multiple?: boolean;
|
|
@@ -33,9 +41,14 @@ export interface AtomicPickerProps {
|
|
|
33
41
|
color?: IconColor;
|
|
34
42
|
size?: PickerSize;
|
|
35
43
|
modalTitle?: string;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
/** Empty state message - REQUIRED for i18n, pass translated string */
|
|
45
|
+
emptyMessage: string;
|
|
46
|
+
/** Search placeholder text - REQUIRED for i18n, pass translated string */
|
|
47
|
+
searchPlaceholder: string;
|
|
48
|
+
/** Clear button accessibility label - REQUIRED for i18n, pass translated string */
|
|
49
|
+
clearAccessibilityLabel: string;
|
|
50
|
+
/** Close button accessibility label - REQUIRED for i18n, pass translated string */
|
|
51
|
+
closeAccessibilityLabel: string;
|
|
39
52
|
style?: ViewStyle | ViewStyle[];
|
|
40
53
|
labelStyle?: TextStyle | TextStyle[];
|
|
41
54
|
testID?: string;
|
|
@@ -13,20 +13,15 @@ import { validateScreenDimensions } from '../../responsive/validation';
|
|
|
13
13
|
* Helper function for device detection with fallback
|
|
14
14
|
* @param operation - Operation to perform
|
|
15
15
|
* @param fallback - Fallback value if operation fails
|
|
16
|
-
* @param warningMessage - Warning message for __DEV__
|
|
17
16
|
* @returns Operation result or fallback
|
|
18
17
|
*/
|
|
19
18
|
const withDeviceDetectionFallback = <T>(
|
|
20
19
|
operation: () => T,
|
|
21
|
-
fallback: T
|
|
22
|
-
warningMessage: string
|
|
20
|
+
fallback: T
|
|
23
21
|
): T => {
|
|
24
22
|
try {
|
|
25
23
|
return operation();
|
|
26
24
|
} catch {
|
|
27
|
-
if (__DEV__) {
|
|
28
|
-
console.warn(`[DeviceDetection] ${warningMessage}`);
|
|
29
|
-
}
|
|
30
25
|
return fallback;
|
|
31
26
|
}
|
|
32
27
|
};
|
|
@@ -53,9 +48,6 @@ export const getScreenDimensions = () => {
|
|
|
53
48
|
validateScreenDimensions(width, height);
|
|
54
49
|
return { width, height };
|
|
55
50
|
} catch {
|
|
56
|
-
if (__DEV__) {
|
|
57
|
-
console.warn('[getScreenDimensions] Invalid screen dimensions detected, using fallback values');
|
|
58
|
-
}
|
|
59
51
|
// Fallback to safe default dimensions
|
|
60
52
|
return { width: 414, height: 896 };
|
|
61
53
|
}
|
|
@@ -71,8 +63,7 @@ export const isSmallPhone = (): boolean => {
|
|
|
71
63
|
const { width } = getScreenDimensions();
|
|
72
64
|
return width <= DEVICE_BREAKPOINTS.SMALL_PHONE;
|
|
73
65
|
},
|
|
74
|
-
false
|
|
75
|
-
'Error detecting device type, assuming standard phone'
|
|
66
|
+
false
|
|
76
67
|
);
|
|
77
68
|
};
|
|
78
69
|
|
|
@@ -86,8 +77,7 @@ export const isTablet = (): boolean => {
|
|
|
86
77
|
const { width } = getScreenDimensions();
|
|
87
78
|
return width >= DEVICE_BREAKPOINTS.SMALL_TABLET;
|
|
88
79
|
},
|
|
89
|
-
false
|
|
90
|
-
'Error detecting device type, assuming phone'
|
|
80
|
+
false
|
|
91
81
|
);
|
|
92
82
|
};
|
|
93
83
|
|
|
@@ -101,8 +91,7 @@ export const isLandscape = (): boolean => {
|
|
|
101
91
|
const { width, height } = getScreenDimensions();
|
|
102
92
|
return width > height;
|
|
103
93
|
},
|
|
104
|
-
false
|
|
105
|
-
'Error detecting orientation, assuming portrait'
|
|
94
|
+
false
|
|
106
95
|
);
|
|
107
96
|
};
|
|
108
97
|
|
|
@@ -125,15 +114,14 @@ export const getDeviceType = (): DeviceType => {
|
|
|
125
114
|
|
|
126
115
|
return DeviceType.TABLET;
|
|
127
116
|
},
|
|
128
|
-
DeviceType.MEDIUM_PHONE
|
|
129
|
-
'Error detecting device type, assuming medium phone'
|
|
117
|
+
DeviceType.MEDIUM_PHONE
|
|
130
118
|
);
|
|
131
119
|
};
|
|
132
120
|
|
|
133
121
|
/**
|
|
134
122
|
* Responsive spacing multiplier
|
|
135
123
|
* Returns a multiplier for spacing based on device size
|
|
136
|
-
*
|
|
124
|
+
*
|
|
137
125
|
* @returns Spacing multiplier (0.9-1.2)
|
|
138
126
|
*/
|
|
139
127
|
export const getSpacingMultiplier = (): number => {
|
|
@@ -149,7 +137,6 @@ export const getSpacingMultiplier = (): number => {
|
|
|
149
137
|
|
|
150
138
|
return LAYOUT_CONSTANTS.SPACING_MULTIPLIER_STANDARD;
|
|
151
139
|
},
|
|
152
|
-
LAYOUT_CONSTANTS.SPACING_MULTIPLIER_STANDARD
|
|
153
|
-
'Error calculating spacing multiplier, using fallback'
|
|
140
|
+
LAYOUT_CONSTANTS.SPACING_MULTIPLIER_STANDARD
|
|
154
141
|
);
|
|
155
142
|
};
|
|
@@ -74,9 +74,8 @@ const ScreenHeaderBackButton: React.FC<{
|
|
|
74
74
|
const handleBackPress = React.useCallback(() => {
|
|
75
75
|
if (onBackPress) {
|
|
76
76
|
onBackPress();
|
|
77
|
-
} else if (__DEV__) {
|
|
78
|
-
console.warn('ScreenHeader: onBackPress is required when back button is visible');
|
|
79
77
|
}
|
|
78
|
+
// If no onBackPress provided, do nothing silently
|
|
80
79
|
}, [onBackPress]);
|
|
81
80
|
|
|
82
81
|
if (hideBackButton) return null;
|
|
@@ -48,7 +48,7 @@ export function AlertToast({ alert }: AlertToastProps) {
|
|
|
48
48
|
return { backgroundColor: tokens.colors.backgroundPrimary };
|
|
49
49
|
case 'secondary':
|
|
50
50
|
return {
|
|
51
|
-
backgroundColor:
|
|
51
|
+
backgroundColor: undefined,
|
|
52
52
|
borderWidth: 1,
|
|
53
53
|
borderColor: tokens.colors.textInverse,
|
|
54
54
|
};
|
|
@@ -38,9 +38,7 @@ export const useFireworks = (config: FireworksConfig) => {
|
|
|
38
38
|
} = config;
|
|
39
39
|
|
|
40
40
|
if (!colors || colors.length === 0) {
|
|
41
|
-
|
|
42
|
-
console.warn('useFireworks: colors array is required and cannot be empty');
|
|
43
|
-
}
|
|
41
|
+
// Return no-op when colors not provided
|
|
44
42
|
return { particles: [], trigger: () => { }, isActive: false };
|
|
45
43
|
}
|
|
46
44
|
|
|
@@ -50,9 +50,7 @@ export const AnimationThemeProvider: React.FC<AnimationThemeProviderProps> = ({
|
|
|
50
50
|
export const useAnimationTheme = (): ThemeContext => {
|
|
51
51
|
const context = useContext(ThemeContext);
|
|
52
52
|
if (!context) {
|
|
53
|
-
if
|
|
54
|
-
console.warn('useAnimationTheme must be used within AnimationThemeProvider');
|
|
55
|
-
}
|
|
53
|
+
// Return default theme if used outside provider
|
|
56
54
|
return {
|
|
57
55
|
theme: DEFAULT_ANIMATION_THEME,
|
|
58
56
|
setTheme: () => {},
|
|
@@ -129,7 +129,7 @@ export const Avatar: React.FC<AvatarProps> = ({
|
|
|
129
129
|
styles.initials,
|
|
130
130
|
{
|
|
131
131
|
fontSize: config.fontSize,
|
|
132
|
-
color:
|
|
132
|
+
color: tokens.colors.textInverse,
|
|
133
133
|
},
|
|
134
134
|
]}
|
|
135
135
|
>
|
|
@@ -143,7 +143,7 @@ export const Avatar: React.FC<AvatarProps> = ({
|
|
|
143
143
|
<AtomicIcon
|
|
144
144
|
name={icon}
|
|
145
145
|
customSize={config.iconSize}
|
|
146
|
-
customColor=
|
|
146
|
+
customColor={tokens.colors.textInverse}
|
|
147
147
|
/>
|
|
148
148
|
);
|
|
149
149
|
};
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
declare const __DEV__: boolean;
|
|
2
|
-
|
|
3
1
|
import React, { forwardRef, useCallback, useMemo, useImperativeHandle, useRef } from 'react';
|
|
4
2
|
import { StyleSheet } from 'react-native';
|
|
5
3
|
import {
|
|
@@ -78,10 +76,6 @@ export const BottomSheetModal = forwardRef<BottomSheetModalRef, BottomSheetModal
|
|
|
78
76
|
|
|
79
77
|
useImperativeHandle(ref, () => ({
|
|
80
78
|
present: () => {
|
|
81
|
-
if (__DEV__) {
|
|
82
|
-
console.log('[BottomSheetModal] present() called');
|
|
83
|
-
console.log('[BottomSheetModal] modalRef.current:', modalRef.current);
|
|
84
|
-
}
|
|
85
79
|
modalRef.current?.present();
|
|
86
80
|
},
|
|
87
81
|
dismiss: () => modalRef.current?.dismiss(),
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* - Timezone-aware via CalendarService
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { create
|
|
15
|
-
import { persist, createJSONStorage
|
|
14
|
+
import { create } from 'zustand';
|
|
15
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
16
16
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
17
17
|
import type { CalendarEvent, CreateCalendarEventRequest, UpdateCalendarEventRequest } from '../../domain/entities/CalendarEvent.entity';
|
|
18
18
|
import { CalendarService } from '../services/CalendarService';
|
|
@@ -22,14 +22,12 @@ export interface StackNavigatorProps<T extends ParamListBase> {
|
|
|
22
22
|
export function StackNavigator<T extends ParamListBase>({ config }: StackNavigatorProps<T>) {
|
|
23
23
|
const tokens = useAppDesignTokens();
|
|
24
24
|
|
|
25
|
-
// Validate configuration
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (__DEV__) console.error('[StackNavigator] Configuration validation failed:', error);
|
|
32
|
-
}
|
|
25
|
+
// Validate configuration silently
|
|
26
|
+
try {
|
|
27
|
+
NavigationValidator.validateScreens(config.screens, "stack");
|
|
28
|
+
NavigationValidator.validateInitialRoute(config.initialRouteName, config.screens);
|
|
29
|
+
} catch {
|
|
30
|
+
// Silent validation failure
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
const { screens } = config;
|
|
@@ -55,7 +53,6 @@ export function StackNavigator<T extends ParamListBase>({ config }: StackNavigat
|
|
|
55
53
|
}), [tokens]);
|
|
56
54
|
|
|
57
55
|
if (screens.length === 0) {
|
|
58
|
-
if (__DEV__) console.warn('[StackNavigator] No screens found');
|
|
59
56
|
return null;
|
|
60
57
|
}
|
|
61
58
|
|
|
@@ -24,18 +24,15 @@ export function TabsNavigator<T extends ParamListBase>({
|
|
|
24
24
|
}: TabsNavigatorProps<T>) {
|
|
25
25
|
const tokens = useAppDesignTokens();
|
|
26
26
|
|
|
27
|
-
// Validate configuration
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (__DEV__)
|
|
37
|
-
console.error("[TabsNavigator] Configuration validation failed:", error);
|
|
38
|
-
}
|
|
27
|
+
// Validate configuration silently
|
|
28
|
+
try {
|
|
29
|
+
NavigationValidator.validateScreens(config.screens, "tab");
|
|
30
|
+
NavigationValidator.validateInitialRoute(
|
|
31
|
+
config.initialRouteName,
|
|
32
|
+
config.screens
|
|
33
|
+
);
|
|
34
|
+
} catch {
|
|
35
|
+
// Silent validation failure
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
// Memoize filtered screens
|
|
@@ -74,7 +71,6 @@ export function TabsNavigator<T extends ParamListBase>({
|
|
|
74
71
|
);
|
|
75
72
|
|
|
76
73
|
if (visibleScreens.length === 0) {
|
|
77
|
-
if (__DEV__) console.warn("[TabsNavigator] No visible screens found");
|
|
78
74
|
return null;
|
|
79
75
|
}
|
|
80
76
|
|
|
@@ -31,8 +31,6 @@ export class AppNavigation {
|
|
|
31
31
|
static navigate(name: string, params?: object): void {
|
|
32
32
|
if (this.navigationRef?.isReady()) {
|
|
33
33
|
this.navigationRef.navigate(name, params);
|
|
34
|
-
} else if (__DEV__) {
|
|
35
|
-
console.warn('[AppNavigation] Navigation ref is not ready. Call setRef() first.');
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
36
|
|
|
@@ -28,10 +28,7 @@ export class LabelProcessor {
|
|
|
28
28
|
try {
|
|
29
29
|
const processedLabel = getLabel(label);
|
|
30
30
|
result = typeof processedLabel === "string" ? processedLabel : label;
|
|
31
|
-
} catch
|
|
32
|
-
if (__DEV__) {
|
|
33
|
-
console.error(`[LabelProcessor] Error processing label: ${label}`, error);
|
|
34
|
-
}
|
|
31
|
+
} catch {
|
|
35
32
|
result = label;
|
|
36
33
|
}
|
|
37
34
|
}
|
|
@@ -45,12 +42,6 @@ export class LabelProcessor {
|
|
|
45
42
|
}
|
|
46
43
|
this.labelCache.set(cacheKey, result);
|
|
47
44
|
|
|
48
|
-
// Log cache performance in development
|
|
49
|
-
if (__DEV__ && (this.cacheHits + this.cacheMisses) % 100 === 0) {
|
|
50
|
-
const hitRate = (this.cacheHits / (this.cacheHits + this.cacheMisses)) * 100;
|
|
51
|
-
console.log(`[LabelProcessor] Cache hit rate: ${hitRate.toFixed(1)}%`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
45
|
return result;
|
|
55
46
|
}
|
|
56
47
|
|
|
@@ -36,10 +36,8 @@ export class NavigationCleanupManager {
|
|
|
36
36
|
unsubscribers.forEach(unsubscribe => {
|
|
37
37
|
try {
|
|
38
38
|
unsubscribe();
|
|
39
|
-
} catch
|
|
40
|
-
|
|
41
|
-
console.warn('[NavigationCleanupManager] Error during cleanup:', error);
|
|
42
|
-
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Silent cleanup error handling
|
|
43
41
|
}
|
|
44
42
|
});
|
|
45
43
|
};
|
|
@@ -52,10 +50,8 @@ export class NavigationCleanupManager {
|
|
|
52
50
|
return () => {
|
|
53
51
|
try {
|
|
54
52
|
unsubscribe();
|
|
55
|
-
} catch
|
|
56
|
-
|
|
57
|
-
console.warn('[NavigationCleanupManager] Error during cleanup:', error);
|
|
58
|
-
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Silent cleanup error handling
|
|
59
55
|
}
|
|
60
56
|
};
|
|
61
57
|
}
|
|
@@ -7,9 +7,6 @@ export class NavigationValidator {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
if (screens.length === 0) {
|
|
10
|
-
if (__DEV__) {
|
|
11
|
-
console.warn(`[NavigationValidator] No screens provided for ${type} navigator`);
|
|
12
|
-
}
|
|
13
10
|
return;
|
|
14
11
|
}
|
|
15
12
|
|
|
@@ -43,11 +40,8 @@ export class NavigationValidator {
|
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
console.warn(`[NavigationValidator] Tab screen '${screen.name}' has invalid icon, it will be ignored`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
43
|
+
// Invalid icon check - silently handled
|
|
44
|
+
void (tabScreen.icon !== undefined && (typeof tabScreen.icon !== "string" || tabScreen.icon.trim() === ""));
|
|
51
45
|
}
|
|
52
46
|
});
|
|
53
47
|
}
|
|
@@ -55,9 +49,6 @@ export class NavigationValidator {
|
|
|
55
49
|
static validateInitialRoute(initialRouteName: string | undefined, screens: TabScreen[] | StackScreen[]): void {
|
|
56
50
|
if (initialRouteName && !screens.find(screen => screen.name === initialRouteName)) {
|
|
57
51
|
const error = `Initial route '${initialRouteName}' not found in screens. Available screens: ${screens.map(s => s.name).join(", ")}`;
|
|
58
|
-
if (__DEV__) {
|
|
59
|
-
console.error(`[NavigationValidator] ${error}`);
|
|
60
|
-
}
|
|
61
52
|
throw new Error(error);
|
|
62
53
|
}
|
|
63
54
|
}
|
|
@@ -18,10 +18,6 @@ export function createAdvancedVariants<T extends Record<string, Record<string, S
|
|
|
18
18
|
return (props: VariantProps<T> = {}): Style => {
|
|
19
19
|
let result = baseVariantFn(props);
|
|
20
20
|
|
|
21
|
-
if (__DEV__) {
|
|
22
|
-
console.log('[Design System] Creating advanced variant with compound conditions');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
21
|
if (config.compoundVariants) {
|
|
26
22
|
config.compoundVariants.forEach(compound => {
|
|
27
23
|
const conditionsMet = Object.entries(compound.conditions).every(
|
|
@@ -18,10 +18,6 @@ export function createVariants<T extends Record<string, Record<string, Style>>>(
|
|
|
18
18
|
return (props: VariantProps<T> = {}): Style => {
|
|
19
19
|
let result: Style = { ...config.base };
|
|
20
20
|
|
|
21
|
-
if (__DEV__) {
|
|
22
|
-
console.log('[Design System] Creating variant with props:', props);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
21
|
// Apply default variants
|
|
26
22
|
if (config.defaultVariants) {
|
|
27
23
|
Object.entries(config.defaultVariants).forEach(([variantKey, defaultValue]) => {
|
|
@@ -14,9 +14,6 @@ export function conditionalStyle<T extends Style>(
|
|
|
14
14
|
trueStyle: T,
|
|
15
15
|
falseStyle?: T
|
|
16
16
|
): T | undefined {
|
|
17
|
-
if (__DEV__) {
|
|
18
|
-
console.log('[Design System] Conditional style applied:', condition);
|
|
19
|
-
}
|
|
20
17
|
return condition ? trueStyle : falseStyle;
|
|
21
18
|
}
|
|
22
19
|
|
|
@@ -25,9 +22,6 @@ export function responsiveStyle<T extends Style>(
|
|
|
25
22
|
medium?: T,
|
|
26
23
|
large?: T
|
|
27
24
|
): T {
|
|
28
|
-
if (__DEV__) {
|
|
29
|
-
console.log('[Design System] Responsive style - using small breakpoint');
|
|
30
|
-
}
|
|
31
25
|
void medium;
|
|
32
26
|
void large;
|
|
33
27
|
return small;
|
|
@@ -36,16 +36,6 @@ export const SafeAreaProvider: React.FC<SafeAreaProviderProps> = ({
|
|
|
36
36
|
}) => {
|
|
37
37
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
38
38
|
|
|
39
|
-
if (__DEV__) {
|
|
40
|
-
if (config) {
|
|
41
|
-
Object.entries(config).forEach(([key, value]) => {
|
|
42
|
-
if (typeof value !== 'number' && typeof value !== 'boolean') {
|
|
43
|
-
console.warn(`SafeAreaProvider: ${key} must be a number or boolean, got ${typeof value}`);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
39
|
return (
|
|
50
40
|
<SafeAreaConfigContext.Provider value={mergedConfig}>
|
|
51
41
|
<NativeSafeAreaProvider {...nativeProps}>
|
|
@@ -39,33 +39,9 @@ export const validateNumericInput = (
|
|
|
39
39
|
return isValid;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
const WARNING_THROTTLE = 1000; // 1 second
|
|
46
|
-
|
|
47
|
-
export const throttledWarn = (message: string): void => {
|
|
48
|
-
if (!__DEV__) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const now = Date.now();
|
|
53
|
-
const lastTime = warningTimes.get(message) || 0;
|
|
54
|
-
|
|
55
|
-
if (now - lastTime > WARNING_THROTTLE) {
|
|
56
|
-
console.warn(message);
|
|
57
|
-
warningTimes.set(message, now);
|
|
58
|
-
|
|
59
|
-
// Clean up old entries to prevent memory leaks
|
|
60
|
-
if (warningTimes.size > 50) {
|
|
61
|
-
const cutoffTime = now - WARNING_THROTTLE * 10;
|
|
62
|
-
for (const [key, time] of warningTimes.entries()) {
|
|
63
|
-
if (time < cutoffTime) {
|
|
64
|
-
warningTimes.delete(key);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
42
|
+
export const throttledWarn = (_message: string): void => {
|
|
43
|
+
// Silent validation - no console output in production
|
|
44
|
+
void _message;
|
|
69
45
|
};
|
|
70
46
|
|
|
71
47
|
// Cleanup function to clear validation cache
|
|
@@ -31,16 +31,13 @@ export interface CustomThemeColors {
|
|
|
31
31
|
*/
|
|
32
32
|
export const validateCustomColors = (customColors: CustomThemeColors): boolean => {
|
|
33
33
|
const colorValues = Object.values(customColors).filter(Boolean) as string[];
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
for (const color of colorValues) {
|
|
36
36
|
if (!isValidHexColor(color)) {
|
|
37
|
-
if (__DEV__) {
|
|
38
|
-
console.warn('[validateCustomColors] Invalid hex color:', color);
|
|
39
|
-
}
|
|
40
37
|
return false;
|
|
41
38
|
}
|
|
42
39
|
}
|
|
43
|
-
|
|
40
|
+
|
|
44
41
|
return true;
|
|
45
42
|
};
|
|
46
43
|
|
|
@@ -60,9 +57,6 @@ export const applyCustomColors = (
|
|
|
60
57
|
|
|
61
58
|
// Validate custom colors
|
|
62
59
|
if (!validateCustomColors(customColors)) {
|
|
63
|
-
if (__DEV__) {
|
|
64
|
-
console.error('[applyCustomColors] Invalid custom colors provided, using defaults');
|
|
65
|
-
}
|
|
66
60
|
return palette;
|
|
67
61
|
}
|
|
68
62
|
|
|
@@ -27,16 +27,10 @@ export const isValidHexColor = (hexColor: string): boolean => {
|
|
|
27
27
|
*/
|
|
28
28
|
export const withAlpha = (hexColor: string, alpha: number): string => {
|
|
29
29
|
if (!isValidHexColor(hexColor)) {
|
|
30
|
-
if (__DEV__) {
|
|
31
|
-
console.warn('[withAlpha] Invalid hex color format:', hexColor);
|
|
32
|
-
}
|
|
33
30
|
return hexColor;
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
if (typeof alpha !== 'number' || alpha < 0 || alpha > 1) {
|
|
37
|
-
if (__DEV__) {
|
|
38
|
-
console.warn('[withAlpha] Invalid alpha value, must be 0-1:', alpha);
|
|
39
|
-
}
|
|
40
34
|
return hexColor;
|
|
41
35
|
}
|
|
42
36
|
|
|
@@ -28,15 +28,8 @@ export class ThemeStorage {
|
|
|
28
28
|
return value as ThemeMode;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
if (__DEV__) {
|
|
32
|
-
console.warn('[ThemeStorage] Invalid theme mode value stored:', value);
|
|
33
|
-
}
|
|
34
31
|
return null;
|
|
35
|
-
} catch
|
|
36
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
37
|
-
if (__DEV__) {
|
|
38
|
-
console.error('[ThemeStorage] Error getting theme mode:', errorMessage);
|
|
39
|
-
}
|
|
32
|
+
} catch {
|
|
40
33
|
// Return null instead of throwing to prevent app crashes
|
|
41
34
|
return null;
|
|
42
35
|
}
|
|
@@ -55,9 +48,6 @@ export class ThemeStorage {
|
|
|
55
48
|
await AsyncStorage.setItem(STORAGE_KEY, mode);
|
|
56
49
|
} catch (error) {
|
|
57
50
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
58
|
-
if (__DEV__) {
|
|
59
|
-
console.error('[ThemeStorage] Error saving theme mode:', errorMessage);
|
|
60
|
-
}
|
|
61
51
|
// Re-throw validation errors but swallow storage errors to prevent app crashes
|
|
62
52
|
if (errorMessage.includes('Invalid theme mode')) {
|
|
63
53
|
throw error;
|
|
@@ -71,11 +61,7 @@ export class ThemeStorage {
|
|
|
71
61
|
static async clearThemeMode(): Promise<void> {
|
|
72
62
|
try {
|
|
73
63
|
await AsyncStorage.removeItem(STORAGE_KEY);
|
|
74
|
-
} catch
|
|
75
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
76
|
-
if (__DEV__) {
|
|
77
|
-
console.error('[ThemeStorage] Error clearing theme mode:', errorMessage);
|
|
78
|
-
}
|
|
64
|
+
} catch {
|
|
79
65
|
// Don't throw - clearing storage is not critical
|
|
80
66
|
}
|
|
81
67
|
}
|
|
@@ -28,7 +28,6 @@ export class ClipboardUtils {
|
|
|
28
28
|
try {
|
|
29
29
|
await Clipboard.setStringAsync(text);
|
|
30
30
|
} catch (error) {
|
|
31
|
-
console.error('[ClipboardUtils] Failed to copy to clipboard:', error);
|
|
32
31
|
throw error;
|
|
33
32
|
}
|
|
34
33
|
}
|
|
@@ -40,7 +39,6 @@ export class ClipboardUtils {
|
|
|
40
39
|
try {
|
|
41
40
|
return await Clipboard.getStringAsync();
|
|
42
41
|
} catch (error) {
|
|
43
|
-
console.error('[ClipboardUtils] Failed to get from clipboard:', error);
|
|
44
42
|
throw error;
|
|
45
43
|
}
|
|
46
44
|
}
|
|
@@ -51,8 +49,7 @@ export class ClipboardUtils {
|
|
|
51
49
|
static async hasContent(): Promise<boolean> {
|
|
52
50
|
try {
|
|
53
51
|
return await Clipboard.hasStringAsync();
|
|
54
|
-
} catch
|
|
55
|
-
console.error('[ClipboardUtils] Failed to check clipboard:', error);
|
|
52
|
+
} catch {
|
|
56
53
|
return false;
|
|
57
54
|
}
|
|
58
55
|
}
|
|
@@ -64,7 +61,6 @@ export class ClipboardUtils {
|
|
|
64
61
|
try {
|
|
65
62
|
await Clipboard.setStringAsync('');
|
|
66
63
|
} catch (error) {
|
|
67
|
-
console.error('[ClipboardUtils] Failed to clear clipboard:', error);
|
|
68
64
|
throw error;
|
|
69
65
|
}
|
|
70
66
|
}
|