@momo-kits/foundation 0.169.9-beta.1 → 0.169.9
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/Application/BottomSheet.hooks.ts +27 -17
- package/Application/BottomSheet.tsx +35 -27
- package/Application/BottomTab/ActiveIndicator.tsx +5 -1
- package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
- package/Application/BottomTab/BottomTabBar.tsx +14 -16
- package/Application/BottomTab/CurvedContainer.tsx +8 -2
- package/Application/BottomTab/CustomBottomTabItem.tsx +2 -2
- package/Application/BottomTab/DuoRailTabBar.tsx +5 -5
- package/Application/BottomTab/DuoTabBar.tsx +15 -3
- package/Application/BottomTab/TabScreen.tsx +11 -10
- package/Application/BottomTab/index.tsx +5 -5
- package/Application/BottomTab/metrics.ts +23 -3
- package/Application/BottomTab/usePressLift.ts +3 -3
- package/Application/Components/HeaderAnimated.tsx +13 -38
- package/Application/Components/HeaderBackground.tsx +3 -16
- package/Application/Components/HeaderExtendHeader.tsx +28 -41
- package/Application/Components/HeaderLeft.tsx +18 -4
- package/Application/Components/HeaderRight.styles.ts +13 -15
- package/Application/Components/HeaderTitle.shared.ts +11 -10
- package/Application/Components/HeaderToolkitAction.tsx +54 -21
- package/Application/Components/NavigationButton.tsx +6 -9
- package/Application/Components/SearchHeader.tsx +7 -20
- package/Application/Components/useToolkitActions.tsx +22 -21
- package/Application/ModalScreen.tsx +19 -15
- package/Application/StackScreen.tsx +13 -18
- package/Application/index.ts +1 -11
- package/Application/modalLayout.ts +1 -0
- package/Application/navigationOptions.tsx +15 -6
- package/Application/types.header.ts +2 -7
- package/Application/types.tabs.ts +0 -1
- package/Application/types.ts +0 -7
- package/Context/index.ts +3 -1
- package/Image/index.tsx +6 -6
- package/Input/ErrorView.tsx +59 -0
- package/Input/FloatingView.tsx +101 -0
- package/Input/Input.tsx +40 -37
- package/Input/InputDropDown.tsx +13 -10
- package/Input/InputMoney.tsx +17 -17
- package/Input/InputMoneyView.tsx +12 -12
- package/Input/InputOTP.tsx +46 -34
- package/Input/InputPhoneNumber.tsx +48 -43
- package/Input/InputSearch.tsx +26 -39
- package/Input/InputTextArea.tsx +29 -23
- package/Input/RenderTrailing.tsx +79 -0
- package/Input/constants.ts +8 -0
- package/Input/index.tsx +1 -7
- package/Input/utils.ts +126 -7
- package/Layout/FloatingButton.tsx +38 -24
- package/Layout/Screen.tsx +0 -3
- package/Layout/layoutMetrics.ts +43 -34
- package/Layout/screenHeader.tsx +52 -55
- package/Layout/screenTypes.ts +4 -13
- package/Pagination/PaginationScroll.tsx +5 -3
- package/Pagination/types.ts +3 -5
- package/Popup/PopupNotify.tsx +17 -14
- package/Popup/PopupPromotion.tsx +5 -4
- package/Popup/types.ts +2 -4
- package/Popup/utils.ts +7 -4
- package/Skeleton/index.tsx +15 -12
- package/package.json +1 -1
- package/Application/headerLayout.ts +0 -165
- package/Input/common.tsx +0 -257
- package/Input/moneyUtils.ts +0 -23
|
@@ -20,18 +20,23 @@ import {
|
|
|
20
20
|
} from '../Context';
|
|
21
21
|
import { Colors, Spacing, Styles } from '../Consts';
|
|
22
22
|
import { Icon } from '../Icon';
|
|
23
|
+
import { Image } from '../Image';
|
|
23
24
|
import { useScaleSize, Text, exportFontFamily } from '../Text';
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
getSizeStyle,
|
|
28
|
-
RenderTrailing,
|
|
29
|
-
} from './common';
|
|
25
|
+
import { Typography } from '../Text/types';
|
|
26
|
+
import { CLEAR_ICON_SIZE } from './constants';
|
|
27
|
+
import { ErrorView } from './ErrorView';
|
|
30
28
|
import { InputPhoneNumberProps } from './index';
|
|
29
|
+
import { RenderTrailing } from './RenderTrailing';
|
|
31
30
|
import styles from './styles';
|
|
32
31
|
import SystemTextInput from './SystemTextInput';
|
|
33
|
-
import {
|
|
34
|
-
|
|
32
|
+
import { getBorderColor, getInputHeight, getSizeStyle } from './utils';
|
|
33
|
+
|
|
34
|
+
const SMALL_FONT_SIZE = 14;
|
|
35
|
+
const LARGE_FONT_SIZE = 18;
|
|
36
|
+
const SMALL_DIVIDER_HEIGHT = 24;
|
|
37
|
+
const LARGE_DIVIDER_HEIGHT = 32;
|
|
38
|
+
const FOCUSED_BORDER_WIDTH = 1.5;
|
|
39
|
+
const FLAG_SIZE = 24;
|
|
35
40
|
|
|
36
41
|
/**
|
|
37
42
|
* Vietnamese phone number input with a fixed flag and `+84` prefix, number pad
|
|
@@ -67,10 +72,10 @@ const InputPhoneNumber = forwardRef(
|
|
|
67
72
|
) => {
|
|
68
73
|
const { theme } = useContext(ApplicationContext);
|
|
69
74
|
const context = useContext<any>(MiniAppContext);
|
|
70
|
-
const scaleHeight = useScaleSize(size
|
|
71
|
-
const [
|
|
75
|
+
const scaleHeight = useScaleSize(getInputHeight(size));
|
|
76
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
72
77
|
const [internalText, setInternalText] = useState(defaultValue || '');
|
|
73
|
-
const
|
|
78
|
+
const hasValue = value !== undefined ? !!value : !!internalText;
|
|
74
79
|
const inputRef = useRef<TextInput | null>(null);
|
|
75
80
|
const componentName = 'InputPhoneNumber';
|
|
76
81
|
|
|
@@ -81,29 +86,29 @@ const InputPhoneNumber = forwardRef(
|
|
|
81
86
|
props.accessibilityLabel,
|
|
82
87
|
);
|
|
83
88
|
|
|
84
|
-
const
|
|
89
|
+
const handleClearText = () => {
|
|
85
90
|
inputRef?.current?.clear();
|
|
86
|
-
|
|
91
|
+
handleChangeText('');
|
|
87
92
|
};
|
|
88
93
|
|
|
89
|
-
const
|
|
94
|
+
const handleChangeText = (text: string) => {
|
|
90
95
|
if (value === undefined) {
|
|
91
96
|
setInternalText(text);
|
|
92
97
|
}
|
|
93
98
|
onChangeText?.(text);
|
|
94
99
|
};
|
|
95
100
|
|
|
96
|
-
const
|
|
97
|
-
|
|
101
|
+
const handleFocus = (e: TextInputFocusEvent) => {
|
|
102
|
+
setIsFocused(true);
|
|
98
103
|
onFocus?.(e);
|
|
99
104
|
};
|
|
100
105
|
|
|
101
|
-
const
|
|
102
|
-
|
|
106
|
+
const handleBlur = (e: TextInputFocusEvent) => {
|
|
107
|
+
setIsFocused(false);
|
|
103
108
|
onBlur?.(e);
|
|
104
109
|
};
|
|
105
110
|
|
|
106
|
-
const
|
|
111
|
+
const handleSetText = (text: string) => {
|
|
107
112
|
if (__DEV__ && value !== undefined) {
|
|
108
113
|
console.error(
|
|
109
114
|
`${componentName}: calling ref.setText() on a controlled input (\`value\` is provided) is an anti-pattern. ` +
|
|
@@ -112,48 +117,48 @@ const InputPhoneNumber = forwardRef(
|
|
|
112
117
|
);
|
|
113
118
|
}
|
|
114
119
|
inputRef?.current?.setNativeProps({ text });
|
|
115
|
-
|
|
120
|
+
handleChangeText(text);
|
|
116
121
|
};
|
|
117
122
|
|
|
118
123
|
useImperativeHandle(ref, () => {
|
|
119
124
|
return {
|
|
120
|
-
clear:
|
|
125
|
+
clear: handleClearText,
|
|
121
126
|
focus: () => inputRef.current?.focus(),
|
|
122
127
|
blur: () => inputRef.current?.blur(),
|
|
123
|
-
setText:
|
|
128
|
+
setText: handleSetText,
|
|
124
129
|
};
|
|
125
130
|
});
|
|
126
131
|
|
|
127
132
|
let inputTextStyles = {};
|
|
128
|
-
let dividerHeight =
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
let dividerHeight = SMALL_DIVIDER_HEIGHT;
|
|
134
|
+
const smallFontSize = useScaleSize(SMALL_FONT_SIZE);
|
|
135
|
+
const largeFontSize = useScaleSize(LARGE_FONT_SIZE);
|
|
131
136
|
const semiboldFontFamily = exportFontFamily('semibold');
|
|
132
137
|
const boldFontFamily = exportFontFamily('bold');
|
|
133
138
|
let typography: Typography;
|
|
134
139
|
if (size === 'small') {
|
|
135
140
|
inputTextStyles = {
|
|
136
|
-
fontSize:
|
|
141
|
+
fontSize: smallFontSize,
|
|
137
142
|
fontFamily: semiboldFontFamily,
|
|
138
143
|
};
|
|
139
|
-
dividerHeight =
|
|
144
|
+
dividerHeight = SMALL_DIVIDER_HEIGHT;
|
|
140
145
|
typography = 'header_s_semibold';
|
|
141
146
|
} else {
|
|
142
147
|
inputTextStyles = {
|
|
143
|
-
fontSize:
|
|
148
|
+
fontSize: largeFontSize,
|
|
144
149
|
fontFamily: boldFontFamily,
|
|
145
150
|
};
|
|
146
|
-
dividerHeight =
|
|
151
|
+
dividerHeight = LARGE_DIVIDER_HEIGHT;
|
|
147
152
|
typography = 'header_m_bold';
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
const renderInputView = () => {
|
|
151
156
|
const disabledColor = theme.colors.text.disable;
|
|
152
|
-
const
|
|
157
|
+
const isSecure = secureTextEntry;
|
|
153
158
|
let textColor = theme.colors.text.default;
|
|
154
159
|
let placeholderColor = theme.colors.text.hint;
|
|
155
160
|
let iconTintColor = trailingColor ?? theme.colors.text.default;
|
|
156
|
-
const borderWidth =
|
|
161
|
+
const borderWidth = isFocused ? FOCUSED_BORDER_WIDTH : 1;
|
|
157
162
|
let secureTextStyle = styles.secureTextLarge;
|
|
158
163
|
|
|
159
164
|
if (size === 'small') {
|
|
@@ -173,7 +178,7 @@ const InputPhoneNumber = forwardRef(
|
|
|
173
178
|
{ backgroundColor: theme.colors.background.surface },
|
|
174
179
|
getSizeStyle(size, undefined, scaleHeight),
|
|
175
180
|
{ borderWidth },
|
|
176
|
-
getBorderColor(theme,
|
|
181
|
+
getBorderColor(theme, isFocused, errorMessage, disabled),
|
|
177
182
|
]}
|
|
178
183
|
>
|
|
179
184
|
<View style={styles.inputView}>
|
|
@@ -204,7 +209,7 @@ const InputPhoneNumber = forwardRef(
|
|
|
204
209
|
{...props}
|
|
205
210
|
accessibilityLabel={`${componentId}`}
|
|
206
211
|
editable={editable && !disabled}
|
|
207
|
-
secureTextEntry={
|
|
212
|
+
secureTextEntry={isSecure}
|
|
208
213
|
textAlignVertical="center"
|
|
209
214
|
ref={inputRef}
|
|
210
215
|
accessibilityState={{
|
|
@@ -217,15 +222,15 @@ const InputPhoneNumber = forwardRef(
|
|
|
217
222
|
color: textColor,
|
|
218
223
|
},
|
|
219
224
|
inputTextStyles,
|
|
220
|
-
|
|
225
|
+
isSecure && hasValue && secureTextStyle,
|
|
221
226
|
]}
|
|
222
227
|
defaultValue={defaultValue}
|
|
223
228
|
allowFontScaling={false}
|
|
224
229
|
textBreakStrategy="highQuality"
|
|
225
230
|
value={value}
|
|
226
|
-
onChangeText={
|
|
227
|
-
onFocus={
|
|
228
|
-
onBlur={
|
|
231
|
+
onChangeText={handleChangeText}
|
|
232
|
+
onFocus={handleFocus}
|
|
233
|
+
onBlur={handleBlur}
|
|
229
234
|
placeholder={placeholder}
|
|
230
235
|
selectionColor={theme.colors.primary}
|
|
231
236
|
placeholderTextColor={placeholderColor}
|
|
@@ -233,16 +238,16 @@ const InputPhoneNumber = forwardRef(
|
|
|
233
238
|
/>
|
|
234
239
|
</View>
|
|
235
240
|
<View style={styles.iconView}>
|
|
236
|
-
{showClearIcon &&
|
|
241
|
+
{showClearIcon && isFocused && hasValue && (
|
|
237
242
|
<TouchableOpacity
|
|
238
243
|
accessibilityLabel={`${componentId}|clear-icon-touch`}
|
|
239
244
|
accessible={false}
|
|
240
245
|
style={styles.iconWrapper}
|
|
241
|
-
onPress={
|
|
246
|
+
onPress={handleClearText}
|
|
242
247
|
>
|
|
243
248
|
<Icon
|
|
244
249
|
source="24_navigation_close_circle_full"
|
|
245
|
-
size={
|
|
250
|
+
size={CLEAR_ICON_SIZE}
|
|
246
251
|
color={theme.colors.text.hint}
|
|
247
252
|
accessibilityLabel={`${componentId}|clear-icon`}
|
|
248
253
|
/>
|
|
@@ -299,9 +304,9 @@ const InputPhoneNumber = forwardRef(
|
|
|
299
304
|
|
|
300
305
|
const innerStyle = StyleSheet.create({
|
|
301
306
|
iconFlag: {
|
|
302
|
-
width:
|
|
303
|
-
height:
|
|
304
|
-
marginHorizontal:
|
|
307
|
+
width: FLAG_SIZE,
|
|
308
|
+
height: FLAG_SIZE,
|
|
309
|
+
marginHorizontal: Spacing.XXS,
|
|
305
310
|
},
|
|
306
311
|
});
|
|
307
312
|
|
package/Input/InputSearch.tsx
CHANGED
|
@@ -13,18 +13,18 @@ import {
|
|
|
13
13
|
View,
|
|
14
14
|
} from 'react-native';
|
|
15
15
|
import { useComponentId } from '../Application';
|
|
16
|
-
import { HeaderBarMetrics } from '../Application/headerLayout';
|
|
17
16
|
import {
|
|
18
17
|
ApplicationContext,
|
|
19
18
|
ComponentContext,
|
|
20
19
|
MiniAppContext,
|
|
21
20
|
} from '../Context';
|
|
22
21
|
import { Icon } from '../Icon';
|
|
22
|
+
import { Styles } from '../Consts';
|
|
23
23
|
import { Text, useScaleSize, exportFontFamily } from '../Text';
|
|
24
|
+
import { CLEAR_ICON_SIZE, INPUT_FONT_SIZE, PRESSED_OPACITY } from './constants';
|
|
24
25
|
import { InputRef, InputSearchProps } from './index';
|
|
25
26
|
import { TextTyping } from './SearchTextTyping';
|
|
26
27
|
import styles from './styles';
|
|
27
|
-
import { Styles } from '../Consts';
|
|
28
28
|
import SystemTextInput from './SystemTextInput';
|
|
29
29
|
|
|
30
30
|
const SEARCH_ICON_SIZE = 24;
|
|
@@ -52,29 +52,21 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
52
52
|
backgroundColor,
|
|
53
53
|
onPress,
|
|
54
54
|
typingData,
|
|
55
|
-
size = 'default',
|
|
56
55
|
...props
|
|
57
56
|
},
|
|
58
57
|
ref,
|
|
59
58
|
) => {
|
|
60
59
|
const { theme } = useContext(ApplicationContext);
|
|
61
60
|
const context = useContext<any>(MiniAppContext);
|
|
62
|
-
const scaledFontSize = useScaleSize(
|
|
61
|
+
const scaledFontSize = useScaleSize(INPUT_FONT_SIZE);
|
|
63
62
|
const searchFontFamily = exportFontFamily('regular');
|
|
64
63
|
|
|
65
64
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
66
|
-
const isHeaderSize = size === 'header';
|
|
67
|
-
const inputHeightStyle = isHeaderSize && {
|
|
68
|
-
height: HeaderBarMetrics.searchHeight,
|
|
69
|
-
};
|
|
70
|
-
const searchIconSize = isHeaderSize
|
|
71
|
-
? HeaderBarMetrics.iconSize
|
|
72
|
-
: SEARCH_ICON_SIZE;
|
|
73
65
|
|
|
74
|
-
const [
|
|
66
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
75
67
|
const [internalText, setInternalText] = useState(defaultValue || '');
|
|
76
68
|
const currentValue = value !== undefined ? value : internalText;
|
|
77
|
-
const
|
|
69
|
+
const hasValue = !!currentValue;
|
|
78
70
|
const inputRef = useRef<TextInput | null>(null);
|
|
79
71
|
|
|
80
72
|
const componentName = 'InputSearch';
|
|
@@ -84,40 +76,40 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
84
76
|
props.accessibilityLabel,
|
|
85
77
|
);
|
|
86
78
|
|
|
87
|
-
const
|
|
88
|
-
|
|
79
|
+
const handleFocus = (e: TextInputFocusEvent) => {
|
|
80
|
+
setIsFocused(true);
|
|
89
81
|
onFocus?.(e);
|
|
90
82
|
};
|
|
91
83
|
|
|
92
|
-
const
|
|
93
|
-
|
|
84
|
+
const handleBlur = (e: TextInputFocusEvent) => {
|
|
85
|
+
setIsFocused(false);
|
|
94
86
|
onBlur?.(e);
|
|
95
87
|
};
|
|
96
88
|
|
|
97
|
-
const
|
|
89
|
+
const handleClearText = () => {
|
|
98
90
|
inputRef?.current?.clear();
|
|
99
91
|
inputRef.current?.setNativeProps({ text: '' });
|
|
100
|
-
|
|
92
|
+
handleChangeText('');
|
|
101
93
|
};
|
|
102
94
|
|
|
103
|
-
const
|
|
95
|
+
const handleChangeText = (text: string) => {
|
|
104
96
|
if (value === undefined) {
|
|
105
97
|
setInternalText(text);
|
|
106
98
|
}
|
|
107
99
|
onChangeText?.(text);
|
|
108
100
|
};
|
|
109
101
|
|
|
110
|
-
const
|
|
102
|
+
const handleSetText = (text: string) => {
|
|
111
103
|
inputRef?.current?.setNativeProps({ text });
|
|
112
|
-
|
|
104
|
+
handleChangeText(text);
|
|
113
105
|
};
|
|
114
106
|
|
|
115
107
|
useImperativeHandle(ref, () => {
|
|
116
108
|
return {
|
|
117
|
-
clear:
|
|
109
|
+
clear: handleClearText,
|
|
118
110
|
focus: () => inputRef.current?.focus(),
|
|
119
111
|
blur: () => inputRef.current?.blur(),
|
|
120
|
-
setText:
|
|
112
|
+
setText: handleSetText,
|
|
121
113
|
};
|
|
122
114
|
});
|
|
123
115
|
|
|
@@ -125,13 +117,9 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
125
117
|
if (typingData && typingData?.length > 0) {
|
|
126
118
|
return (
|
|
127
119
|
<TouchableOpacity
|
|
128
|
-
style={[
|
|
129
|
-
styles.searchInput,
|
|
130
|
-
styles.searchInputTyping,
|
|
131
|
-
inputHeightStyle,
|
|
132
|
-
]}
|
|
120
|
+
style={[styles.searchInput, styles.searchInputTyping]}
|
|
133
121
|
onPress={onPress}
|
|
134
|
-
activeOpacity={onPress ?
|
|
122
|
+
activeOpacity={onPress ? PRESSED_OPACITY : 1}
|
|
135
123
|
>
|
|
136
124
|
<TextTyping data={typingData} />
|
|
137
125
|
</TouchableOpacity>
|
|
@@ -147,7 +135,6 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
147
135
|
ref={inputRef}
|
|
148
136
|
style={[
|
|
149
137
|
styles.searchInput,
|
|
150
|
-
inputHeightStyle,
|
|
151
138
|
{
|
|
152
139
|
fontSize: scaledFontSize,
|
|
153
140
|
color: theme.colors.text.default,
|
|
@@ -155,10 +142,10 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
155
142
|
},
|
|
156
143
|
]}
|
|
157
144
|
value={value}
|
|
158
|
-
onChangeText={
|
|
145
|
+
onChangeText={handleChangeText}
|
|
159
146
|
defaultValue={defaultValue}
|
|
160
|
-
onFocus={
|
|
161
|
-
onBlur={
|
|
147
|
+
onFocus={handleFocus}
|
|
148
|
+
onBlur={handleBlur}
|
|
162
149
|
placeholder={placeholder}
|
|
163
150
|
selectionColor={theme.colors.primary}
|
|
164
151
|
placeholderTextColor={theme.colors.text.hint}
|
|
@@ -169,15 +156,15 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
169
156
|
const renderTrailing = () => {
|
|
170
157
|
return (
|
|
171
158
|
<View style={Styles.row}>
|
|
172
|
-
{
|
|
159
|
+
{hasValue && (
|
|
173
160
|
<TouchableOpacity
|
|
174
161
|
style={styles.iconWrapper}
|
|
175
|
-
onPress={
|
|
162
|
+
onPress={handleClearText}
|
|
176
163
|
accessibilityLabel={`${componentId}|close-icon-touch`}
|
|
177
164
|
>
|
|
178
165
|
<Icon
|
|
179
166
|
source="24_navigation_close_circle_full"
|
|
180
|
-
size={
|
|
167
|
+
size={CLEAR_ICON_SIZE}
|
|
181
168
|
color={theme.colors.text.hint}
|
|
182
169
|
/>
|
|
183
170
|
</TouchableOpacity>
|
|
@@ -208,7 +195,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
208
195
|
};
|
|
209
196
|
|
|
210
197
|
let inputState = 'empty';
|
|
211
|
-
if (
|
|
198
|
+
if (isFocused) {
|
|
212
199
|
inputState = 'focus';
|
|
213
200
|
}
|
|
214
201
|
if (value && value?.length > 0) {
|
|
@@ -241,7 +228,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
241
228
|
>
|
|
242
229
|
<Icon
|
|
243
230
|
source={'navigation_search'}
|
|
244
|
-
size={
|
|
231
|
+
size={SEARCH_ICON_SIZE}
|
|
245
232
|
color={theme.colors.text.hint}
|
|
246
233
|
/>
|
|
247
234
|
{renderInputView()}
|
package/Input/InputTextArea.tsx
CHANGED
|
@@ -12,14 +12,20 @@ import {
|
|
|
12
12
|
TouchableOpacity,
|
|
13
13
|
View,
|
|
14
14
|
} from 'react-native';
|
|
15
|
+
import { setAutomationID, useComponentId } from '../Application';
|
|
15
16
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
16
17
|
import { Icon } from '../Icon';
|
|
17
18
|
import { Text, useScaleSize, exportFontFamily } from '../Text';
|
|
18
|
-
import {
|
|
19
|
+
import { CLEAR_ICON_SIZE, INPUT_FONT_SIZE } from './constants';
|
|
20
|
+
import { ErrorView } from './ErrorView';
|
|
21
|
+
import { FloatingView } from './FloatingView';
|
|
19
22
|
import { InputTextAreaProps } from './index';
|
|
20
23
|
import styles from './styles';
|
|
21
24
|
import SystemTextInput from './SystemTextInput';
|
|
22
|
-
import {
|
|
25
|
+
import { getBorderColor } from './utils';
|
|
26
|
+
|
|
27
|
+
const DEFAULT_MAX_LENGTH = 300;
|
|
28
|
+
const DEFAULT_HEIGHT = 104;
|
|
23
29
|
|
|
24
30
|
/**
|
|
25
31
|
* Multiline input with floating label, clear icon and a `length/maxLength`
|
|
@@ -40,7 +46,7 @@ const InputTextArea = forwardRef(
|
|
|
40
46
|
defaultValue,
|
|
41
47
|
height,
|
|
42
48
|
placeholder,
|
|
43
|
-
maxLength =
|
|
49
|
+
maxLength = DEFAULT_MAX_LENGTH,
|
|
44
50
|
required,
|
|
45
51
|
errorSpacing,
|
|
46
52
|
style,
|
|
@@ -57,12 +63,12 @@ const InputTextArea = forwardRef(
|
|
|
57
63
|
);
|
|
58
64
|
const { theme } = useContext(ApplicationContext);
|
|
59
65
|
const context = useContext<any>(MiniAppContext);
|
|
60
|
-
const
|
|
66
|
+
const defaultHeight = useScaleSize(DEFAULT_HEIGHT);
|
|
61
67
|
|
|
62
68
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
63
|
-
const scaledFontSize = useScaleSize(
|
|
69
|
+
const scaledFontSize = useScaleSize(INPUT_FONT_SIZE);
|
|
64
70
|
const inputFontFamily = exportFontFamily('regular');
|
|
65
|
-
const [
|
|
71
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
66
72
|
const [inputValue, setInputValue] = useState(defaultValue ?? '');
|
|
67
73
|
const inputRef = useRef<TextInput | null>(null);
|
|
68
74
|
|
|
@@ -70,33 +76,33 @@ const InputTextArea = forwardRef(
|
|
|
70
76
|
setInputValue(value ?? '');
|
|
71
77
|
}, [value]);
|
|
72
78
|
|
|
73
|
-
const
|
|
79
|
+
const handleClearText = () => {
|
|
74
80
|
inputRef?.current?.clear();
|
|
75
81
|
inputRef.current?.setNativeProps({ text: '' });
|
|
76
|
-
|
|
82
|
+
handleChangeText('');
|
|
77
83
|
};
|
|
78
84
|
|
|
79
|
-
const
|
|
85
|
+
const handleChangeText = (text: string) => {
|
|
80
86
|
onChangeText?.(text);
|
|
81
87
|
setInputValue(text);
|
|
82
88
|
};
|
|
83
89
|
|
|
84
|
-
const
|
|
85
|
-
|
|
90
|
+
const handleFocus = (e: TextInputFocusEvent) => {
|
|
91
|
+
setIsFocused(true);
|
|
86
92
|
onFocus?.(e);
|
|
87
93
|
};
|
|
88
94
|
|
|
89
|
-
const
|
|
90
|
-
|
|
95
|
+
const handleBlur = (e: TextInputFocusEvent) => {
|
|
96
|
+
setIsFocused(false);
|
|
91
97
|
onBlur?.(e);
|
|
92
98
|
};
|
|
93
99
|
|
|
94
100
|
useImperativeHandle(ref, () => {
|
|
95
101
|
return {
|
|
96
|
-
clear:
|
|
102
|
+
clear: handleClearText,
|
|
97
103
|
focus: () => inputRef.current?.focus(),
|
|
98
104
|
blur: () => inputRef.current?.blur(),
|
|
99
|
-
setText: (text: string) =>
|
|
105
|
+
setText: (text: string) => handleChangeText(text),
|
|
100
106
|
};
|
|
101
107
|
});
|
|
102
108
|
|
|
@@ -129,10 +135,10 @@ const InputTextArea = forwardRef(
|
|
|
129
135
|
style={[
|
|
130
136
|
styles.textAreaWrapper,
|
|
131
137
|
{
|
|
132
|
-
height: height ||
|
|
138
|
+
height: height || defaultHeight,
|
|
133
139
|
backgroundColor: theme.colors.background.surface,
|
|
134
140
|
},
|
|
135
|
-
getBorderColor(theme,
|
|
141
|
+
getBorderColor(theme, isFocused, errorMessage, disabled),
|
|
136
142
|
]}
|
|
137
143
|
>
|
|
138
144
|
<FloatingView
|
|
@@ -175,23 +181,23 @@ const InputTextArea = forwardRef(
|
|
|
175
181
|
textBreakStrategy="highQuality"
|
|
176
182
|
multiline={true}
|
|
177
183
|
value={inputValue}
|
|
178
|
-
onChangeText={
|
|
179
|
-
onFocus={
|
|
180
|
-
onBlur={
|
|
184
|
+
onChangeText={handleChangeText}
|
|
185
|
+
onFocus={handleFocus}
|
|
186
|
+
onBlur={handleBlur}
|
|
181
187
|
placeholder={placeholder}
|
|
182
188
|
selectionColor={theme.colors.primary}
|
|
183
189
|
placeholderTextColor={placeholderColor}
|
|
184
190
|
/>
|
|
185
191
|
</View>
|
|
186
|
-
{
|
|
192
|
+
{isFocused && inputValue.length > 0 && (
|
|
187
193
|
<TouchableOpacity
|
|
188
194
|
style={styles.iconWrapper}
|
|
189
|
-
onPress={
|
|
195
|
+
onPress={handleClearText}
|
|
190
196
|
accessibilityLabel={`${componentId}|clear-icon-touch`}
|
|
191
197
|
>
|
|
192
198
|
<Icon
|
|
193
199
|
source="24_navigation_close_circle_full"
|
|
194
|
-
size={
|
|
200
|
+
size={CLEAR_ICON_SIZE}
|
|
195
201
|
color={theme.colors.text.hint}
|
|
196
202
|
accessibilityLabel={`${componentId}|clear-icon`}
|
|
197
203
|
/>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, { type FC, type ReactNode, useContext } from 'react';
|
|
2
|
+
import { TouchableOpacity } from 'react-native';
|
|
3
|
+
import IconSources from '../Assets/icon.json';
|
|
4
|
+
import { ApplicationContext } from '../Context';
|
|
5
|
+
import { Icon } from '../Icon';
|
|
6
|
+
import { Loader } from '../Loader';
|
|
7
|
+
import { Text } from '../Text';
|
|
8
|
+
import styles from './styles';
|
|
9
|
+
|
|
10
|
+
const ICON_SIZE = 24;
|
|
11
|
+
const TEXT_MAX_LENGTH = 15;
|
|
12
|
+
|
|
13
|
+
type IconSourceMap = Record<string, { uri?: string } | undefined>;
|
|
14
|
+
|
|
15
|
+
type TrailingProps = {
|
|
16
|
+
loading?: boolean;
|
|
17
|
+
color?: string;
|
|
18
|
+
icon?: string;
|
|
19
|
+
componentId?: string;
|
|
20
|
+
onPressIcon?: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Trailing slot: spinner while `loading`, an icon when `icon` is a known icon
|
|
25
|
+
* name or URL, otherwise `icon` as a text action (first 15 chars).
|
|
26
|
+
*/
|
|
27
|
+
const RenderTrailing: FC<TrailingProps> = ({
|
|
28
|
+
loading,
|
|
29
|
+
color,
|
|
30
|
+
icon,
|
|
31
|
+
onPressIcon,
|
|
32
|
+
componentId,
|
|
33
|
+
}) => {
|
|
34
|
+
const { theme } = useContext(ApplicationContext);
|
|
35
|
+
if (loading) {
|
|
36
|
+
return <Loader type={'spinner'} color={color} style={styles.icon} />;
|
|
37
|
+
}
|
|
38
|
+
if (!icon) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const iconSources: IconSourceMap = IconSources;
|
|
43
|
+
const isIconName = !!iconSources[icon]?.uri;
|
|
44
|
+
const isIcon = isIconName || icon.includes('http');
|
|
45
|
+
const renderIconTouchable = (content: ReactNode) => {
|
|
46
|
+
return (
|
|
47
|
+
<TouchableOpacity
|
|
48
|
+
accessibilityLabel={`${componentId}|trailing-touch`}
|
|
49
|
+
onPress={onPressIcon}
|
|
50
|
+
style={styles.icon}
|
|
51
|
+
>
|
|
52
|
+
{content}
|
|
53
|
+
</TouchableOpacity>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (isIcon) {
|
|
58
|
+
return renderIconTouchable(
|
|
59
|
+
<Icon
|
|
60
|
+
color={color}
|
|
61
|
+
source={icon}
|
|
62
|
+
size={ICON_SIZE}
|
|
63
|
+
accessibilityLabel={`${componentId}|trailing-icon`}
|
|
64
|
+
/>,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return renderIconTouchable(
|
|
68
|
+
<Text
|
|
69
|
+
typography="action_xs_bold"
|
|
70
|
+
color={color ?? theme.colors.primary}
|
|
71
|
+
numberOfLines={1}
|
|
72
|
+
accessibilityLabel={`${componentId}|trailing-text`}
|
|
73
|
+
>
|
|
74
|
+
{icon.substring(0, TEXT_MAX_LENGTH)}
|
|
75
|
+
</Text>,
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export { RenderTrailing };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Input text size before font scaling. */
|
|
2
|
+
const INPUT_FONT_SIZE = 14;
|
|
3
|
+
/** Size of the clear (x) icon shown while a focused field has a value. */
|
|
4
|
+
const CLEAR_ICON_SIZE = 16;
|
|
5
|
+
/** Pressed opacity of the floating icon, dropdown and typing placeholder. */
|
|
6
|
+
const PRESSED_OPACITY = 0.6;
|
|
7
|
+
|
|
8
|
+
export { INPUT_FONT_SIZE, CLEAR_ICON_SIZE, PRESSED_OPACITY };
|
package/Input/index.tsx
CHANGED
|
@@ -194,12 +194,6 @@ export interface InputSearchProps extends TextInputProps {
|
|
|
194
194
|
*/
|
|
195
195
|
typingData?: string[];
|
|
196
196
|
|
|
197
|
-
/**
|
|
198
|
-
* Optional. `header` is the 32pt field with a 20pt search icon that sits in the
|
|
199
|
-
* navigation header. Defaults to `default`, the 36pt field for content.
|
|
200
|
-
*/
|
|
201
|
-
size?: 'default' | 'header';
|
|
202
|
-
|
|
203
197
|
/**
|
|
204
198
|
* Optional. Called when the field is pressed while showing `typingData`.
|
|
205
199
|
*/
|
|
@@ -373,4 +367,4 @@ export {
|
|
|
373
367
|
InputPhoneNumber,
|
|
374
368
|
};
|
|
375
369
|
|
|
376
|
-
export {
|
|
370
|
+
export {checkValidPhoneNumber};
|