@momo-kits/foundation 0.167.1 → 0.168.1-beta.1

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 (53) hide show
  1. package/Application/BottomSheet.tsx +113 -14
  2. package/Application/BottomTab/BottomTabBar.tsx +6 -3
  3. package/Application/BottomTab/CustomBottomTabItem.tsx +3 -5
  4. package/Application/BottomTab/DuoBarCompression.tsx +135 -0
  5. package/Application/BottomTab/DuoTabBar.tsx +537 -0
  6. package/Application/BottomTab/index.tsx +145 -134
  7. package/Application/Components/DuoGlassBackground.tsx +158 -0
  8. package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
  9. package/Application/Components/HeaderAnimated.tsx +9 -3
  10. package/Application/Components/HeaderExtendHeader.tsx +70 -28
  11. package/Application/Components/HeaderLeft.tsx +38 -0
  12. package/Application/Components/HeaderRight.tsx +144 -9
  13. package/Application/Components/HeaderTitle.tsx +43 -7
  14. package/Application/Components/SearchHeader.tsx +20 -15
  15. package/Application/Components/SideBarButton.tsx +134 -0
  16. package/Application/ModalScreen.tsx +16 -2
  17. package/Application/NavigationContainer.tsx +22 -38
  18. package/Application/Navigator.ts +1 -3
  19. package/Application/StackScreen.tsx +2 -5
  20. package/Application/device.ts +29 -0
  21. package/Application/types.ts +10 -1
  22. package/Application/utils.tsx +10 -7
  23. package/Badge/Badge.tsx +2 -3
  24. package/Badge/BadgeDotAnimation.tsx +3 -3
  25. package/Button/index.tsx +11 -11
  26. package/Consts/theme.ts +3 -4
  27. package/Input/Input.tsx +3 -4
  28. package/Input/InputMoney.tsx +8 -12
  29. package/Input/InputOTP.tsx +9 -12
  30. package/Input/InputPhoneNumber.tsx +3 -4
  31. package/Input/InputSearch.tsx +17 -14
  32. package/Input/InputTextArea.tsx +3 -4
  33. package/Input/SystemTextInput.tsx +2 -3
  34. package/Input/TextTyping.tsx +11 -6
  35. package/Input/utils.ts +2 -3
  36. package/Layout/Card.tsx +8 -26
  37. package/Layout/FloatingButton.tsx +3 -3
  38. package/Layout/Item.tsx +1 -10
  39. package/Layout/ReservedRegions.tsx +78 -0
  40. package/Layout/Screen.tsx +242 -48
  41. package/Layout/Section.tsx +5 -18
  42. package/Layout/index.ts +4 -4
  43. package/Layout/styles.ts +5 -0
  44. package/Layout/utils.ts +477 -5
  45. package/Pagination/Dot.tsx +3 -6
  46. package/Popup/PopupNotify.tsx +33 -22
  47. package/Popup/PopupPromotion.tsx +21 -8
  48. package/Popup/utils.ts +32 -0
  49. package/Radio/index.tsx +2 -3
  50. package/Text/utils.ts +2 -3
  51. package/Title/index.tsx +8 -11
  52. package/package.json +5 -2
  53. package/Layout/GridSystem.tsx +0 -137
@@ -1,6 +1,6 @@
1
1
  import React, { Ref, useContext } from 'react';
2
2
  import LinearGradient from 'react-native-linear-gradient';
3
- import { Dimensions, Platform, StyleSheet, View } from 'react-native';
3
+ import { Platform, StyleSheet, View } from 'react-native';
4
4
  import Animated, {
5
5
  Extrapolation,
6
6
  interpolate,
@@ -14,21 +14,19 @@ import { Colors, Radius, Spacing } from '../../Consts';
14
14
  import { Image } from '../../Image';
15
15
  import { SearchHeaderProps } from '../types';
16
16
  import BackgroundImageView from './BackgroundImageView';
17
- import {
18
- AnimatedCompatValue,
19
- useNormalizedSharedValue,
20
- } from '../utils';
17
+ import { AnimatedCompatValue, useNormalizedSharedValue } from '../utils';
18
+ import { useScreenLayout } from '../../Layout';
19
+ import { DuoMetrics, HeaderMetrics } from '../../Layout/utils';
21
20
 
22
21
  const SCREEN_PADDING = 12;
23
22
  const BACK_WIDTH = 28;
24
-
25
- const { width: screenWidth } = Dimensions.get('window');
23
+ const TOOLKIT_WIDTH = 73;
26
24
 
27
25
  const HeaderExtendHeader: React.FC<{
28
26
  headerType?: HeaderType;
29
27
  animatedValue: AnimatedCompatValue;
30
28
  heightHeader: number;
31
- headerRightWidth: number;
29
+ headerRightWidth?: number;
32
30
  inputSearchProps?: SearchHeaderProps;
33
31
  inputSearchRef?: Ref<InputRef>;
34
32
  navigation?: Navigation;
@@ -39,7 +37,7 @@ const HeaderExtendHeader: React.FC<{
39
37
  headerType = 'default',
40
38
  animatedValue,
41
39
  heightHeader,
42
- headerRightWidth = 73,
40
+ headerRightWidth: customHeaderRightWidth,
43
41
  inputSearchProps,
44
42
  inputSearchRef,
45
43
  useShadowHeader: useShadowHeaderProp = true,
@@ -51,7 +49,32 @@ const HeaderExtendHeader: React.FC<{
51
49
  const sharedValue = useNormalizedSharedValue(animatedValue);
52
50
  const gradientColor = customGradientColor ?? theme.colors.gradient;
53
51
  const headerBackground = customBackground ?? theme.assets?.headerBackground;
54
- const leftPosition = inputSearchProps?.leftPosition || BACK_WIDTH + 20;
52
+ const layout = useScreenLayout();
53
+
54
+ // the iPhone Duo status bar row keeps side bar sized back and toolkit
55
+ const isStatusBarRow = !!layout.statusBarClearance;
56
+ const duoToolkitWidth = DuoMetrics.itemPitch * 2 + SCREEN_PADDING;
57
+ const backWidth = isStatusBarRow ? DuoMetrics.controlSize : BACK_WIDTH;
58
+ const toolkitWidth = isStatusBarRow ? duoToolkitWidth : TOOLKIT_WIDTH;
59
+ const defaultLeft = layout.sideBar ? SCREEN_PADDING : backWidth + 20;
60
+ const leftPosition = inputSearchProps?.leftPosition || defaultLeft;
61
+ const defaultRightWidth = layout.sideBar ? 0 : toolkitWidth;
62
+ const headerRightWidth = customHeaderRightWidth ?? defaultRightWidth;
63
+ const contentLeft = layout.insetLeft + SCREEN_PADDING;
64
+ const contentRight = layout.insetLeft + layout.contentWidth;
65
+ // collapsed on the status bar row, the search stays between its clusters
66
+ const collapsedLeft =
67
+ layout.insetLeft + leftPosition + (layout.statusBarClearance?.left ?? 0);
68
+ const collapsedRight = contentRight - (layout.statusBarClearance?.right ?? 0);
69
+ const searchWidth = layout.contentWidth - SCREEN_PADDING * 2;
70
+ const collapsedSearchWidth =
71
+ collapsedRight - collapsedLeft - SCREEN_PADDING - headerRightWidth;
72
+ // a row taller than barHeight (iPhone Duo) centers the collapsed search on the title
73
+ const collapsedRaise =
74
+ (layout.headerHeight -
75
+ layout.headerStatusBarHeight -
76
+ HeaderMetrics.barHeight) /
77
+ 2;
55
78
 
56
79
  let useShadowHeader = useShadowHeaderProp;
57
80
  if (inputSearchProps && Platform.OS === 'android') {
@@ -70,7 +93,12 @@ const HeaderExtendHeader: React.FC<{
70
93
  }));
71
94
 
72
95
  const gradientOpacityStyle = useAnimatedStyle(() => ({
73
- opacity: interpolate(sharedValue.value, [0, 52], [1, 0], Extrapolation.CLAMP),
96
+ opacity: interpolate(
97
+ sharedValue.value,
98
+ [0, 52],
99
+ [1, 0],
100
+ Extrapolation.CLAMP,
101
+ ),
74
102
  }));
75
103
 
76
104
  const searchTranslateStyle = useAnimatedStyle(() => ({
@@ -79,7 +107,15 @@ const HeaderExtendHeader: React.FC<{
79
107
  translateX: interpolate(
80
108
  sharedValue.value,
81
109
  [0, 100],
82
- [SCREEN_PADDING, leftPosition],
110
+ [contentLeft, collapsedLeft],
111
+ Extrapolation.CLAMP,
112
+ ),
113
+ },
114
+ {
115
+ translateY: interpolate(
116
+ sharedValue.value,
117
+ [0, 100],
118
+ [0, -collapsedRaise],
83
119
  Extrapolation.CLAMP,
84
120
  ),
85
121
  },
@@ -87,19 +123,17 @@ const HeaderExtendHeader: React.FC<{
87
123
  width: interpolate(
88
124
  sharedValue.value,
89
125
  [0, 100],
90
- [
91
- screenWidth - SCREEN_PADDING * 2,
92
- screenWidth - leftPosition - 12 - headerRightWidth,
93
- ],
126
+ [searchWidth, collapsedSearchWidth],
94
127
  Extrapolation.CLAMP,
95
128
  ),
96
129
  }));
97
130
 
98
131
  const searchBackgroundStyle = useAnimatedStyle(() => {
99
132
  const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
100
- return { backgroundColor: t === 0
101
- ? theme.colors.background.surface
102
- : theme.colors.background.default };
133
+ const { background } = theme.colors;
134
+ return {
135
+ backgroundColor: t === 0 ? background.surface : background.default,
136
+ };
103
137
  });
104
138
 
105
139
  const extendedHeightStyle = useAnimatedStyle(() => ({
@@ -124,14 +158,17 @@ const HeaderExtendHeader: React.FC<{
124
158
  <Animated.View
125
159
  style={[
126
160
  styles.headerBox,
127
- headerType === 'extended'
128
- ? heightStyle
129
- : { height: heightHeader },
161
+ headerType === 'extended' ? heightStyle : { height: heightHeader },
130
162
  ]}
131
163
  >
132
164
  {!!gradientColor && (
133
165
  <Animated.View
134
- style={[styles.extendedHeader, gradientOpacityStyle]}
166
+ style={[
167
+ styles.extendedHeader,
168
+ // fade out within the header box, as DuoScrollGradient does
169
+ !!layout.sideBar && styles.fillHeaderBox,
170
+ gradientOpacityStyle,
171
+ ]}
135
172
  >
136
173
  <LinearGradient
137
174
  colors={[gradientColor, gradientColor + '00']}
@@ -162,10 +199,7 @@ const HeaderExtendHeader: React.FC<{
162
199
  style={[{ marginVertical: Spacing.S }, searchTranslateStyle]}
163
200
  >
164
201
  <Animated.View
165
- style={[
166
- { borderRadius: Radius.XL },
167
- searchBackgroundStyle,
168
- ]}
202
+ style={[{ borderRadius: Radius.XL }, searchBackgroundStyle]}
169
203
  >
170
204
  <InputSearch
171
205
  {...inputSearchProps}
@@ -191,7 +225,11 @@ const HeaderExtendHeader: React.FC<{
191
225
  />
192
226
  {!!gradientColor && (
193
227
  <Animated.View
194
- style={[styles.extendedHeader, gradientOpacityStyle, extendedHeightStyle]}
228
+ style={[
229
+ styles.extendedHeader,
230
+ gradientOpacityStyle,
231
+ extendedHeightStyle,
232
+ ]}
195
233
  >
196
234
  <LinearGradient
197
235
  colors={[gradientColor, gradientColor + '00']}
@@ -227,6 +265,10 @@ const styles = StyleSheet.create({
227
265
  position: 'absolute',
228
266
  width: '100%',
229
267
  },
268
+ fillHeaderBox: {
269
+ aspectRatio: undefined,
270
+ height: '100%',
271
+ },
230
272
  headerBox: {
231
273
  width: '100%',
232
274
  position: 'absolute',
@@ -4,6 +4,10 @@ import React, { useCallback, useContext, useEffect } from 'react';
4
4
  import { ApplicationContext, MiniAppContext } from '../../Context';
5
5
  import { PopupNotify } from '../../Popup';
6
6
  import { NavigationButton } from './NavigationButton';
7
+ import { SideBarButton } from './SideBarButton';
8
+ import Animated from 'react-native-reanimated';
9
+ import { useScreenLayout, useSideBarItemStyle } from '../../Layout/utils';
10
+ import { useSideBarCounterStyle } from './DuoQuickReturnHeader';
7
11
 
8
12
  /**
9
13
  * default header left used for nav
@@ -13,9 +17,13 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
13
17
  preventBack,
14
18
  onPressLeftHeader,
15
19
  onBackHandler,
20
+ floating = false,
16
21
  }) => {
17
22
  const context = useContext<any>(MiniAppContext);
18
23
  const { navigator } = useContext(ApplicationContext);
24
+ const sideBarStyle = useSideBarItemStyle('left', 0);
25
+ const counterStyle = useSideBarCounterStyle();
26
+ const { statusBarClearance } = useScreenLayout();
19
27
 
20
28
  const goBack = useCallback(() => {
21
29
  const canGoBack = navigator?.ref?.current?.canGoBack?.();
@@ -72,6 +80,36 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
72
80
  return () => backHandler.remove();
73
81
  }, [goBackSafe]);
74
82
 
83
+ if (floating && sideBarStyle) {
84
+ return (
85
+ <Animated.View
86
+ style={[sideBarStyle, counterStyle]}
87
+ pointerEvents={'box-none'}
88
+ >
89
+ <SideBarButton
90
+ accessibilityLabel="btn_navigation_back"
91
+ icon="arrow-back"
92
+ tintColor={tintColor}
93
+ onPress={goBackSafe}
94
+ />
95
+ </Animated.View>
96
+ );
97
+ }
98
+
99
+ // on the status bar row the controls keep the side bar size
100
+ if (statusBarClearance) {
101
+ return (
102
+ <View style={styles.headerLeft}>
103
+ <SideBarButton
104
+ accessibilityLabel="btn_navigation_back"
105
+ icon="arrow-back"
106
+ tintColor={tintColor}
107
+ onPress={goBackSafe}
108
+ />
109
+ </View>
110
+ );
111
+ }
112
+
75
113
  return (
76
114
  <View style={styles.headerLeft}>
77
115
  <NavigationButton
@@ -13,16 +13,26 @@ import { PopupNotify } from '../../Popup';
13
13
  import { Tool, ToolGroup } from '../types';
14
14
  import { Icon } from '../../Icon';
15
15
  import { Text } from '../../Text';
16
+ import Animated from 'react-native-reanimated';
17
+ import { useScreenLayout, useSideBarItemStyle } from '../../Layout/utils';
18
+ import { useSideBarCounterStyle } from './DuoQuickReturnHeader';
19
+ import { SideBarButton, SideBarGroup, SideBarGroupItem } from './SideBarButton';
20
+ import { useDuoBarCompression } from '../BottomTab/DuoBarCompression';
21
+ import { DuoMetrics } from '../../Layout/utils';
16
22
 
17
23
  const DID_SYNC_NEW_HOME = 'did_sync_new_home';
24
+ const CLOSE_ICON = '16_navigation_close_circle';
25
+ const HOME_ICON = '16_basic_home';
18
26
 
19
27
  /**
20
28
  * main component for header right
21
29
  */
22
30
  const HeaderRight: React.FC<any> = props => {
23
31
  const { translate } = useContext(ApplicationContext);
24
- const { headerRight = {} } = props;
32
+ const { headerRight = {}, floating = false } = props;
25
33
  const { useOnBoarding = false, buttonOnBoarding, onPress } = headerRight;
34
+ const sideBarStyle = useSideBarItemStyle('right', 1);
35
+ const counterStyle = useSideBarCounterStyle();
26
36
 
27
37
  if (typeof props.headerRight === 'function') {
28
38
  return props.headerRight(props);
@@ -38,6 +48,21 @@ const HeaderRight: React.FC<any> = props => {
38
48
  );
39
49
  }
40
50
 
51
+ if (floating && sideBarStyle) {
52
+ return (
53
+ <Animated.View
54
+ style={[sideBarStyle, counterStyle]}
55
+ pointerEvents={'box-none'}
56
+ >
57
+ <HeaderToolkitAction
58
+ {...props.headerRight}
59
+ tintColor={props.tintColor}
60
+ vertical
61
+ />
62
+ </Animated.View>
63
+ );
64
+ }
65
+
41
66
  return (
42
67
  <View style={styles.headerRightButton}>
43
68
  <HeaderToolkitAction {...props.headerRight} tintColor={props.tintColor} />
@@ -56,9 +81,13 @@ const HeaderToolkitAction: React.FC<any> = ({
56
81
  useMore = false,
57
82
  tools = [],
58
83
  useCloseIcon = false,
84
+ vertical = false,
59
85
  }) => {
60
86
  const { navigator } = useContext(ApplicationContext);
61
87
  const context = useContext<any>(MiniAppContext);
88
+ const { toolbarCompressed, toolbarHidden, expandToolbar, setToolbarHeight } =
89
+ useDuoBarCompression();
90
+ const { statusBarClearance } = useScreenLayout();
62
91
 
63
92
  const [isFavorite, setIsFavorite] = useState<boolean>(false);
64
93
  const [isLoading, setIsLoading] = useState(false);
@@ -260,6 +289,111 @@ const HeaderToolkitAction: React.FC<any> = ({
260
289
  group?.items?.some?.(tool => tool?.showBadge),
261
290
  );
262
291
 
292
+ const useHelpCenter = context.appId !== 'vn.momo.helpcenter';
293
+ const closeIcon = useCloseIcon ? CLOSE_ICON : HOME_ICON;
294
+
295
+ const groupCount = useHelpCenter ? 2 : 1;
296
+ let capsuleHeight = DuoMetrics.controlSize;
297
+ if (groupCount > 1) {
298
+ capsuleHeight = groupCount * DuoMetrics.itemPitch;
299
+ }
300
+ // Back slot, the optional shortcut, then the help/home capsule
301
+ const fullToolbarHeight =
302
+ DuoMetrics.controlSize +
303
+ Spacing.M +
304
+ (useShortcut ? DuoMetrics.controlSize + Spacing.M : 0) +
305
+ capsuleHeight;
306
+
307
+ useEffect(() => {
308
+ if (vertical) {
309
+ setToolbarHeight?.(fullToolbarHeight);
310
+ }
311
+ }, [vertical, fullToolbarHeight, setToolbarHeight]);
312
+
313
+ // an open tab rail takes this slot for a moment
314
+ if (vertical && toolbarHidden) {
315
+ return null;
316
+ }
317
+
318
+ // HIG "Toolbar compressed": tools fold into one button; tapping it expands the
319
+ // toolbar and compresses the tab bar instead. Not the useMore icon, to tell them apart
320
+ if (vertical && toolbarCompressed) {
321
+ return (
322
+ <SideBarButton
323
+ accessibilityLabel={'btn_navigation_toolbar'}
324
+ icon={'navigation_menu'}
325
+ tintColor={tintColor}
326
+ showBadge={showBadge}
327
+ onPress={expandToolbar}
328
+ />
329
+ );
330
+ }
331
+
332
+ if (vertical) {
333
+ return (
334
+ <View style={styles.sideBarColumn}>
335
+ {useShortcut && (
336
+ <SideBarButton
337
+ disabled={isLoading}
338
+ icon={iconShortcut}
339
+ onPress={shortcutOnPress}
340
+ tintColor={tintColor}
341
+ showBadge={showBadge}
342
+ />
343
+ )}
344
+ <SideBarGroup tintColor={tintColor}>
345
+ {useHelpCenter && (
346
+ <SideBarGroupItem
347
+ accessibilityLabel={'btn_navigation_help_center'}
348
+ icon={'help_center'}
349
+ tintColor={tintColor}
350
+ onPress={onPressHelpCenter}
351
+ />
352
+ )}
353
+ <SideBarGroupItem
354
+ accessibilityLabel={'btn_navigation_close'}
355
+ icon={closeIcon}
356
+ tintColor={tintColor}
357
+ onPress={onClose}
358
+ />
359
+ </SideBarGroup>
360
+ </View>
361
+ );
362
+ }
363
+
364
+ // on the status bar row the controls keep the side bar size
365
+ if (statusBarClearance) {
366
+ return (
367
+ <View style={styles.sideBarRow}>
368
+ {useShortcut && (
369
+ <SideBarButton
370
+ disabled={isLoading}
371
+ icon={iconShortcut}
372
+ onPress={shortcutOnPress}
373
+ tintColor={tintColor}
374
+ showBadge={showBadge}
375
+ />
376
+ )}
377
+ <SideBarGroup tintColor={tintColor} horizontal>
378
+ {useHelpCenter && (
379
+ <SideBarGroupItem
380
+ accessibilityLabel={'btn_navigation_help_center'}
381
+ icon={'help_center'}
382
+ tintColor={tintColor}
383
+ onPress={onPressHelpCenter}
384
+ />
385
+ )}
386
+ <SideBarGroupItem
387
+ accessibilityLabel={'btn_navigation_close'}
388
+ icon={closeIcon}
389
+ tintColor={tintColor}
390
+ onPress={onClose}
391
+ />
392
+ </SideBarGroup>
393
+ </View>
394
+ );
395
+ }
396
+
263
397
  return (
264
398
  <View style={Styles.row}>
265
399
  {useShortcut && (
@@ -280,7 +414,7 @@ const HeaderToolkitAction: React.FC<any> = ({
280
414
  },
281
415
  ]}
282
416
  >
283
- {context.appId !== 'vn.momo.helpcenter' && (
417
+ {useHelpCenter && (
284
418
  <>
285
419
  <TouchableOpacity
286
420
  accessibilityLabel={'btn_navigation_help_center'}
@@ -299,13 +433,7 @@ const HeaderToolkitAction: React.FC<any> = ({
299
433
  style={styles.toolkitButton}
300
434
  hitSlop={{ top: 7, bottom: 7, left: 0, right: 7 }}
301
435
  >
302
- <Icon
303
- color={tintColor}
304
- source={
305
- useCloseIcon ? '16_navigation_close_circle' : '16_basic_home'
306
- }
307
- size={20}
308
- />
436
+ <Icon color={tintColor} source={closeIcon} size={20} />
309
437
  </TouchableOpacity>
310
438
  </View>
311
439
  </View>
@@ -376,6 +504,13 @@ const styles = StyleSheet.create({
376
504
  width: 0.5,
377
505
  height: 12,
378
506
  },
507
+ sideBarColumn: {
508
+ gap: Spacing.M,
509
+ },
510
+ sideBarRow: {
511
+ flexDirection: 'row',
512
+ gap: Spacing.M,
513
+ },
379
514
  headerLeft: {
380
515
  marginLeft: 12,
381
516
  },
@@ -21,12 +21,44 @@ import {
21
21
  isWhiteListDesignSystem,
22
22
  useNormalizedSharedValue,
23
23
  } from '../utils';
24
+ import { useScreenLayout } from '../../Layout';
24
25
 
25
26
  const useHeaderFontFamily = (): string => {
26
27
  const context = useContext<any>(MiniAppContext);
27
28
  return isWhiteListDesignSystem(context) ? 'MoMoTrustDisplay' : 'SFProText';
28
29
  };
29
30
 
31
+ /**
32
+ * back button and toolkit sharing the navigation bar with the title
33
+ */
34
+ const HEADER_ACTIONS_WIDTH = 172;
35
+
36
+ /**
37
+ * extra width the 48pt back and toolkit take on the iPhone Duo status bar row
38
+ */
39
+ const STATUS_BAR_ROW_ACTIONS_EXTRA = 55;
40
+
41
+ /**
42
+ * max title width, the iPhone Duo side bar takes back and toolkit out of the bar
43
+ */
44
+ const useTitleMaxWidth = () => {
45
+ const { width, contentWidth, sideBar, statusBarClearance } =
46
+ useScreenLayout();
47
+ let clearance = 0;
48
+ if (statusBarClearance) {
49
+ clearance = statusBarClearance.left + statusBarClearance.right;
50
+ }
51
+ if (sideBar) {
52
+ return contentWidth - Spacing.M * 2 - clearance;
53
+ }
54
+ return (
55
+ width -
56
+ HEADER_ACTIONS_WIDTH -
57
+ (statusBarClearance ? STATUS_BAR_ROW_ACTIONS_EXTRA : 0) -
58
+ clearance
59
+ );
60
+ };
61
+
30
62
  type HeaderTitleInterpolate = {
31
63
  inputRange: number[];
32
64
  outputRange: number[];
@@ -47,6 +79,7 @@ const HeaderTitle: React.FC<
47
79
  > = props => {
48
80
  const context = useContext<any>(MiniAppContext);
49
81
  const headerFontFamily = useHeaderFontFamily();
82
+ const maxWidth = useTitleMaxWidth();
50
83
 
51
84
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
52
85
 
@@ -77,7 +110,7 @@ const HeaderTitle: React.FC<
77
110
  style={[
78
111
  {
79
112
  justifyContent: 'center',
80
- maxWidth: Dimensions.get('window').width - 172,
113
+ maxWidth,
81
114
  },
82
115
  showBaseLineDebug && styles.debugBaseLine,
83
116
  ]}
@@ -141,7 +174,7 @@ const TitleUser: React.FC<TitleUserProps> = ({
141
174
  }) => {
142
175
  const { theme } = useContext(ApplicationContext);
143
176
  const headerFontFamily = useHeaderFontFamily();
144
- const maxWidth = Dimensions.get('window').width - 172;
177
+ const maxWidth = useTitleMaxWidth();
145
178
  const getShortName = (name: string) => {
146
179
  const words = name.split(' ');
147
180
  const lastTwoWords = words.slice(-2);
@@ -424,7 +457,7 @@ const TitleLocation: React.FC<TitleLocationProps> = ({
424
457
  isLoading,
425
458
  }) => {
426
459
  const headerFontFamily = useHeaderFontFamily();
427
- const maxWidth = Dimensions.get('window').width - 172;
460
+ const maxWidth = useTitleMaxWidth();
428
461
  const renderShimmer = () => {
429
462
  return (
430
463
  <View style={Styles.flex}>
@@ -493,10 +526,13 @@ const TitleJourney: React.FC<TitleJourneyProps> = ({
493
526
  isLoading,
494
527
  }) => {
495
528
  const headerFontFamily = useHeaderFontFamily();
496
- const { width } = Dimensions.get('screen');
497
- const maxWidth = Dimensions.get('window').width - 172;
498
- const startWidth = width * 0.25;
499
- const endWidth = width * 0.75 - 212;
529
+ const maxWidth = useTitleMaxWidth();
530
+ const { sideBar } = useScreenLayout();
531
+ // journey split is tuned against the whole bar width
532
+ const screenWidth = Dimensions.get('screen').width;
533
+ const barWidth = sideBar ? maxWidth + HEADER_ACTIONS_WIDTH : screenWidth;
534
+ const startWidth = barWidth * 0.25;
535
+ const endWidth = barWidth * 0.75 - 212;
500
536
  const renderShimmer = () => {
501
537
  return (
502
538
  <View style={Styles.flex}>
@@ -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,29 @@ 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);
46
+ const { background } = theme.colors;
38
47
  return {
39
- backgroundColor: t === 0
40
- ? theme.colors.background.surface
41
- : theme.colors.background.default,
48
+ backgroundColor: t === 0 ? background.surface : background.default,
42
49
  };
43
50
  });
44
51
 
@@ -77,13 +84,11 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
77
84
  style={[
78
85
  Styles.flex,
79
86
  renderButtons && {
80
- maxWidth: screenWidth - leftPosition - 12 - headerRightWidth,
87
+ maxWidth: searchMaxWidth,
81
88
  },
82
89
  ]}
83
90
  >
84
- <Animated.View
85
- style={[{ borderRadius: Radius.XL }, backgroundStyle]}
86
- >
91
+ <Animated.View style={[{ borderRadius: Radius.XL }, backgroundStyle]}>
87
92
  <InputSearch
88
93
  ref={ref}
89
94
  {...props}