@umituz/react-native-design-system 1.15.0 → 2.0.1
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/AtomicCard.tsx +84 -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 +133 -52
- 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,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FormContainer Component
|
|
3
|
+
*
|
|
4
|
+
* A reusable container for forms with proper keyboard handling and responsive layout.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Pure React Native implementation (no Paper dependency)
|
|
8
|
+
* - Universal keyboard handling (no platform-specific code)
|
|
9
|
+
* - ScrollView with automatic content padding
|
|
10
|
+
* - Safe area insets for bottom tab navigation overlap
|
|
11
|
+
* - Responsive max width for large screens (tablets)
|
|
12
|
+
* - Consistent vertical spacing between form elements
|
|
13
|
+
* - Theme-aware surface colors
|
|
14
|
+
* - Optimized performance with memoized styles
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <FormContainer>
|
|
19
|
+
* <AtomicInput label="Name" value={name} onChangeText={setName} />
|
|
20
|
+
* <AtomicTextArea label="Description" value={desc} onChangeText={setDesc} />
|
|
21
|
+
* <AtomicButton variant="primary" onPress={handleSubmit}>
|
|
22
|
+
* Submit
|
|
23
|
+
* </AtomicButton>
|
|
24
|
+
* </FormContainer>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Why This Component:
|
|
28
|
+
* - Prevents keyboard from covering input fields (universal solution)
|
|
29
|
+
* - Handles safe area (notch, bottom tabs) automatically
|
|
30
|
+
* - Consistent form layout across all 100+ generated apps
|
|
31
|
+
* - Responsive design for tablets (max 700px) and phones (full width)
|
|
32
|
+
* - Automatic vertical spacing between form elements (no manual marginBottom)
|
|
33
|
+
* - Reduces boilerplate in form screens
|
|
34
|
+
* - Universal code - no platform checks, works on iOS, Android, Web
|
|
35
|
+
*
|
|
36
|
+
* Technical Details:
|
|
37
|
+
* - Uses ScrollView with contentContainerStyle for keyboard handling
|
|
38
|
+
* - Pure React Native View for surface (lightweight)
|
|
39
|
+
* - Vertical spacing via Children.map() wrapping (universal compatibility)
|
|
40
|
+
* - Safe area insets from react-native-safe-area-context
|
|
41
|
+
* - Responsive values from useResponsive hook
|
|
42
|
+
*
|
|
43
|
+
* @module FormContainer
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import React, { Children } from 'react';
|
|
47
|
+
import {
|
|
48
|
+
ScrollView,
|
|
49
|
+
View,
|
|
50
|
+
StyleSheet,
|
|
51
|
+
StyleProp,
|
|
52
|
+
ViewStyle,
|
|
53
|
+
} from 'react-native';
|
|
54
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
55
|
+
import { useAppDesignTokens } from '../theme';
|
|
56
|
+
import { useResponsive } from '../responsive';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Props for FormContainer component
|
|
60
|
+
*/
|
|
61
|
+
export interface FormContainerProps {
|
|
62
|
+
/** Form content (inputs, buttons, etc.) */
|
|
63
|
+
children: React.ReactNode;
|
|
64
|
+
/** Container style override (for outer View) */
|
|
65
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
66
|
+
/** Content container style override (for ScrollView content) */
|
|
67
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
68
|
+
/** Show vertical scroll indicator */
|
|
69
|
+
showsVerticalScrollIndicator?: boolean;
|
|
70
|
+
/** Optional test ID for E2E testing */
|
|
71
|
+
testID?: string;
|
|
72
|
+
/** Show surface border (default: true) */
|
|
73
|
+
showBorder?: boolean;
|
|
74
|
+
/** Accessibility label for the container */
|
|
75
|
+
accessibilityLabel?: string;
|
|
76
|
+
/** Accessibility hint for the container */
|
|
77
|
+
accessibilityHint?: string;
|
|
78
|
+
/** Whether the container is accessible */
|
|
79
|
+
accessible?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* FormContainer - Universal form wrapper component
|
|
84
|
+
*
|
|
85
|
+
* Wraps forms with:
|
|
86
|
+
* - Pure React Native surface
|
|
87
|
+
* - Universal keyboard handling (no platform checks)
|
|
88
|
+
* - ScrollView for content overflow
|
|
89
|
+
* - Safe area insets (bottom tabs, notch)
|
|
90
|
+
* - Responsive max width (tablets)
|
|
91
|
+
* - Theme integration
|
|
92
|
+
*/
|
|
93
|
+
export const FormContainer: React.FC<FormContainerProps> = ({
|
|
94
|
+
children,
|
|
95
|
+
containerStyle,
|
|
96
|
+
contentContainerStyle,
|
|
97
|
+
showsVerticalScrollIndicator = false,
|
|
98
|
+
testID,
|
|
99
|
+
showBorder = true,
|
|
100
|
+
}) => {
|
|
101
|
+
const tokens = useAppDesignTokens();
|
|
102
|
+
const insets = useSafeAreaInsets();
|
|
103
|
+
const { maxContentWidth, bottomPosition } = useResponsive();
|
|
104
|
+
const formContentWidth = maxContentWidth;
|
|
105
|
+
const formBottomPadding = bottomPosition;
|
|
106
|
+
const formElementSpacing = tokens.spacing.lg;
|
|
107
|
+
|
|
108
|
+
// Create styles for form container
|
|
109
|
+
const styles = StyleSheet.create({
|
|
110
|
+
container: {
|
|
111
|
+
flex: 1,
|
|
112
|
+
backgroundColor: tokens.colors.backgroundPrimary,
|
|
113
|
+
},
|
|
114
|
+
surface: {
|
|
115
|
+
flex: 1,
|
|
116
|
+
backgroundColor: tokens.colors.surface,
|
|
117
|
+
borderWidth: showBorder ? 1 : 0,
|
|
118
|
+
borderColor: tokens.colors.border,
|
|
119
|
+
borderRadius: tokens.borders.radius.md,
|
|
120
|
+
},
|
|
121
|
+
scrollView: {
|
|
122
|
+
flex: 1,
|
|
123
|
+
},
|
|
124
|
+
contentContainer: {
|
|
125
|
+
flexGrow: 1,
|
|
126
|
+
padding: tokens.spacing.lg,
|
|
127
|
+
paddingTop: tokens.spacing.xl,
|
|
128
|
+
paddingBottom: formBottomPadding + insets.bottom,
|
|
129
|
+
maxWidth: formContentWidth,
|
|
130
|
+
alignSelf: 'center',
|
|
131
|
+
width: '100%',
|
|
132
|
+
},
|
|
133
|
+
formElementWrapper: {
|
|
134
|
+
marginBottom: formElementSpacing,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Wrap each child with spacing View (universal gap replacement)
|
|
139
|
+
// Children.map() handles arrays, fragments, single elements correctly
|
|
140
|
+
const childrenWithSpacing = Children.map(children, (child, index) => {
|
|
141
|
+
const childArray = Children.toArray(children);
|
|
142
|
+
const childKey = (child as React.ReactElement)?.key || `child-${index}`;
|
|
143
|
+
return (
|
|
144
|
+
<View
|
|
145
|
+
key={childKey}
|
|
146
|
+
style={index < childArray.length - 1 ? styles.formElementWrapper : undefined}
|
|
147
|
+
>
|
|
148
|
+
{child}
|
|
149
|
+
</View>
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<View style={[styles.container, containerStyle]} testID={testID}>
|
|
155
|
+
<View style={styles.surface}>
|
|
156
|
+
<ScrollView
|
|
157
|
+
style={styles.scrollView}
|
|
158
|
+
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
|
|
159
|
+
keyboardShouldPersistTaps="handled"
|
|
160
|
+
keyboardDismissMode="on-drag"
|
|
161
|
+
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
|
162
|
+
testID={testID ? `${testID}-scroll` : undefined}
|
|
163
|
+
>
|
|
164
|
+
{childrenWithSpacing}
|
|
165
|
+
</ScrollView>
|
|
166
|
+
</View>
|
|
167
|
+
</View>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScreenLayout - Universal Screen Container Component
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent layout structure for all screens:
|
|
5
|
+
* - SafeAreaView with configurable edges
|
|
6
|
+
* - Optional ScrollView for content
|
|
7
|
+
* - Theme-aware background colors
|
|
8
|
+
* - Optional header/footer slots
|
|
9
|
+
* - Consistent spacing and padding
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* <ScreenLayout>
|
|
13
|
+
* <View>Your content here</View>
|
|
14
|
+
* </ScreenLayout>
|
|
15
|
+
*
|
|
16
|
+
* Advanced:
|
|
17
|
+
* <ScreenLayout
|
|
18
|
+
* scrollable={false}
|
|
19
|
+
* edges={['top', 'bottom']}
|
|
20
|
+
* header={<CustomHeader />}
|
|
21
|
+
* >
|
|
22
|
+
* <View>Content</View>
|
|
23
|
+
* </ScreenLayout>
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import React, { useMemo } from 'react';
|
|
27
|
+
import { View, ScrollView, StyleSheet, ViewStyle } from 'react-native';
|
|
28
|
+
import { SafeAreaView, Edge } from 'react-native-safe-area-context';
|
|
29
|
+
import { useAppDesignTokens } from '../theme';
|
|
30
|
+
|
|
31
|
+
export interface ScreenLayoutProps {
|
|
32
|
+
/**
|
|
33
|
+
* Content to render inside the layout
|
|
34
|
+
*/
|
|
35
|
+
children: React.ReactNode;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Enable scrolling (default: true)
|
|
39
|
+
* Set to false for screens with custom scroll logic
|
|
40
|
+
*/
|
|
41
|
+
scrollable?: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Safe area edges to apply (default: ['top'])
|
|
45
|
+
* Common values:
|
|
46
|
+
* - ['top'] - For screens with bottom tabs
|
|
47
|
+
* - ['top', 'bottom'] - For modal screens
|
|
48
|
+
* - [] - No safe area (use cautiously)
|
|
49
|
+
*/
|
|
50
|
+
edges?: Edge[];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional header component
|
|
54
|
+
* Rendered above scrollable content
|
|
55
|
+
*/
|
|
56
|
+
header?: React.ReactNode;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Optional footer component
|
|
60
|
+
* Rendered below scrollable content
|
|
61
|
+
*/
|
|
62
|
+
footer?: React.ReactNode;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Override background color
|
|
66
|
+
* If not provided, uses theme's backgroundPrimary
|
|
67
|
+
*/
|
|
68
|
+
backgroundColor?: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Custom container style
|
|
72
|
+
*/
|
|
73
|
+
containerStyle?: ViewStyle;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Custom content container style (for ScrollView)
|
|
77
|
+
*/
|
|
78
|
+
contentContainerStyle?: ViewStyle;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Test ID for automation
|
|
82
|
+
*/
|
|
83
|
+
testID?: string;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Hide vertical scroll indicator (default: false)
|
|
87
|
+
*/
|
|
88
|
+
hideScrollIndicator?: boolean;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Enable keyboard avoiding behavior (default: false)
|
|
92
|
+
* Useful for screens with inputs
|
|
93
|
+
*/
|
|
94
|
+
keyboardAvoiding?: boolean;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Accessibility label for the layout
|
|
98
|
+
*/
|
|
99
|
+
accessibilityLabel?: string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Accessibility hint for the layout
|
|
103
|
+
*/
|
|
104
|
+
accessibilityHint?: string;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whether the layout is accessible
|
|
108
|
+
*/
|
|
109
|
+
accessible?: boolean;
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
114
|
+
children,
|
|
115
|
+
scrollable = true,
|
|
116
|
+
edges = ['top'],
|
|
117
|
+
header,
|
|
118
|
+
footer,
|
|
119
|
+
backgroundColor,
|
|
120
|
+
containerStyle,
|
|
121
|
+
contentContainerStyle,
|
|
122
|
+
testID,
|
|
123
|
+
hideScrollIndicator = false,
|
|
124
|
+
keyboardAvoiding = false,
|
|
125
|
+
}) => {
|
|
126
|
+
// Automatically uses current theme from global store
|
|
127
|
+
const tokens = useAppDesignTokens();
|
|
128
|
+
const styles = useMemo(() => StyleSheet.create({
|
|
129
|
+
container: {
|
|
130
|
+
flex: 1,
|
|
131
|
+
},
|
|
132
|
+
content: {
|
|
133
|
+
flex: 1,
|
|
134
|
+
},
|
|
135
|
+
scrollView: {
|
|
136
|
+
flex: 1,
|
|
137
|
+
},
|
|
138
|
+
scrollContent: {
|
|
139
|
+
flexGrow: 1,
|
|
140
|
+
paddingHorizontal: tokens.spacing.md,
|
|
141
|
+
paddingBottom: tokens.spacing.lg,
|
|
142
|
+
},
|
|
143
|
+
}), [tokens]);
|
|
144
|
+
|
|
145
|
+
const bgColor = backgroundColor || tokens.colors.backgroundPrimary;
|
|
146
|
+
|
|
147
|
+
// Non-scrollable layout
|
|
148
|
+
if (!scrollable) {
|
|
149
|
+
return (
|
|
150
|
+
<SafeAreaView
|
|
151
|
+
style={[styles.container, { backgroundColor: bgColor }, containerStyle]}
|
|
152
|
+
edges={edges}
|
|
153
|
+
testID={testID}
|
|
154
|
+
>
|
|
155
|
+
{header}
|
|
156
|
+
<View style={[styles.content, contentContainerStyle]}>
|
|
157
|
+
{children}
|
|
158
|
+
</View>
|
|
159
|
+
{footer}
|
|
160
|
+
</SafeAreaView>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Scrollable layout (default)
|
|
165
|
+
return (
|
|
166
|
+
<SafeAreaView
|
|
167
|
+
style={[styles.container, { backgroundColor: bgColor }, containerStyle]}
|
|
168
|
+
edges={edges}
|
|
169
|
+
testID={testID}
|
|
170
|
+
>
|
|
171
|
+
{header}
|
|
172
|
+
<ScrollView
|
|
173
|
+
style={styles.scrollView}
|
|
174
|
+
contentContainerStyle={[styles.scrollContent, contentContainerStyle]}
|
|
175
|
+
showsVerticalScrollIndicator={!hideScrollIndicator}
|
|
176
|
+
keyboardShouldPersistTaps={keyboardAvoiding ? 'handled' : 'never'}
|
|
177
|
+
>
|
|
178
|
+
{children}
|
|
179
|
+
</ScrollView>
|
|
180
|
+
{footer}
|
|
181
|
+
</SafeAreaView>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-design-system-organisms - Public API
|
|
3
|
+
*
|
|
4
|
+
* Organism design components for React Native - Complex UI patterns
|
|
5
|
+
* Built from atoms and molecules following atomic design principles
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { ScreenLayout, AppHeader, FormContainer } from '@umituz/react-native-design-system-organisms';
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Component exports
|
|
14
|
+
export { AppHeader } from './AppHeader';
|
|
15
|
+
export { ScreenLayout } from './ScreenLayout';
|
|
16
|
+
export { FormContainer } from './FormContainer';
|
|
17
|
+
|
|
18
|
+
// Type exports
|
|
19
|
+
export type { AppHeaderProps } from './AppHeader';
|
|
20
|
+
export type { ScreenLayoutProps } from './ScreenLayout';
|
|
21
|
+
export type { FormContainerProps } from './FormContainer';
|
|
22
|
+
|
|
23
|
+
// Union type for all organism props (used for type narrowing)
|
|
24
|
+
import type { AppHeaderProps } from './AppHeader';
|
|
25
|
+
import type { ScreenLayoutProps } from './ScreenLayout';
|
|
26
|
+
import type { FormContainerProps } from './FormContainer';
|
|
27
|
+
|
|
28
|
+
export type OrganismComponentProps =
|
|
29
|
+
| { type: 'AppHeader'; props: AppHeaderProps }
|
|
30
|
+
| { type: 'ScreenLayout'; props: ScreenLayoutProps }
|
|
31
|
+
| { type: 'FormContainer'; props: FormContainerProps };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive Design Configuration
|
|
3
|
+
*
|
|
4
|
+
* Centralized configuration for responsive design system.
|
|
5
|
+
* All magic numbers and breakpoints are defined here for maintainability.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Device breakpoints based on standard device sizes
|
|
10
|
+
* These values determine when responsive behaviors change
|
|
11
|
+
*/
|
|
12
|
+
export const DEVICE_BREAKPOINTS = {
|
|
13
|
+
SMALL_PHONE: 375, // iPhone 13 mini and smaller
|
|
14
|
+
MEDIUM_PHONE: 414, // iPhone 13/14/15
|
|
15
|
+
LARGE_PHONE: 428, // iPhone 14 Pro Max
|
|
16
|
+
SMALL_TABLET: 768, // iPad mini
|
|
17
|
+
TABLET: 1024, // iPad Air and larger tablets
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Responsive sizing percentages
|
|
22
|
+
* These percentages control how elements scale relative to screen dimensions
|
|
23
|
+
*/
|
|
24
|
+
export const RESPONSIVE_PERCENTAGES = {
|
|
25
|
+
// Logo sizing percentages
|
|
26
|
+
LOGO_SMALL_PHONE_MAX: 0.28, // 28% of screen width for small phones
|
|
27
|
+
LOGO_TABLET_MAX: 0.15, // 15% of screen width for tablets
|
|
28
|
+
|
|
29
|
+
// Icon container sizing percentages
|
|
30
|
+
ICON_CONTAINER_SMALL_PHONE: 0.30, // 30% of screen width for small phones
|
|
31
|
+
ICON_CONTAINER_TABLET: 0.20, // 20% of screen width for tablets
|
|
32
|
+
|
|
33
|
+
// Content width percentages
|
|
34
|
+
CONTENT_SMALL_PHONE: 0.90, // 90% of screen width for small phones
|
|
35
|
+
CONTENT_PHONE: 0.85, // 85% of screen width for standard phones
|
|
36
|
+
CONTENT_TABLET: 0.60, // 60% of screen width for tablets
|
|
37
|
+
|
|
38
|
+
// Input height percentages
|
|
39
|
+
INPUT_SMALL_DEVICE: 0.15, // 15% of screen height for small devices
|
|
40
|
+
INPUT_MEDIUM_DEVICE: 0.18, // 18% of screen height for medium devices
|
|
41
|
+
|
|
42
|
+
// Font scaling factors
|
|
43
|
+
FONT_SMALL_PHONE: 0.90, // 90% of base font size for small phones
|
|
44
|
+
FONT_TABLET: 1.10, // 110% of base font size for tablets
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Size constraints and limits
|
|
49
|
+
* These values define minimum and maximum sizes for responsive elements
|
|
50
|
+
*/
|
|
51
|
+
export const SIZE_CONSTRAINTS = {
|
|
52
|
+
// Logo size constraints
|
|
53
|
+
LOGO_MIN_SMALL: 100, // Minimum logo size for small phones
|
|
54
|
+
LOGO_MAX_SMALL: 120, // Maximum logo size for small phones
|
|
55
|
+
LOGO_MIN_TABLET: 140, // Minimum logo size for tablets
|
|
56
|
+
LOGO_MAX_TABLET: 200, // Maximum logo size for tablets
|
|
57
|
+
|
|
58
|
+
// Input height constraints
|
|
59
|
+
INPUT_MAX_SMALL: 120, // Maximum input height for small devices
|
|
60
|
+
INPUT_MAX_MEDIUM: 150, // Maximum input height for medium devices
|
|
61
|
+
INPUT_MAX_LARGE: 200, // Maximum input height for large devices
|
|
62
|
+
|
|
63
|
+
// Icon container constraints
|
|
64
|
+
ICON_MAX_SMALL: 120, // Maximum icon container for small phones
|
|
65
|
+
ICON_MAX_TABLET: 180, // Maximum icon container for tablets
|
|
66
|
+
|
|
67
|
+
// Content width constraints
|
|
68
|
+
CONTENT_MAX_TABLET: 600, // Maximum content width for tablets
|
|
69
|
+
|
|
70
|
+
// Modal height constraints
|
|
71
|
+
MODAL_MIN_SMALL: 250, // Minimum modal height for small devices
|
|
72
|
+
MODAL_MIN_STANDARD: 300, // Minimum modal height for standard devices
|
|
73
|
+
MODAL_MIN_TABLET: 350, // Minimum modal height for tablets
|
|
74
|
+
MODAL_MAX_TABLET: 500, // Maximum modal height for tablets
|
|
75
|
+
|
|
76
|
+
// Font size constraints
|
|
77
|
+
FONT_MIN_SIZE: 11, // Minimum font size
|
|
78
|
+
} as const;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Layout spacing and positioning
|
|
82
|
+
* These values control spacing, padding, and positioning
|
|
83
|
+
*/
|
|
84
|
+
export const LAYOUT_CONSTANTS = {
|
|
85
|
+
// Spacing multipliers
|
|
86
|
+
SPACING_MULTIPLIER_SMALL: 0.90, // 90% spacing for small devices
|
|
87
|
+
SPACING_MULTIPLIER_TABLET: 1.20, // 120% spacing for tablets
|
|
88
|
+
SPACING_MULTIPLIER_STANDARD: 1.0, // 100% spacing for standard devices
|
|
89
|
+
|
|
90
|
+
// Padding and margins
|
|
91
|
+
HORIZONTAL_PADDING_BASE: 16, // Base horizontal padding
|
|
92
|
+
BOTTOM_POSITION_BASE: 32, // Base bottom position
|
|
93
|
+
|
|
94
|
+
// Safe area offsets
|
|
95
|
+
SAFE_AREA_OFFSET: 16, // Safe area offset for positioning
|
|
96
|
+
TAB_BAR_OFFSET: 90, // Tab bar height + spacing for FAB positioning
|
|
97
|
+
|
|
98
|
+
// FAB positioning
|
|
99
|
+
FAB_BOTTOM_TABLET: 100, // FAB bottom position for tablets
|
|
100
|
+
FAB_RIGHT_TABLET: 24, // FAB right position for tablets
|
|
101
|
+
FAB_RIGHT_PHONE: 20, // FAB right position for phones
|
|
102
|
+
|
|
103
|
+
// Modal heights
|
|
104
|
+
MODAL_HEIGHT_SMALL: '75%', // Modal max height for small devices
|
|
105
|
+
MODAL_HEIGHT_STANDARD: '70%', // Modal max height for standard devices
|
|
106
|
+
MODAL_HEIGHT_TABLET: '60%', // Modal max height for tablets
|
|
107
|
+
} as const;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Device height thresholds
|
|
111
|
+
* These values determine responsive behavior based on screen height
|
|
112
|
+
*/
|
|
113
|
+
export const HEIGHT_THRESHOLDS = {
|
|
114
|
+
SMALL_DEVICE: 667, // iPhone SE, iPhone 8 height
|
|
115
|
+
MEDIUM_DEVICE: 844, // iPhone 13 mini, iPhone 13 height
|
|
116
|
+
LARGE_DEVICE: 1024, // Tablet height threshold
|
|
117
|
+
} as const;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Grid layout configuration
|
|
121
|
+
* Controls responsive grid behavior
|
|
122
|
+
*/
|
|
123
|
+
export const GRID_CONFIG = {
|
|
124
|
+
DEFAULT_MOBILE_COLUMNS: 2, // Default columns for mobile
|
|
125
|
+
DEFAULT_TABLET_COLUMNS: 4, // Default columns for tablet
|
|
126
|
+
} as const;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Input validation constraints
|
|
130
|
+
* Defines valid ranges for input parameters
|
|
131
|
+
*/
|
|
132
|
+
export const VALIDATION_CONSTRAINTS = {
|
|
133
|
+
MIN_BASE_SIZE: 0, // Minimum valid base size
|
|
134
|
+
MAX_BASE_SIZE: 1000, // Maximum valid base size (realistic UI limit)
|
|
135
|
+
MIN_BASE_FONT_SIZE: 1, // Minimum valid font size
|
|
136
|
+
MAX_BASE_FONT_SIZE: 1000, // Maximum valid font size
|
|
137
|
+
MIN_SCREEN_DIMENSION: 100, // Minimum valid screen dimension
|
|
138
|
+
MAX_SCREEN_DIMENSION: 5000, // Maximum valid screen dimension (realistic)
|
|
139
|
+
} as const;
|
|
@@ -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
|
+
};
|