@momo-kits/foundation 0.168.1-beta.37 → 0.168.1-beta.39
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 +8 -8
- package/Application/BottomSheet.parts.tsx +44 -34
- package/Application/BottomSheet.tsx +35 -26
- package/Application/BottomTab/ActiveIndicator.tsx +15 -14
- package/Application/BottomTab/BottomTabBar.tsx +49 -62
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +14 -12
- package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
- package/Application/BottomTab/DuoTabBar.tsx +8 -10
- package/Application/BottomTab/TabBarIcon.tsx +15 -29
- package/Application/BottomTab/TabScreen.tsx +1 -5
- package/Application/BottomTab/index.tsx +33 -30
- package/Application/BottomTab/metrics.ts +23 -23
- package/Application/BottomTab/tabItems.ts +1 -1
- package/Application/BottomTab/usePressLift.ts +2 -2
- package/Application/Components/BackgroundImageView.tsx +4 -0
- package/Application/Components/DuoGlassBackground.tsx +5 -4
- package/Application/Components/HeaderAnimated.tsx +40 -12
- package/Application/Components/HeaderBackground.tsx +19 -2
- package/Application/Components/HeaderExtendHeader.tsx +51 -37
- package/Application/Components/HeaderLeft.tsx +5 -18
- package/Application/Components/HeaderRight.styles.ts +15 -13
- package/Application/Components/HeaderRight.tsx +2 -1
- package/Application/Components/HeaderTitle.shared.ts +17 -16
- package/Application/Components/HeaderTitle.tsx +4 -3
- package/Application/Components/HeaderToolkitAction.tsx +29 -57
- package/Application/Components/NavigationButton.tsx +10 -6
- package/Application/Components/SearchHeader.tsx +37 -17
- package/Application/Components/TitleJourney.tsx +2 -2
- package/Application/Components/TitleLocation.tsx +2 -2
- package/Application/Components/TitleUser.tsx +2 -2
- package/Application/Components/useToolkitActions.tsx +17 -32
- package/Application/Localize.ts +7 -0
- package/Application/ModalScreen.tsx +9 -3
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -16
- package/Application/Navigator.ts +20 -48
- package/Application/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -26
- package/Application/StackScreen.tsx +17 -35
- package/Application/TooltipPortal.tsx +12 -5
- package/Application/WidgetContainer.tsx +10 -4
- package/Application/headerLayout.ts +158 -0
- package/Application/index.ts +11 -1
- package/Application/modalLayout.ts +13 -14
- package/Application/navigationOptions.tsx +27 -32
- package/Application/types.header.ts +7 -2
- package/Application/types.ts +7 -0
- package/Badge/Badge.tsx +5 -0
- package/Badge/BadgeDot.tsx +1 -0
- package/Badge/BadgeDotAnimation.tsx +15 -7
- package/Badge/BadgeRibbon.tsx +46 -38
- package/Badge/Shape.tsx +2 -0
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +5 -16
- package/Button/utils.ts +9 -15
- package/CheckBox/index.tsx +4 -0
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +5 -1
- package/Divider/DashDivider.tsx +1 -0
- package/Divider/index.tsx +4 -0
- package/FoundationList/index.tsx +4 -0
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +4 -12
- package/Image/index.tsx +9 -7
- package/Input/Input.tsx +8 -9
- package/Input/InputDropDown.tsx +10 -12
- package/Input/InputMoney.tsx +12 -8
- package/Input/InputMoneyView.tsx +14 -7
- package/Input/InputOTP.tsx +35 -33
- package/Input/InputPhoneNumber.tsx +8 -9
- package/Input/InputSearch.tsx +22 -8
- package/Input/InputTextArea.tsx +4 -0
- package/Input/SearchTextTyping.tsx +4 -0
- package/Input/SystemTextInput.tsx +7 -2
- package/Input/TextTyping.tsx +4 -0
- package/Input/common.tsx +26 -12
- package/Input/index.tsx +24 -21
- package/Input/moneyUtils.ts +10 -4
- package/Input/styles.ts +0 -5
- package/Input/utils.ts +14 -12
- package/Layout/Card.tsx +7 -4
- package/Layout/FloatingButton.tsx +4 -0
- package/Layout/Item.tsx +6 -2
- package/Layout/ItemList.tsx +9 -7
- package/Layout/ItemSectionList.tsx +2 -1
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +59 -58
- package/Layout/Section.tsx +8 -5
- package/Layout/TrackingScope.tsx +1 -0
- package/Layout/screenContent.tsx +4 -2
- package/Layout/screenFooter.tsx +28 -18
- package/Layout/screenHeader.tsx +88 -82
- package/Layout/screenQuickReturn.tsx +17 -20
- package/Layout/screenScroll.ts +4 -7
- package/Layout/screenTypes.ts +13 -4
- package/Layout/types.ts +2 -4
- package/Layout/utils.ts +31 -37
- package/Loader/DotLoader.tsx +4 -0
- package/Loader/Loader.tsx +4 -0
- package/Loader/ProgressBar.tsx +1 -0
- package/Loader/Spinner.tsx +4 -0
- package/Loader/utils.ts +11 -7
- package/Pagination/Dot.tsx +4 -0
- package/Pagination/PaginationDot.tsx +1 -0
- package/Pagination/PaginationNumber.tsx +5 -3
- package/Pagination/PaginationScroll.tsx +5 -0
- package/Pagination/PaginationWhiteDot.tsx +1 -0
- package/Pagination/index.tsx +4 -0
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +44 -48
- package/Popup/PopupPromotion.tsx +4 -7
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +4 -0
- package/Radio/index.tsx +4 -0
- package/Skeleton/index.tsx +8 -0
- package/Switch/index.tsx +1 -0
- package/Tag/index.tsx +5 -1
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +14 -3
- package/Text/styles.ts +1 -1
- package/Text/types.ts +1 -6
- package/Text/utils.ts +20 -4
- package/Title/index.tsx +41 -32
- package/ViewBoundary/index.tsx +4 -2
- package/index.ts +0 -6
- package/package.json +37 -37
- package/Application/__tests__/modalLayout.test.ts +0 -128
package/IconButton/index.tsx
CHANGED
|
@@ -41,6 +41,10 @@ export interface IconButtonProps extends TouchableOpacityProps {
|
|
|
41
41
|
params?: any;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Icon-only button. `type` sets the background and icon color; `'disabled'`
|
|
46
|
+
* also blocks presses.
|
|
47
|
+
*/
|
|
44
48
|
const IconButton: React.FC<IconButtonProps> = ({
|
|
45
49
|
type = 'primary',
|
|
46
50
|
icon,
|
|
@@ -54,9 +58,6 @@ const IconButton: React.FC<IconButtonProps> = ({
|
|
|
54
58
|
|
|
55
59
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
56
60
|
|
|
57
|
-
/**
|
|
58
|
-
* get size icon button
|
|
59
|
-
*/
|
|
60
61
|
const getSizeStyle = () => {
|
|
61
62
|
switch (size) {
|
|
62
63
|
case 'small':
|
|
@@ -68,16 +69,10 @@ const IconButton: React.FC<IconButtonProps> = ({
|
|
|
68
69
|
}
|
|
69
70
|
};
|
|
70
71
|
|
|
71
|
-
/**
|
|
72
|
-
* get shape icon button
|
|
73
|
-
*/
|
|
74
72
|
const getShapeStyle = () => {
|
|
75
73
|
return shape === 'square' ? styles.square : styles.circle;
|
|
76
74
|
};
|
|
77
75
|
|
|
78
|
-
/**
|
|
79
|
-
* get style for icon button by type
|
|
80
|
-
*/
|
|
81
76
|
const getTypeStyle = () => {
|
|
82
77
|
switch (type) {
|
|
83
78
|
case 'disabled':
|
|
@@ -111,9 +106,6 @@ const IconButton: React.FC<IconButtonProps> = ({
|
|
|
111
106
|
}
|
|
112
107
|
};
|
|
113
108
|
|
|
114
|
-
/**
|
|
115
|
-
* get color for icon
|
|
116
|
-
*/
|
|
117
109
|
const getIconColor = () => {
|
|
118
110
|
switch (type) {
|
|
119
111
|
case 'disabled':
|
package/Image/index.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { ImageProps } from './types';
|
|
|
15
15
|
|
|
16
16
|
type Status = 'success' | 'loading' | 'error';
|
|
17
17
|
|
|
18
|
+
/** Last path segment of a `{ uri }` source, used in QA labels; '' otherwise. */
|
|
18
19
|
export const getImageName = (source: any): string => {
|
|
19
20
|
const parts = source?.uri?.split?.('/');
|
|
20
21
|
if (parts?.length > 0) {
|
|
@@ -23,6 +24,10 @@ export const getImageName = (source: any): string => {
|
|
|
23
24
|
return '';
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* FastImage that shows a skeleton while loading (if `loading`) or inside a
|
|
29
|
+
* loading `SkeletonProvider`, and a fail icon on error.
|
|
30
|
+
*/
|
|
26
31
|
const Image: React.FC<ImageProps> = ({
|
|
27
32
|
style,
|
|
28
33
|
source,
|
|
@@ -44,15 +49,12 @@ const Image: React.FC<ImageProps> = ({
|
|
|
44
49
|
|
|
45
50
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
const imageName = getImageName(source);
|
|
53
|
+
let accessibilityLabel: any = `img|${imageName}`;
|
|
48
54
|
if (component?.componentId) {
|
|
49
|
-
accessibilityLabel = `${component?.componentId}|img|${
|
|
50
|
-
source,
|
|
51
|
-
)}`;
|
|
55
|
+
accessibilityLabel = `${component?.componentId}|img|${imageName}`;
|
|
52
56
|
}
|
|
53
|
-
|
|
54
|
-
* render content loading | fail | rendered
|
|
55
|
-
*/
|
|
57
|
+
|
|
56
58
|
const renderContent = () => {
|
|
57
59
|
if (status === 'loading' || status === 'error') {
|
|
58
60
|
let content = <Skeleton />;
|
package/Input/Input.tsx
CHANGED
|
@@ -31,7 +31,8 @@ import {
|
|
|
31
31
|
} from '../Context';
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Text input with floating label, leading/trailing/clear icons and an error or
|
|
35
|
+
* hint row; controlled via `value` or uncontrolled via `defaultValue`.
|
|
35
36
|
*/
|
|
36
37
|
const Input = forwardRef(
|
|
37
38
|
(
|
|
@@ -131,15 +132,17 @@ const Input = forwardRef(
|
|
|
131
132
|
};
|
|
132
133
|
});
|
|
133
134
|
|
|
134
|
-
/**
|
|
135
|
-
* Render input view
|
|
136
|
-
*/
|
|
137
135
|
const renderInputView = () => {
|
|
138
136
|
const disabledColor = theme.colors.text.disable;
|
|
139
137
|
const secure = secureTextEntry;
|
|
140
138
|
let textColor = theme.colors.text.default;
|
|
141
139
|
let placeholderColor = theme.colors.text.hint;
|
|
142
140
|
let iconTintColor = trailingColor;
|
|
141
|
+
let secureTextStyle = styles.secureTextLarge;
|
|
142
|
+
|
|
143
|
+
if (size === 'small') {
|
|
144
|
+
secureTextStyle = styles.secureTextSmall;
|
|
145
|
+
}
|
|
143
146
|
|
|
144
147
|
if (disabled) {
|
|
145
148
|
textColor = disabledColor;
|
|
@@ -199,11 +202,7 @@ const Input = forwardRef(
|
|
|
199
202
|
color: textColor,
|
|
200
203
|
fontFamily: exportFontFamily(fontWeight ?? 'regular'),
|
|
201
204
|
},
|
|
202
|
-
secure &&
|
|
203
|
-
haveValue &&
|
|
204
|
-
(size === 'small'
|
|
205
|
-
? styles.secureTextSmall
|
|
206
|
-
: styles.secureTextLarge),
|
|
205
|
+
secure && haveValue && secureTextStyle,
|
|
207
206
|
]}
|
|
208
207
|
defaultValue={defaultValue}
|
|
209
208
|
allowFontScaling={false}
|
package/Input/InputDropDown.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
|
-
import { TouchableOpacity, View } from 'react-native';
|
|
2
|
+
import { TouchableOpacity, View, type ViewStyle } from 'react-native';
|
|
3
3
|
import { useComponentId } from '../Application';
|
|
4
4
|
import {
|
|
5
5
|
ApplicationContext,
|
|
@@ -18,6 +18,10 @@ import { Text, useScaleSize } from '../Text';
|
|
|
18
18
|
import styles from './styles';
|
|
19
19
|
import { Spacing } from '../Consts';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Pressable field styled like an input with a chevron; shows `value` or
|
|
23
|
+
* `placeholder`. Opening the picker is left to `onPress`.
|
|
24
|
+
*/
|
|
21
25
|
const InputDropDown = ({
|
|
22
26
|
value,
|
|
23
27
|
floatingValue,
|
|
@@ -50,18 +54,19 @@ const InputDropDown = ({
|
|
|
50
54
|
props.accessibilityLabel,
|
|
51
55
|
);
|
|
52
56
|
|
|
53
|
-
/**
|
|
54
|
-
* Render the input view
|
|
55
|
-
*/
|
|
56
57
|
const renderInputView = () => {
|
|
57
58
|
const disabledColor = theme.colors.text.disable;
|
|
58
59
|
let textColor = theme.colors.text.default;
|
|
59
60
|
let placeholderColor = theme.colors.text.hint;
|
|
61
|
+
let chevronStyle: ViewStyle = styles.iconViewDropDownSingle;
|
|
60
62
|
|
|
61
63
|
if (disabled) {
|
|
62
64
|
textColor = disabledColor;
|
|
63
65
|
placeholderColor = disabledColor;
|
|
64
66
|
}
|
|
67
|
+
if (multiline) {
|
|
68
|
+
chevronStyle = styles.iconViewDropDownMultiline;
|
|
69
|
+
}
|
|
65
70
|
|
|
66
71
|
return (
|
|
67
72
|
<View
|
|
@@ -111,14 +116,7 @@ const InputDropDown = ({
|
|
|
111
116
|
</Text>
|
|
112
117
|
</View>
|
|
113
118
|
</View>
|
|
114
|
-
<View
|
|
115
|
-
style={[
|
|
116
|
-
styles.iconViewDropDown,
|
|
117
|
-
multiline
|
|
118
|
-
? styles.iconViewDropDownMultiline
|
|
119
|
-
: styles.iconViewDropDownSingle,
|
|
120
|
-
]}
|
|
121
|
-
>
|
|
119
|
+
<View style={[styles.iconViewDropDown, chevronStyle]}>
|
|
122
120
|
<Icon source={'arrow_chevron_down_small'} />
|
|
123
121
|
</View>
|
|
124
122
|
</View>
|
package/Input/InputMoney.tsx
CHANGED
|
@@ -16,6 +16,11 @@ import { InputMoneyView } from './InputMoneyView';
|
|
|
16
16
|
import { findEdit, moneyFormatter } from './moneyUtils';
|
|
17
17
|
import styles from './styles';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Money input: keeps digits only, shows them grouped with a currency suffix and
|
|
21
|
+
* reports raw digits to `onChangeText`. On Android it restores the caret by the
|
|
22
|
+
* count of digits after it, since regrouping shifts positions.
|
|
23
|
+
*/
|
|
19
24
|
const InputMoney = forwardRef(
|
|
20
25
|
(
|
|
21
26
|
{
|
|
@@ -88,9 +93,9 @@ const InputMoney = forwardRef(
|
|
|
88
93
|
|
|
89
94
|
useEffect(() => {
|
|
90
95
|
if (!caretSelection) return;
|
|
91
|
-
setCaretSelection(current =>
|
|
92
|
-
current === caretSelection ? undefined : current
|
|
93
|
-
);
|
|
96
|
+
setCaretSelection(current => {
|
|
97
|
+
return current === caretSelection ? undefined : current;
|
|
98
|
+
});
|
|
94
99
|
}, [caretSelection]);
|
|
95
100
|
|
|
96
101
|
const updateDisplayValue = (numeric: string) => {
|
|
@@ -142,11 +147,10 @@ const InputMoney = forwardRef(
|
|
|
142
147
|
lastRawText.current = text;
|
|
143
148
|
const formattedEdit = findEdit(lastDisplayValue, text);
|
|
144
149
|
const rawEdit = findEdit(raw, text);
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
).length;
|
|
150
|
+
const isRawEditSmaller = !!raw && rawEdit.size < formattedEdit.size;
|
|
151
|
+
const { old, start, suffix } = isRawEditSmaller ? rawEdit : formattedEdit;
|
|
152
|
+
const textAfterEdit = text.slice(text.length - suffix);
|
|
153
|
+
caretDigitsAfter.current = parseInputText(textAfterEdit).length;
|
|
150
154
|
if (text.length < old.length && digits === numericValue) {
|
|
151
155
|
const index = parseInputText(text.slice(0, start)).length - 1;
|
|
152
156
|
if (index >= 0) {
|
package/Input/InputMoneyView.tsx
CHANGED
|
@@ -52,6 +52,10 @@ type InputMoneyViewProps = Pick<
|
|
|
52
52
|
inputProps: TextInputProps;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Presentational body of InputMoney: bordered field, currency suffix (kept
|
|
57
|
+
* after a placeholder that ends with it) and the clear/trailing icons.
|
|
58
|
+
*/
|
|
55
59
|
export const InputMoneyView = ({
|
|
56
60
|
size,
|
|
57
61
|
errorMessage,
|
|
@@ -98,9 +102,12 @@ export const InputMoneyView = ({
|
|
|
98
102
|
fontFamily: inputFontFamily,
|
|
99
103
|
};
|
|
100
104
|
const placeholderHasCurrency = placeholder.endsWith(currency);
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
const shouldShowCurrency = !!displayValue || placeholderHasCurrency;
|
|
106
|
+
const shouldShowClearIcon = !!(showClearIcon && focused && displayValue);
|
|
107
|
+
let inputPlaceholder = placeholder;
|
|
108
|
+
if (placeholderHasCurrency) {
|
|
109
|
+
inputPlaceholder = placeholder.slice(0, -currency.length);
|
|
110
|
+
}
|
|
104
111
|
return (
|
|
105
112
|
<View
|
|
106
113
|
style={[
|
|
@@ -147,7 +154,7 @@ export const InputMoneyView = ({
|
|
|
147
154
|
keyboardType={'numeric'}
|
|
148
155
|
maxLength={undefined}
|
|
149
156
|
/>
|
|
150
|
-
{
|
|
157
|
+
{shouldShowCurrency && (
|
|
151
158
|
<Text
|
|
152
159
|
style={[
|
|
153
160
|
textStyle,
|
|
@@ -157,10 +164,10 @@ export const InputMoneyView = ({
|
|
|
157
164
|
>
|
|
158
165
|
{currency}
|
|
159
166
|
</Text>
|
|
160
|
-
)
|
|
167
|
+
)}
|
|
161
168
|
</Pressable>
|
|
162
169
|
<View style={styles.iconView}>
|
|
163
|
-
{
|
|
170
|
+
{shouldShowClearIcon && (
|
|
164
171
|
<TouchableOpacity
|
|
165
172
|
accessibilityLabel={`${componentId}|clear-icon-touch`}
|
|
166
173
|
style={styles.iconWrapper}
|
|
@@ -173,7 +180,7 @@ export const InputMoneyView = ({
|
|
|
173
180
|
accessibilityLabel={`${componentId}|clear-icon`}
|
|
174
181
|
/>
|
|
175
182
|
</TouchableOpacity>
|
|
176
|
-
)
|
|
183
|
+
)}
|
|
177
184
|
<RenderTrailing
|
|
178
185
|
color={iconTintColor}
|
|
179
186
|
icon={trailing}
|
package/Input/InputOTP.tsx
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
MiniAppContext,
|
|
39
39
|
} from '../Context';
|
|
40
40
|
|
|
41
|
+
/** Blinking caret shown in the OTP slot being typed. */
|
|
41
42
|
const OTPCaret: FC<CaretProps> = ({ index, length }) => {
|
|
42
43
|
const DURATION = 300;
|
|
43
44
|
const { theme } = useContext(ApplicationContext);
|
|
@@ -60,9 +61,7 @@ const OTPCaret: FC<CaretProps> = ({ index, length }) => {
|
|
|
60
61
|
};
|
|
61
62
|
}, [opacity]);
|
|
62
63
|
|
|
63
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
64
|
-
opacity: opacity.value,
|
|
65
|
-
}));
|
|
64
|
+
const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
|
66
65
|
|
|
67
66
|
const hasNextInput = !isNaN(Number(length)) && index !== Number(length) - 1;
|
|
68
67
|
const spacingStyle = hasNextInput && { marginRight: Spacing.L };
|
|
@@ -86,6 +85,10 @@ const OTPCaret: FC<CaretProps> = ({ index, length }) => {
|
|
|
86
85
|
);
|
|
87
86
|
};
|
|
88
87
|
|
|
88
|
+
/**
|
|
89
|
+
* OTP input: a hidden TextInput drives `length` slots (or a free-length row
|
|
90
|
+
* when `length` is unset) with a blinking caret; accepts at most 10 chars.
|
|
91
|
+
*/
|
|
89
92
|
const InputOTP = forwardRef(
|
|
90
93
|
(
|
|
91
94
|
{
|
|
@@ -121,14 +124,16 @@ const InputOTP = forwardRef(
|
|
|
121
124
|
props.accessibilityLabel,
|
|
122
125
|
);
|
|
123
126
|
|
|
124
|
-
useImperativeHandle(ref, () =>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
useImperativeHandle(ref, () => {
|
|
128
|
+
return {
|
|
129
|
+
onChangeText: (text: string) => {
|
|
130
|
+
_onChangeText(text);
|
|
131
|
+
},
|
|
132
|
+
focus: () => inputRef.current?.focus(),
|
|
133
|
+
blur: () => inputRef.current?.blur(),
|
|
134
|
+
setText: (text: string) => _onChangeText(text),
|
|
135
|
+
};
|
|
136
|
+
});
|
|
132
137
|
|
|
133
138
|
const onFocusInput = (e: TextInputFocusEvent) => {
|
|
134
139
|
setFocused(true);
|
|
@@ -142,13 +147,10 @@ const InputOTP = forwardRef(
|
|
|
142
147
|
|
|
143
148
|
const _onChangeText = (text: string) => {
|
|
144
149
|
if (text.length > MAX_LENGTH) return;
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
text.length >= value.length
|
|
150
|
-
)
|
|
151
|
-
return;
|
|
150
|
+
const isNumeric = !isNaN(Number(text));
|
|
151
|
+
if (dataType === 'number' && !isNumeric) return;
|
|
152
|
+
const isFull = value.length >= Number(length);
|
|
153
|
+
if (isNumeric && isFull && text.length >= value.length) return;
|
|
152
154
|
|
|
153
155
|
setValue(text);
|
|
154
156
|
onChangeText?.(text);
|
|
@@ -164,21 +166,21 @@ const InputOTP = forwardRef(
|
|
|
164
166
|
const TextInputs: React.ReactNode[] = [];
|
|
165
167
|
const textColors = theme.colors.text;
|
|
166
168
|
for (let i = 0; i < inputLength; i++) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
169
|
+
let slot: React.ReactNode;
|
|
170
|
+
if (focused && value.length === i) {
|
|
171
|
+
slot = <OTPCaret index={i} length={inputLength} />;
|
|
172
|
+
} else {
|
|
173
|
+
slot = (
|
|
174
|
+
<Text
|
|
175
|
+
style={i !== inputLength - 1 && { marginRight: Spacing.L }}
|
|
176
|
+
color={value[i] ? textColors.default : textColors.hint}
|
|
177
|
+
typography={value[i] ? 'header_m_bold' : 'body_default_regular'}
|
|
178
|
+
>
|
|
179
|
+
{value[i] || '-'}
|
|
180
|
+
</Text>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
TextInputs.push(<View key={`otp-input-${i}`}>{slot}</View>);
|
|
182
184
|
}
|
|
183
185
|
return TextInputs;
|
|
184
186
|
};
|
|
@@ -34,7 +34,8 @@ import { Image } from '../Image';
|
|
|
34
34
|
import { Typography } from '../Text/types';
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* Vietnamese phone number input with a fixed flag and `+84` prefix, number pad
|
|
38
|
+
* by default, plus clear and trailing icons.
|
|
38
39
|
*/
|
|
39
40
|
const InputPhoneNumber = forwardRef(
|
|
40
41
|
(
|
|
@@ -146,9 +147,6 @@ const InputPhoneNumber = forwardRef(
|
|
|
146
147
|
typography = 'header_m_bold';
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
/**
|
|
150
|
-
* Render input view
|
|
151
|
-
*/
|
|
152
150
|
const renderInputView = () => {
|
|
153
151
|
const disabledColor = theme.colors.text.disable;
|
|
154
152
|
const secure = secureTextEntry;
|
|
@@ -156,6 +154,11 @@ const InputPhoneNumber = forwardRef(
|
|
|
156
154
|
let placeholderColor = theme.colors.text.hint;
|
|
157
155
|
let iconTintColor = trailingColor ?? theme.colors.text.default;
|
|
158
156
|
const borderWidth = focused ? 1.5 : 1;
|
|
157
|
+
let secureTextStyle = styles.secureTextLarge;
|
|
158
|
+
|
|
159
|
+
if (size === 'small') {
|
|
160
|
+
secureTextStyle = styles.secureTextSmall;
|
|
161
|
+
}
|
|
159
162
|
|
|
160
163
|
if (disabled) {
|
|
161
164
|
textColor = disabledColor;
|
|
@@ -214,11 +217,7 @@ const InputPhoneNumber = forwardRef(
|
|
|
214
217
|
color: textColor,
|
|
215
218
|
},
|
|
216
219
|
inputTextStyles,
|
|
217
|
-
secure &&
|
|
218
|
-
haveValue &&
|
|
219
|
-
(size === 'small'
|
|
220
|
-
? styles.secureTextSmall
|
|
221
|
-
: styles.secureTextLarge),
|
|
220
|
+
secure && haveValue && secureTextStyle,
|
|
222
221
|
]}
|
|
223
222
|
defaultValue={defaultValue}
|
|
224
223
|
allowFontScaling={false}
|
package/Input/InputSearch.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
View,
|
|
14
14
|
} from 'react-native';
|
|
15
15
|
import { useComponentId } from '../Application';
|
|
16
|
+
import { HeaderBarMetrics } from '../Application/headerLayout';
|
|
16
17
|
import {
|
|
17
18
|
ApplicationContext,
|
|
18
19
|
ComponentContext,
|
|
@@ -26,6 +27,12 @@ import styles from './styles';
|
|
|
26
27
|
import { Styles } from '../Consts';
|
|
27
28
|
import SystemTextInput from './SystemTextInput';
|
|
28
29
|
|
|
30
|
+
const SEARCH_ICON_SIZE = 24;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Search field with search icon, clear icon, optional trailing icon and a
|
|
34
|
+
* cancel button; with `typingData` it shows an animated placeholder instead.
|
|
35
|
+
*/
|
|
29
36
|
const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
30
37
|
{
|
|
31
38
|
placeholder,
|
|
@@ -45,6 +52,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
45
52
|
backgroundColor,
|
|
46
53
|
onPress,
|
|
47
54
|
typingData,
|
|
55
|
+
size = 'default',
|
|
48
56
|
...props
|
|
49
57
|
},
|
|
50
58
|
ref,
|
|
@@ -55,6 +63,13 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
55
63
|
const searchFontFamily = exportFontFamily('regular');
|
|
56
64
|
|
|
57
65
|
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;
|
|
58
73
|
|
|
59
74
|
const [focused, setFocused] = useState(false);
|
|
60
75
|
const [internalText, setInternalText] = useState(defaultValue || '');
|
|
@@ -106,14 +121,15 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
106
121
|
};
|
|
107
122
|
});
|
|
108
123
|
|
|
109
|
-
/**
|
|
110
|
-
* Render the input view
|
|
111
|
-
*/
|
|
112
124
|
const renderInputView = () => {
|
|
113
125
|
if (typingData && typingData?.length > 0) {
|
|
114
126
|
return (
|
|
115
127
|
<TouchableOpacity
|
|
116
|
-
style={[
|
|
128
|
+
style={[
|
|
129
|
+
styles.searchInput,
|
|
130
|
+
styles.searchInputTyping,
|
|
131
|
+
inputHeightStyle,
|
|
132
|
+
]}
|
|
117
133
|
onPress={onPress}
|
|
118
134
|
activeOpacity={onPress ? 0.6 : 1}
|
|
119
135
|
>
|
|
@@ -131,6 +147,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
131
147
|
ref={inputRef}
|
|
132
148
|
style={[
|
|
133
149
|
styles.searchInput,
|
|
150
|
+
inputHeightStyle,
|
|
134
151
|
{
|
|
135
152
|
fontSize: scaledFontSize,
|
|
136
153
|
color: theme.colors.text.default,
|
|
@@ -149,9 +166,6 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
149
166
|
);
|
|
150
167
|
};
|
|
151
168
|
|
|
152
|
-
/**
|
|
153
|
-
* Render trailing icon or text
|
|
154
|
-
*/
|
|
155
169
|
const renderTrailing = () => {
|
|
156
170
|
return (
|
|
157
171
|
<View style={Styles.row}>
|
|
@@ -227,7 +241,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
|
|
|
227
241
|
>
|
|
228
242
|
<Icon
|
|
229
243
|
source={'navigation_search'}
|
|
230
|
-
size={
|
|
244
|
+
size={searchIconSize}
|
|
231
245
|
color={theme.colors.text.hint}
|
|
232
246
|
/>
|
|
233
247
|
{renderInputView()}
|
package/Input/InputTextArea.tsx
CHANGED
|
@@ -21,6 +21,10 @@ import styles from './styles';
|
|
|
21
21
|
import SystemTextInput from './SystemTextInput';
|
|
22
22
|
import { setAutomationID, useComponentId } from '../Application';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Multiline input with floating label, clear icon and a `length/maxLength`
|
|
26
|
+
* counter; the ref exposes clear, focus, blur and setText.
|
|
27
|
+
*/
|
|
24
28
|
const InputTextArea = forwardRef(
|
|
25
29
|
(
|
|
26
30
|
{
|
|
@@ -4,6 +4,10 @@ import { TextInput } from 'react-native';
|
|
|
4
4
|
import { useScaleSize, exportFontFamily } from '../Text';
|
|
5
5
|
import styles from './styles';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Animated search placeholder: types each string of `data` char by char, then
|
|
9
|
+
* erases it, looping forever. Writes via setNativeProps to avoid re-renders.
|
|
10
|
+
*/
|
|
7
11
|
export const TextTyping: FC<any> = ({
|
|
8
12
|
data = [],
|
|
9
13
|
timeDelayEnd = 2000,
|
|
@@ -11,11 +11,16 @@ interface SystemTextInputProps extends TextInputProps {
|
|
|
11
11
|
ref?: React.RefObject<TextInput | null>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* TextInput that still fires onPressIn/onPressOut when not editable on
|
|
16
|
+
* Android, by moving them to a wrapping touchable.
|
|
17
|
+
*/
|
|
14
18
|
const SystemTextInput: FC<SystemTextInputProps> = props => {
|
|
15
19
|
const { editable, onPressIn, onPressOut, ref } = props;
|
|
16
20
|
|
|
17
|
-
const
|
|
18
|
-
|
|
21
|
+
const isAndroidReadOnly = Platform.OS === 'android' && !editable;
|
|
22
|
+
const hasPressHandler = !!(onPressIn || onPressOut);
|
|
23
|
+
const needsAndroidWrapper = isAndroidReadOnly && hasPressHandler;
|
|
19
24
|
|
|
20
25
|
const noPressHandlers = { onPressIn: undefined, onPressOut: undefined };
|
|
21
26
|
const inputProps: TextInputProps = {
|
package/Input/TextTyping.tsx
CHANGED
|
@@ -11,6 +11,10 @@ export interface TextTypingProps {
|
|
|
11
11
|
inputStyle?: TextStyle;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Typing placeholder: types each string of `data` char by char, then erases
|
|
16
|
+
* it, looping forever. Writes via setNativeProps to avoid re-renders.
|
|
17
|
+
*/
|
|
14
18
|
const TextTyping: FC<TextTypingProps> = ({
|
|
15
19
|
data = [],
|
|
16
20
|
timeDelayEnd = 2000,
|