@umituz/react-native-design-system 2.6.33 → 2.6.36
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/atoms/AtomicInput.tsx +21 -4
- package/src/atoms/AtomicKeyboardAvoidingView.tsx +49 -0
- package/src/atoms/AtomicTextArea.tsx +78 -29
- package/src/atoms/index.ts +3 -0
- package/src/device/infrastructure/services/DeviceExtrasCollector.ts +29 -1
- package/src/layouts/FormLayout/FormLayout.tsx +4 -3
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.36",
|
|
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",
|
|
@@ -43,7 +43,15 @@ export interface AtomicInputProps {
|
|
|
43
43
|
/** Show character counter */
|
|
44
44
|
showCharacterCount?: boolean;
|
|
45
45
|
/** Keyboard type */
|
|
46
|
-
keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'url' | 'number-pad' | 'decimal-pad';
|
|
46
|
+
keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'url' | 'number-pad' | 'decimal-pad' | 'web-search' | 'twitter' | 'numeric' | 'visible-password';
|
|
47
|
+
/** Return key type */
|
|
48
|
+
returnKeyType?: 'done' | 'go' | 'next' | 'search' | 'send';
|
|
49
|
+
/** Callback when submit button is pressed */
|
|
50
|
+
onSubmitEditing?: () => void;
|
|
51
|
+
/** Blur on submit */
|
|
52
|
+
blurOnSubmit?: boolean;
|
|
53
|
+
/** Auto focus */
|
|
54
|
+
autoFocus?: boolean;
|
|
47
55
|
/** Auto-capitalize */
|
|
48
56
|
autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
|
|
49
57
|
/** Auto-correct */
|
|
@@ -78,7 +86,7 @@ export interface AtomicInputProps {
|
|
|
78
86
|
* - Responsive sizing
|
|
79
87
|
* - Full accessibility support
|
|
80
88
|
*/
|
|
81
|
-
export const AtomicInput
|
|
89
|
+
export const AtomicInput = React.forwardRef<TextInput, AtomicInputProps>(({
|
|
82
90
|
variant = 'outlined',
|
|
83
91
|
state = 'default',
|
|
84
92
|
size = 'md',
|
|
@@ -95,6 +103,10 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
95
103
|
maxLength,
|
|
96
104
|
showCharacterCount = false,
|
|
97
105
|
keyboardType = 'default',
|
|
106
|
+
returnKeyType,
|
|
107
|
+
onSubmitEditing,
|
|
108
|
+
blurOnSubmit,
|
|
109
|
+
autoFocus,
|
|
98
110
|
autoCapitalize = 'sentences',
|
|
99
111
|
autoCorrect = true,
|
|
100
112
|
disabled = false,
|
|
@@ -105,7 +117,7 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
105
117
|
onFocus,
|
|
106
118
|
multiline = false,
|
|
107
119
|
numberOfLines,
|
|
108
|
-
}) => {
|
|
120
|
+
}, ref) => {
|
|
109
121
|
const tokens = useAppDesignTokens();
|
|
110
122
|
|
|
111
123
|
const {
|
|
@@ -198,6 +210,7 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
198
210
|
)}
|
|
199
211
|
|
|
200
212
|
<TextInput
|
|
213
|
+
ref={ref}
|
|
201
214
|
value={localValue}
|
|
202
215
|
onChangeText={handleTextChange}
|
|
203
216
|
placeholder={placeholder}
|
|
@@ -205,6 +218,10 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
205
218
|
secureTextEntry={secureTextEntry && !isPasswordVisible}
|
|
206
219
|
maxLength={maxLength}
|
|
207
220
|
keyboardType={keyboardType}
|
|
221
|
+
returnKeyType={returnKeyType}
|
|
222
|
+
onSubmitEditing={onSubmitEditing}
|
|
223
|
+
blurOnSubmit={blurOnSubmit}
|
|
224
|
+
autoFocus={autoFocus}
|
|
208
225
|
autoCapitalize={autoCapitalize}
|
|
209
226
|
autoCorrect={autoCorrect}
|
|
210
227
|
editable={!isDisabled}
|
|
@@ -274,7 +291,7 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
|
|
|
274
291
|
)}
|
|
275
292
|
</View>
|
|
276
293
|
);
|
|
277
|
-
};
|
|
294
|
+
});
|
|
278
295
|
|
|
279
296
|
const styles = StyleSheet.create({
|
|
280
297
|
container: {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
KeyboardAvoidingView,
|
|
4
|
+
Platform,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
type KeyboardAvoidingViewProps,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
|
|
9
|
+
export interface AtomicKeyboardAvoidingViewProps extends KeyboardAvoidingViewProps {
|
|
10
|
+
/**
|
|
11
|
+
* Optional offset to adjust the position of the content.
|
|
12
|
+
* On iOS, this is often necessary to account for headers, tabs, etc.
|
|
13
|
+
*/
|
|
14
|
+
offset?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* AtomicKeyboardAvoidingView - A consistent wrapper for React Native's KeyboardAvoidingView
|
|
19
|
+
*
|
|
20
|
+
* Provides sensible defaults and OS-specific behaviors:
|
|
21
|
+
* - iOS: behavior="padding"
|
|
22
|
+
* - Android: behavior=undefined (handled by windowSoftInputMode="adjustResize")
|
|
23
|
+
*/
|
|
24
|
+
export const AtomicKeyboardAvoidingView: React.FC<AtomicKeyboardAvoidingViewProps> = ({
|
|
25
|
+
children,
|
|
26
|
+
behavior,
|
|
27
|
+
style,
|
|
28
|
+
offset = 0,
|
|
29
|
+
...props
|
|
30
|
+
}) => {
|
|
31
|
+
const defaultBehavior = Platform.OS === 'ios' ? 'padding' : undefined;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<KeyboardAvoidingView
|
|
35
|
+
behavior={behavior ?? defaultBehavior}
|
|
36
|
+
style={[styles.container, style]}
|
|
37
|
+
keyboardVerticalOffset={offset}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</KeyboardAvoidingView>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const styles = StyleSheet.create({
|
|
46
|
+
container: {
|
|
47
|
+
flex: 1,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -1,32 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Atomic Design Level: ATOM
|
|
5
|
-
* Purpose: Multiline text input across all apps
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { View, TextInput, StyleSheet, ViewStyle } from 'react-native';
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { View, TextInput, StyleSheet, type ViewStyle, type StyleProp, type TextStyle } from 'react-native';
|
|
10
3
|
import { useAppDesignTokens } from '../theme';
|
|
11
4
|
import { AtomicText } from './AtomicText';
|
|
12
5
|
|
|
13
6
|
export interface AtomicTextAreaProps {
|
|
7
|
+
/** Text area label */
|
|
14
8
|
label?: string;
|
|
9
|
+
/** Current value */
|
|
15
10
|
value?: string;
|
|
11
|
+
/** Value change callback */
|
|
16
12
|
onChangeText?: (text: string) => void;
|
|
13
|
+
/** Placeholder text */
|
|
17
14
|
placeholder?: string;
|
|
15
|
+
/** Helper text below input */
|
|
18
16
|
helperText?: string;
|
|
17
|
+
/** Error message to display */
|
|
19
18
|
errorText?: string;
|
|
19
|
+
/** Maximum character length */
|
|
20
20
|
maxLength?: number;
|
|
21
|
+
/** Number of lines (default: 4) */
|
|
21
22
|
numberOfLines?: number;
|
|
23
|
+
/** Alternative to numberOfLines */
|
|
22
24
|
rows?: number;
|
|
25
|
+
/** Minimum height override */
|
|
23
26
|
minHeight?: number;
|
|
27
|
+
/** Disabled state */
|
|
24
28
|
disabled?: boolean;
|
|
25
|
-
style
|
|
29
|
+
/** Container style */
|
|
30
|
+
style?: StyleProp<ViewStyle>;
|
|
31
|
+
/** Input text style */
|
|
32
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
33
|
+
/** Auto focus */
|
|
34
|
+
autoFocus?: boolean;
|
|
35
|
+
/** Return key type */
|
|
36
|
+
returnKeyType?: 'done' | 'go' | 'next' | 'search' | 'send';
|
|
37
|
+
/** Callback when submit button is pressed */
|
|
38
|
+
onSubmitEditing?: () => void;
|
|
39
|
+
/** Blur on submit */
|
|
40
|
+
blurOnSubmit?: boolean;
|
|
41
|
+
/** Test ID */
|
|
26
42
|
testID?: string;
|
|
27
43
|
}
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
/**
|
|
46
|
+
* AtomicTextArea - Multiline Text Input Component
|
|
47
|
+
* Consistent with AtomicInput but optimized for multiline usage.
|
|
48
|
+
*/
|
|
49
|
+
export const AtomicTextArea = forwardRef<TextInput, AtomicTextAreaProps>(({
|
|
30
50
|
label,
|
|
31
51
|
value,
|
|
32
52
|
onChangeText,
|
|
@@ -39,8 +59,13 @@ export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
|
|
|
39
59
|
minHeight,
|
|
40
60
|
disabled = false,
|
|
41
61
|
style,
|
|
62
|
+
inputStyle,
|
|
63
|
+
autoFocus,
|
|
64
|
+
returnKeyType,
|
|
65
|
+
onSubmitEditing,
|
|
66
|
+
blurOnSubmit,
|
|
42
67
|
testID,
|
|
43
|
-
}) => {
|
|
68
|
+
}, ref) => {
|
|
44
69
|
const lineCount = numberOfLines ?? rows;
|
|
45
70
|
const calculatedMinHeight = minHeight ?? lineCount * 24;
|
|
46
71
|
const tokens = useAppDesignTokens();
|
|
@@ -51,20 +76,26 @@ export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
|
|
|
51
76
|
{label && (
|
|
52
77
|
<AtomicText
|
|
53
78
|
type="labelMedium"
|
|
54
|
-
|
|
79
|
+
color={hasError ? 'error' : 'secondary'}
|
|
80
|
+
style={styles.label}
|
|
55
81
|
>
|
|
56
82
|
{label}
|
|
57
83
|
</AtomicText>
|
|
58
84
|
)}
|
|
59
85
|
<TextInput
|
|
86
|
+
ref={ref}
|
|
60
87
|
value={value}
|
|
61
88
|
onChangeText={onChangeText}
|
|
62
89
|
placeholder={placeholder}
|
|
63
|
-
placeholderTextColor={tokens.colors.
|
|
90
|
+
placeholderTextColor={tokens.colors.textSecondary}
|
|
64
91
|
maxLength={maxLength}
|
|
65
92
|
numberOfLines={lineCount}
|
|
66
93
|
multiline
|
|
67
94
|
editable={!disabled}
|
|
95
|
+
autoFocus={autoFocus}
|
|
96
|
+
returnKeyType={returnKeyType}
|
|
97
|
+
onSubmitEditing={onSubmitEditing}
|
|
98
|
+
blurOnSubmit={blurOnSubmit}
|
|
68
99
|
textAlignVertical="top"
|
|
69
100
|
style={[
|
|
70
101
|
styles.input,
|
|
@@ -73,39 +104,57 @@ export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
|
|
|
73
104
|
borderColor: hasError ? tokens.colors.error : tokens.colors.border,
|
|
74
105
|
color: tokens.colors.textPrimary,
|
|
75
106
|
minHeight: calculatedMinHeight,
|
|
107
|
+
padding: tokens.spacing.md,
|
|
108
|
+
borderRadius: tokens.borderRadius.md,
|
|
109
|
+
fontSize: 16,
|
|
76
110
|
},
|
|
111
|
+
inputStyle,
|
|
77
112
|
disabled && { opacity: 0.5 },
|
|
78
113
|
]}
|
|
79
114
|
/>
|
|
80
115
|
{(helperText || errorText) && (
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
<View style={styles.helperRow}>
|
|
117
|
+
<AtomicText
|
|
118
|
+
type="bodySmall"
|
|
119
|
+
color={hasError ? 'error' : 'secondary'}
|
|
120
|
+
style={styles.helperText}
|
|
121
|
+
>
|
|
122
|
+
{errorText || helperText}
|
|
123
|
+
</AtomicText>
|
|
124
|
+
{maxLength && value !== undefined && (
|
|
125
|
+
<AtomicText
|
|
126
|
+
type="labelSmall"
|
|
127
|
+
color="secondary"
|
|
128
|
+
style={styles.characterCount}
|
|
129
|
+
>
|
|
130
|
+
{value.length}/{maxLength}
|
|
131
|
+
</AtomicText>
|
|
132
|
+
)}
|
|
133
|
+
</View>
|
|
90
134
|
)}
|
|
91
135
|
</View>
|
|
92
136
|
);
|
|
93
|
-
};
|
|
137
|
+
});
|
|
94
138
|
|
|
95
139
|
const styles = StyleSheet.create({
|
|
96
140
|
container: {
|
|
97
|
-
|
|
141
|
+
width: '100%',
|
|
98
142
|
},
|
|
99
143
|
label: {
|
|
100
144
|
marginBottom: 8,
|
|
101
145
|
},
|
|
102
146
|
input: {
|
|
103
147
|
borderWidth: 1,
|
|
104
|
-
borderRadius: 12,
|
|
105
|
-
padding: 12,
|
|
106
|
-
fontSize: 16,
|
|
107
148
|
},
|
|
108
|
-
|
|
149
|
+
helperRow: {
|
|
150
|
+
flexDirection: 'row',
|
|
151
|
+
justifyContent: 'space-between',
|
|
109
152
|
marginTop: 4,
|
|
110
153
|
},
|
|
154
|
+
helperText: {
|
|
155
|
+
flex: 1,
|
|
156
|
+
},
|
|
157
|
+
characterCount: {
|
|
158
|
+
marginLeft: 8,
|
|
159
|
+
},
|
|
111
160
|
});
|
package/src/atoms/index.ts
CHANGED
|
@@ -111,3 +111,6 @@ export { AtomicTouchable, type AtomicTouchableProps } from './AtomicTouchable';
|
|
|
111
111
|
|
|
112
112
|
// StatusBar
|
|
113
113
|
export { AtomicStatusBar, type AtomicStatusBarProps } from './status-bar';
|
|
114
|
+
|
|
115
|
+
// Keyboard Avoiding
|
|
116
|
+
export { AtomicKeyboardAvoidingView, type AtomicKeyboardAvoidingViewProps } from './AtomicKeyboardAvoidingView';
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @layer infrastructure/services
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { Dimensions } from 'react-native';
|
|
11
12
|
import * as Localization from 'expo-localization';
|
|
12
13
|
import { DeviceInfoService } from './DeviceInfoService';
|
|
13
14
|
import { ApplicationInfoService } from './ApplicationInfoService';
|
|
@@ -19,16 +20,29 @@ import { PersistentDeviceIdService } from './PersistentDeviceIdService';
|
|
|
19
20
|
* Index signature added for Record<string, unknown> compatibility
|
|
20
21
|
*/
|
|
21
22
|
export interface DeviceExtras {
|
|
22
|
-
[key: string]: string | undefined;
|
|
23
|
+
[key: string]: string | number | boolean | undefined;
|
|
23
24
|
deviceId?: string;
|
|
24
25
|
platform?: string;
|
|
25
26
|
deviceModel?: string;
|
|
26
27
|
deviceBrand?: string;
|
|
28
|
+
deviceName?: string;
|
|
29
|
+
deviceType?: number;
|
|
30
|
+
deviceYearClass?: number;
|
|
31
|
+
isDevice?: boolean;
|
|
32
|
+
osName?: string;
|
|
27
33
|
osVersion?: string;
|
|
34
|
+
osBuildId?: string;
|
|
35
|
+
totalMemory?: number;
|
|
28
36
|
appVersion?: string;
|
|
29
37
|
buildNumber?: string;
|
|
30
38
|
locale?: string;
|
|
39
|
+
region?: string;
|
|
31
40
|
timezone?: string;
|
|
41
|
+
screenWidth?: number;
|
|
42
|
+
screenHeight?: number;
|
|
43
|
+
screenScale?: number;
|
|
44
|
+
fontScale?: number;
|
|
45
|
+
isLandscape?: boolean;
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
/**
|
|
@@ -70,17 +84,31 @@ export async function collectDeviceExtras(): Promise<DeviceExtras> {
|
|
|
70
84
|
]);
|
|
71
85
|
|
|
72
86
|
const locale = getDeviceLocale();
|
|
87
|
+
const { width, height, scale, fontScale } = Dimensions.get('screen');
|
|
73
88
|
|
|
74
89
|
return {
|
|
75
90
|
deviceId,
|
|
76
91
|
platform: deviceInfo.platform,
|
|
77
92
|
deviceModel: deviceInfo.modelName || undefined,
|
|
78
93
|
deviceBrand: deviceInfo.brand || undefined,
|
|
94
|
+
deviceName: deviceInfo.deviceName || undefined,
|
|
95
|
+
deviceType: deviceInfo.deviceType ?? undefined,
|
|
96
|
+
deviceYearClass: deviceInfo.deviceYearClass ?? undefined,
|
|
97
|
+
isDevice: deviceInfo.isDevice,
|
|
98
|
+
osName: deviceInfo.osName || undefined,
|
|
79
99
|
osVersion: deviceInfo.osVersion || undefined,
|
|
100
|
+
osBuildId: deviceInfo.osBuildId || undefined,
|
|
101
|
+
totalMemory: deviceInfo.totalMemory ?? undefined,
|
|
80
102
|
appVersion: appInfo.nativeApplicationVersion || undefined,
|
|
81
103
|
buildNumber: appInfo.nativeBuildVersion || undefined,
|
|
82
104
|
locale,
|
|
105
|
+
region: deviceInfo.region || undefined,
|
|
83
106
|
timezone: deviceInfo.timezone || undefined,
|
|
107
|
+
screenWidth: Math.round(width),
|
|
108
|
+
screenHeight: Math.round(height),
|
|
109
|
+
screenScale: scale,
|
|
110
|
+
fontScale,
|
|
111
|
+
isLandscape: width > height,
|
|
84
112
|
};
|
|
85
113
|
} catch {
|
|
86
114
|
return {};
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React, { useMemo } from 'react';
|
|
9
|
-
import { View, ScrollView,
|
|
9
|
+
import { View, ScrollView, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
|
|
10
10
|
import { useAppDesignTokens } from '../../theme';
|
|
11
11
|
import { useResponsive } from '../../responsive';
|
|
12
|
+
import { AtomicKeyboardAvoidingView } from '../../atoms';
|
|
12
13
|
|
|
13
14
|
export interface FormLayoutProps {
|
|
14
15
|
/** Form fields and content */
|
|
@@ -103,9 +104,9 @@ export const FormLayout: React.FC<FormLayoutProps> = ({
|
|
|
103
104
|
const mainContent = disableKeyboardAvoid ? (
|
|
104
105
|
scrollableContent
|
|
105
106
|
) : (
|
|
106
|
-
<
|
|
107
|
+
<AtomicKeyboardAvoidingView style={styles.container}>
|
|
107
108
|
{scrollableContent}
|
|
108
|
-
</
|
|
109
|
+
</AtomicKeyboardAvoidingView>
|
|
109
110
|
);
|
|
110
111
|
|
|
111
112
|
return (
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import React, { useMemo } from 'react';
|
|
27
|
-
import { View, ScrollView, StyleSheet,
|
|
27
|
+
import { View, ScrollView, StyleSheet, type ViewStyle, type RefreshControlProps } from 'react-native';
|
|
28
28
|
import { SafeAreaView, useSafeAreaInsets, type Edge } from '../../safe-area';
|
|
29
29
|
import { useAppDesignTokens } from '../../theme';
|
|
30
30
|
import { getScreenLayoutConfig } from '../../responsive/responsiveLayout';
|
|
31
|
+
import { AtomicKeyboardAvoidingView } from '../../atoms';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* NOTE: This component now works in conjunction with the SafeAreaProvider
|
|
@@ -206,12 +207,11 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
|
206
207
|
const ContentWrapper: React.FC<{ children: React.ReactNode }> = ({ children: wrapperChildren }) => {
|
|
207
208
|
if (keyboardAvoiding) {
|
|
208
209
|
return (
|
|
209
|
-
<
|
|
210
|
+
<AtomicKeyboardAvoidingView
|
|
210
211
|
style={styles.keyboardAvoidingView}
|
|
211
|
-
behavior="padding"
|
|
212
212
|
>
|
|
213
213
|
{wrapperChildren}
|
|
214
|
-
</
|
|
214
|
+
</AtomicKeyboardAvoidingView>
|
|
215
215
|
);
|
|
216
216
|
}
|
|
217
217
|
return <>{wrapperChildren}</>;
|