@umituz/react-native-design-system 1.14.0 → 2.0.0
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 +26 -19
- package/src/atoms/AtomicAvatar.tsx +161 -0
- package/src/atoms/AtomicButton.tsx +241 -0
- package/src/atoms/AtomicChip.tsx +226 -0
- package/src/atoms/AtomicDatePicker.tsx +255 -0
- package/src/atoms/AtomicFab.tsx +99 -0
- package/src/atoms/AtomicIcon.tsx +149 -0
- package/src/atoms/AtomicInput.tsx +308 -0
- package/src/atoms/AtomicPicker.tsx +310 -0
- package/src/atoms/AtomicProgress.tsx +149 -0
- package/src/atoms/AtomicText.tsx +55 -0
- package/src/atoms/__tests__/AtomicButton.test.tsx +107 -0
- package/src/atoms/__tests__/AtomicIcon.test.tsx +110 -0
- package/src/atoms/__tests__/AtomicInput.test.tsx +195 -0
- package/src/atoms/datepicker/components/DatePickerButton.tsx +112 -0
- package/src/atoms/datepicker/components/DatePickerModal.tsx +143 -0
- package/src/atoms/fab/styles/fabStyles.ts +98 -0
- package/src/atoms/fab/types/index.ts +88 -0
- package/src/atoms/index.ts +70 -0
- package/src/atoms/input/hooks/useInputState.ts +63 -0
- package/src/atoms/input/styles/inputStylesHelper.ts +120 -0
- package/src/atoms/picker/components/PickerChips.tsx +57 -0
- package/src/atoms/picker/components/PickerModal.tsx +214 -0
- package/src/atoms/picker/styles/pickerStyles.ts +223 -0
- package/src/atoms/picker/types/index.ts +42 -0
- package/src/index.ts +148 -79
- package/src/molecules/ConfirmationModal.tsx +42 -0
- package/src/molecules/ConfirmationModalContent.tsx +87 -0
- package/src/molecules/ConfirmationModalMain.tsx +91 -0
- package/src/molecules/FormField.tsx +155 -0
- package/src/molecules/IconContainer.tsx +79 -0
- package/src/molecules/ListItem.tsx +35 -0
- package/src/molecules/ScreenHeader.tsx +171 -0
- package/src/molecules/SearchBar.tsx +198 -0
- package/src/molecules/confirmation-modal/components.tsx +94 -0
- package/src/molecules/confirmation-modal/index.ts +7 -0
- package/src/molecules/confirmation-modal/styles/confirmationModalStyles.ts +133 -0
- package/src/molecules/confirmation-modal/types/index.ts +41 -0
- package/src/molecules/confirmation-modal/useConfirmationModal.ts +50 -0
- package/src/molecules/index.ts +19 -0
- package/src/molecules/listitem/index.ts +6 -0
- package/src/molecules/listitem/styles/listItemStyles.ts +37 -0
- package/src/molecules/listitem/types/index.ts +21 -0
- package/src/organisms/AppHeader.tsx +136 -0
- package/src/organisms/FormContainer.tsx +169 -0
- package/src/organisms/ScreenLayout.tsx +183 -0
- package/src/organisms/index.ts +31 -0
- package/src/responsive/config.ts +139 -0
- package/src/responsive/deviceDetection.ts +155 -0
- package/src/responsive/gridUtils.ts +79 -0
- package/src/responsive/index.ts +52 -0
- package/src/responsive/platformConstants.ts +98 -0
- package/src/responsive/responsive.ts +61 -0
- package/src/responsive/responsiveLayout.ts +137 -0
- package/src/responsive/responsiveSizing.ts +134 -0
- package/src/responsive/useResponsive.ts +140 -0
- package/src/responsive/validation.ts +158 -0
- package/src/theme/core/BaseTokens.ts +42 -0
- package/src/theme/core/ColorPalette.ts +29 -0
- package/src/theme/core/CustomColors.ts +122 -0
- package/src/theme/core/NavigationTheme.ts +72 -0
- package/src/theme/core/TokenFactory.ts +103 -0
- package/src/theme/core/colors/ColorUtils.ts +53 -0
- package/src/theme/core/colors/DarkColors.ts +146 -0
- package/src/theme/core/colors/LightColors.ts +146 -0
- package/src/theme/core/constants/DesignConstants.ts +31 -0
- package/src/theme/core/themes.ts +118 -0
- package/src/theme/core/tokens/BaseTokens.ts +144 -0
- package/src/theme/core/tokens/Borders.ts +43 -0
- package/src/theme/core/tokens/Sizes.ts +51 -0
- package/src/theme/core/tokens/Spacing.ts +38 -0
- package/src/theme/core/tokens/Typography.ts +143 -0
- package/src/theme/hooks/useAppDesignTokens.ts +45 -0
- package/src/theme/hooks/useCommonStyles.ts +248 -0
- package/src/theme/hooks/useThemedStyles.ts +68 -0
- package/src/theme/index.ts +94 -0
- package/src/theme/infrastructure/globalThemeStore.ts +69 -0
- package/src/theme/infrastructure/storage/ThemeStorage.ts +93 -0
- package/src/theme/infrastructure/stores/themeStore.ts +109 -0
- package/src/typography/__tests__/colorValidationUtils.test.ts +180 -0
- package/src/typography/__tests__/textColorUtils.test.ts +185 -0
- package/src/typography/__tests__/textStyleUtils.test.ts +168 -0
- package/src/typography/domain/entities/TypographyTypes.ts +88 -0
- package/src/typography/index.ts +53 -0
- package/src/typography/presentation/utils/colorValidationUtils.ts +133 -0
- package/src/typography/presentation/utils/textColorUtils.ts +205 -0
- package/src/typography/presentation/utils/textStyleUtils.ts +159 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Detection Utilities
|
|
3
|
+
*
|
|
4
|
+
* Utilities for detecting device types and screen dimensions.
|
|
5
|
+
* Follows universal design principles for cross-platform compatibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Dimensions } from 'react-native';
|
|
9
|
+
import { DEVICE_BREAKPOINTS, LAYOUT_CONSTANTS } from './config';
|
|
10
|
+
import { validateScreenDimensions } from './validation';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Helper function for device detection with fallback
|
|
14
|
+
* @param operation - Operation to perform
|
|
15
|
+
* @param fallback - Fallback value if operation fails
|
|
16
|
+
* @param warningMessage - Warning message for __DEV__
|
|
17
|
+
* @returns Operation result or fallback
|
|
18
|
+
*/
|
|
19
|
+
const withDeviceDetectionFallback = <T>(
|
|
20
|
+
operation: () => T,
|
|
21
|
+
fallback: T,
|
|
22
|
+
warningMessage: string
|
|
23
|
+
): T => {
|
|
24
|
+
try {
|
|
25
|
+
return operation();
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (__DEV__) {
|
|
28
|
+
console.warn(`[DeviceDetection] ${warningMessage}`);
|
|
29
|
+
}
|
|
30
|
+
return fallback;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Device type enum for conditional rendering
|
|
36
|
+
*/
|
|
37
|
+
export enum DeviceType {
|
|
38
|
+
SMALL_PHONE = 'SMALL_PHONE',
|
|
39
|
+
MEDIUM_PHONE = 'MEDIUM_PHONE',
|
|
40
|
+
LARGE_PHONE = 'LARGE_PHONE',
|
|
41
|
+
TABLET = 'TABLET',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get current screen dimensions
|
|
46
|
+
* @returns Screen width and height
|
|
47
|
+
* @throws ResponsiveValidationError if dimensions are invalid
|
|
48
|
+
*/
|
|
49
|
+
export const getScreenDimensions = () => {
|
|
50
|
+
const { width, height } = Dimensions.get('window');
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
validateScreenDimensions(width, height);
|
|
54
|
+
return { width, height };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (__DEV__) {
|
|
57
|
+
console.warn('[getScreenDimensions] Invalid screen dimensions detected, using fallback values');
|
|
58
|
+
}
|
|
59
|
+
// Fallback to safe default dimensions
|
|
60
|
+
return { width: 414, height: 896 };
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if current device is a small phone (iPhone 13 mini, SE)
|
|
66
|
+
* @returns true if device is a small phone
|
|
67
|
+
*/
|
|
68
|
+
export const isSmallPhone = (): boolean => {
|
|
69
|
+
return withDeviceDetectionFallback(
|
|
70
|
+
() => {
|
|
71
|
+
const { width } = getScreenDimensions();
|
|
72
|
+
return width <= DEVICE_BREAKPOINTS.SMALL_PHONE;
|
|
73
|
+
},
|
|
74
|
+
false,
|
|
75
|
+
'Error detecting device type, assuming standard phone'
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if current device is a tablet (iPad)
|
|
81
|
+
* @returns true if device is a tablet
|
|
82
|
+
*/
|
|
83
|
+
export const isTablet = (): boolean => {
|
|
84
|
+
return withDeviceDetectionFallback(
|
|
85
|
+
() => {
|
|
86
|
+
const { width } = getScreenDimensions();
|
|
87
|
+
return width >= DEVICE_BREAKPOINTS.SMALL_TABLET;
|
|
88
|
+
},
|
|
89
|
+
false,
|
|
90
|
+
'Error detecting device type, assuming phone'
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if device is in landscape mode
|
|
96
|
+
* @returns true if device is in landscape orientation
|
|
97
|
+
*/
|
|
98
|
+
export const isLandscape = (): boolean => {
|
|
99
|
+
return withDeviceDetectionFallback(
|
|
100
|
+
() => {
|
|
101
|
+
const { width, height } = getScreenDimensions();
|
|
102
|
+
return width > height;
|
|
103
|
+
},
|
|
104
|
+
false,
|
|
105
|
+
'Error detecting orientation, assuming portrait'
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get current device type
|
|
111
|
+
* @returns Device type enum value
|
|
112
|
+
*/
|
|
113
|
+
export const getDeviceType = (): DeviceType => {
|
|
114
|
+
return withDeviceDetectionFallback(
|
|
115
|
+
() => {
|
|
116
|
+
const { width } = getScreenDimensions();
|
|
117
|
+
|
|
118
|
+
if (width <= DEVICE_BREAKPOINTS.SMALL_PHONE) {
|
|
119
|
+
return DeviceType.SMALL_PHONE;
|
|
120
|
+
} else if (width <= DEVICE_BREAKPOINTS.MEDIUM_PHONE) {
|
|
121
|
+
return DeviceType.MEDIUM_PHONE;
|
|
122
|
+
} else if (width <= DEVICE_BREAKPOINTS.LARGE_PHONE) {
|
|
123
|
+
return DeviceType.LARGE_PHONE;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return DeviceType.TABLET;
|
|
127
|
+
},
|
|
128
|
+
DeviceType.MEDIUM_PHONE,
|
|
129
|
+
'Error detecting device type, assuming medium phone'
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Responsive spacing multiplier
|
|
135
|
+
* Returns a multiplier for spacing based on device size
|
|
136
|
+
*
|
|
137
|
+
* @returns Spacing multiplier (0.9-1.2)
|
|
138
|
+
*/
|
|
139
|
+
export const getSpacingMultiplier = (): number => {
|
|
140
|
+
return withDeviceDetectionFallback(
|
|
141
|
+
() => {
|
|
142
|
+
const { width } = getScreenDimensions();
|
|
143
|
+
|
|
144
|
+
if (width <= DEVICE_BREAKPOINTS.SMALL_PHONE) {
|
|
145
|
+
return LAYOUT_CONSTANTS.SPACING_MULTIPLIER_SMALL;
|
|
146
|
+
} else if (width >= DEVICE_BREAKPOINTS.TABLET) {
|
|
147
|
+
return LAYOUT_CONSTANTS.SPACING_MULTIPLIER_TABLET;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return LAYOUT_CONSTANTS.SPACING_MULTIPLIER_STANDARD;
|
|
151
|
+
},
|
|
152
|
+
LAYOUT_CONSTANTS.SPACING_MULTIPLIER_STANDARD,
|
|
153
|
+
'Error calculating spacing multiplier, using fallback'
|
|
154
|
+
);
|
|
155
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid Utilities
|
|
3
|
+
* Responsive grid sizing and column calculations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getScreenDimensions } from './deviceDetection';
|
|
7
|
+
import { DEVICE_BREAKPOINTS, GRID_CONFIG } from './config';
|
|
8
|
+
import { validateNumber } from './validation';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Responsive grid columns
|
|
12
|
+
* Returns number of columns for grid layouts
|
|
13
|
+
*
|
|
14
|
+
* @param mobileColumns - Number of columns for mobile devices (default: 2)
|
|
15
|
+
* @param tabletColumns - Number of columns for tablet devices (default: 4)
|
|
16
|
+
* @returns Responsive number of grid columns
|
|
17
|
+
*/
|
|
18
|
+
export const getResponsiveGridColumns = (
|
|
19
|
+
mobileColumns: number = GRID_CONFIG.DEFAULT_MOBILE_COLUMNS,
|
|
20
|
+
tabletColumns: number = GRID_CONFIG.DEFAULT_TABLET_COLUMNS
|
|
21
|
+
): number => {
|
|
22
|
+
try {
|
|
23
|
+
const validatedMobile = validateNumber(mobileColumns, 'mobileColumns', 1, 20);
|
|
24
|
+
const validatedTablet = validateNumber(tabletColumns, 'tabletColumns', 1, 20);
|
|
25
|
+
|
|
26
|
+
const { width } = getScreenDimensions();
|
|
27
|
+
return width >= DEVICE_BREAKPOINTS.TABLET ? validatedTablet : validatedMobile;
|
|
28
|
+
} catch {
|
|
29
|
+
return 2;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export interface GridCellSizeConfig {
|
|
34
|
+
columns: number;
|
|
35
|
+
rows: number;
|
|
36
|
+
horizontalPadding?: number;
|
|
37
|
+
verticalPadding?: number;
|
|
38
|
+
gap?: number;
|
|
39
|
+
headerHeight?: number;
|
|
40
|
+
tabBarHeight?: number;
|
|
41
|
+
statusBarHeight?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Responsive grid cell size
|
|
46
|
+
* Calculates optimal square cell size for a grid that fills available space
|
|
47
|
+
*
|
|
48
|
+
* @param config - Grid configuration
|
|
49
|
+
* @returns Responsive cell size (width = height for square cells)
|
|
50
|
+
*/
|
|
51
|
+
export const getResponsiveGridCellSize = (config: GridCellSizeConfig): number => {
|
|
52
|
+
try {
|
|
53
|
+
const {
|
|
54
|
+
columns,
|
|
55
|
+
rows,
|
|
56
|
+
horizontalPadding = 32,
|
|
57
|
+
verticalPadding = 32,
|
|
58
|
+
gap = 8,
|
|
59
|
+
headerHeight = 120,
|
|
60
|
+
tabBarHeight = 80,
|
|
61
|
+
statusBarHeight = 50,
|
|
62
|
+
} = config;
|
|
63
|
+
|
|
64
|
+
const { width, height } = getScreenDimensions();
|
|
65
|
+
|
|
66
|
+
const totalHorizontalGap = gap * (columns - 1);
|
|
67
|
+
const availableWidth = width - horizontalPadding - totalHorizontalGap;
|
|
68
|
+
const maxCellWidth = availableWidth / columns;
|
|
69
|
+
|
|
70
|
+
const totalVerticalGap = gap * (rows - 1);
|
|
71
|
+
const usedHeight = headerHeight + tabBarHeight + statusBarHeight + verticalPadding;
|
|
72
|
+
const availableHeight = height - usedHeight - totalVerticalGap;
|
|
73
|
+
const maxCellHeight = availableHeight / rows;
|
|
74
|
+
|
|
75
|
+
return Math.floor(Math.min(maxCellWidth, maxCellHeight));
|
|
76
|
+
} catch {
|
|
77
|
+
return 60;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-design-system-responsive - Public API
|
|
3
|
+
*
|
|
4
|
+
* Responsive design utilities for React Native - Screen dimensions, device detection,
|
|
5
|
+
* and responsive sizing utilities following Material Design 3 and iOS HIG principles.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { useResponsive, isTablet, getResponsiveLogoSize } from '@umituz/react-native-design-system-responsive';
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Hook exports
|
|
14
|
+
export { useResponsive } from './useResponsive';
|
|
15
|
+
export type { UseResponsiveReturn } from './useResponsive';
|
|
16
|
+
|
|
17
|
+
// Utility function exports
|
|
18
|
+
export {
|
|
19
|
+
getScreenDimensions,
|
|
20
|
+
isSmallPhone,
|
|
21
|
+
isTablet,
|
|
22
|
+
getResponsiveLogoSize,
|
|
23
|
+
getResponsiveInputHeight,
|
|
24
|
+
getResponsiveHorizontalPadding,
|
|
25
|
+
getResponsiveBottomPosition,
|
|
26
|
+
getResponsiveFABPosition,
|
|
27
|
+
getResponsiveModalMaxHeight,
|
|
28
|
+
getResponsiveMinModalHeight,
|
|
29
|
+
getResponsiveIconContainerSize,
|
|
30
|
+
getResponsiveGridColumns,
|
|
31
|
+
getResponsiveGridCellSize,
|
|
32
|
+
type GridCellSizeConfig,
|
|
33
|
+
getResponsiveMaxWidth,
|
|
34
|
+
getResponsiveFontSize,
|
|
35
|
+
isLandscape,
|
|
36
|
+
getDeviceType,
|
|
37
|
+
DeviceType,
|
|
38
|
+
} from './responsive';
|
|
39
|
+
|
|
40
|
+
// Device detection exports
|
|
41
|
+
export { getSpacingMultiplier } from './deviceDetection';
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// Platform constants exports
|
|
46
|
+
export {
|
|
47
|
+
IOS_HIG,
|
|
48
|
+
PLATFORM_CONSTANTS,
|
|
49
|
+
isValidTouchTarget,
|
|
50
|
+
getMinTouchTarget,
|
|
51
|
+
} from './platformConstants';
|
|
52
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-Specific Constants
|
|
3
|
+
*
|
|
4
|
+
* Design system constants that ensure compliance with platform guidelines.
|
|
5
|
+
* These values are based on official Human Interface Guidelines (HIG) from Apple and Material Design from Google.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* iOS Human Interface Guidelines (HIG) Constants
|
|
10
|
+
*
|
|
11
|
+
* @see https://developer.apple.com/design/human-interface-guidelines/layout
|
|
12
|
+
*/
|
|
13
|
+
export const IOS_HIG = {
|
|
14
|
+
/**
|
|
15
|
+
* Minimum Touch Target Size
|
|
16
|
+
*
|
|
17
|
+
* Apple requires a minimum tappable area of 44pt x 44pt for ALL interactive controls.
|
|
18
|
+
* This is enforced during App Store review.
|
|
19
|
+
*
|
|
20
|
+
* @critical Violating this can result in App Store rejection
|
|
21
|
+
*/
|
|
22
|
+
MIN_TOUCH_TARGET: 44,
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Recommended Minimum Touch Target Size
|
|
26
|
+
*
|
|
27
|
+
* For better accessibility and usability, Apple recommends 48pt x 48pt.
|
|
28
|
+
*/
|
|
29
|
+
RECOMMENDED_TOUCH_TARGET: 48,
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Minimum Text Size
|
|
33
|
+
*
|
|
34
|
+
* Minimum font size for body text to ensure readability.
|
|
35
|
+
*/
|
|
36
|
+
MIN_TEXT_SIZE: 17,
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Universal Platform Constants
|
|
45
|
+
*
|
|
46
|
+
* These values work across both iOS and Android, taking the more restrictive requirement.
|
|
47
|
+
*/
|
|
48
|
+
export const PLATFORM_CONSTANTS = {
|
|
49
|
+
/**
|
|
50
|
+
* Minimum Touch Target Size
|
|
51
|
+
*
|
|
52
|
+
* Uses iOS requirement (44pt) as it's more restrictive than Android (48dp).
|
|
53
|
+
* This ensures compliance on both platforms.
|
|
54
|
+
*/
|
|
55
|
+
MIN_TOUCH_TARGET: Math.max(IOS_HIG.MIN_TOUCH_TARGET, 48),
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Recommended Touch Target Size
|
|
59
|
+
*
|
|
60
|
+
* Uses the higher value between iOS and Android recommendations.
|
|
61
|
+
*/
|
|
62
|
+
RECOMMENDED_TOUCH_TARGET: 48,
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Minimum Text Size
|
|
66
|
+
*
|
|
67
|
+
* Uses iOS requirement as it's larger.
|
|
68
|
+
*/
|
|
69
|
+
MIN_TEXT_SIZE: Math.max(IOS_HIG.MIN_TEXT_SIZE, 14),
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Helper function to validate touch target size
|
|
74
|
+
*
|
|
75
|
+
* @param size - The size to validate (in pt/dp)
|
|
76
|
+
* @returns true if size meets platform requirements
|
|
77
|
+
*/
|
|
78
|
+
export const isValidTouchTarget = (size: number): boolean => {
|
|
79
|
+
return size >= IOS_HIG.MIN_TOUCH_TARGET;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Helper function to get minimum touch target for component
|
|
84
|
+
*
|
|
85
|
+
* @param componentType - The type of component ('button' | 'input' | 'icon' | 'generic')
|
|
86
|
+
* @returns The minimum touch target size for that component type
|
|
87
|
+
*/
|
|
88
|
+
export const getMinTouchTarget = (componentType: 'button' | 'input' | 'icon' | 'generic' = 'generic'): number => {
|
|
89
|
+
switch (componentType) {
|
|
90
|
+
case 'button':
|
|
91
|
+
case 'input':
|
|
92
|
+
return PLATFORM_CONSTANTS.RECOMMENDED_TOUCH_TARGET; // 48pt recommended for buttons and inputs
|
|
93
|
+
case 'icon':
|
|
94
|
+
case 'generic':
|
|
95
|
+
default:
|
|
96
|
+
return IOS_HIG.MIN_TOUCH_TARGET; // 44pt minimum for icons and generic elements
|
|
97
|
+
}
|
|
98
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive Design Utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralized responsive sizing and spacing utilities to prevent
|
|
5
|
+
* Apple App Store rejection due to layout issues on different devices.
|
|
6
|
+
*
|
|
7
|
+
* This is the main export file that imports from specialized modules.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Device detection
|
|
11
|
+
export {
|
|
12
|
+
getScreenDimensions,
|
|
13
|
+
isSmallPhone,
|
|
14
|
+
isTablet,
|
|
15
|
+
isLandscape,
|
|
16
|
+
getDeviceType,
|
|
17
|
+
DeviceType,
|
|
18
|
+
} from './deviceDetection';
|
|
19
|
+
|
|
20
|
+
// Responsive sizing
|
|
21
|
+
export {
|
|
22
|
+
getResponsiveLogoSize,
|
|
23
|
+
getResponsiveInputHeight,
|
|
24
|
+
getResponsiveIconContainerSize,
|
|
25
|
+
getResponsiveMaxWidth,
|
|
26
|
+
getResponsiveFontSize,
|
|
27
|
+
getResponsiveGridColumns,
|
|
28
|
+
getResponsiveGridCellSize,
|
|
29
|
+
type GridCellSizeConfig,
|
|
30
|
+
} from './responsiveSizing';
|
|
31
|
+
|
|
32
|
+
// Responsive layout
|
|
33
|
+
export {
|
|
34
|
+
getResponsiveHorizontalPadding,
|
|
35
|
+
getResponsiveBottomPosition,
|
|
36
|
+
getResponsiveFABPosition,
|
|
37
|
+
getResponsiveModalMaxHeight,
|
|
38
|
+
getResponsiveMinModalHeight,
|
|
39
|
+
} from './responsiveLayout';
|
|
40
|
+
|
|
41
|
+
// Platform constants
|
|
42
|
+
export {
|
|
43
|
+
IOS_HIG,
|
|
44
|
+
PLATFORM_CONSTANTS,
|
|
45
|
+
isValidTouchTarget,
|
|
46
|
+
getMinTouchTarget,
|
|
47
|
+
} from './platformConstants';
|
|
48
|
+
|
|
49
|
+
// Configuration
|
|
50
|
+
export {
|
|
51
|
+
DEVICE_BREAKPOINTS,
|
|
52
|
+
RESPONSIVE_PERCENTAGES,
|
|
53
|
+
SIZE_CONSTRAINTS,
|
|
54
|
+
LAYOUT_CONSTANTS,
|
|
55
|
+
HEIGHT_THRESHOLDS,
|
|
56
|
+
GRID_CONFIG,
|
|
57
|
+
VALIDATION_CONSTRAINTS,
|
|
58
|
+
} from './config';
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive Layout Utilities
|
|
3
|
+
* Layout utilities for positioning and spacing.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getScreenDimensions } from './deviceDetection';
|
|
7
|
+
import {
|
|
8
|
+
DEVICE_BREAKPOINTS,
|
|
9
|
+
LAYOUT_CONSTANTS,
|
|
10
|
+
HEIGHT_THRESHOLDS,
|
|
11
|
+
SIZE_CONSTRAINTS,
|
|
12
|
+
} from './config';
|
|
13
|
+
import { validateNumber, validateSafeAreaInsets } from './validation';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Responsive horizontal padding
|
|
17
|
+
* @param basePadding - Base padding value (default: 16)
|
|
18
|
+
* @param insets - Safe area insets object
|
|
19
|
+
*/
|
|
20
|
+
export const getResponsiveHorizontalPadding = (
|
|
21
|
+
basePadding: number = LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE,
|
|
22
|
+
insets: { left?: number; right?: number } = { left: 0, right: 0 }
|
|
23
|
+
): number => {
|
|
24
|
+
try {
|
|
25
|
+
const validatedBasePadding = validateNumber(basePadding, 'basePadding', 0, 100);
|
|
26
|
+
validateSafeAreaInsets(insets);
|
|
27
|
+
|
|
28
|
+
const { width } = getScreenDimensions();
|
|
29
|
+
const { left = 0, right = 0 } = insets;
|
|
30
|
+
|
|
31
|
+
if (width >= DEVICE_BREAKPOINTS.TABLET) {
|
|
32
|
+
const tabletPadding = validatedBasePadding * 1.5;
|
|
33
|
+
return Math.max(
|
|
34
|
+
tabletPadding,
|
|
35
|
+
left + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE,
|
|
36
|
+
right + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Math.max(
|
|
41
|
+
validatedBasePadding,
|
|
42
|
+
left + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET,
|
|
43
|
+
right + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET
|
|
44
|
+
);
|
|
45
|
+
} catch {
|
|
46
|
+
return 16;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Responsive bottom positioning
|
|
52
|
+
* @param basePosition - Base bottom position (default: 32)
|
|
53
|
+
* @param insets - Safe area insets object
|
|
54
|
+
*/
|
|
55
|
+
export const getResponsiveBottomPosition = (
|
|
56
|
+
basePosition: number = LAYOUT_CONSTANTS.BOTTOM_POSITION_BASE,
|
|
57
|
+
insets: { bottom?: number } = { bottom: 0 }
|
|
58
|
+
): number => {
|
|
59
|
+
try {
|
|
60
|
+
const validatedBasePosition = validateNumber(basePosition, 'basePosition', 0, 500);
|
|
61
|
+
validateSafeAreaInsets(insets);
|
|
62
|
+
|
|
63
|
+
const { bottom = 0 } = insets;
|
|
64
|
+
return Math.max(validatedBasePosition, bottom + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET);
|
|
65
|
+
} catch {
|
|
66
|
+
return 32;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Responsive FAB position
|
|
72
|
+
* @param insets - Safe area insets object
|
|
73
|
+
*/
|
|
74
|
+
export const getResponsiveFABPosition = (
|
|
75
|
+
insets: { bottom?: number; right?: number } = { bottom: 0, right: 0 }
|
|
76
|
+
): { bottom: number; right: number } => {
|
|
77
|
+
try {
|
|
78
|
+
validateSafeAreaInsets(insets);
|
|
79
|
+
const { width } = getScreenDimensions();
|
|
80
|
+
const { bottom = 0, right = 0 } = insets;
|
|
81
|
+
|
|
82
|
+
if (width >= DEVICE_BREAKPOINTS.TABLET) {
|
|
83
|
+
return {
|
|
84
|
+
bottom: Math.max(LAYOUT_CONSTANTS.FAB_BOTTOM_TABLET, bottom + LAYOUT_CONSTANTS.TAB_BAR_OFFSET),
|
|
85
|
+
right: Math.max(LAYOUT_CONSTANTS.FAB_RIGHT_TABLET, right + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
bottom: Math.max(LAYOUT_CONSTANTS.TAB_BAR_OFFSET, bottom + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET),
|
|
91
|
+
right: Math.max(LAYOUT_CONSTANTS.FAB_RIGHT_PHONE, right + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET),
|
|
92
|
+
};
|
|
93
|
+
} catch {
|
|
94
|
+
return { bottom: 90, right: 20 };
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Responsive modal max height
|
|
100
|
+
*/
|
|
101
|
+
export const getResponsiveModalMaxHeight = (): string => {
|
|
102
|
+
try {
|
|
103
|
+
const { height } = getScreenDimensions();
|
|
104
|
+
|
|
105
|
+
if (height <= HEIGHT_THRESHOLDS.SMALL_DEVICE) {
|
|
106
|
+
return LAYOUT_CONSTANTS.MODAL_HEIGHT_SMALL;
|
|
107
|
+
} else if (height >= HEIGHT_THRESHOLDS.LARGE_DEVICE) {
|
|
108
|
+
return LAYOUT_CONSTANTS.MODAL_HEIGHT_TABLET;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return LAYOUT_CONSTANTS.MODAL_HEIGHT_STANDARD;
|
|
112
|
+
} catch {
|
|
113
|
+
return '70%';
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Responsive modal min height
|
|
119
|
+
*/
|
|
120
|
+
export const getResponsiveMinModalHeight = (): number => {
|
|
121
|
+
try {
|
|
122
|
+
const { height } = getScreenDimensions();
|
|
123
|
+
|
|
124
|
+
if (height <= HEIGHT_THRESHOLDS.SMALL_DEVICE) {
|
|
125
|
+
const calculatedHeight = height * 0.4;
|
|
126
|
+
return Math.max(calculatedHeight, SIZE_CONSTRAINTS.MODAL_MIN_SMALL);
|
|
127
|
+
} else if (height >= HEIGHT_THRESHOLDS.LARGE_DEVICE) {
|
|
128
|
+
const calculatedHeight = height * 0.35;
|
|
129
|
+
return Math.min(Math.max(calculatedHeight, SIZE_CONSTRAINTS.MODAL_MIN_TABLET), SIZE_CONSTRAINTS.MODAL_MAX_TABLET);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const calculatedHeight = height * 0.45;
|
|
133
|
+
return Math.max(calculatedHeight, SIZE_CONSTRAINTS.MODAL_MIN_STANDARD);
|
|
134
|
+
} catch {
|
|
135
|
+
return 300;
|
|
136
|
+
}
|
|
137
|
+
};
|