@momo-kits/foundation 0.169.6-beta.1 → 0.169.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.
Files changed (128) hide show
  1. package/Application/BottomSheet.hooks.ts +8 -8
  2. package/Application/BottomSheet.parts.tsx +44 -34
  3. package/Application/BottomSheet.tsx +35 -26
  4. package/Application/BottomTab/ActiveIndicator.tsx +15 -14
  5. package/Application/BottomTab/BottomTabBar.styles.ts +1 -18
  6. package/Application/BottomTab/BottomTabBar.tsx +58 -57
  7. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  8. package/Application/BottomTab/CurvedContainer.tsx +16 -20
  9. package/Application/BottomTab/CustomBottomTabItem.tsx +14 -15
  10. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  11. package/Application/BottomTab/DuoRailTabBar.tsx +20 -20
  12. package/Application/BottomTab/DuoTabBar.tsx +11 -24
  13. package/Application/BottomTab/TabBarIcon.tsx +15 -29
  14. package/Application/BottomTab/TabScreen.tsx +1 -5
  15. package/Application/BottomTab/index.tsx +33 -30
  16. package/Application/BottomTab/metrics.ts +25 -48
  17. package/Application/BottomTab/tabItems.ts +1 -1
  18. package/Application/BottomTab/usePressLift.ts +2 -2
  19. package/Application/Components/BackgroundImageView.tsx +4 -0
  20. package/Application/Components/DuoGlassBackground.tsx +5 -4
  21. package/Application/Components/HeaderAnimated.tsx +4 -0
  22. package/Application/Components/HeaderBackground.tsx +4 -0
  23. package/Application/Components/HeaderExtendHeader.tsx +11 -10
  24. package/Application/Components/HeaderLeft.tsx +2 -1
  25. package/Application/Components/HeaderRight.tsx +2 -1
  26. package/Application/Components/HeaderTitle.shared.ts +11 -9
  27. package/Application/Components/HeaderTitle.tsx +4 -3
  28. package/Application/Components/HeaderToolkitAction.tsx +17 -20
  29. package/Application/Components/NavigationButton.tsx +2 -1
  30. package/Application/Components/SearchHeader.tsx +17 -10
  31. package/Application/Components/TitleJourney.tsx +2 -2
  32. package/Application/Components/TitleLocation.tsx +2 -2
  33. package/Application/Components/TitleUser.tsx +2 -2
  34. package/Application/Components/useToolkitActions.tsx +17 -32
  35. package/Application/Localize.ts +7 -0
  36. package/Application/ModalScreen.tsx +9 -3
  37. package/Application/Navigation.ts +14 -16
  38. package/Application/NavigationContainer.tsx +19 -16
  39. package/Application/Navigator.ts +20 -48
  40. package/Application/ScaleSizeProvider.tsx +13 -6
  41. package/Application/StackScreen.tracking.ts +11 -26
  42. package/Application/StackScreen.tsx +8 -35
  43. package/Application/TooltipPortal.tsx +12 -5
  44. package/Application/WidgetContainer.tsx +10 -4
  45. package/Application/modalLayout.ts +13 -14
  46. package/Application/navigationOptions.tsx +21 -17
  47. package/Application/types.tabs.ts +1 -0
  48. package/Badge/Badge.tsx +5 -0
  49. package/Badge/BadgeDot.tsx +1 -0
  50. package/Badge/BadgeDotAnimation.tsx +15 -7
  51. package/Badge/BadgeRibbon.tsx +46 -38
  52. package/Badge/Shape.tsx +2 -0
  53. package/Badge/types.ts +2 -2
  54. package/Button/index.tsx +5 -16
  55. package/Button/utils.ts +9 -15
  56. package/CheckBox/index.tsx +4 -0
  57. package/Consts/theme.ts +10 -10
  58. package/Context/index.ts +5 -1
  59. package/Divider/DashDivider.tsx +1 -0
  60. package/Divider/index.tsx +4 -0
  61. package/FoundationList/index.tsx +4 -0
  62. package/Icon/index.tsx +6 -6
  63. package/Icon/types.ts +1 -1
  64. package/IconButton/index.tsx +4 -12
  65. package/Image/index.tsx +9 -7
  66. package/Input/Input.tsx +8 -9
  67. package/Input/InputDropDown.tsx +10 -12
  68. package/Input/InputMoney.tsx +12 -8
  69. package/Input/InputMoneyView.tsx +14 -7
  70. package/Input/InputOTP.tsx +35 -33
  71. package/Input/InputPhoneNumber.tsx +8 -9
  72. package/Input/InputSearch.tsx +4 -6
  73. package/Input/InputTextArea.tsx +4 -0
  74. package/Input/SearchTextTyping.tsx +4 -0
  75. package/Input/SystemTextInput.tsx +7 -2
  76. package/Input/TextTyping.tsx +4 -0
  77. package/Input/common.tsx +26 -12
  78. package/Input/index.tsx +17 -20
  79. package/Input/moneyUtils.ts +10 -4
  80. package/Input/styles.ts +0 -5
  81. package/Input/utils.ts +14 -12
  82. package/Layout/Card.tsx +7 -4
  83. package/Layout/FloatingButton.tsx +4 -0
  84. package/Layout/Item.tsx +6 -2
  85. package/Layout/ItemList.tsx +9 -7
  86. package/Layout/ItemSectionList.tsx +2 -1
  87. package/Layout/ReservedRegions.tsx +25 -25
  88. package/Layout/Screen.tsx +56 -58
  89. package/Layout/Section.tsx +8 -5
  90. package/Layout/TrackingScope.tsx +1 -0
  91. package/Layout/screenContent.tsx +4 -2
  92. package/Layout/screenFooter.tsx +28 -18
  93. package/Layout/screenHeader.tsx +34 -31
  94. package/Layout/screenQuickReturn.tsx +17 -20
  95. package/Layout/screenScroll.ts +4 -7
  96. package/Layout/screenTypes.ts +3 -2
  97. package/Layout/types.ts +2 -4
  98. package/Layout/utils.ts +31 -37
  99. package/Loader/DotLoader.tsx +4 -0
  100. package/Loader/Loader.tsx +4 -0
  101. package/Loader/ProgressBar.tsx +1 -0
  102. package/Loader/Spinner.tsx +4 -0
  103. package/Loader/utils.ts +11 -7
  104. package/Pagination/Dot.tsx +4 -0
  105. package/Pagination/PaginationDot.tsx +1 -0
  106. package/Pagination/PaginationNumber.tsx +5 -3
  107. package/Pagination/PaginationScroll.tsx +5 -0
  108. package/Pagination/PaginationWhiteDot.tsx +1 -0
  109. package/Pagination/index.tsx +4 -0
  110. package/Pagination/types.ts +2 -2
  111. package/Popup/PopupNotify.tsx +44 -48
  112. package/Popup/PopupPromotion.tsx +4 -7
  113. package/Popup/types.ts +4 -4
  114. package/Popup/utils.ts +4 -0
  115. package/Radio/index.tsx +4 -0
  116. package/Skeleton/index.tsx +8 -0
  117. package/Switch/index.tsx +1 -0
  118. package/Tag/index.tsx +5 -1
  119. package/Tag/types.ts +3 -3
  120. package/Text/index.tsx +14 -3
  121. package/Text/styles.ts +1 -1
  122. package/Text/types.ts +1 -6
  123. package/Text/utils.ts +20 -4
  124. package/Title/index.tsx +41 -32
  125. package/ViewBoundary/index.tsx +4 -2
  126. package/index.ts +0 -6
  127. package/package.json +1 -1
  128. package/Application/__tests__/modalLayout.test.ts +0 -128
@@ -3,47 +3,31 @@ import { Easing } from 'react-native-reanimated';
3
3
  import { Colors, Radius, Spacing } from '../../Consts';
4
4
 
5
5
  /**
6
- * Navigation bar measurements (Figma "Specs Navigation", DS-984), kept in sync with
7
- * momo-native-kits `TabBarMetrics`
8
- * - horizontal: floating container with items split evenly, center action in a notch
9
- * - vertical: iPhone Duo rail, center action under the container
6
+ * Navigation bar measurements (Figma "Specs Navigation", DS-984), in sync with momo-native-kits
7
+ * `TabBarMetrics`: the horizontal floating bar (center action in a notch) and the vertical
8
+ * iPhone Duo rail (center action under the container).
10
9
  */
11
10
  const TabBarMetrics = {
12
- height: 60,
11
+ height: 68,
13
12
  margin: Spacing.S,
14
- paddingVertical: Spacing.XS,
13
+ paddingVertical: Spacing.S,
15
14
  paddingHorizontal: Spacing.M,
16
- itemGap: 1,
17
- // gap between the icon slot and the label inside a tab (Figma: none)
18
- itemIconLabelGap: 0,
19
- // .Nav/Background: no backdrop blur, so use the Figma Blur=Off spec (opacity 95%, no blur) plus the 1px
20
- // border — nearly solid, matching the frosted sample; surface keeps dark-mode support
21
- backgroundOpacity: 0.95,
22
- // white 60% hairline around the center action button (Figma Icon Button border)
23
- ctaBorderOpacity: 0.6,
15
+ itemGap: Spacing.XS,
24
16
  iconSize: 24,
25
17
  // active indicator behind the icon: 24 icon + XS·M padding
26
18
  indicatorPaddingVertical: Spacing.XS,
27
19
  indicatorPaddingHorizontal: Spacing.M,
28
20
  indicatorRadius: Radius.XL,
29
21
  indicatorHeight: 32,
30
- // label_s_medium line height: the label box keeps this height so a scaled label grows
31
- // downward instead of pushing the icon up
22
+ labelMaxScale: 1.2,
32
23
  labelHeight: 18,
33
- // home indicator / gesture bar area under the container
24
+ railLabelHeight: 14,
34
25
  systemIndicatorHeight: 24,
35
- // unfolded foldables (short side from 600, the smallest in the spec) use 65% of the short
36
- // side, centered; phones and folded outer displays fill the window
37
26
  foldableMinShortSide: 600,
38
27
  foldableWidthRatio: 0.65,
39
- // a 2-tab bar doesn't stretch to the full width: each item is this fixed width and the bar is
40
- // sized to its content, centered (Figma Navigation · 2 Tab: item 100, bar ~225)
41
- twoTabItemWidth: 100,
42
28
  ctaSlotWidth: 92,
43
29
  ctaSize: 48,
44
- // the icon sits 10 inside the 48 button (prototype .cta: 28 icon), 12 inside the 52 rail one
45
30
  ctaIconSize: 28,
46
- // the button rises 22 above the container top edge
47
31
  ctaLift: 22,
48
32
  ctaLabelGap: Spacing.M,
49
33
  ctaLabelHeight: 18,
@@ -64,10 +48,9 @@ const TabBarMetrics = {
64
48
  };
65
49
 
66
50
  /**
67
- * motion of the navigation bar (MoMo Navigation Prototype):
68
- * - one active indicator slides to the selected item, overshooting a little
69
- * - icons cross-fade and the label color follows
70
- * - the center action shrinks while pressed
51
+ * Navigation bar motion (MoMo Navigation Prototype): the active indicator slides to the
52
+ * selected item with a little overshoot, icon and label tints cross-fade, and the center
53
+ * action shrinks while pressed.
71
54
  */
72
55
  const TabBarMotion = {
73
56
  indicator: { duration: 550, easing: Easing.bezier(0.34, 1.4, 0.5, 1) },
@@ -98,7 +81,7 @@ const CTA_SHADOW: BoxShadowValue[] = [
98
81
  ];
99
82
 
100
83
  /**
101
- * inner light and tint of the active indicator: white 12% from below, primary 12% from above
84
+ * Inner light and tint of the active indicator: white 12% from below, primary 12% from above.
102
85
  */
103
86
  const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
104
87
  {
@@ -112,9 +95,9 @@ const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
112
95
  ];
113
96
 
114
97
  /**
115
- * notch dipped into the container top edge under the center action (Figma "Backgroud" path):
98
+ * Notch dipped into the container top edge under the center action (Figma "Backgroud" path):
116
99
  * starts NOTCH_HALF_WIDTH left of the center, then cubic segments as [control1, control2, end],
117
- * each point [dx from the center, dy below the top edge]
100
+ * each point [dx from the center, dy below the top edge].
118
101
  */
119
102
  const NOTCH_HALF_WIDTH = 50.758;
120
103
  const NOTCH_CURVES: [number, number][][] = [
@@ -141,25 +124,15 @@ const NOTCH_CURVES: [number, number][][] = [
141
124
  ];
142
125
 
143
126
  /**
144
- * width of the horizontal bar: 65% of the short side on an unfolded foldable, otherwise the
145
- * window minus the side safe areas and margins
127
+ * Width of the horizontal bar: 65% of the short side on an unfolded foldable, otherwise the
128
+ * window minus the side safe areas and margins.
146
129
  */
147
130
  const getHorizontalBarWidth = (
148
131
  width: number,
149
132
  height: number,
150
133
  insetLeft: number,
151
134
  insetRight: number,
152
- itemCount: number,
153
- hasCta: boolean,
154
135
  ) => {
155
- // 2 tabs (no center action) stay content-sized and centered instead of filling the window
156
- if (itemCount === 2 && !hasCta) {
157
- return (
158
- TabBarMetrics.twoTabItemWidth * 2 +
159
- TabBarMetrics.paddingHorizontal * 2 +
160
- TabBarMetrics.itemGap
161
- );
162
- }
163
136
  const shortSide = Math.min(width, height);
164
137
  if (shortSide >= TabBarMetrics.foldableMinShortSide) {
165
138
  return shortSide * TabBarMetrics.foldableWidthRatio;
@@ -168,12 +141,16 @@ const getHorizontalBarWidth = (
168
141
  };
169
142
 
170
143
  /**
171
- * height of the vertical rail container, plus the center action under it
144
+ * Height of the vertical rail container, plus the center action under it.
172
145
  */
173
- const getRailHeight = (tabCount: number, hasCta: boolean) =>
174
- tabCount * TabBarMetrics.railItemHeight +
175
- TabBarMetrics.railPaddingBottom +
176
- (hasCta ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
146
+ const getRailHeight = (tabCount: number, hasCta: boolean) => {
147
+ const itemsHeight = tabCount * TabBarMetrics.railItemHeight;
148
+ let ctaHeight = 0;
149
+ if (hasCta) {
150
+ ctaHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
151
+ }
152
+ return itemsHeight + TabBarMetrics.railPaddingBottom + ctaHeight;
153
+ };
177
154
 
178
155
  export {
179
156
  TabBarMetrics,
@@ -2,7 +2,7 @@ import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
2
2
  import { CommonActions, Route } from '@react-navigation/native';
3
3
 
4
4
  /**
5
- * navigation items of the tab bar, with the press handlers of the system tab bar
5
+ * Navigation items of the tab bar, with the press handlers of the system tab bar.
6
6
  */
7
7
  const getTabItems = ({ state, descriptors, navigation }: BottomTabBarProps) => {
8
8
  return state.routes.map((route: Route<string>, index: number) => {
@@ -6,11 +6,11 @@ import {
6
6
  } from 'react-native-reanimated';
7
7
 
8
8
  const SPRING = { damping: 18, stiffness: 220, mass: 0.8 };
9
- // the button grows by a tenth while held
10
9
  const PRESSED_SCALE = 1.1;
11
10
 
12
11
  /**
13
- * lift for a single button, e.g. the compressed rail
12
+ * Press lift for a single button (e.g. the compressed rail): it scales up while held,
13
+ * unless the OS asks for reduced motion.
14
14
  */
15
15
  const usePressLift = () => {
16
16
  const reduceMotion = useReducedMotion();
@@ -17,6 +17,10 @@ type BackgroundImageViewProps = {
17
17
  animatedValue: SharedValue<number>;
18
18
  };
19
19
 
20
+ /**
21
+ * Header surface (shadow or divider) and image, fading in over the first 52pt of
22
+ * scroll. iOS keeps the surface out of the clipped view so its shadow isn't cut.
23
+ */
20
24
  const BackgroundImageView: FC<BackgroundImageViewProps> = ({
21
25
  useShadowHeader = true,
22
26
  headerBackground = null,
@@ -30,6 +30,7 @@ const DuoGlassBackground: React.FC<{
30
30
  const { headerHeight } = useScreenLayout();
31
31
  const sharedValue = useNormalizedSharedValue(animatedValue);
32
32
  const fadeStart = Math.max(bannerBottom - headerHeight, 0);
33
+ const translucentColor = theme.colors.background.surface + TRANSLUCENT_ALPHA;
33
34
 
34
35
  const fadeStyle = useAnimatedStyle(() => ({
35
36
  opacity: interpolate(
@@ -47,8 +48,7 @@ const DuoGlassBackground: React.FC<{
47
48
  StyleSheet.absoluteFill,
48
49
  styles.translucentBar,
49
50
  {
50
- backgroundColor:
51
- theme.colors.background.surface + TRANSLUCENT_ALPHA,
51
+ backgroundColor: translucentColor,
52
52
  borderBottomColor: theme.colors.border.default,
53
53
  },
54
54
  fadeStyle,
@@ -59,8 +59,9 @@ const DuoGlassBackground: React.FC<{
59
59
  };
60
60
 
61
61
  /**
62
- * pink header gradient drawn under the content, fading out on scroll as main's extended
63
- * header does: header-high for default headers, 52 taller (collapsing) for extended
62
+ * Header gradient (and theme image) drawn under the content, fading out on scroll:
63
+ * header-high, or 52pt taller and collapsing when `extended`. `followScroll`
64
+ * scrolls it away with the content instead of fading it.
64
65
  */
65
66
  const DuoScrollGradient: React.FC<{
66
67
  animatedValue?: AnimatedCompatValue;
@@ -19,6 +19,10 @@ const ASPECT_RATIOS: Record<
19
19
  '1/1': 1 / 1,
20
20
  };
21
21
 
22
+ /**
23
+ * Parallax header image (or `children`): stretches when pulled down and fades out
24
+ * as the content scrolls up.
25
+ */
22
26
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
23
27
  animatedValue,
24
28
  image,
@@ -9,6 +9,10 @@ import { Image } from '../../Image';
9
9
  import BackgroundImageView from './BackgroundImageView';
10
10
  import { useNormalizedSharedValue } from '../hooks';
11
11
 
12
+ /**
13
+ * Header background: surface and image that fade in on scroll, with an optional
14
+ * gradient (custom or theme) on top. Outlined when `showBaseLineDebug` is on.
15
+ */
12
16
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
13
17
  animatedValue,
14
18
  useGradient = true,
@@ -23,6 +23,10 @@ const SCREEN_PADDING = 12;
23
23
  const BACK_WIDTH = 28;
24
24
  const TOOLKIT_WIDTH = 73;
25
25
 
26
+ /**
27
+ * Extended header background: a gradient that collapses on scroll and, with
28
+ * `inputSearchProps`, a full-width search that slides into the bar beside back/toolkit.
29
+ */
26
30
  const HeaderExtendHeader: React.FC<{
27
31
  headerType?: HeaderType;
28
32
  animatedValue: AnimatedCompatValue;
@@ -64,18 +68,15 @@ const HeaderExtendHeader: React.FC<{
64
68
  const contentLeft = layout.insetLeft + SCREEN_PADDING;
65
69
  const contentRight = layout.insetLeft + layout.contentWidth;
66
70
  // collapsed on the status bar row, the search stays between its clusters
67
- const collapsedLeft =
68
- layout.insetLeft + leftPosition + (layout.statusBarClearance?.left ?? 0);
69
- const collapsedRight = contentRight - (layout.statusBarClearance?.right ?? 0);
71
+ const clearanceLeft = layout.statusBarClearance?.left ?? 0;
72
+ const clearanceRight = layout.statusBarClearance?.right ?? 0;
73
+ const collapsedLeft = layout.insetLeft + leftPosition + clearanceLeft;
74
+ const collapsedRight = contentRight - clearanceRight - headerRightWidth;
70
75
  const searchWidth = layout.contentWidth - SCREEN_PADDING * 2;
71
- const collapsedSearchWidth =
72
- collapsedRight - collapsedLeft - SCREEN_PADDING - headerRightWidth;
76
+ const collapsedSearchWidth = collapsedRight - collapsedLeft - SCREEN_PADDING;
73
77
  // a row taller than barHeight (iPhone Duo) centers the collapsed search on the title
74
- const collapsedRaise =
75
- (layout.headerHeight -
76
- layout.headerStatusBarHeight -
77
- HeaderMetrics.barHeight) /
78
- 2;
78
+ const titleRowHeight = layout.headerHeight - layout.headerStatusBarHeight;
79
+ const collapsedRaise = (titleRowHeight - HeaderMetrics.barHeight) / 2;
79
80
 
80
81
  let useShadowHeader = useShadowHeaderProp;
81
82
  if (inputSearchProps && Platform.OS === 'android') {
@@ -10,7 +10,8 @@ import { useScreenLayout, useSideBarItemStyle } from '../../Layout/utils';
10
10
  import { useSideBarCounterStyle } from './DuoQuickReturnHeader';
11
11
 
12
12
  /**
13
- * default header left used for nav
13
+ * Header back button, also bound to the Android hardware back. `preventBack`
14
+ * confirms in a popup first; the iPhone Duo layouts use the 48pt side bar button.
14
15
  */
15
16
  const HeaderLeft: React.FC<HeaderBackProps> = ({
16
17
  tintColor,
@@ -9,7 +9,8 @@ import { HeaderToolkitAction } from './HeaderToolkitAction';
9
9
  import { styles } from './HeaderRight.styles';
10
10
 
11
11
  /**
12
- * main component for header right
12
+ * Header right slot: a custom `headerRight` render function, the onboarding skip
13
+ * button, or the toolkit (stacked vertically in the floating iPhone Duo side bar).
13
14
  */
14
15
  const HeaderRight: React.FC<any> = props => {
15
16
  const { translate } = useContext(ApplicationContext);
@@ -4,6 +4,10 @@ import { Spacing } from '../../Consts';
4
4
  import { isWhiteListDesignSystem } from '../utils';
5
5
  import { useScreenLayout } from '../../Layout';
6
6
 
7
+ /**
8
+ * Header title font: MoMoTrustDisplay for design-system whitelisted mini apps,
9
+ * SFProText otherwise.
10
+ */
7
11
  const useHeaderFontFamily = (): string => {
8
12
  const context = useContext<any>(MiniAppContext);
9
13
  return isWhiteListDesignSystem(context) ? 'MoMoTrustDisplay' : 'SFProText';
@@ -20,24 +24,22 @@ const HEADER_ACTIONS_WIDTH = 172;
20
24
  const STATUS_BAR_ROW_ACTIONS_EXTRA = 55;
21
25
 
22
26
  /**
23
- * max title width, the iPhone Duo side bar takes back and toolkit out of the bar
27
+ * Max header title width. The iPhone Duo side bar takes back and toolkit out of
28
+ * the bar, so the title gets the content width; otherwise it shares the bar with them.
24
29
  */
25
30
  const useTitleMaxWidth = () => {
26
- const { width, contentWidth, sideBar, statusBarClearance } =
27
- useScreenLayout();
31
+ const layout = useScreenLayout();
32
+ const { width, contentWidth, sideBar, statusBarClearance } = layout;
28
33
  let clearance = 0;
34
+ let rowActionsExtra = 0;
29
35
  if (statusBarClearance) {
30
36
  clearance = statusBarClearance.left + statusBarClearance.right;
37
+ rowActionsExtra = STATUS_BAR_ROW_ACTIONS_EXTRA;
31
38
  }
32
39
  if (sideBar) {
33
40
  return contentWidth - Spacing.M * 2 - clearance;
34
41
  }
35
- return (
36
- width -
37
- HEADER_ACTIONS_WIDTH -
38
- (statusBarClearance ? STATUS_BAR_ROW_ACTIONS_EXTRA : 0) -
39
- clearance
40
- );
42
+ return width - HEADER_ACTIONS_WIDTH - rowActionsExtra - clearance;
41
43
  };
42
44
 
43
45
  export { useHeaderFontFamily, HEADER_ACTIONS_WIDTH, useTitleMaxWidth };
@@ -33,7 +33,8 @@ type HeaderTitleExtraProps = {
33
33
  };
34
34
 
35
35
  /**
36
- * default header title used for nav
36
+ * Default navigation header title. With `animatedValue` its opacity follows the
37
+ * scroll through `interpolate` (0 to 1 over the first 200pt by default).
37
38
  */
38
39
  const HeaderTitle: React.FC<
39
40
  HeaderTitleExtraProps & { [key: string]: any }
@@ -100,8 +101,8 @@ const HeaderTitle: React.FC<
100
101
  };
101
102
 
102
103
  /**
103
- * Title custom for header
104
- * @constructor
104
+ * Header title picked by `type` (user, location or journey) with `data` as its
105
+ * props; an empty view for an unknown type.
105
106
  */
106
107
  const TitleCustom: React.FC<any> = ({ type, data, ...props }) => {
107
108
  switch (type) {
@@ -14,7 +14,8 @@ const CLOSE_ICON = '16_navigation_close_circle';
14
14
  const HOME_ICON = '16_basic_home';
15
15
 
16
16
  /**
17
- * Header toolkit action
17
+ * Header toolkit: optional shortcut plus the help center / close capsule. When
18
+ * `vertical` (iPhone Duo side bar) it reports its height and may fold into one button.
18
19
  */
19
20
  const HeaderToolkitAction: React.FC<any> = ({
20
21
  tintColor,
@@ -27,8 +28,9 @@ const HeaderToolkitAction: React.FC<any> = ({
27
28
  vertical = false,
28
29
  }) => {
29
30
  const context = useContext<any>(MiniAppContext);
30
- const { toolbarCompressed, toolbarHidden, expandToolbar, setToolbarHeight } =
31
- useDuoBarCompression();
31
+ const compression = useDuoBarCompression();
32
+ const { toolbarCompressed, toolbarHidden } = compression;
33
+ const { expandToolbar, setToolbarHeight } = compression;
32
34
  const { statusBarClearance } = useScreenLayout();
33
35
  const {
34
36
  isLoading,
@@ -56,18 +58,22 @@ const HeaderToolkitAction: React.FC<any> = ({
56
58
 
57
59
  const useHelpCenter = context.appId !== 'vn.momo.helpcenter';
58
60
  const closeIcon = useCloseIcon ? CLOSE_ICON : HOME_ICON;
61
+ let toolkitWidthStyle: ViewStyle = styles.toolkitNarrow;
62
+ if (useHelpCenter) {
63
+ toolkitWidthStyle = styles.toolkitWide;
64
+ }
59
65
 
60
66
  const groupCount = useHelpCenter ? 2 : 1;
61
67
  let capsuleHeight = DuoMetrics.controlSize;
62
68
  if (groupCount > 1) {
63
69
  capsuleHeight = groupCount * DuoMetrics.itemPitch;
64
70
  }
65
- // Back slot, the optional shortcut, then the help/home capsule
66
- const fullToolbarHeight =
67
- DuoMetrics.controlSize +
68
- Spacing.M +
69
- (useShortcut ? DuoMetrics.controlSize + Spacing.M : 0) +
70
- capsuleHeight;
71
+ const backSlotHeight = DuoMetrics.controlSize + Spacing.M;
72
+ let shortcutHeight = 0;
73
+ if (useShortcut) {
74
+ shortcutHeight = DuoMetrics.controlSize + Spacing.M;
75
+ }
76
+ const fullToolbarHeight = backSlotHeight + shortcutHeight + capsuleHeight;
71
77
 
72
78
  useEffect(() => {
73
79
  if (vertical) {
@@ -80,8 +86,7 @@ const HeaderToolkitAction: React.FC<any> = ({
80
86
  return null;
81
87
  }
82
88
 
83
- // HIG "Toolbar compressed": tools fold into one button; tapping it expands the
84
- // toolbar and compresses the tab bar instead. Not the useMore icon, to tell them apart
89
+ // HIG "Toolbar compressed": a menu icon (not the "more" one) that expands the toolbar
85
90
  if (vertical && toolbarCompressed) {
86
91
  return (
87
92
  <SideBarButton
@@ -170,15 +175,7 @@ const HeaderToolkitAction: React.FC<any> = ({
170
175
  showBadge={showBadge}
171
176
  />
172
177
  )}
173
- <View
174
- style={[
175
- styles.toolkitContainer,
176
- buttonStyle,
177
- context.appId !== 'vn.momo.helpcenter'
178
- ? styles.toolkitWide
179
- : styles.toolkitNarrow,
180
- ]}
181
- >
178
+ <View style={[styles.toolkitContainer, buttonStyle, toolkitWidthStyle]}>
182
179
  {useHelpCenter && (
183
180
  <>
184
181
  <TouchableOpacity
@@ -7,7 +7,8 @@ import { Icon } from '../../Icon';
7
7
  import { BadgeDot } from '../../Badge';
8
8
 
9
9
  /**
10
- * default navigation button used header nav
10
+ * Round 28pt icon button of the header (back, shortcut) with an optional badge
11
+ * dot; switches to a dark fill under a white tint so the icon stays readable.
11
12
  */
12
13
  const NavigationButton: React.FC<NavigationButtonProps> = ({
13
14
  icon,
@@ -10,6 +10,10 @@ import { useNormalizedSharedValue } from '../hooks';
10
10
  import { useScreenLayout } from '../../Layout';
11
11
  import { DuoMetrics } from '../../Layout/utils';
12
12
 
13
+ /**
14
+ * Header search bar whose fill turns from surface to default once scrolled, with
15
+ * a cancel button that goes back (or dismisses the mini app) unless `renderButtons`.
16
+ */
13
17
  const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
14
18
  (
15
19
  {
@@ -36,9 +40,9 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
36
40
  if (layout.sideBar) {
37
41
  searchMaxWidth = layout.contentWidth - Spacing.M * 2 - headerRightWidth;
38
42
  }
39
- if (layout.statusBarClearance) {
40
- searchMaxWidth -=
41
- layout.statusBarClearance.left + layout.statusBarClearance.right;
43
+ const clearance = layout.statusBarClearance;
44
+ if (clearance) {
45
+ searchMaxWidth -= clearance.left + clearance.right;
42
46
  }
43
47
 
44
48
  const backgroundStyle = useAnimatedStyle(() => {
@@ -76,6 +80,15 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
76
80
 
77
81
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
78
82
 
83
+ let buttons: React.ReactNode = (
84
+ <TouchableOpacity onPress={goBackSafe} style={styles.searchAction}>
85
+ <Text typography={'action_default_bold'}>{buttonText}</Text>
86
+ </TouchableOpacity>
87
+ );
88
+ if (typeof renderButtons === 'function') {
89
+ buttons = renderButtons();
90
+ }
91
+
79
92
  return (
80
93
  <View
81
94
  style={[styles.container, showBaseLineDebug && styles.debugBaseLine]}
@@ -97,13 +110,7 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
97
110
  />
98
111
  </Animated.View>
99
112
  </View>
100
- {renderButtons && typeof renderButtons === 'function' ? (
101
- renderButtons()
102
- ) : (
103
- <TouchableOpacity onPress={goBackSafe} style={styles.searchAction}>
104
- <Text typography={'action_default_bold'}>{buttonText}</Text>
105
- </TouchableOpacity>
106
- )}
113
+ {buttons}
107
114
  </View>
108
115
  );
109
116
  },
@@ -14,8 +14,8 @@ import {
14
14
  } from './HeaderTitle.shared';
15
15
 
16
16
  /**
17
- * Header user title
18
- * @constructor
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 user title
13
- * @constructor
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 title
14
- * @constructor
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
- * state and handlers behind the header toolkit buttons
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
- * check app is favorite
36
- */
37
- const checkAppIsFavorite = useCallback(
38
- () =>
39
- navigator?.maxApi?.dispatchFunction?.(
40
- 'isFavoriteApp',
41
- { code: context?.code },
42
- (result: boolean) => {
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 singleTool = tools.find(
187
+ const singleToolGroup = tools.find(
204
188
  (group: ToolGroup) => group?.items?.length === 1,
205
- )?.items[0];
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,