@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.
Files changed (45) hide show
  1. package/Application/BottomSheet.tsx +115 -14
  2. package/Application/BottomTab/DuoBarCompression.tsx +135 -0
  3. package/Application/BottomTab/DuoTabBar.tsx +462 -0
  4. package/Application/BottomTab/index.tsx +150 -134
  5. package/Application/Components/DuoGlassBackground.tsx +179 -0
  6. package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
  7. package/Application/Components/HeaderExtendHeader.tsx +75 -28
  8. package/Application/Components/HeaderLeft.tsx +38 -0
  9. package/Application/Components/HeaderRight.tsx +142 -9
  10. package/Application/Components/HeaderTitle.tsx +67 -17
  11. package/Application/Components/OptionalGlassView.ts +11 -0
  12. package/Application/Components/SearchHeader.tsx +22 -15
  13. package/Application/Components/SideBarButton.tsx +139 -0
  14. package/Application/ModalScreen.tsx +16 -2
  15. package/Application/NavigationContainer.tsx +36 -45
  16. package/Application/Navigator.ts +1 -3
  17. package/Application/StackScreen.tsx +2 -5
  18. package/Application/WidgetContainer.tsx +9 -7
  19. package/Application/device.ts +29 -0
  20. package/Application/types.ts +10 -1
  21. package/Application/utils.tsx +10 -3
  22. package/Consts/index.ts +0 -1
  23. package/Consts/theme.ts +5 -6
  24. package/Input/Input.tsx +3 -4
  25. package/Input/InputMoney.tsx +31 -51
  26. package/Input/InputPhoneNumber.tsx +3 -4
  27. package/Input/InputSearch.tsx +3 -4
  28. package/Input/InputTextArea.tsx +3 -4
  29. package/Layout/Card.tsx +8 -26
  30. package/Layout/FloatingButton.tsx +3 -3
  31. package/Layout/Item.tsx +1 -10
  32. package/Layout/ReservedRegions.tsx +47 -0
  33. package/Layout/Screen.tsx +244 -48
  34. package/Layout/Section.tsx +5 -18
  35. package/Layout/index.ts +4 -4
  36. package/Layout/styles.ts +5 -0
  37. package/Layout/utils.ts +465 -5
  38. package/Popup/PopupNotify.tsx +33 -22
  39. package/Popup/PopupPromotion.tsx +20 -8
  40. package/Popup/utils.ts +32 -0
  41. package/Text/index.tsx +2 -11
  42. package/Text/utils.ts +1 -2
  43. package/package.json +4 -3
  44. package/Consts/designSystemFont.ts +0 -38
  45. 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 BACK_WIDTH = 28;
30
- const { width: screenWidth } = Dimensions.get('window');
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: t === 0
40
- ? theme.colors.background.surface
41
- : theme.colors.background.default,
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: screenWidth - leftPosition - 12 - headerRightWidth,
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 { runOnJS } from 'react-native-reanimated';
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
- runOnJS(callback)();
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 { getModalOptions, getStackOptions, useNativeCanGoBack } from './utils';
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
- <MiniAppContext.Provider value={mergedContext}>
77
- <Navigation
78
- screen={screen}
79
- theme={theme}
80
- options={options}
81
- maxApi={maxApi}
82
- setCurrentContext={setCurrentContext}
83
- initialParams={initialParams}
84
- localize={localize}
85
- />
86
- </MiniAppContext.Provider>
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 [showGrid, setShowGrid] = useState(false);
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: defaultRegularFontFamily,
177
+ fontFamily: navigationFontFamily,
187
178
  fontWeight: '400',
188
179
  },
189
180
  medium: {
190
- fontFamily: defaultRegularFontFamily,
181
+ fontFamily: navigationFontFamily,
191
182
  fontWeight: '400',
192
183
  },
193
184
  bold: {
194
- fontFamily: defaultRegularFontFamily,
185
+ fontFamily: navigationFontFamily,
195
186
  fontWeight: '400',
196
187
  },
197
188
  heavy: {
198
- fontFamily: defaultRegularFontFamily,
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
- headerStyle: {
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) => <HeaderRight {...props} />,
235
+ headerRight: (props: any) => (
236
+ <HeaderRight {...props} floating />
237
+ ),
247
238
  headerTintColor: Colors.black_17,
248
239
  ...TransitionPresets.ModalTransition,
249
240
  }}
@@ -118,9 +118,7 @@ class Navigator {
118
118
  navigate = (name: string) => {
119
119
  if (this.isReady.current) {
120
120
  this.ref.current?.dispatch?.(
121
- CommonActions.navigate({
122
- name,
123
- })
121
+ CommonActions.navigate(name)
124
122
  );
125
123
  }
126
124
  };
@@ -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 { showGrid, navigator } = useContext(ApplicationContext);
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: defaultRegularFontFamily,
121
+ fontFamily: navigationFontFamily,
120
122
  fontWeight: '400',
121
123
  },
122
124
  medium: {
123
- fontFamily: defaultRegularFontFamily,
125
+ fontFamily: navigationFontFamily,
124
126
  fontWeight: '400',
125
127
  },
126
128
  bold: {
127
- fontFamily: defaultRegularFontFamily,
129
+ fontFamily: navigationFontFamily,
128
130
  fontWeight: '400',
129
131
  },
130
132
  heavy: {
131
- fontFamily: defaultRegularFontFamily,
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 };
@@ -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 = {
@@ -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 <HeaderRight {...props} headerRight={params.headerRight} />;
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
@@ -2,4 +2,3 @@ export * from './colors+spacing+radius';
2
2
  export * from './theme';
3
3
  export * from './styles';
4
4
  export const GRIDVIEW = false;
5
- export * from './designSystemFont';