@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Colors, Radius, Spacing, Styles } from '../../Consts';
|
|
2
|
-
import { InputRef, InputSearch } from '../../Input';
|
|
2
|
+
import { InputRef, InputSearch, type InputSearchProps } from '../../Input';
|
|
3
3
|
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
4
4
|
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
|
|
5
5
|
import React, { useContext } from 'react';
|
|
@@ -9,7 +9,18 @@ import { Text } from '../../Text';
|
|
|
9
9
|
import { useNormalizedSharedValue } from '../hooks';
|
|
10
10
|
import { useScreenLayout } from '../../Layout';
|
|
11
11
|
import { DuoMetrics } from '../../Layout/utils';
|
|
12
|
+
import { HeaderBarMetrics } from '../headerLayout';
|
|
12
13
|
|
|
14
|
+
const { buttonSize, pillWidth, sidePadding, titleGap } = HeaderBarMetrics;
|
|
15
|
+
// help center | home pill and the bar padding right of it
|
|
16
|
+
const TOOLKIT_WIDTH = pillWidth + sidePadding;
|
|
17
|
+
// the iPhone Duo side bar and status bar row keep their right header width
|
|
18
|
+
const DUO_TOOLKIT_WIDTH = 73;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Header search bar whose fill turns from surface to default once scrolled, with
|
|
22
|
+
* a cancel button that goes back (or dismisses the mini app) unless `renderButtons`.
|
|
23
|
+
*/
|
|
13
24
|
const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
14
25
|
(
|
|
15
26
|
{
|
|
@@ -25,20 +36,31 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
25
36
|
const context = useContext<any>(MiniAppContext);
|
|
26
37
|
const layout = useScreenLayout();
|
|
27
38
|
// the iPhone Duo status bar row keeps a side bar sized back button
|
|
28
|
-
|
|
39
|
+
let backWidth = buttonSize;
|
|
40
|
+
if (layout.statusBarClearance) {
|
|
41
|
+
backWidth = DuoMetrics.controlSize;
|
|
42
|
+
}
|
|
43
|
+
let defaultRightWidth = TOOLKIT_WIDTH;
|
|
44
|
+
let searchSize: InputSearchProps['size'] = 'header';
|
|
45
|
+
if (layout.sideBar || layout.statusBarClearance) {
|
|
46
|
+
defaultRightWidth = DUO_TOOLKIT_WIDTH;
|
|
47
|
+
searchSize = 'default';
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
31
|
-
const
|
|
32
|
-
const
|
|
51
|
+
const defaultLeft = backWidth + sidePadding + titleGap;
|
|
52
|
+
const leftPosition = props?.leftPosition ?? defaultLeft;
|
|
53
|
+
const headerRightWidth = props?.headerRightWidth ?? defaultRightWidth;
|
|
33
54
|
|
|
34
55
|
// iPhone Duo side bar holds back: the search shares the content width with the buttons only
|
|
35
|
-
|
|
56
|
+
const rowWidth = layout.width - leftPosition - sidePadding;
|
|
57
|
+
let searchMaxWidth = rowWidth - headerRightWidth;
|
|
36
58
|
if (layout.sideBar) {
|
|
37
59
|
searchMaxWidth = layout.contentWidth - Spacing.M * 2 - headerRightWidth;
|
|
38
60
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
const clearance = layout.statusBarClearance;
|
|
62
|
+
if (clearance) {
|
|
63
|
+
searchMaxWidth -= clearance.left + clearance.right;
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
const backgroundStyle = useAnimatedStyle(() => {
|
|
@@ -76,6 +98,15 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
76
98
|
|
|
77
99
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
78
100
|
|
|
101
|
+
let buttons: React.ReactNode = (
|
|
102
|
+
<TouchableOpacity onPress={goBackSafe} style={styles.searchAction}>
|
|
103
|
+
<Text typography={'action_default_bold'}>{buttonText}</Text>
|
|
104
|
+
</TouchableOpacity>
|
|
105
|
+
);
|
|
106
|
+
if (typeof renderButtons === 'function') {
|
|
107
|
+
buttons = renderButtons();
|
|
108
|
+
}
|
|
109
|
+
|
|
79
110
|
return (
|
|
80
111
|
<View
|
|
81
112
|
style={[styles.container, showBaseLineDebug && styles.debugBaseLine]}
|
|
@@ -92,18 +123,13 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
92
123
|
<InputSearch
|
|
93
124
|
ref={ref}
|
|
94
125
|
{...props}
|
|
126
|
+
size={searchSize}
|
|
95
127
|
backgroundColor={'transparent'}
|
|
96
128
|
showButtonText={false}
|
|
97
129
|
/>
|
|
98
130
|
</Animated.View>
|
|
99
131
|
</View>
|
|
100
|
-
{
|
|
101
|
-
renderButtons()
|
|
102
|
-
) : (
|
|
103
|
-
<TouchableOpacity onPress={goBackSafe} style={styles.searchAction}>
|
|
104
|
-
<Text typography={'action_default_bold'}>{buttonText}</Text>
|
|
105
|
-
</TouchableOpacity>
|
|
106
|
-
)}
|
|
132
|
+
{buttons}
|
|
107
133
|
</View>
|
|
108
134
|
);
|
|
109
135
|
},
|
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
} from './HeaderTitle.shared';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Header
|
|
18
|
-
*
|
|
17
|
+
* Header title for a journey: `start` and `end` around an icon, with an optional
|
|
18
|
+
* description below; a shimmer while `isLoading`.
|
|
19
19
|
*/
|
|
20
20
|
const TitleJourney: React.FC<TitleJourneyProps> = ({
|
|
21
21
|
start,
|
|
@@ -9,8 +9,8 @@ import { styles } from './HeaderTitle.styles';
|
|
|
9
9
|
import { useHeaderFontFamily, useTitleMaxWidth } from './HeaderTitle.shared';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Header
|
|
13
|
-
*
|
|
12
|
+
* Header title showing a location with a dropdown chevron and an optional
|
|
13
|
+
* description above it; a shimmer while `isLoading`.
|
|
14
14
|
*/
|
|
15
15
|
const TitleLocation: React.FC<TitleLocationProps> = ({
|
|
16
16
|
tintColor,
|
|
@@ -10,8 +10,8 @@ import { styles } from './HeaderTitle.styles';
|
|
|
10
10
|
import { useHeaderFontFamily, useTitleMaxWidth } from './HeaderTitle.shared';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Header user
|
|
14
|
-
*
|
|
13
|
+
* Header title for a user or group: avatar (1 to 4 images, a `+N` stack, or name
|
|
14
|
+
* initials), title with verified icons and subtitle; a shimmer while `isLoading`.
|
|
15
15
|
*/
|
|
16
16
|
const TitleUser: React.FC<TitleUserProps> = ({
|
|
17
17
|
title,
|
|
@@ -15,7 +15,8 @@ type ToolkitActionsParams = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* State and handlers behind the header toolkit: the favorite shortcut (kept in sync
|
|
19
|
+
* with the home), single tool or "more" shortcut, help center and close.
|
|
19
20
|
*/
|
|
20
21
|
const useToolkitActions = ({
|
|
21
22
|
preventClose,
|
|
@@ -31,20 +32,15 @@ const useToolkitActions = ({
|
|
|
31
32
|
const [isFavorite, setIsFavorite] = useState<boolean>(false);
|
|
32
33
|
const [isLoading, setIsLoading] = useState(false);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
setIsFavorite(result);
|
|
44
|
-
},
|
|
45
|
-
),
|
|
46
|
-
[context?.code, navigator?.maxApi],
|
|
47
|
-
);
|
|
35
|
+
const checkAppIsFavorite = useCallback(() => {
|
|
36
|
+
return navigator?.maxApi?.dispatchFunction?.(
|
|
37
|
+
'isFavoriteApp',
|
|
38
|
+
{ code: context?.code },
|
|
39
|
+
(result: boolean) => {
|
|
40
|
+
setIsFavorite(result);
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
}, [context?.code, navigator?.maxApi]);
|
|
48
44
|
|
|
49
45
|
useEffect(() => {
|
|
50
46
|
if (useShortcut) {
|
|
@@ -75,9 +71,6 @@ const useToolkitActions = ({
|
|
|
75
71
|
}
|
|
76
72
|
};
|
|
77
73
|
|
|
78
|
-
/**
|
|
79
|
-
* close navigation container
|
|
80
|
-
*/
|
|
81
74
|
const onClose = () => {
|
|
82
75
|
const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
|
|
83
76
|
context?.autoTracking?.({
|
|
@@ -107,9 +100,6 @@ const useToolkitActions = ({
|
|
|
107
100
|
}
|
|
108
101
|
};
|
|
109
102
|
|
|
110
|
-
/**
|
|
111
|
-
* on press shortcut
|
|
112
|
-
*/
|
|
113
103
|
const onPressShortcut = () => {
|
|
114
104
|
const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
|
|
115
105
|
const expected = !isFavorite;
|
|
@@ -139,9 +129,6 @@ const useToolkitActions = ({
|
|
|
139
129
|
}, 1000);
|
|
140
130
|
};
|
|
141
131
|
|
|
142
|
-
/**
|
|
143
|
-
* on press help center
|
|
144
|
-
*/
|
|
145
132
|
const onPressHelpCenter = () => {
|
|
146
133
|
const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
|
|
147
134
|
context?.autoTracking?.({
|
|
@@ -154,9 +141,6 @@ const useToolkitActions = ({
|
|
|
154
141
|
navigator?.maxApi?.dispatchFunction?.('showHelpCenter', context);
|
|
155
142
|
};
|
|
156
143
|
|
|
157
|
-
/**
|
|
158
|
-
* on press icon more
|
|
159
|
-
*/
|
|
160
144
|
const onPressIconMore = () => {
|
|
161
145
|
const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
|
|
162
146
|
context?.autoTracking?.({
|
|
@@ -200,9 +184,10 @@ const useToolkitActions = ({
|
|
|
200
184
|
iconShortcut = 'navigation_more_icon';
|
|
201
185
|
shortcutOnPress = onPressIconMore;
|
|
202
186
|
} else if (isHaveOneTool) {
|
|
203
|
-
const
|
|
187
|
+
const singleToolGroup = tools.find(
|
|
204
188
|
(group: ToolGroup) => group?.items?.length === 1,
|
|
205
|
-
)
|
|
189
|
+
);
|
|
190
|
+
const singleTool = singleToolGroup?.items[0];
|
|
206
191
|
iconShortcut = singleTool?.icon;
|
|
207
192
|
shortcutOnPress = () => {
|
|
208
193
|
singleTool?.onPress?.();
|
|
@@ -216,9 +201,9 @@ const useToolkitActions = ({
|
|
|
216
201
|
};
|
|
217
202
|
}
|
|
218
203
|
|
|
219
|
-
const showBadge = tools.some((group: ToolGroup) =>
|
|
220
|
-
group?.items?.some?.(tool => tool?.showBadge)
|
|
221
|
-
);
|
|
204
|
+
const showBadge = tools.some((group: ToolGroup) => {
|
|
205
|
+
return group?.items?.some?.(tool => tool?.showBadge);
|
|
206
|
+
});
|
|
222
207
|
|
|
223
208
|
return {
|
|
224
209
|
isLoading,
|
package/Application/Localize.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { NativeModules } from 'react-native';
|
|
|
2
2
|
import { LocalizationObject } from './types';
|
|
3
3
|
import defaultLanguage from '../Assets/language.json';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* String table for kit and feature copy. The language is read once from the
|
|
7
|
+
* native `RNResource` module at construction; Vietnamese is the default.
|
|
8
|
+
*/
|
|
5
9
|
class Localize {
|
|
6
10
|
private assets: LocalizationObject;
|
|
7
11
|
private readonly currentLanguage: 'en' | 'vi';
|
|
@@ -25,14 +29,17 @@ class Localize {
|
|
|
25
29
|
return this.assets;
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
/** Text for `key` in the current language, or the key itself if missing. */
|
|
28
33
|
translate(key: string) {
|
|
29
34
|
return this.assets[this.currentLanguage]?.[key] || key;
|
|
30
35
|
}
|
|
31
36
|
|
|
37
|
+
/** Picks the current-language text of a `{ vi, en }` pair, else its JSON. */
|
|
32
38
|
translateData(data: { vi: string; en: string }) {
|
|
33
39
|
return data[this.currentLanguage] ?? JSON.stringify(data);
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
/** Merges extra translations; keys that already exist keep their text. */
|
|
36
43
|
addTranslations(translations: LocalizationObject) {
|
|
37
44
|
this.assets = {
|
|
38
45
|
vi: { ...translations?.vi, ...this.assets.vi },
|
|
@@ -24,10 +24,13 @@ import Navigation from './Navigation';
|
|
|
24
24
|
import { ModalParams } from './types';
|
|
25
25
|
import BottomSheet from './BottomSheet';
|
|
26
26
|
import { isIPhoneDuo } from './utils';
|
|
27
|
-
import { AdoptionProbe } from './KitAdoption';
|
|
28
27
|
import { useModalLayout } from './modalLayout';
|
|
29
28
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
30
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Screen of the `Modal` route: a bottom sheet when pushed by
|
|
32
|
+
* `showBottomSheet`, otherwise a centered popup.
|
|
33
|
+
*/
|
|
31
34
|
const ModalScreen: React.FC<any> = props => {
|
|
32
35
|
if (props.route?.params?.isBottomSheet) {
|
|
33
36
|
return <BottomSheet {...props} />;
|
|
@@ -35,6 +38,10 @@ const ModalScreen: React.FC<any> = props => {
|
|
|
35
38
|
return <Modal {...props} />;
|
|
36
39
|
};
|
|
37
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Centered popup that fades and scales in over a scrim. On foldables it is
|
|
43
|
+
* centered in the modal placement area, at most a popup wide.
|
|
44
|
+
*/
|
|
38
45
|
const Modal: React.FC<ModalParams> = props => {
|
|
39
46
|
const { navigator } = useContext(ApplicationContext);
|
|
40
47
|
const context = useContext<any>(MiniAppContext);
|
|
@@ -51,9 +58,7 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
51
58
|
const isDuo = isIPhoneDuo();
|
|
52
59
|
const { isFoldable, placement, popup, scrimOpacity } = useModalLayout();
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
* Fire auto_popup_displayed tracking event on mount
|
|
56
|
-
*/
|
|
61
|
+
// Auto-tracks the popup as displayed on the current screen.
|
|
57
62
|
useEffect(() => {
|
|
58
63
|
navigator?.maxApi?.getDataObserver?.('current_screen', (curScreen: any) => {
|
|
59
64
|
context.autoTracking({
|
|
@@ -183,12 +188,7 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
183
188
|
modalStyle,
|
|
184
189
|
]}
|
|
185
190
|
>
|
|
186
|
-
<
|
|
187
|
-
screenName={Component?.name || Component?.type?.name || 'Invalid'}
|
|
188
|
-
navigation={props.navigation}
|
|
189
|
-
>
|
|
190
|
-
<Component {...params} onRequestClose={onDismiss} />
|
|
191
|
-
</AdoptionProbe>
|
|
191
|
+
<Component {...params} onRequestClose={onDismiss} />
|
|
192
192
|
</Animated.View>
|
|
193
193
|
</KeyboardAvoidingView>
|
|
194
194
|
</Container>
|
|
@@ -11,6 +11,10 @@ export const WHITE_LIST = [
|
|
|
11
11
|
'vn.momo.ekyc',
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Screen-level navigation handle given to features; `setOptions` enforces the
|
|
16
|
+
* design system's header rules before applying React Navigation options.
|
|
17
|
+
*/
|
|
14
18
|
class Navigation {
|
|
15
19
|
instance: NavigationProp<any>;
|
|
16
20
|
context: any;
|
|
@@ -21,39 +25,33 @@ class Navigation {
|
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
28
|
+
* Drops header overrides the design system forbids: any `headerLeft`, and
|
|
29
|
+
* render-function `headerRight`/`headerTitle` outside `WHITE_LIST` apps.
|
|
26
30
|
*/
|
|
27
31
|
private filterParams = (params: NavigationOptions) => {
|
|
32
|
+
const isWhiteListed = WHITE_LIST.includes(this.context?.appId);
|
|
33
|
+
const rightType = typeof params.headerRight;
|
|
34
|
+
const titleType = typeof params.headerTitle;
|
|
35
|
+
const isTitleRenderer = titleType !== 'string' && titleType !== 'object';
|
|
36
|
+
|
|
28
37
|
if (params.headerLeft) {
|
|
29
38
|
console.warn('headerLeft not allow override render by design system!');
|
|
30
39
|
delete params.headerLeft;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
if (
|
|
34
|
-
params.headerRight &&
|
|
35
|
-
typeof params.headerRight !== 'object' &&
|
|
36
|
-
!WHITE_LIST.includes(this.context?.appId)
|
|
37
|
-
) {
|
|
42
|
+
if (params.headerRight && rightType !== 'object' && !isWhiteListed) {
|
|
38
43
|
console.warn('headerRight not allow type func render by design system!');
|
|
39
44
|
delete params.headerRight;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
if (
|
|
43
|
-
params.headerTitle &&
|
|
44
|
-
!['string', 'object'].includes(typeof params.headerTitle) &&
|
|
45
|
-
!WHITE_LIST.includes(this.context?.appId)
|
|
46
|
-
) {
|
|
47
|
+
if (params.headerTitle && isTitleRenderer && !isWhiteListed) {
|
|
47
48
|
console.warn('headerTitle not allow type func render by design system!');
|
|
48
49
|
delete params.headerTitle;
|
|
49
50
|
}
|
|
50
51
|
return params;
|
|
51
52
|
};
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
-
* navigation setOptions support props NavigationOptions base on react-navigation options
|
|
55
|
-
* @param params
|
|
56
|
-
*/
|
|
54
|
+
/** Applies kit `NavigationOptions` to the screen after `filterParams`. */
|
|
57
55
|
setOptions = (params: NavigationOptions) => {
|
|
58
56
|
const options = getOptions(this.filterParams(params));
|
|
59
57
|
this.instance.setOptions(options as StackNavigationOptions);
|
|
@@ -30,10 +30,13 @@ import { HeaderBackground } from './Components/HeaderBackground';
|
|
|
30
30
|
import { useScreenLayout } from '../Layout';
|
|
31
31
|
import { getHeaderRowOptions } from '../Layout/utils';
|
|
32
32
|
import { ReservedRegionsProvider } from '../Layout/ReservedRegions';
|
|
33
|
-
import { AdoptionBadge } from './KitAdoption';
|
|
34
33
|
|
|
35
34
|
const Stack = createStackNavigator();
|
|
36
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Root of a mini app: safe area, reserved regions and the merged mini app
|
|
38
|
+
* context around the kit's navigator.
|
|
39
|
+
*/
|
|
37
40
|
const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
38
41
|
screen,
|
|
39
42
|
theme = defaultTheme,
|
|
@@ -87,13 +90,16 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
87
90
|
initialParams={initialParams}
|
|
88
91
|
localize={localize}
|
|
89
92
|
/>
|
|
90
|
-
<AdoptionBadge />
|
|
91
93
|
</MiniAppContext.Provider>
|
|
92
94
|
</ReservedRegionsProvider>
|
|
93
95
|
</SafeAreaProvider>
|
|
94
96
|
);
|
|
95
97
|
};
|
|
96
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Stack navigator with its `Stack`, `Dialog` and `Modal` routes, under an
|
|
101
|
+
* `ApplicationContext` carrying the navigator, themed fonts and `translate`.
|
|
102
|
+
*/
|
|
97
103
|
const Navigation: React.FC<any> = ({
|
|
98
104
|
screen,
|
|
99
105
|
theme = defaultTheme,
|
|
@@ -111,9 +117,10 @@ const Navigation: React.FC<any> = ({
|
|
|
111
117
|
const syncCanGoBack = useNativeCanGoBack();
|
|
112
118
|
const isWhiteList = isWhiteListDesignSystem(context);
|
|
113
119
|
const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
let navigationFontFamily = 'SFProText';
|
|
121
|
+
if (isWhiteList) {
|
|
122
|
+
navigationFontFamily = 'MomoTrustSans-Regular';
|
|
123
|
+
}
|
|
117
124
|
let headerBackground = context?.designSystemConfig?.config?.headerBar;
|
|
118
125
|
let headerGradient = theme.colors?.gradient;
|
|
119
126
|
|
|
@@ -124,15 +131,9 @@ const Navigation: React.FC<any> = ({
|
|
|
124
131
|
headerGradient = context?.designSystemConfig?.config?.headerGradient;
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
/**
|
|
128
|
-
* inject data for navigator
|
|
129
|
-
*/
|
|
130
134
|
navigator.current.maxApi = maxApi;
|
|
131
135
|
|
|
132
|
-
/**
|
|
133
|
-
* translate context method
|
|
134
|
-
* @param data
|
|
135
|
-
*/
|
|
136
|
+
/** Text for a localization key, or the current-language side of `{ vi, en }`. */
|
|
136
137
|
const translate = (data?: string | { vi: string; en: string }) => {
|
|
137
138
|
if (data && typeof data !== 'string') {
|
|
138
139
|
return localize?.translateData(data);
|
|
@@ -230,12 +231,12 @@ const Navigation: React.FC<any> = ({
|
|
|
230
231
|
backgroundColor: theme.colors.background.default,
|
|
231
232
|
},
|
|
232
233
|
headerLeft: (props: any) => <HeaderLeft {...props} floating />,
|
|
233
|
-
headerBackground: (props: any) =>
|
|
234
|
-
<HeaderBackground {...props}
|
|
235
|
-
|
|
236
|
-
headerRight: (props: any) =>
|
|
237
|
-
<HeaderRight {...props} floating
|
|
238
|
-
|
|
234
|
+
headerBackground: (props: any) => {
|
|
235
|
+
return <HeaderBackground {...props} />;
|
|
236
|
+
},
|
|
237
|
+
headerRight: (props: any) => {
|
|
238
|
+
return <HeaderRight {...props} floating />;
|
|
239
|
+
},
|
|
239
240
|
headerTintColor: Colors.black_17,
|
|
240
241
|
...TransitionPresets.ModalTransition,
|
|
241
242
|
}}
|
package/Application/Navigator.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {CommonActions, StackActions} from '@react-navigation/native';
|
|
2
2
|
import {BottomSheetParams, ModalParams, ScreenParams} from './types';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Imperative navigation API exposed to features through `ApplicationContext`.
|
|
6
|
+
* Every action is a no-op until the container reports ready.
|
|
7
|
+
*/
|
|
4
8
|
class Navigator {
|
|
5
9
|
ref?: any;
|
|
6
10
|
isReady?: any;
|
|
@@ -14,40 +18,28 @@ class Navigator {
|
|
|
14
18
|
this.isWidget = isWidget;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
/**
|
|
18
|
-
* push new stack screen
|
|
19
|
-
* @param params
|
|
20
|
-
*/
|
|
21
|
+
/** Pushes a new stack screen. */
|
|
21
22
|
push = (params: ScreenParams) => {
|
|
22
23
|
if (this.isReady.current) {
|
|
23
24
|
this.ref.current?.dispatch?.(StackActions.push('Stack', params));
|
|
24
25
|
}
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
-
* replace current screen with new screen
|
|
29
|
-
* @param params
|
|
30
|
-
*/
|
|
28
|
+
/** Replaces the current screen with a new stack screen. */
|
|
31
29
|
replace = (params: ScreenParams) => {
|
|
32
30
|
if (this.isReady.current) {
|
|
33
31
|
this.ref.current?.dispatch?.(StackActions.replace('Stack', params));
|
|
34
32
|
}
|
|
35
33
|
};
|
|
36
34
|
|
|
37
|
-
/**
|
|
38
|
-
* pop to dismiss a screen
|
|
39
|
-
* @param count
|
|
40
|
-
*/
|
|
35
|
+
/** Pops `count` screens, 1 by default. */
|
|
41
36
|
pop = (count?: number) => {
|
|
42
37
|
if (this.isReady.current) {
|
|
43
38
|
this.ref.current?.dispatch?.(StackActions.pop(count ?? 1));
|
|
44
39
|
}
|
|
45
40
|
};
|
|
46
41
|
|
|
47
|
-
/**
|
|
48
|
-
* present a new screen, show from bottom iOS
|
|
49
|
-
* @param params
|
|
50
|
-
*/
|
|
42
|
+
/** Presents a screen as a dialog (slides up from the bottom on iOS). */
|
|
51
43
|
present = (params: ScreenParams) => {
|
|
52
44
|
if (this.isReady.current) {
|
|
53
45
|
this.ref.current?.dispatch?.(StackActions.push('Dialog', params));
|
|
@@ -55,9 +47,8 @@ class Navigator {
|
|
|
55
47
|
};
|
|
56
48
|
|
|
57
49
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* @param onError
|
|
50
|
+
* Shows a modal popup; inside a widget it always uses the native modal.
|
|
51
|
+
* Failures are reported through `onError` instead of throwing.
|
|
61
52
|
*/
|
|
62
53
|
showModal = (params: ModalParams, onError?: (error: string) => void) => {
|
|
63
54
|
try {
|
|
@@ -76,11 +67,7 @@ class Navigator {
|
|
|
76
67
|
}
|
|
77
68
|
};
|
|
78
69
|
|
|
79
|
-
/**
|
|
80
|
-
* show a bottom sheet
|
|
81
|
-
* @param params
|
|
82
|
-
* @param onError
|
|
83
|
-
*/
|
|
70
|
+
/** Shows a bottom sheet through the modal route; failures go to `onError`. */
|
|
84
71
|
showBottomSheet = (
|
|
85
72
|
params: BottomSheetParams,
|
|
86
73
|
onError?: (error: string) => void
|
|
@@ -102,31 +89,21 @@ class Navigator {
|
|
|
102
89
|
}
|
|
103
90
|
};
|
|
104
91
|
|
|
105
|
-
/**
|
|
106
|
-
* pop all screen route
|
|
107
|
-
*/
|
|
92
|
+
/** Pops back to the first screen of the stack. */
|
|
108
93
|
popToTop = () => {
|
|
109
94
|
if (this.isReady.current) {
|
|
110
95
|
this.ref.current?.dispatch?.(StackActions.popToTop());
|
|
111
96
|
}
|
|
112
97
|
};
|
|
113
98
|
|
|
114
|
-
/**
|
|
115
|
-
* navigate route name bottom tab only
|
|
116
|
-
* @param name
|
|
117
|
-
*/
|
|
99
|
+
/** Navigates to a route by name; meant for bottom tab routes only. */
|
|
118
100
|
navigate = (name: string) => {
|
|
119
101
|
if (this.isReady.current) {
|
|
120
|
-
this.ref.current?.dispatch?.(
|
|
121
|
-
CommonActions.navigate(name)
|
|
122
|
-
);
|
|
102
|
+
this.ref.current?.dispatch?.(CommonActions.navigate(name));
|
|
123
103
|
}
|
|
124
104
|
};
|
|
125
105
|
|
|
126
|
-
/**
|
|
127
|
-
* reset a navigation flow with new screen
|
|
128
|
-
* @param params
|
|
129
|
-
*/
|
|
106
|
+
/** Resets the flow to a single new stack screen. */
|
|
130
107
|
reset = (params: ScreenParams) => {
|
|
131
108
|
if (this.isReady.current) {
|
|
132
109
|
this.ref.current?.dispatch?.(
|
|
@@ -144,26 +121,21 @@ class Navigator {
|
|
|
144
121
|
}
|
|
145
122
|
};
|
|
146
123
|
|
|
147
|
-
/**
|
|
148
|
-
* dismiss a feature data
|
|
149
|
-
* @param data
|
|
150
|
-
*/
|
|
124
|
+
/** Sets the payload the header passes to `maxApi.dismiss` on close. */
|
|
151
125
|
setDismissData = (data: any) => {
|
|
152
126
|
this.dismissData = data;
|
|
153
127
|
};
|
|
154
128
|
|
|
155
129
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
130
|
+
* No-op placeholder; `NavigationContainer` and `WidgetContainer` replace it
|
|
131
|
+
* with their context state setter.
|
|
158
132
|
*/
|
|
159
|
-
setCurrentContext = (
|
|
133
|
+
setCurrentContext = (_context: {
|
|
160
134
|
code?: string;
|
|
161
135
|
name?: {vi: string; en: string};
|
|
162
136
|
description?: {vi: string; en: string};
|
|
163
137
|
icon?: string;
|
|
164
|
-
}) => {
|
|
165
|
-
console.log(context);
|
|
166
|
-
};
|
|
138
|
+
}) => {};
|
|
167
139
|
}
|
|
168
140
|
|
|
169
141
|
export default Navigator;
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import React, { FC } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { FC, useContext, useMemo } from 'react';
|
|
2
|
+
import { MiniAppContext } from '../Context';
|
|
3
3
|
import { ScaleSizeProviderProps } from './types';
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Caps the font scale of its subtree at `scaleSizeMaxRate` × the design size
|
|
7
|
+
* (`useScaleSize`), passed down in MiniAppContext. The nearest provider wins;
|
|
8
|
+
* an undefined rate lifts an outer cap.
|
|
9
|
+
*/
|
|
6
10
|
const ScaleSizeProvider: FC<ScaleSizeProviderProps> = ({
|
|
7
11
|
scaleSizeMaxRate,
|
|
8
12
|
children,
|
|
9
13
|
}) => {
|
|
14
|
+
const context = useContext<object | undefined>(MiniAppContext);
|
|
15
|
+
const value = useMemo(() => {
|
|
16
|
+
return { ...context, scaleSizeMaxRate };
|
|
17
|
+
}, [context, scaleSizeMaxRate]);
|
|
18
|
+
|
|
10
19
|
return (
|
|
11
|
-
<
|
|
12
|
-
{children}
|
|
13
|
-
</ScaleSizeContext.Provider>
|
|
20
|
+
<MiniAppContext.Provider value={value}>{children}</MiniAppContext.Provider>
|
|
14
21
|
);
|
|
15
22
|
};
|
|
16
23
|
|