@momo-kits/foundation 0.169.7-beta.4 → 0.169.7-beta.6
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 +40 -37
- 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 +56 -34
- package/Application/Components/HeaderLeft.tsx +4 -2
- package/Application/Components/HeaderRight.styles.ts +15 -9
- package/Application/Components/HeaderRight.tsx +2 -1
- package/Application/Components/HeaderTitle.shared.ts +19 -12
- package/Application/Components/HeaderTitle.tsx +4 -3
- package/Application/Components/HeaderToolkitAction.tsx +28 -22
- package/Application/Components/NavigationButton.tsx +10 -6
- package/Application/Components/SearchHeader.tsx +41 -15
- 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 +10 -10
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -18
- package/Application/Navigator.ts +20 -48
- package/Application/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -33
- package/Application/StackScreen.tsx +19 -54
- 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 +1 -1
- package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
- package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/Authorship/utils.ts +0 -137
- package/Application/KitAdoption/Slots/utils.ts +0 -68
- package/Application/KitAdoption/constants.ts +0 -120
- package/Application/KitAdoption/index.ts +0 -12
- package/Application/KitAdoption/styles.ts +0 -55
- package/Application/KitAdoption/types.ts +0 -61
- package/Application/KitAdoption/utils.ts +0 -376
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
- package/Application/__tests__/KitAdoption.test.ts +0 -283
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
- package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
- package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
- package/Application/__tests__/modalLayout.test.ts +0 -128
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,
|
package/Input/common.tsx
CHANGED
|
@@ -35,6 +35,7 @@ type TrailingProps = {
|
|
|
35
35
|
|
|
36
36
|
export const MAX_LENGTH = 300;
|
|
37
37
|
|
|
38
|
+
/** Border color by state; priority is disabled > error > focused > default. */
|
|
38
39
|
export const getBorderColor = (
|
|
39
40
|
theme: Theme,
|
|
40
41
|
focused: boolean,
|
|
@@ -58,6 +59,10 @@ export const getBorderColor = (
|
|
|
58
59
|
return { borderColor };
|
|
59
60
|
};
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Container style for `size`; multiline uses `height` as minHeight so the
|
|
64
|
+
* field can grow with its content.
|
|
65
|
+
*/
|
|
61
66
|
export const getSizeStyle = (
|
|
62
67
|
size?: 'small' | 'large',
|
|
63
68
|
multiline: boolean = false,
|
|
@@ -68,13 +73,14 @@ export const getSizeStyle = (
|
|
|
68
73
|
scaleStyle = { height };
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
if (multiline)
|
|
76
|
+
if (multiline) {
|
|
72
77
|
return [
|
|
73
78
|
styles.multilineContainer,
|
|
74
79
|
{
|
|
75
80
|
minHeight: height,
|
|
76
81
|
},
|
|
77
82
|
];
|
|
83
|
+
}
|
|
78
84
|
|
|
79
85
|
if (size === 'small') {
|
|
80
86
|
return [styles.smallContainer, scaleStyle];
|
|
@@ -82,6 +88,10 @@ export const getSizeStyle = (
|
|
|
82
88
|
return [styles.container, scaleStyle];
|
|
83
89
|
};
|
|
84
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Error or hint row under an input (error wins); with neither, `errorSpacing`
|
|
93
|
+
* keeps an empty row so the layout doesn't jump when an error appears.
|
|
94
|
+
*/
|
|
85
95
|
export const ErrorView: FC<{
|
|
86
96
|
errorMessage?: string;
|
|
87
97
|
errorSpacing?: boolean;
|
|
@@ -95,21 +105,17 @@ export const ErrorView: FC<{
|
|
|
95
105
|
const scaledIconSize = useScaleSize(16);
|
|
96
106
|
|
|
97
107
|
if (errorMessage || hintText) {
|
|
108
|
+
const messageColor = errorMessage ? errorColor : hintColor;
|
|
109
|
+
const messageType = errorMessage ? 'error' : 'hint';
|
|
98
110
|
return (
|
|
99
111
|
<View style={styles.errorView}>
|
|
100
112
|
<View style={styles.errorIcon}>
|
|
101
|
-
<Icon
|
|
102
|
-
color={errorMessage ? errorColor : hintColor}
|
|
103
|
-
source="ic_error"
|
|
104
|
-
size={scaledIconSize}
|
|
105
|
-
/>
|
|
113
|
+
<Icon color={messageColor} source="ic_error" size={scaledIconSize} />
|
|
106
114
|
</View>
|
|
107
115
|
<Text
|
|
108
|
-
accessibilityLabel={`${componentId}|${
|
|
109
|
-
errorMessage ? 'error' : 'hint'
|
|
110
|
-
}-text`}
|
|
116
|
+
accessibilityLabel={`${componentId}|${messageType}-text`}
|
|
111
117
|
style={Styles.flex}
|
|
112
|
-
color={
|
|
118
|
+
color={messageColor}
|
|
113
119
|
typography={'description_default_regular'}
|
|
114
120
|
>
|
|
115
121
|
{errorMessage ?? hintTextDefault}
|
|
@@ -123,6 +129,10 @@ export const ErrorView: FC<{
|
|
|
123
129
|
return <View />;
|
|
124
130
|
};
|
|
125
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Floating label on the input's top border with a required `*` and an
|
|
134
|
+
* optional pressable icon; renders nothing without `floatingValue`.
|
|
135
|
+
*/
|
|
126
136
|
export const FloatingView: FC<FloatingViewProps> = ({
|
|
127
137
|
floatingValue,
|
|
128
138
|
floatingIconColor,
|
|
@@ -191,6 +201,10 @@ export const FloatingView: FC<FloatingViewProps> = ({
|
|
|
191
201
|
return null;
|
|
192
202
|
};
|
|
193
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Trailing slot: spinner while `loading`, an icon when `icon` is a known icon
|
|
206
|
+
* name or URL, otherwise `icon` as a text action (first 15 chars).
|
|
207
|
+
*/
|
|
194
208
|
export const RenderTrailing: FC<TrailingProps> = ({
|
|
195
209
|
loading,
|
|
196
210
|
color,
|
|
@@ -204,8 +218,8 @@ export const RenderTrailing: FC<TrailingProps> = ({
|
|
|
204
218
|
}
|
|
205
219
|
|
|
206
220
|
if (icon) {
|
|
207
|
-
const
|
|
208
|
-
|
|
221
|
+
const isIconName = !!(IconSources as any)?.[icon]?.uri;
|
|
222
|
+
const isIcon = isIconName || icon.includes('http');
|
|
209
223
|
const renderIconTouchable = (content: ReactNode) => {
|
|
210
224
|
return (
|
|
211
225
|
<TouchableOpacity
|