@umituz/react-native-design-system 4.25.42 → 4.25.44
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/package.json +1 -1
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +43 -22
- package/src/layouts/ScreenLayout/styles/screenLayoutStyles.ts +1 -1
- package/src/molecules/StepHeader/StepHeader.tsx +1 -1
- package/src/molecules/alerts/hooks/useAlertDismissHandler.ts +1 -1
- package/src/molecules/bottom-sheet/components/filter/FilterBottomSheet.tsx +1 -1
- package/src/molecules/navigation/TabsNavigator.tsx +1 -1
- package/src/molecules/navigation/hooks/useTabConfig.ts +2 -2
- package/src/molecules/splash/components/SplashScreen.tsx +1 -1
- package/src/onboarding/presentation/components/BackgroundVideo.tsx +0 -1
- package/src/responsive/padding/paddingUtils.ts +2 -3
- package/src/safe-area/index.ts +15 -18
- package/src/storage/presentation/hooks/useStore.ts +1 -8
- package/src/tanstack/presentation/hooks/usePrefetch.ts +2 -1
- package/src/theme/infrastructure/providers/DesignSystemProvider.tsx +11 -1
- package/src/typography/domain/entities/TypographyTypes.ts +1 -9
- package/src/typography/presentation/utils/textColorUtils.ts +1 -7
- package/src/utils/hooks/useAsyncOperation.ts +1 -1
- package/src/safe-area/__tests__/components/SafeAreaProvider.test.tsx +0 -15
- package/src/safe-area/__tests__/hooks/useContentSafeAreaPadding.test.tsx +0 -15
- package/src/safe-area/__tests__/hooks/useHeaderSafeAreaPadding.test.tsx +0 -15
- package/src/safe-area/__tests__/hooks/useSafeAreaInsets.test.tsx +0 -15
- package/src/safe-area/__tests__/hooks/useStatusBarSafeAreaPadding.test.tsx +0 -15
- package/src/safe-area/__tests__/integration/completeFlow.test.tsx +0 -26
- package/src/safe-area/__tests__/setup.ts +0 -50
- package/src/safe-area/__tests__/utils/performance.test.tsx +0 -18
- package/src/safe-area/__tests__/utils/testUtils.tsx +0 -44
- package/src/safe-area/components/SafeAreaProvider.tsx +0 -46
- package/src/safe-area/hooks/useContentSafeAreaPadding.ts +0 -41
- package/src/safe-area/hooks/useHeaderSafeAreaPadding.ts +0 -35
- package/src/safe-area/hooks/useStatusBarSafeAreaPadding.ts +0 -36
- package/src/safe-area/utils/optimization.ts +0 -49
- package/src/safe-area/utils/validation.ts +0 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.25.
|
|
3
|
+
"version": "4.25.44",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -5,28 +5,29 @@
|
|
|
5
5
|
|
|
6
6
|
import React, { useMemo } from 'react';
|
|
7
7
|
import { View, ScrollView } from 'react-native';
|
|
8
|
-
import {
|
|
8
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
9
9
|
import { useAppDesignTokens } from '../../theme';
|
|
10
10
|
import { getScreenLayoutConfig } from '../../responsive/responsiveLayout';
|
|
11
11
|
import { AtomicKeyboardAvoidingView } from '../../atoms';
|
|
12
12
|
import { getScreenLayoutStyles } from './styles/screenLayoutStyles';
|
|
13
13
|
import type { ScreenLayoutProps } from './types';
|
|
14
14
|
|
|
15
|
-
export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
export const ScreenLayout: React.FC<ScreenLayoutProps> = (props: ScreenLayoutProps) => {
|
|
16
|
+
const {
|
|
17
|
+
children,
|
|
18
|
+
scrollable = true,
|
|
19
|
+
edges = ['top', 'bottom', 'left', 'right'],
|
|
20
|
+
header,
|
|
21
|
+
footer,
|
|
22
|
+
backgroundColor,
|
|
23
|
+
containerStyle,
|
|
24
|
+
contentContainerStyle,
|
|
25
|
+
testID,
|
|
26
|
+
hideScrollIndicator = false,
|
|
27
|
+
keyboardAvoiding = false,
|
|
28
|
+
maxWidth,
|
|
29
|
+
refreshControl,
|
|
30
|
+
} = props;
|
|
30
31
|
const tokens = useAppDesignTokens();
|
|
31
32
|
const insets = useSafeAreaInsets();
|
|
32
33
|
|
|
@@ -49,13 +50,30 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
49
50
|
|
|
50
51
|
const bgColor = backgroundColor || tokens.colors.backgroundPrimary;
|
|
51
52
|
|
|
53
|
+
// Robust safe area handling
|
|
54
|
+
const paddingTop = edges.includes('top') ? insets.top : 0;
|
|
55
|
+
const paddingBottom = edges.includes('bottom') ? insets.bottom : 0;
|
|
56
|
+
const paddingLeft = edges.includes('left') ? insets.left : 0;
|
|
57
|
+
const paddingRight = edges.includes('right') ? insets.right : 0;
|
|
58
|
+
|
|
52
59
|
const content = (
|
|
53
|
-
<View style={
|
|
60
|
+
<View style={[
|
|
61
|
+
styles.responsiveWrapper,
|
|
62
|
+
{
|
|
63
|
+
paddingTop,
|
|
64
|
+
paddingBottom: footer ? 0 : paddingBottom,
|
|
65
|
+
paddingLeft,
|
|
66
|
+
paddingRight,
|
|
67
|
+
}
|
|
68
|
+
]}>
|
|
54
69
|
{header}
|
|
55
70
|
{scrollable ? (
|
|
56
71
|
<ScrollView
|
|
57
72
|
style={styles.scrollView}
|
|
58
|
-
contentContainerStyle={[
|
|
73
|
+
contentContainerStyle={[
|
|
74
|
+
styles.scrollContent,
|
|
75
|
+
contentContainerStyle,
|
|
76
|
+
]}
|
|
59
77
|
showsVerticalScrollIndicator={!hideScrollIndicator}
|
|
60
78
|
keyboardShouldPersistTaps={keyboardAvoiding ? 'handled' : 'never'}
|
|
61
79
|
refreshControl={refreshControl}
|
|
@@ -67,14 +85,17 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
67
85
|
{children}
|
|
68
86
|
</View>
|
|
69
87
|
)}
|
|
70
|
-
{footer
|
|
88
|
+
{footer && (
|
|
89
|
+
<View style={{ paddingBottom }}>
|
|
90
|
+
{footer}
|
|
91
|
+
</View>
|
|
92
|
+
)}
|
|
71
93
|
</View>
|
|
72
94
|
);
|
|
73
95
|
|
|
74
96
|
return (
|
|
75
|
-
<
|
|
97
|
+
<View
|
|
76
98
|
style={[styles.container, { backgroundColor: bgColor }, containerStyle]}
|
|
77
|
-
edges={edges}
|
|
78
99
|
testID={testID}
|
|
79
100
|
>
|
|
80
101
|
{keyboardAvoiding ? (
|
|
@@ -86,7 +107,7 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
86
107
|
{content}
|
|
87
108
|
</View>
|
|
88
109
|
)}
|
|
89
|
-
</
|
|
110
|
+
</View>
|
|
90
111
|
);
|
|
91
112
|
};
|
|
92
113
|
|
|
@@ -25,7 +25,7 @@ export const getScreenLayoutStyles = (
|
|
|
25
25
|
responsiveWrapper: {
|
|
26
26
|
flex: 1,
|
|
27
27
|
width: '100%',
|
|
28
|
-
...(maxWidth ? { maxWidth, alignSelf: '
|
|
28
|
+
...(maxWidth ? { maxWidth, alignSelf: 'center' as const } : {}),
|
|
29
29
|
},
|
|
30
30
|
content: {
|
|
31
31
|
flex: 1,
|
|
@@ -48,7 +48,7 @@ export const StepHeader: React.FC<StepHeaderProps> = ({
|
|
|
48
48
|
style,
|
|
49
49
|
}) => {
|
|
50
50
|
const tokens = useAppDesignTokens();
|
|
51
|
-
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
51
|
+
const cfg = useMemo(() => ({ ...DEFAULT_CONFIG, ...config }), [config]);
|
|
52
52
|
|
|
53
53
|
const styles = useMemo(
|
|
54
54
|
() =>
|
|
@@ -15,7 +15,7 @@ export function useAlertDismissHandler(alert: Alert) {
|
|
|
15
15
|
const handleDismiss = useCallback(() => {
|
|
16
16
|
dismissAlert(alert.id);
|
|
17
17
|
alert.onDismiss?.();
|
|
18
|
-
}, [alert
|
|
18
|
+
}, [alert, dismissAlert]);
|
|
19
19
|
|
|
20
20
|
return handleDismiss;
|
|
21
21
|
}
|
|
@@ -64,7 +64,7 @@ export const FilterBottomSheet = forwardRef<BottomSheetModalRef, FilterBottomShe
|
|
|
64
64
|
scrollView: { flex: 1 },
|
|
65
65
|
}), [tokens]);
|
|
66
66
|
|
|
67
|
-
const safeSelectedIds = selectedIds ?? [];
|
|
67
|
+
const safeSelectedIds = useMemo(() => selectedIds ?? [], [selectedIds]);
|
|
68
68
|
|
|
69
69
|
const renderOption = useCallback((option: FilterOption, categoryId: string) => {
|
|
70
70
|
const isSelected = safeSelectedIds.includes(option.id);
|
|
@@ -30,7 +30,7 @@ const getIconNameForState = (baseName: string, focused: boolean): string => {
|
|
|
30
30
|
export function useTabConfig<T extends ParamListBase>(props: UseTabConfigProps<T>): TabNavigatorConfig<T> {
|
|
31
31
|
const { config } = props;
|
|
32
32
|
const tokens = useAppDesignTokens();
|
|
33
|
-
const { tabBarConfig
|
|
33
|
+
const { tabBarConfig } = useResponsive();
|
|
34
34
|
|
|
35
35
|
const finalConfig: TabNavigatorConfig<T> = useMemo(() => {
|
|
36
36
|
const screens = config.screens as TabScreen<T>[];
|
|
@@ -109,7 +109,7 @@ export function useTabConfig<T extends ParamListBase>(props: UseTabConfigProps<T
|
|
|
109
109
|
: {}),
|
|
110
110
|
},
|
|
111
111
|
};
|
|
112
|
-
}, [tokens, config, tabBarConfig
|
|
112
|
+
}, [tokens, config, tabBarConfig]);
|
|
113
113
|
|
|
114
114
|
return finalConfig;
|
|
115
115
|
}
|
|
@@ -16,7 +16,6 @@ try {
|
|
|
16
16
|
const { useVideoPlayer, VideoView } = require('expo-video'); // eslint-disable-line @typescript-eslint/no-require-imports
|
|
17
17
|
|
|
18
18
|
BackgroundVideoImpl = ({ source, overlayOpacity = 0.5 }: BackgroundVideoProps) => {
|
|
19
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
20
19
|
const player = useVideoPlayer(source, (p: any) => {
|
|
21
20
|
p.loop = true;
|
|
22
21
|
p.play();
|
|
@@ -11,7 +11,6 @@ export const getResponsiveVerticalPadding = (
|
|
|
11
11
|
): number => {
|
|
12
12
|
try {
|
|
13
13
|
validateSafeAreaInsets(insets);
|
|
14
|
-
const { top = 0 } = insets;
|
|
15
14
|
const isTabletDevice = isTablet();
|
|
16
15
|
const isSmall = isSmallPhone();
|
|
17
16
|
const spacingMultiplier = getSpacingMultiplier();
|
|
@@ -27,8 +26,8 @@ export const getResponsiveVerticalPadding = (
|
|
|
27
26
|
// Apply spacing multiplier for consistency
|
|
28
27
|
const adjustedPadding = basePadding * spacingMultiplier;
|
|
29
28
|
|
|
30
|
-
//
|
|
31
|
-
return
|
|
29
|
+
// We now return the base padding; safe areas are handled by ScreenLayout
|
|
30
|
+
return adjustedPadding;
|
|
32
31
|
} catch {
|
|
33
32
|
return LAYOUT_CONSTANTS.VERTICAL_PADDING_STANDARD;
|
|
34
33
|
}
|
package/src/safe-area/index.ts
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Safe Area Module
|
|
3
|
-
*
|
|
3
|
+
* Refactored: Simplified to only provide essential safe area utilities.
|
|
4
|
+
*
|
|
5
|
+
* We use manual inset management in ScreenLayout for maximum stability
|
|
6
|
+
* across different React Native versions and devices.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
|
-
export { SafeAreaProvider, useSafeAreaConfig } from './components/SafeAreaProvider';
|
|
7
|
-
export type { SafeAreaProviderProps, SafeAreaConfig } from './components/SafeAreaProvider';
|
|
8
|
-
|
|
9
9
|
export { useSafeAreaInsets } from './hooks/useSafeAreaInsets';
|
|
10
|
-
export { useStatusBarSafeAreaPadding } from './hooks/useStatusBarSafeAreaPadding';
|
|
11
|
-
export type { StatusBarPaddingOptions } from './hooks/useStatusBarSafeAreaPadding';
|
|
12
|
-
|
|
13
|
-
export { useHeaderSafeAreaPadding } from './hooks/useHeaderSafeAreaPadding';
|
|
14
|
-
export type { HeaderPaddingOptions } from './hooks/useHeaderSafeAreaPadding';
|
|
15
|
-
|
|
16
|
-
export { useContentSafeAreaPadding } from './hooks/useContentSafeAreaPadding';
|
|
17
|
-
export type { ContentPaddingOptions, ContentPaddingResult } from './hooks/useContentSafeAreaPadding';
|
|
18
10
|
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
export {
|
|
11
|
+
// Re-export essential components from react-native-safe-area-context
|
|
12
|
+
// This provides a single entry point for all safe area needs
|
|
13
|
+
export {
|
|
14
|
+
SafeAreaProvider,
|
|
15
|
+
initialWindowMetrics,
|
|
16
|
+
SafeAreaView
|
|
17
|
+
} from 'react-native-safe-area-context';
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
export type {
|
|
20
|
+
Edge,
|
|
21
|
+
SafeAreaProviderProps
|
|
22
|
+
} from 'react-native-safe-area-context';
|
|
@@ -8,16 +8,9 @@ import { createStore } from '../../domain/factories/StoreFactory';
|
|
|
8
8
|
import type { StoreConfig } from '../../domain/types/Store';
|
|
9
9
|
|
|
10
10
|
export function useStore<T extends object>(config: StoreConfig<T>) {
|
|
11
|
-
// Stabilize entire config to track all property changes
|
|
12
11
|
const stableConfig = useMemo(
|
|
13
12
|
() => config,
|
|
14
|
-
[
|
|
15
|
-
config.name,
|
|
16
|
-
config.version,
|
|
17
|
-
config.persist,
|
|
18
|
-
config.storage,
|
|
19
|
-
JSON.stringify(config.initialState), // Track initialState changes
|
|
20
|
-
]
|
|
13
|
+
[config]
|
|
21
14
|
);
|
|
22
15
|
|
|
23
16
|
const store = useMemo(() => createStore(stableConfig), [stableConfig]);
|
|
@@ -83,7 +83,17 @@ export const DesignSystemProvider: React.FC<DesignSystemProviderProps> = ({
|
|
|
83
83
|
setIsInitialized(true);
|
|
84
84
|
onError?.(error);
|
|
85
85
|
});
|
|
86
|
-
}, [
|
|
86
|
+
}, [
|
|
87
|
+
customColors,
|
|
88
|
+
initialThemeMode,
|
|
89
|
+
initialize,
|
|
90
|
+
onError,
|
|
91
|
+
setCustomColors,
|
|
92
|
+
setDefaultColors,
|
|
93
|
+
setDefaultThemeMode,
|
|
94
|
+
setGlobalCustomColors,
|
|
95
|
+
setGlobalThemeMode,
|
|
96
|
+
]);
|
|
87
97
|
|
|
88
98
|
useEffect(() => {
|
|
89
99
|
if (isInitialized && fontsLoaded) {
|
|
@@ -76,13 +76,5 @@ export type ColorVariant =
|
|
|
76
76
|
| 'success'
|
|
77
77
|
| 'error'
|
|
78
78
|
| 'warning'
|
|
79
|
-
| 'info'
|
|
80
|
-
// Legacy support (deprecated - use textPrimary/textSecondary instead)
|
|
81
|
-
| 'primary'
|
|
82
|
-
| 'secondary'
|
|
83
|
-
| 'tertiary'
|
|
84
|
-
| 'disabled'
|
|
85
|
-
| 'inverse'
|
|
86
|
-
// Legacy: surfaceVariant is a background color, maps to textSecondary
|
|
87
|
-
| 'surfaceVariant';
|
|
79
|
+
| 'info';
|
|
88
80
|
|
|
@@ -10,7 +10,7 @@ import type { DesignTokens } from '../../../theme';
|
|
|
10
10
|
const COLOR_VARIANT_SET = new Set<string>([
|
|
11
11
|
'textPrimary', 'textSecondary', 'textTertiary', 'textDisabled', 'textInverse',
|
|
12
12
|
'onSurface', 'onBackground', 'onPrimary', 'onSecondary', 'onSuccess', 'onError', 'onWarning', 'onInfo',
|
|
13
|
-
'success', 'error', 'warning', 'info',
|
|
13
|
+
'success', 'error', 'warning', 'info',
|
|
14
14
|
]);
|
|
15
15
|
|
|
16
16
|
const COLOR_MAP: Record<ColorVariant, keyof DesignTokens['colors']> = {
|
|
@@ -31,12 +31,6 @@ const COLOR_MAP: Record<ColorVariant, keyof DesignTokens['colors']> = {
|
|
|
31
31
|
error: 'error',
|
|
32
32
|
warning: 'warning',
|
|
33
33
|
info: 'info',
|
|
34
|
-
primary: 'primary',
|
|
35
|
-
secondary: 'secondary',
|
|
36
|
-
tertiary: 'textTertiary',
|
|
37
|
-
disabled: 'textDisabled',
|
|
38
|
-
inverse: 'textInverse',
|
|
39
|
-
surfaceVariant: 'surfaceVariant',
|
|
40
34
|
};
|
|
41
35
|
|
|
42
36
|
const colorCache = new Map<string, string>();
|
|
@@ -182,7 +182,7 @@ export function useAsyncOperation<T, E = Error>(
|
|
|
182
182
|
if (immediate && !skip) {
|
|
183
183
|
execute();
|
|
184
184
|
}
|
|
185
|
-
}, [immediate, skip]);
|
|
185
|
+
}, [immediate, skip, execute]);
|
|
186
186
|
|
|
187
187
|
// Derived state
|
|
188
188
|
const isSuccess = !isIdle && !isLoading && error === null && data !== null;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for SafeAreaProvider component
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from '@jest/globals';
|
|
5
|
-
import { SafeAreaProvider, useSafeAreaConfig } from '../../components/SafeAreaProvider';
|
|
6
|
-
|
|
7
|
-
describe('SafeAreaProvider', () => {
|
|
8
|
-
it('should be defined', () => {
|
|
9
|
-
expect(SafeAreaProvider).toBeDefined();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should have useSafeAreaConfig export', () => {
|
|
13
|
-
expect(useSafeAreaConfig).toBeDefined();
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for useContentSafeAreaPadding hook
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from '@jest/globals';
|
|
5
|
-
import { useContentSafeAreaPadding } from '../../hooks/useContentSafeAreaPadding';
|
|
6
|
-
|
|
7
|
-
describe('useContentSafeAreaPadding', () => {
|
|
8
|
-
it('should be defined', () => {
|
|
9
|
-
expect(useContentSafeAreaPadding).toBeDefined();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should return function', () => {
|
|
13
|
-
expect(typeof useContentSafeAreaPadding).toBe('function');
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for useHeaderSafeAreaPadding hook
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from '@jest/globals';
|
|
5
|
-
import { useHeaderSafeAreaPadding } from '../../hooks/useHeaderSafeAreaPadding';
|
|
6
|
-
|
|
7
|
-
describe('useHeaderSafeAreaPadding', () => {
|
|
8
|
-
it('should be defined', () => {
|
|
9
|
-
expect(useHeaderSafeAreaPadding).toBeDefined();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should return function', () => {
|
|
13
|
-
expect(typeof useHeaderSafeAreaPadding).toBe('function');
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for useSafeAreaInsets hook
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from '@jest/globals';
|
|
5
|
-
import { useSafeAreaInsets } from '../../hooks/useSafeAreaInsets';
|
|
6
|
-
|
|
7
|
-
describe('useSafeAreaInsets', () => {
|
|
8
|
-
it('should be defined', () => {
|
|
9
|
-
expect(useSafeAreaInsets).toBeDefined();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should return function', () => {
|
|
13
|
-
expect(typeof useSafeAreaInsets).toBe('function');
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for useStatusBarSafeAreaPadding hook
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from '@jest/globals';
|
|
5
|
-
import { useStatusBarSafeAreaPadding } from '../../hooks/useStatusBarSafeAreaPadding';
|
|
6
|
-
|
|
7
|
-
describe('useStatusBarSafeAreaPadding', () => {
|
|
8
|
-
it('should be defined', () => {
|
|
9
|
-
expect(useStatusBarSafeAreaPadding).toBeDefined();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should return function', () => {
|
|
13
|
-
expect(typeof useStatusBarSafeAreaPadding).toBe('function');
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for complete flow
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from '@jest/globals';
|
|
5
|
-
import { useSafeAreaInsets } from '../../hooks/useSafeAreaInsets';
|
|
6
|
-
import { useStatusBarSafeAreaPadding } from '../../hooks/useStatusBarSafeAreaPadding';
|
|
7
|
-
import { useHeaderSafeAreaPadding } from '../../hooks/useHeaderSafeAreaPadding';
|
|
8
|
-
import { useContentSafeAreaPadding } from '../../hooks/useContentSafeAreaPadding';
|
|
9
|
-
|
|
10
|
-
describe('Integration Tests', () => {
|
|
11
|
-
it('should import useSafeAreaInsets', () => {
|
|
12
|
-
expect(useSafeAreaInsets).toBeDefined();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should import useStatusBarSafeAreaPadding', () => {
|
|
16
|
-
expect(useStatusBarSafeAreaPadding).toBeDefined();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should import useHeaderSafeAreaPadding', () => {
|
|
20
|
-
expect(useHeaderSafeAreaPadding).toBeDefined();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should import useContentSafeAreaPadding', () => {
|
|
24
|
-
expect(useContentSafeAreaPadding).toBeDefined();
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Jest setup file
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// Define __DEV__ for tests
|
|
6
|
-
(global as any).__DEV__ = true;
|
|
7
|
-
|
|
8
|
-
// Mock react-native-safe-area-context
|
|
9
|
-
jest.mock('react-native-safe-area-context', () => ({
|
|
10
|
-
SafeAreaProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
11
|
-
useSafeAreaInsets: () => ({
|
|
12
|
-
top: 44,
|
|
13
|
-
bottom: 34,
|
|
14
|
-
left: 0,
|
|
15
|
-
right: 0,
|
|
16
|
-
}),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
// Mock SafeAreaProvider to avoid JSX issues
|
|
20
|
-
jest.mock('../components/SafeAreaProvider', () => ({
|
|
21
|
-
SafeAreaProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
22
|
-
useSafeAreaConfig: () => ({
|
|
23
|
-
minHeaderPadding: 0,
|
|
24
|
-
minContentPadding: 0,
|
|
25
|
-
minStatusBarPadding: 0,
|
|
26
|
-
additionalPadding: 0,
|
|
27
|
-
iosStatusBarUsesSafeArea: true,
|
|
28
|
-
}),
|
|
29
|
-
}), { virtual: true });
|
|
30
|
-
|
|
31
|
-
// Mock Platform
|
|
32
|
-
jest.mock('react-native', () => ({
|
|
33
|
-
Platform: {
|
|
34
|
-
OS: 'ios',
|
|
35
|
-
select: (obj: Record<string, any>) => obj.ios,
|
|
36
|
-
},
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
// Mock performance API for performance tests
|
|
40
|
-
global.performance = {
|
|
41
|
-
...global.performance,
|
|
42
|
-
now: jest.fn(() => Date.now()),
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Simple test to make test suite valid
|
|
46
|
-
describe('setup', () => {
|
|
47
|
-
it('should define __DEV__', () => {
|
|
48
|
-
expect((global as any).__DEV__).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Performance tests for utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
describe('Performance Tests', () => {
|
|
6
|
-
it('should complete quickly', () => {
|
|
7
|
-
const startTime = performance.now();
|
|
8
|
-
|
|
9
|
-
// Simple operation
|
|
10
|
-
const result = Math.random();
|
|
11
|
-
|
|
12
|
-
const endTime = performance.now();
|
|
13
|
-
const duration = endTime - startTime;
|
|
14
|
-
|
|
15
|
-
expect(duration).toBeLessThan(100);
|
|
16
|
-
expect(typeof result).toBe('number');
|
|
17
|
-
});
|
|
18
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple test utilities for safe area package
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, jest } from '@jest/globals';
|
|
5
|
-
|
|
6
|
-
export const mockSafeAreaInsets = {
|
|
7
|
-
top: 44,
|
|
8
|
-
bottom: 34,
|
|
9
|
-
left: 0,
|
|
10
|
-
right: 0,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const createWrapper = () => {
|
|
14
|
-
return ({ children }: { children: any }) => children;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const renderHookWithSafeArea = <T, P>(
|
|
18
|
-
hook: (props: P) => T,
|
|
19
|
-
options?: {
|
|
20
|
-
initialProps?: P;
|
|
21
|
-
wrapper?: any;
|
|
22
|
-
insets?: typeof mockSafeAreaInsets;
|
|
23
|
-
},
|
|
24
|
-
) => {
|
|
25
|
-
// wrapper not used in this stub implementation
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
result: { current: hook(options?.initialProps as P) },
|
|
29
|
-
rerender: () => { },
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export const resetMocks = () => {
|
|
34
|
-
jest.clearAllMocks();
|
|
35
|
-
jest.resetModules();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
describe('testUtils', () => {
|
|
39
|
-
it('should export functions', () => {
|
|
40
|
-
expect(typeof mockSafeAreaInsets).toBe('object');
|
|
41
|
-
expect(typeof createWrapper).toBe('function');
|
|
42
|
-
expect(typeof resetMocks).toBe('function');
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SafeAreaProvider Component
|
|
3
|
-
* Enhanced wrapper around react-native-safe-area-context with configurable defaults
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { createContext, useContext } from 'react';
|
|
7
|
-
import {
|
|
8
|
-
SafeAreaProvider as NativeSafeAreaProvider,
|
|
9
|
-
} from 'react-native-safe-area-context';
|
|
10
|
-
import type { SafeAreaProviderProps as NativeSafeAreaProviderProps } from 'react-native-safe-area-context';
|
|
11
|
-
import { DEFAULT_CONFIG } from '../constants';
|
|
12
|
-
|
|
13
|
-
export interface SafeAreaConfig {
|
|
14
|
-
minHeaderPadding: number;
|
|
15
|
-
minContentPadding: number;
|
|
16
|
-
minStatusBarPadding: number;
|
|
17
|
-
additionalPadding: number;
|
|
18
|
-
iosStatusBarUsesSafeArea: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface SafeAreaProviderProps extends NativeSafeAreaProviderProps {
|
|
22
|
-
children?: React.ReactNode;
|
|
23
|
-
config?: SafeAreaConfig;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const SafeAreaConfigContext = createContext<SafeAreaConfig>(DEFAULT_CONFIG);
|
|
27
|
-
|
|
28
|
-
export const useSafeAreaConfig = (): SafeAreaConfig => {
|
|
29
|
-
return useContext(SafeAreaConfigContext);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const SafeAreaProvider: React.FC<SafeAreaProviderProps> = ({
|
|
33
|
-
children,
|
|
34
|
-
config,
|
|
35
|
-
...nativeProps
|
|
36
|
-
}) => {
|
|
37
|
-
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<SafeAreaConfigContext.Provider value={mergedConfig}>
|
|
41
|
-
<NativeSafeAreaProvider {...nativeProps}>
|
|
42
|
-
{children}
|
|
43
|
-
</NativeSafeAreaProvider>
|
|
44
|
-
</SafeAreaConfigContext.Provider>
|
|
45
|
-
);
|
|
46
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useContentSafeAreaPadding Hook
|
|
3
|
-
* Calculate safe area padding for content components
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useMemo } from 'react';
|
|
7
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
8
|
-
import { useSafeAreaConfig } from '../components/SafeAreaProvider';
|
|
9
|
-
import { useStableOptions } from '../utils/optimization';
|
|
10
|
-
import { validateNumericInput } from '../utils/validation';
|
|
11
|
-
|
|
12
|
-
export interface ContentPaddingOptions {
|
|
13
|
-
minBottomPadding?: number;
|
|
14
|
-
additionalPadding?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ContentPaddingResult {
|
|
18
|
-
paddingBottom: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const useContentSafeAreaPadding = (
|
|
22
|
-
options: ContentPaddingOptions = {},
|
|
23
|
-
): ContentPaddingResult => {
|
|
24
|
-
const insets = useSafeAreaInsets();
|
|
25
|
-
const config = useSafeAreaConfig();
|
|
26
|
-
const stableOptions = useStableOptions(options);
|
|
27
|
-
const minBottomPadding = stableOptions.minBottomPadding ?? config.minContentPadding;
|
|
28
|
-
const additionalPadding = stableOptions.additionalPadding ?? config.additionalPadding;
|
|
29
|
-
|
|
30
|
-
// Validate inputs once
|
|
31
|
-
useMemo(() => {
|
|
32
|
-
validateNumericInput(minBottomPadding, 'useContentSafeAreaPadding.minBottomPadding');
|
|
33
|
-
validateNumericInput(additionalPadding, 'useContentSafeAreaPadding.additionalPadding');
|
|
34
|
-
}, [minBottomPadding, additionalPadding]);
|
|
35
|
-
|
|
36
|
-
const paddingBottom = useMemo(() => {
|
|
37
|
-
return Math.max(insets.bottom, minBottomPadding) + additionalPadding;
|
|
38
|
-
}, [insets.bottom, minBottomPadding, additionalPadding]);
|
|
39
|
-
|
|
40
|
-
return useMemo(() => ({ paddingBottom }), [paddingBottom]);
|
|
41
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useHeaderSafeAreaPadding Hook
|
|
3
|
-
* Calculate safe area padding for header components
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useMemo } from 'react';
|
|
7
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
8
|
-
import { useSafeAreaConfig } from '../components/SafeAreaProvider';
|
|
9
|
-
import { useStableOptions } from '../utils/optimization';
|
|
10
|
-
import { validateNumericInput } from '../utils/validation';
|
|
11
|
-
|
|
12
|
-
export interface HeaderPaddingOptions {
|
|
13
|
-
minPadding?: number;
|
|
14
|
-
additionalPadding?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const useHeaderSafeAreaPadding = (
|
|
18
|
-
options: HeaderPaddingOptions = {},
|
|
19
|
-
): number => {
|
|
20
|
-
const insets = useSafeAreaInsets();
|
|
21
|
-
const config = useSafeAreaConfig();
|
|
22
|
-
const stableOptions = useStableOptions(options);
|
|
23
|
-
const minPadding = stableOptions.minPadding ?? config.minHeaderPadding;
|
|
24
|
-
const additionalPadding = stableOptions.additionalPadding ?? config.additionalPadding;
|
|
25
|
-
|
|
26
|
-
// Validate inputs once
|
|
27
|
-
useMemo(() => {
|
|
28
|
-
validateNumericInput(minPadding, 'useHeaderSafeAreaPadding.minPadding');
|
|
29
|
-
validateNumericInput(additionalPadding, 'useHeaderSafeAreaPadding.additionalPadding');
|
|
30
|
-
}, [minPadding, additionalPadding]);
|
|
31
|
-
|
|
32
|
-
return useMemo(() => {
|
|
33
|
-
return Math.max(insets.top, minPadding) + additionalPadding;
|
|
34
|
-
}, [insets.top, minPadding, additionalPadding]);
|
|
35
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useStatusBarSafeAreaPadding Hook
|
|
3
|
-
* Calculate safe area padding for status bar components
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useMemo } from 'react';
|
|
7
|
-
import { Platform } from 'react-native';
|
|
8
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
9
|
-
import { useSafeAreaConfig } from '../components/SafeAreaProvider';
|
|
10
|
-
import { useStableOptions } from '../utils/optimization';
|
|
11
|
-
import { validateNumericInput } from '../utils/validation';
|
|
12
|
-
|
|
13
|
-
export interface StatusBarPaddingOptions {
|
|
14
|
-
minPadding?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const useStatusBarSafeAreaPadding = (
|
|
18
|
-
options: StatusBarPaddingOptions = {},
|
|
19
|
-
): number => {
|
|
20
|
-
const insets = useSafeAreaInsets();
|
|
21
|
-
const config = useSafeAreaConfig();
|
|
22
|
-
const stableOptions = useStableOptions(options);
|
|
23
|
-
const minPadding = stableOptions.minPadding ?? config.minStatusBarPadding;
|
|
24
|
-
|
|
25
|
-
// Validate input once
|
|
26
|
-
useMemo(() => {
|
|
27
|
-
validateNumericInput(minPadding, 'useStatusBarSafeAreaPadding.minPadding');
|
|
28
|
-
}, [minPadding]);
|
|
29
|
-
|
|
30
|
-
return useMemo(() => {
|
|
31
|
-
if (Platform.OS === 'ios' && config.iosStatusBarUsesSafeArea) {
|
|
32
|
-
return minPadding;
|
|
33
|
-
}
|
|
34
|
-
return Math.max(insets.top, minPadding);
|
|
35
|
-
}, [insets.top, minPadding, config.iosStatusBarUsesSafeArea]);
|
|
36
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Performance optimization utilities for safe area hooks
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useMemo, useRef } from "react";
|
|
6
|
-
import { clearValidationCache } from "./validation";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Memoize options object to prevent unnecessary re-renders
|
|
10
|
-
* Uses shallow comparison for better performance
|
|
11
|
-
*/
|
|
12
|
-
export const useStableOptions = <T extends Record<string, any>>(
|
|
13
|
-
options: T,
|
|
14
|
-
): T => {
|
|
15
|
-
const prevOptionsRef = useRef<T | undefined>(undefined);
|
|
16
|
-
|
|
17
|
-
return useMemo(() => {
|
|
18
|
-
const prev = prevOptionsRef.current;
|
|
19
|
-
|
|
20
|
-
if (!prev) {
|
|
21
|
-
prevOptionsRef.current = options;
|
|
22
|
-
return options;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const prevKeys = Object.keys(prev);
|
|
26
|
-
const currentKeys = Object.keys(options);
|
|
27
|
-
|
|
28
|
-
if (prevKeys.length !== currentKeys.length) {
|
|
29
|
-
prevOptionsRef.current = options;
|
|
30
|
-
return options;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const hasChanged = prevKeys.some((key) => prev[key] !== options[key]);
|
|
34
|
-
|
|
35
|
-
if (hasChanged) {
|
|
36
|
-
prevOptionsRef.current = options;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return prevOptionsRef.current ?? options;
|
|
40
|
-
}, [options]);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Cleanup function to clear all performance caches
|
|
45
|
-
* Call this in useEffect cleanup if needed
|
|
46
|
-
*/
|
|
47
|
-
export const clearPerformanceCaches = (): void => {
|
|
48
|
-
clearValidationCache();
|
|
49
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validation utilities for safe area hooks
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// Validate numeric input with performance optimization
|
|
6
|
-
// Caches validation results to avoid repeated checks
|
|
7
|
-
const validationCache = new Map<string, boolean>();
|
|
8
|
-
|
|
9
|
-
export const validateNumericInput = (
|
|
10
|
-
value: number,
|
|
11
|
-
name: string,
|
|
12
|
-
allowNegative = false,
|
|
13
|
-
): boolean => {
|
|
14
|
-
if (!__DEV__) {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const cacheKey = `${name}:${value}:${allowNegative}`;
|
|
19
|
-
|
|
20
|
-
if (validationCache.has(cacheKey)) {
|
|
21
|
-
return validationCache.get(cacheKey)!;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const isValid = typeof value === 'number' && !isNaN(value) && (allowNegative || value >= 0);
|
|
25
|
-
|
|
26
|
-
if (!isValid) {
|
|
27
|
-
throttledWarn(`${name}: must be a ${allowNegative ? 'number' : 'non-negative number'}, got ${value}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Limit cache size to prevent memory leaks
|
|
31
|
-
if (validationCache.size > 100) {
|
|
32
|
-
const firstKey = validationCache.keys().next().value;
|
|
33
|
-
if (firstKey) {
|
|
34
|
-
validationCache.delete(firstKey);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
validationCache.set(cacheKey, isValid);
|
|
39
|
-
return isValid;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const throttledWarn = (_message: string): void => {
|
|
43
|
-
// Silent validation - no console output in production
|
|
44
|
-
void _message;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// Cleanup function to clear validation cache
|
|
48
|
-
export const clearValidationCache = (): void => {
|
|
49
|
-
validationCache.clear();
|
|
50
|
-
};
|