@momo-kits/foundation 0.167.1-duo.1 → 0.167.1-duo.13
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.tsx +115 -14
- package/Application/BottomTab/DuoBarCompression.tsx +135 -0
- package/Application/BottomTab/DuoTabBar.tsx +462 -0
- package/Application/BottomTab/index.tsx +150 -134
- package/Application/Components/DuoGlassBackground.tsx +179 -0
- package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
- package/Application/Components/HeaderExtendHeader.tsx +75 -28
- package/Application/Components/HeaderLeft.tsx +38 -0
- package/Application/Components/HeaderRight.tsx +142 -9
- package/Application/Components/HeaderTitle.tsx +67 -17
- package/Application/Components/OptionalGlassView.ts +11 -0
- package/Application/Components/SearchHeader.tsx +22 -15
- package/Application/Components/SideBarButton.tsx +139 -0
- package/Application/ModalScreen.tsx +16 -2
- package/Application/NavigationContainer.tsx +36 -45
- package/Application/Navigator.ts +1 -3
- package/Application/StackScreen.tsx +2 -5
- package/Application/WidgetContainer.tsx +9 -7
- package/Application/device.ts +29 -0
- package/Application/types.ts +10 -1
- package/Application/utils.tsx +10 -3
- package/Consts/index.ts +0 -1
- package/Consts/theme.ts +5 -6
- package/Input/Input.tsx +3 -4
- package/Input/InputMoney.tsx +31 -51
- package/Input/InputPhoneNumber.tsx +3 -4
- package/Input/InputSearch.tsx +3 -4
- package/Input/InputTextArea.tsx +3 -4
- package/Layout/Card.tsx +8 -26
- package/Layout/FloatingButton.tsx +3 -3
- package/Layout/Item.tsx +1 -10
- package/Layout/ReservedRegions.tsx +47 -0
- package/Layout/Screen.tsx +244 -48
- package/Layout/Section.tsx +5 -18
- package/Layout/index.ts +4 -4
- package/Layout/styles.ts +5 -0
- package/Layout/utils.ts +465 -5
- package/Popup/PopupNotify.tsx +33 -22
- package/Popup/PopupPromotion.tsx +20 -8
- package/Popup/utils.ts +32 -0
- package/Text/index.tsx +2 -11
- package/Text/utils.ts +1 -2
- package/package.json +4 -3
- package/Consts/designSystemFont.ts +0 -38
- package/Layout/GridSystem.tsx +0 -137
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { Colors, Radius, Spacing, Styles } from '../../Consts';
|
|
2
2
|
import { InputRef, InputSearch } from '../../Input';
|
|
3
|
-
import {
|
|
4
|
-
Dimensions,
|
|
5
|
-
StyleSheet,
|
|
6
|
-
TouchableOpacity,
|
|
7
|
-
View,
|
|
8
|
-
} from 'react-native';
|
|
3
|
+
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
9
4
|
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
|
|
10
5
|
import React, { useContext } from 'react';
|
|
11
6
|
import { SearchHeaderProps } from '../types';
|
|
12
7
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
13
8
|
import { Text } from '../../Text';
|
|
14
9
|
import { useNormalizedSharedValue } from '../utils';
|
|
10
|
+
import { useScreenLayout } from '../../Layout';
|
|
11
|
+
import { DuoMetrics } from '../../Layout/utils';
|
|
15
12
|
|
|
16
13
|
const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
17
14
|
(
|
|
@@ -26,19 +23,31 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
26
23
|
) => {
|
|
27
24
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
28
25
|
const context = useContext<any>(MiniAppContext);
|
|
29
|
-
const
|
|
30
|
-
|
|
26
|
+
const layout = useScreenLayout();
|
|
27
|
+
// the iPhone Duo status bar row keeps a side bar sized back button
|
|
28
|
+
const BACK_WIDTH = layout.statusBarClearance ? DuoMetrics.controlSize : 28;
|
|
31
29
|
|
|
32
30
|
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
33
31
|
const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
|
|
34
32
|
const headerRightWidth = props?.headerRightWidth ?? 73;
|
|
35
33
|
|
|
34
|
+
// iPhone Duo side bar holds back: the search shares the content width with the buttons only
|
|
35
|
+
let searchMaxWidth = layout.width - leftPosition - 12 - headerRightWidth;
|
|
36
|
+
if (layout.sideBar) {
|
|
37
|
+
searchMaxWidth = layout.contentWidth - Spacing.M * 2 - headerRightWidth;
|
|
38
|
+
}
|
|
39
|
+
if (layout.statusBarClearance) {
|
|
40
|
+
searchMaxWidth -=
|
|
41
|
+
layout.statusBarClearance.left + layout.statusBarClearance.right;
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
const backgroundStyle = useAnimatedStyle(() => {
|
|
37
45
|
const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
|
|
38
46
|
return {
|
|
39
|
-
backgroundColor:
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
backgroundColor:
|
|
48
|
+
t === 0
|
|
49
|
+
? theme.colors.background.surface
|
|
50
|
+
: theme.colors.background.default,
|
|
42
51
|
};
|
|
43
52
|
});
|
|
44
53
|
|
|
@@ -77,13 +86,11 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
77
86
|
style={[
|
|
78
87
|
Styles.flex,
|
|
79
88
|
renderButtons && {
|
|
80
|
-
maxWidth:
|
|
89
|
+
maxWidth: searchMaxWidth,
|
|
81
90
|
},
|
|
82
91
|
]}
|
|
83
92
|
>
|
|
84
|
-
<Animated.View
|
|
85
|
-
style={[{ borderRadius: Radius.XL }, backgroundStyle]}
|
|
86
|
-
>
|
|
93
|
+
<Animated.View style={[{ borderRadius: Radius.XL }, backgroundStyle]}>
|
|
87
94
|
<InputSearch
|
|
88
95
|
ref={ref}
|
|
89
96
|
{...props}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { ReactNode, useContext } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
StyleProp,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
View,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import { NavigationButtonProps } from '../types';
|
|
10
|
+
import { ApplicationContext } from '../../Context';
|
|
11
|
+
import { Colors, Radius, Shadow, Spacing } from '../../Consts';
|
|
12
|
+
import { Icon } from '../../Icon';
|
|
13
|
+
import { BadgeDot } from '../../Badge';
|
|
14
|
+
import { DuoMetrics } from '../../Layout/utils';
|
|
15
|
+
|
|
16
|
+
const SIDE_BAR_ICON_SIZE = 24;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* glass material for side bar controls: dark glass under light icons (banner headers),
|
|
20
|
+
* light glass otherwise
|
|
21
|
+
*/
|
|
22
|
+
const getGlassStyle = (tintColor?: string) =>
|
|
23
|
+
tintColor === Colors.black_01 || tintColor === 'white'
|
|
24
|
+
? styles.glassDark
|
|
25
|
+
: styles.glassLight;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 48pt glass circle used for a single control in the iPhone Duo side bar
|
|
29
|
+
*/
|
|
30
|
+
const SideBarButton: React.FC<NavigationButtonProps> = ({
|
|
31
|
+
icon,
|
|
32
|
+
tintColor,
|
|
33
|
+
showBadge = false,
|
|
34
|
+
style,
|
|
35
|
+
...props
|
|
36
|
+
}) => {
|
|
37
|
+
const { theme } = useContext(ApplicationContext);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View>
|
|
41
|
+
<TouchableOpacity
|
|
42
|
+
{...props}
|
|
43
|
+
style={[styles.control, getGlassStyle(tintColor), style]}
|
|
44
|
+
>
|
|
45
|
+
<Icon
|
|
46
|
+
source={icon}
|
|
47
|
+
color={tintColor ?? theme.colors.text.default}
|
|
48
|
+
size={SIDE_BAR_ICON_SIZE}
|
|
49
|
+
/>
|
|
50
|
+
</TouchableOpacity>
|
|
51
|
+
{showBadge && <BadgeDot size={'small'} style={styles.badgeDot} />}
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 48pt glass capsule grouping related controls, one item per 50pt, laid out vertically
|
|
58
|
+
* in the side bar or horizontally on a header row
|
|
59
|
+
*/
|
|
60
|
+
const SideBarGroup: React.FC<{
|
|
61
|
+
tintColor?: string;
|
|
62
|
+
horizontal?: boolean;
|
|
63
|
+
style?: StyleProp<ViewStyle>;
|
|
64
|
+
children: ReactNode;
|
|
65
|
+
}> = ({ tintColor, horizontal = false, style, children }) => {
|
|
66
|
+
const count = React.Children.toArray(children).length;
|
|
67
|
+
const length =
|
|
68
|
+
count > 1 ? count * DuoMetrics.itemPitch : DuoMetrics.controlSize;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<View
|
|
72
|
+
style={[
|
|
73
|
+
styles.control,
|
|
74
|
+
getGlassStyle(tintColor),
|
|
75
|
+
horizontal
|
|
76
|
+
? { width: length, flexDirection: 'row' }
|
|
77
|
+
: { height: length },
|
|
78
|
+
style,
|
|
79
|
+
]}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
</View>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* icon item inside a SideBarGroup
|
|
88
|
+
*/
|
|
89
|
+
const SideBarGroupItem: React.FC<NavigationButtonProps> = ({
|
|
90
|
+
icon,
|
|
91
|
+
tintColor,
|
|
92
|
+
...props
|
|
93
|
+
}) => {
|
|
94
|
+
const { theme } = useContext(ApplicationContext);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<TouchableOpacity {...props} style={styles.groupItem}>
|
|
98
|
+
<Icon
|
|
99
|
+
source={icon}
|
|
100
|
+
color={tintColor ?? theme.colors.text.default}
|
|
101
|
+
size={SIDE_BAR_ICON_SIZE}
|
|
102
|
+
/>
|
|
103
|
+
</TouchableOpacity>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const styles = StyleSheet.create({
|
|
108
|
+
control: {
|
|
109
|
+
width: DuoMetrics.controlSize,
|
|
110
|
+
height: DuoMetrics.controlSize,
|
|
111
|
+
borderRadius: Radius.XL,
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
},
|
|
115
|
+
glassLight: {
|
|
116
|
+
backgroundColor: Colors.black_01 + 'D9',
|
|
117
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
118
|
+
borderColor: Colors.black_20 + '14',
|
|
119
|
+
...Shadow.Light,
|
|
120
|
+
},
|
|
121
|
+
glassDark: {
|
|
122
|
+
backgroundColor: Colors.black_20 + '80',
|
|
123
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
124
|
+
borderColor: Colors.black_01 + '33',
|
|
125
|
+
},
|
|
126
|
+
groupItem: {
|
|
127
|
+
flex: 1,
|
|
128
|
+
alignSelf: 'stretch',
|
|
129
|
+
justifyContent: 'center',
|
|
130
|
+
alignItems: 'center',
|
|
131
|
+
},
|
|
132
|
+
badgeDot: {
|
|
133
|
+
position: 'absolute',
|
|
134
|
+
top: Spacing.XXS,
|
|
135
|
+
right: Spacing.XXS,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export { SideBarButton, SideBarGroup, SideBarGroupItem };
|
|
@@ -8,15 +8,18 @@ import {
|
|
|
8
8
|
Platform,
|
|
9
9
|
Pressable,
|
|
10
10
|
StyleSheet,
|
|
11
|
+
useWindowDimensions,
|
|
11
12
|
View,
|
|
12
13
|
} from 'react-native';
|
|
14
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
13
15
|
|
|
14
16
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
15
17
|
import { Styles } from '../Consts';
|
|
16
18
|
import Navigation from './Navigation';
|
|
17
19
|
import { ModalParams } from './types';
|
|
18
20
|
import BottomSheet from './BottomSheet';
|
|
19
|
-
import {
|
|
21
|
+
import { getDuoTopInset, isIPhoneDuo } from './device';
|
|
22
|
+
import { scheduleOnRN } from 'react-native-worklets';
|
|
20
23
|
|
|
21
24
|
const ModalScreen: React.FC<any> = props => {
|
|
22
25
|
if (props.route?.params?.isBottomSheet) {
|
|
@@ -37,6 +40,8 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
37
40
|
title,
|
|
38
41
|
} = props.route.params;
|
|
39
42
|
const Component = useRef(screen).current;
|
|
43
|
+
const insets = useSafeAreaInsets();
|
|
44
|
+
const windowSize = useWindowDimensions();
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
47
|
* Fire auto_popup_displayed tracking event on mount
|
|
@@ -106,7 +111,7 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
106
111
|
}),
|
|
107
112
|
]).start(() => {
|
|
108
113
|
navigator?.pop();
|
|
109
|
-
|
|
114
|
+
scheduleOnRN(callback);
|
|
110
115
|
});
|
|
111
116
|
},
|
|
112
117
|
[navigator, opacity, scale],
|
|
@@ -147,6 +152,15 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
147
152
|
opacity,
|
|
148
153
|
transform: [{ scale }],
|
|
149
154
|
},
|
|
155
|
+
// center below the side status bar and above the home indicator
|
|
156
|
+
isIPhoneDuo() && {
|
|
157
|
+
marginTop: getDuoTopInset(
|
|
158
|
+
insets,
|
|
159
|
+
windowSize.width,
|
|
160
|
+
windowSize.height,
|
|
161
|
+
),
|
|
162
|
+
marginBottom: insets.bottom,
|
|
163
|
+
},
|
|
150
164
|
modalStyle,
|
|
151
165
|
]}
|
|
152
166
|
>
|
|
@@ -2,7 +2,6 @@ import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
SafeAreaInsetsContext,
|
|
4
4
|
SafeAreaProvider,
|
|
5
|
-
useSafeAreaInsets,
|
|
6
5
|
} from 'react-native-safe-area-context';
|
|
7
6
|
import {
|
|
8
7
|
NavigationContainer as ReactNavigationContainer,
|
|
@@ -13,14 +12,17 @@ import {
|
|
|
13
12
|
StackNavigationOptions,
|
|
14
13
|
TransitionPresets,
|
|
15
14
|
} from '@react-navigation/stack';
|
|
16
|
-
import { DeviceEventEmitter } from 'react-native';
|
|
17
15
|
import StackScreen from './StackScreen';
|
|
18
16
|
import ModalScreen from './ModalScreen';
|
|
19
17
|
import Navigator from './Navigator';
|
|
20
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
getModalOptions,
|
|
20
|
+
getStackOptions,
|
|
21
|
+
isWhiteListDesignSystem,
|
|
22
|
+
useNativeCanGoBack,
|
|
23
|
+
} from './utils';
|
|
21
24
|
import { NavigationContainerProps } from './types';
|
|
22
25
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
23
|
-
import { useDefaultRegularFontFamily } from '../Consts/designSystemFont';
|
|
24
26
|
import Localize from './Localize';
|
|
25
27
|
import { Colors, defaultTheme } from '../Consts';
|
|
26
28
|
import { HeaderLeft } from './Components/HeaderLeft';
|
|
@@ -28,6 +30,9 @@ import { HeaderRight } from './Components/HeaderRight';
|
|
|
28
30
|
import { HeaderTitle } from './Components/HeaderTitle';
|
|
29
31
|
import { HeaderBackground } from './Components/HeaderBackground';
|
|
30
32
|
import { type EdgeInsets } from 'react-native-safe-area-context';
|
|
33
|
+
import { useScreenLayout } from '../Layout';
|
|
34
|
+
import { getHeaderRowOptions } from '../Layout/utils';
|
|
35
|
+
import { ReservedRegionsProvider } from '../Layout/ReservedRegions';
|
|
31
36
|
|
|
32
37
|
const Stack = createStackNavigator();
|
|
33
38
|
|
|
@@ -73,17 +78,19 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
73
78
|
frame: { x: 0, y: 0, width: 0, height: 0 },
|
|
74
79
|
}}
|
|
75
80
|
>
|
|
76
|
-
<
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
<ReservedRegionsProvider>
|
|
82
|
+
<MiniAppContext.Provider value={mergedContext}>
|
|
83
|
+
<Navigation
|
|
84
|
+
screen={screen}
|
|
85
|
+
theme={theme}
|
|
86
|
+
options={options}
|
|
87
|
+
maxApi={maxApi}
|
|
88
|
+
setCurrentContext={setCurrentContext}
|
|
89
|
+
initialParams={initialParams}
|
|
90
|
+
localize={localize}
|
|
91
|
+
/>
|
|
92
|
+
</MiniAppContext.Provider>
|
|
93
|
+
</ReservedRegionsProvider>
|
|
87
94
|
</SafeAreaProvider>
|
|
88
95
|
);
|
|
89
96
|
};
|
|
@@ -101,10 +108,11 @@ const Navigation: React.FC<any> = ({
|
|
|
101
108
|
const navigationRef = useRef<any>(null);
|
|
102
109
|
const isReady = useRef(false);
|
|
103
110
|
const navigator = useRef(new Navigator(navigationRef, isReady));
|
|
104
|
-
const
|
|
105
|
-
const insets = useSafeAreaInsets();
|
|
106
|
-
const defaultRegularFontFamily = useDefaultRegularFontFamily();
|
|
111
|
+
const headerRowOptions = getHeaderRowOptions(useScreenLayout());
|
|
107
112
|
const syncCanGoBack = useNativeCanGoBack();
|
|
113
|
+
const isWhiteList = isWhiteListDesignSystem(context);
|
|
114
|
+
const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
|
|
115
|
+
const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
|
|
108
116
|
let headerBackground = context?.designSystemConfig?.config?.headerBar;
|
|
109
117
|
let headerGradient = theme.colors?.gradient;
|
|
110
118
|
|
|
@@ -120,23 +128,6 @@ const Navigation: React.FC<any> = ({
|
|
|
120
128
|
*/
|
|
121
129
|
navigator.current.maxApi = maxApi;
|
|
122
130
|
|
|
123
|
-
/**
|
|
124
|
-
* handle mini language & listen change
|
|
125
|
-
* engine only shake to enable grid view
|
|
126
|
-
*/
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
const subscription = DeviceEventEmitter.addListener(
|
|
129
|
-
'onChangeGrid',
|
|
130
|
-
enable => {
|
|
131
|
-
setShowGrid(!!enable);
|
|
132
|
-
},
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
return () => {
|
|
136
|
-
subscription?.remove?.();
|
|
137
|
-
};
|
|
138
|
-
}, []);
|
|
139
|
-
|
|
140
131
|
/**
|
|
141
132
|
* translate context method
|
|
142
133
|
* @param data
|
|
@@ -156,6 +147,7 @@ const Navigation: React.FC<any> = ({
|
|
|
156
147
|
navigator: navigator.current,
|
|
157
148
|
theme: {
|
|
158
149
|
...theme,
|
|
150
|
+
font: fontFamily,
|
|
159
151
|
colors: {
|
|
160
152
|
...theme.colors,
|
|
161
153
|
gradient: headerGradient,
|
|
@@ -165,7 +157,6 @@ const Navigation: React.FC<any> = ({
|
|
|
165
157
|
headerBackground,
|
|
166
158
|
},
|
|
167
159
|
},
|
|
168
|
-
showGrid,
|
|
169
160
|
translate,
|
|
170
161
|
}}
|
|
171
162
|
>
|
|
@@ -183,19 +174,19 @@ const Navigation: React.FC<any> = ({
|
|
|
183
174
|
},
|
|
184
175
|
fonts: {
|
|
185
176
|
regular: {
|
|
186
|
-
fontFamily:
|
|
177
|
+
fontFamily: navigationFontFamily,
|
|
187
178
|
fontWeight: '400',
|
|
188
179
|
},
|
|
189
180
|
medium: {
|
|
190
|
-
fontFamily:
|
|
181
|
+
fontFamily: navigationFontFamily,
|
|
191
182
|
fontWeight: '400',
|
|
192
183
|
},
|
|
193
184
|
bold: {
|
|
194
|
-
fontFamily:
|
|
185
|
+
fontFamily: navigationFontFamily,
|
|
195
186
|
fontWeight: '400',
|
|
196
187
|
},
|
|
197
188
|
heavy: {
|
|
198
|
-
fontFamily:
|
|
189
|
+
fontFamily: navigationFontFamily,
|
|
199
190
|
fontWeight: '400',
|
|
200
191
|
},
|
|
201
192
|
},
|
|
@@ -214,9 +205,7 @@ const Navigation: React.FC<any> = ({
|
|
|
214
205
|
gestureEnabled: false,
|
|
215
206
|
headerTitleAlign: 'center',
|
|
216
207
|
headerTintColor: theme.colors.text.default,
|
|
217
|
-
|
|
218
|
-
height: 52 + insets.top,
|
|
219
|
-
},
|
|
208
|
+
...headerRowOptions,
|
|
220
209
|
}}
|
|
221
210
|
>
|
|
222
211
|
<Stack.Screen
|
|
@@ -239,11 +228,13 @@ const Navigation: React.FC<any> = ({
|
|
|
239
228
|
cardStyle: {
|
|
240
229
|
backgroundColor: theme.colors.background.default,
|
|
241
230
|
},
|
|
242
|
-
headerLeft: (props: any) => <HeaderLeft {...props} />,
|
|
231
|
+
headerLeft: (props: any) => <HeaderLeft {...props} floating />,
|
|
243
232
|
headerBackground: (props: any) => (
|
|
244
233
|
<HeaderBackground {...props} />
|
|
245
234
|
),
|
|
246
|
-
headerRight: (props: any) =>
|
|
235
|
+
headerRight: (props: any) => (
|
|
236
|
+
<HeaderRight {...props} floating />
|
|
237
|
+
),
|
|
247
238
|
headerTintColor: Colors.black_17,
|
|
248
239
|
...TransitionPresets.ModalTransition,
|
|
249
240
|
}}
|
package/Application/Navigator.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { useHeaderHeight } from '@react-navigation/elements';
|
|
|
4
4
|
import { ScreenTrackingParams } from './types';
|
|
5
5
|
import Navigation from './Navigation';
|
|
6
6
|
import { ApplicationContext, MiniAppContext, ScreenContext } from '../Context';
|
|
7
|
-
import { GridSystem } from '../Layout';
|
|
8
7
|
import { version } from '../package.json';
|
|
9
8
|
import { SwipeBackHaptic, useAppState } from './utils';
|
|
10
9
|
import { TooltipPortalHost, TooltipPortalProvider } from './TooltipPortal';
|
|
@@ -17,7 +16,7 @@ const runAfterInteractions = InteractionManager.runAfterInteractions;
|
|
|
17
16
|
* @constructor
|
|
18
17
|
*/
|
|
19
18
|
const StackScreen: React.FC<any> = props => {
|
|
20
|
-
const {
|
|
19
|
+
const { navigator } = useContext(ApplicationContext);
|
|
21
20
|
const tracking = useRef<any>({
|
|
22
21
|
mounted: false,
|
|
23
22
|
timeoutLoad: undefined,
|
|
@@ -42,6 +41,7 @@ const StackScreen: React.FC<any> = props => {
|
|
|
42
41
|
});
|
|
43
42
|
const widgets = useRef<any>([]);
|
|
44
43
|
const context = useContext<any>(MiniAppContext);
|
|
44
|
+
|
|
45
45
|
const {
|
|
46
46
|
screen: Component,
|
|
47
47
|
options,
|
|
@@ -411,10 +411,7 @@ const StackScreen: React.FC<any> = props => {
|
|
|
411
411
|
>
|
|
412
412
|
<TooltipPortalProvider>
|
|
413
413
|
<Component heightHeader={heightHeader} {...data} />
|
|
414
|
-
{showGrid && <GridSystem />}
|
|
415
414
|
<TooltipPortalHost />
|
|
416
|
-
{/* a leaf on purpose: it subscribes to CardAnimationContext, which changes on every
|
|
417
|
-
Card render, and must not pull this screen into those renders */}
|
|
418
415
|
<SwipeBackHaptic />
|
|
419
416
|
</TooltipPortalProvider>
|
|
420
417
|
</ScreenContext.Provider>
|
|
@@ -3,7 +3,6 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
|
3
3
|
import Navigator from './Navigator';
|
|
4
4
|
import { WidgetContainerProps } from './types';
|
|
5
5
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
6
|
-
import { useDefaultRegularFontFamily } from '../Consts/designSystemFont';
|
|
7
6
|
import Localize from './Localize';
|
|
8
7
|
import { defaultTheme } from '../Consts';
|
|
9
8
|
import {
|
|
@@ -11,7 +10,7 @@ import {
|
|
|
11
10
|
NavigationContainerRef,
|
|
12
11
|
NavigationIndependentTree,
|
|
13
12
|
} from '@react-navigation/native';
|
|
14
|
-
import { getModalOptions } from './utils';
|
|
13
|
+
import { getModalOptions, isWhiteListDesignSystem } from './utils';
|
|
15
14
|
import { createStackNavigator } from '@react-navigation/stack';
|
|
16
15
|
import ModalScreen from './ModalScreen';
|
|
17
16
|
import { View } from 'react-native';
|
|
@@ -43,7 +42,6 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
43
42
|
const isReady = useRef(false);
|
|
44
43
|
const navigator = useRef(new Navigator(navigationRef, isReady, true));
|
|
45
44
|
const [currentContext, setCurrentContext] = useState({});
|
|
46
|
-
const defaultRegularFontFamily = useDefaultRegularFontFamily();
|
|
47
45
|
|
|
48
46
|
useEffect(() => {
|
|
49
47
|
const sub = maxApi?.observer?.('font_scale_config', (data: any) => {
|
|
@@ -80,6 +78,9 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
80
78
|
...context,
|
|
81
79
|
...currentContext,
|
|
82
80
|
};
|
|
81
|
+
const isWhiteList = isWhiteListDesignSystem(mergedContext);
|
|
82
|
+
const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
|
|
83
|
+
const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
|
|
83
84
|
|
|
84
85
|
return (
|
|
85
86
|
<View style={{ height: height ?? '100%', minHeight: 1 }}>
|
|
@@ -90,6 +91,7 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
90
91
|
navigator: navigator.current,
|
|
91
92
|
theme: {
|
|
92
93
|
...theme,
|
|
94
|
+
font: fontFamily,
|
|
93
95
|
colors: {
|
|
94
96
|
...theme.colors,
|
|
95
97
|
gradient: headerGradient,
|
|
@@ -116,19 +118,19 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
116
118
|
},
|
|
117
119
|
fonts: {
|
|
118
120
|
regular: {
|
|
119
|
-
fontFamily:
|
|
121
|
+
fontFamily: navigationFontFamily,
|
|
120
122
|
fontWeight: '400',
|
|
121
123
|
},
|
|
122
124
|
medium: {
|
|
123
|
-
fontFamily:
|
|
125
|
+
fontFamily: navigationFontFamily,
|
|
124
126
|
fontWeight: '400',
|
|
125
127
|
},
|
|
126
128
|
bold: {
|
|
127
|
-
fontFamily:
|
|
129
|
+
fontFamily: navigationFontFamily,
|
|
128
130
|
fontWeight: '400',
|
|
129
131
|
},
|
|
130
132
|
heavy: {
|
|
131
|
-
fontFamily:
|
|
133
|
+
fontFamily: navigationFontFamily,
|
|
132
134
|
fontWeight: '400',
|
|
133
135
|
},
|
|
134
136
|
},
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Dimensions, Platform, ScaledSize } from 'react-native';
|
|
2
|
+
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
3
|
+
|
|
4
|
+
// widest short side of any single-display iPhone
|
|
5
|
+
const IPHONE_MAX_SHORT_SIDE = 440;
|
|
6
|
+
|
|
7
|
+
// iPhone Duo reports idiom "phone"; 'screen' stays on the outer display, so it
|
|
8
|
+
// still matches when Split View narrows the window
|
|
9
|
+
const isIPhoneDuo = () => {
|
|
10
|
+
if (Platform.OS !== 'ios' || Platform.isPad) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const shortSide = (size: ScaledSize) => Math.min(size.width, size.height);
|
|
14
|
+
return (
|
|
15
|
+
Math.max(
|
|
16
|
+
shortSide(Dimensions.get('screen')),
|
|
17
|
+
shortSide(Dimensions.get('window')),
|
|
18
|
+
) > IPHONE_MAX_SHORT_SIDE
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// portrait side status bar is about twice as tall as its inset is wide
|
|
23
|
+
const getDuoTopInset = (insets: EdgeInsets, width: number, height: number) =>
|
|
24
|
+
Math.max(
|
|
25
|
+
insets.top,
|
|
26
|
+
height > width ? Math.max(insets.left, insets.right) * 2 : 0,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export { isIPhoneDuo, getDuoTopInset };
|
package/Application/types.ts
CHANGED
|
@@ -117,7 +117,6 @@ export type Context = {
|
|
|
117
117
|
[key: string]: any;
|
|
118
118
|
theme: Theme;
|
|
119
119
|
navigator?: NavigatorProps;
|
|
120
|
-
showGrid?: boolean;
|
|
121
120
|
translate?: (data: string | { vi: string; en: string }) => string;
|
|
122
121
|
};
|
|
123
122
|
|
|
@@ -258,6 +257,11 @@ export interface HeaderBackProps extends NavigationButtonProps {
|
|
|
258
257
|
preventBack?: PopupNotifyProps;
|
|
259
258
|
onPressLeftHeader?: () => void;
|
|
260
259
|
onBackHandler?: (goBack: () => void) => void;
|
|
260
|
+
/**
|
|
261
|
+
* Optional. If `true`, the button floats into the iPhone Duo side bar while the
|
|
262
|
+
* device shows one. Set when the button is rendered by the navigation header.
|
|
263
|
+
*/
|
|
264
|
+
floating?: boolean;
|
|
261
265
|
}
|
|
262
266
|
|
|
263
267
|
export type HeaderBackgroundProps = {
|
|
@@ -329,6 +333,11 @@ export type BottomTabProps = {
|
|
|
329
333
|
blur?: (e: EventArg<'blur', false, unknown>) => void;
|
|
330
334
|
};
|
|
331
335
|
floatingButton?: FloatingButtonProps;
|
|
336
|
+
/**
|
|
337
|
+
* iPhone Duo: when the side bar can't hold both bars, which one starts compressed;
|
|
338
|
+
* tapping the compressed one swaps them. Defaults to 'toolbar'
|
|
339
|
+
*/
|
|
340
|
+
compressionBehavior?: 'toolbar' | 'tabBar';
|
|
332
341
|
};
|
|
333
342
|
|
|
334
343
|
export type FloatingButtonProps = {
|
package/Application/utils.tsx
CHANGED
|
@@ -73,8 +73,8 @@ const getStackOptions = (): StackNavigationOptions => {
|
|
|
73
73
|
left: Spacing.S,
|
|
74
74
|
},
|
|
75
75
|
headerBackground: (props: any) => <HeaderBackground {...props} />,
|
|
76
|
-
headerLeft: (props: any) => <HeaderLeft {...props} />,
|
|
77
|
-
headerRight: (props: any) => <HeaderRight {...props} />,
|
|
76
|
+
headerLeft: (props: any) => <HeaderLeft {...props} floating />,
|
|
77
|
+
headerRight: (props: any) => <HeaderRight {...props} floating />,
|
|
78
78
|
headerTintColor: Colors.black_17,
|
|
79
79
|
gestureEnabled: Platform.OS === 'ios',
|
|
80
80
|
...TransitionPresets.SlideFromRightIOS,
|
|
@@ -129,6 +129,7 @@ const getOptions = (
|
|
|
129
129
|
preventBack={params.preventBack}
|
|
130
130
|
onPressLeftHeader={params.onPressLeftHeader}
|
|
131
131
|
onBackHandler={params.onBackHandler}
|
|
132
|
+
floating
|
|
132
133
|
/>
|
|
133
134
|
);
|
|
134
135
|
if (params.hiddenBack) {
|
|
@@ -167,7 +168,9 @@ const getOptions = (
|
|
|
167
168
|
...options,
|
|
168
169
|
...{
|
|
169
170
|
headerRight: (props: any) => {
|
|
170
|
-
return
|
|
171
|
+
return (
|
|
172
|
+
<HeaderRight {...props} headerRight={params.headerRight} floating />
|
|
173
|
+
);
|
|
171
174
|
},
|
|
172
175
|
},
|
|
173
176
|
};
|
|
@@ -475,6 +478,9 @@ const SwipeBackHaptic = () => {
|
|
|
475
478
|
return null;
|
|
476
479
|
};
|
|
477
480
|
|
|
481
|
+
const isWhiteListDesignSystem = (context: any): boolean =>
|
|
482
|
+
context?.designSystemConfig?.isWhiteListDesignSystem === true;
|
|
483
|
+
|
|
478
484
|
export {
|
|
479
485
|
getStackOptions,
|
|
480
486
|
getModalOptions,
|
|
@@ -487,4 +493,5 @@ export {
|
|
|
487
493
|
useNativeCanGoBack,
|
|
488
494
|
useNormalizedSharedValue,
|
|
489
495
|
SwipeBackHaptic,
|
|
496
|
+
isWhiteListDesignSystem,
|
|
490
497
|
};
|
package/Consts/index.ts
CHANGED