@momo-kits/foundation 0.168.1-beta.37 → 0.168.1-beta.39

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 (131) 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.tsx +49 -62
  6. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  7. package/Application/BottomTab/CurvedContainer.tsx +14 -12
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  10. package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
  11. package/Application/BottomTab/DuoTabBar.tsx +8 -10
  12. package/Application/BottomTab/TabBarIcon.tsx +15 -29
  13. package/Application/BottomTab/TabScreen.tsx +1 -5
  14. package/Application/BottomTab/index.tsx +33 -30
  15. package/Application/BottomTab/metrics.ts +23 -23
  16. package/Application/BottomTab/tabItems.ts +1 -1
  17. package/Application/BottomTab/usePressLift.ts +2 -2
  18. package/Application/Components/BackgroundImageView.tsx +4 -0
  19. package/Application/Components/DuoGlassBackground.tsx +5 -4
  20. package/Application/Components/HeaderAnimated.tsx +40 -12
  21. package/Application/Components/HeaderBackground.tsx +19 -2
  22. package/Application/Components/HeaderExtendHeader.tsx +51 -37
  23. package/Application/Components/HeaderLeft.tsx +5 -18
  24. package/Application/Components/HeaderRight.styles.ts +15 -13
  25. package/Application/Components/HeaderRight.tsx +2 -1
  26. package/Application/Components/HeaderTitle.shared.ts +17 -16
  27. package/Application/Components/HeaderTitle.tsx +4 -3
  28. package/Application/Components/HeaderToolkitAction.tsx +29 -57
  29. package/Application/Components/NavigationButton.tsx +10 -6
  30. package/Application/Components/SearchHeader.tsx +37 -17
  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 +17 -35
  43. package/Application/TooltipPortal.tsx +12 -5
  44. package/Application/WidgetContainer.tsx +10 -4
  45. package/Application/headerLayout.ts +158 -0
  46. package/Application/index.ts +11 -1
  47. package/Application/modalLayout.ts +13 -14
  48. package/Application/navigationOptions.tsx +27 -32
  49. package/Application/types.header.ts +7 -2
  50. package/Application/types.ts +7 -0
  51. package/Badge/Badge.tsx +5 -0
  52. package/Badge/BadgeDot.tsx +1 -0
  53. package/Badge/BadgeDotAnimation.tsx +15 -7
  54. package/Badge/BadgeRibbon.tsx +46 -38
  55. package/Badge/Shape.tsx +2 -0
  56. package/Badge/types.ts +2 -2
  57. package/Button/index.tsx +5 -16
  58. package/Button/utils.ts +9 -15
  59. package/CheckBox/index.tsx +4 -0
  60. package/Consts/theme.ts +10 -10
  61. package/Context/index.ts +5 -1
  62. package/Divider/DashDivider.tsx +1 -0
  63. package/Divider/index.tsx +4 -0
  64. package/FoundationList/index.tsx +4 -0
  65. package/Icon/index.tsx +6 -6
  66. package/Icon/types.ts +1 -1
  67. package/IconButton/index.tsx +4 -12
  68. package/Image/index.tsx +9 -7
  69. package/Input/Input.tsx +8 -9
  70. package/Input/InputDropDown.tsx +10 -12
  71. package/Input/InputMoney.tsx +12 -8
  72. package/Input/InputMoneyView.tsx +14 -7
  73. package/Input/InputOTP.tsx +35 -33
  74. package/Input/InputPhoneNumber.tsx +8 -9
  75. package/Input/InputSearch.tsx +22 -8
  76. package/Input/InputTextArea.tsx +4 -0
  77. package/Input/SearchTextTyping.tsx +4 -0
  78. package/Input/SystemTextInput.tsx +7 -2
  79. package/Input/TextTyping.tsx +4 -0
  80. package/Input/common.tsx +26 -12
  81. package/Input/index.tsx +24 -21
  82. package/Input/moneyUtils.ts +10 -4
  83. package/Input/styles.ts +0 -5
  84. package/Input/utils.ts +14 -12
  85. package/Layout/Card.tsx +7 -4
  86. package/Layout/FloatingButton.tsx +4 -0
  87. package/Layout/Item.tsx +6 -2
  88. package/Layout/ItemList.tsx +9 -7
  89. package/Layout/ItemSectionList.tsx +2 -1
  90. package/Layout/ReservedRegions.tsx +25 -25
  91. package/Layout/Screen.tsx +59 -58
  92. package/Layout/Section.tsx +8 -5
  93. package/Layout/TrackingScope.tsx +1 -0
  94. package/Layout/screenContent.tsx +4 -2
  95. package/Layout/screenFooter.tsx +28 -18
  96. package/Layout/screenHeader.tsx +88 -82
  97. package/Layout/screenQuickReturn.tsx +17 -20
  98. package/Layout/screenScroll.ts +4 -7
  99. package/Layout/screenTypes.ts +13 -4
  100. package/Layout/types.ts +2 -4
  101. package/Layout/utils.ts +31 -37
  102. package/Loader/DotLoader.tsx +4 -0
  103. package/Loader/Loader.tsx +4 -0
  104. package/Loader/ProgressBar.tsx +1 -0
  105. package/Loader/Spinner.tsx +4 -0
  106. package/Loader/utils.ts +11 -7
  107. package/Pagination/Dot.tsx +4 -0
  108. package/Pagination/PaginationDot.tsx +1 -0
  109. package/Pagination/PaginationNumber.tsx +5 -3
  110. package/Pagination/PaginationScroll.tsx +5 -0
  111. package/Pagination/PaginationWhiteDot.tsx +1 -0
  112. package/Pagination/index.tsx +4 -0
  113. package/Pagination/types.ts +2 -2
  114. package/Popup/PopupNotify.tsx +44 -48
  115. package/Popup/PopupPromotion.tsx +4 -7
  116. package/Popup/types.ts +4 -4
  117. package/Popup/utils.ts +4 -0
  118. package/Radio/index.tsx +4 -0
  119. package/Skeleton/index.tsx +8 -0
  120. package/Switch/index.tsx +1 -0
  121. package/Tag/index.tsx +5 -1
  122. package/Tag/types.ts +3 -3
  123. package/Text/index.tsx +14 -3
  124. package/Text/styles.ts +1 -1
  125. package/Text/types.ts +1 -6
  126. package/Text/utils.ts +20 -4
  127. package/Title/index.tsx +41 -32
  128. package/ViewBoundary/index.tsx +4 -2
  129. package/index.ts +0 -6
  130. package/package.json +37 -37
  131. package/Application/__tests__/modalLayout.test.ts +0 -128
@@ -4,17 +4,19 @@ import { NavigationButton } from './NavigationButton';
4
4
  import { MiniAppContext } from '../../Context';
5
5
  import { Colors, Spacing, Styles } from '../../Consts';
6
6
  import { Icon } from '../../Icon';
7
- import { DuoMetrics, useScreenLayout } from '../../Layout/utils';
7
+ import { DuoMetrics } from '../../Layout/utils';
8
8
  import { SideBarButton, SideBarGroup, SideBarGroupItem } from './SideBarButton';
9
9
  import { useDuoBarCompression } from '../BottomTab/DuoBarCompression';
10
10
  import { styles } from './HeaderRight.styles';
11
11
  import { useToolkitActions } from './useToolkitActions';
12
+ import { HeaderBarMetrics } from '../headerLayout';
12
13
 
13
14
  const CLOSE_ICON = '16_navigation_close_circle';
14
15
  const HOME_ICON = '16_basic_home';
15
16
 
16
17
  /**
17
- * Header toolkit action
18
+ * Header toolkit: optional shortcut plus the help center / close capsule. When
19
+ * `vertical` (iPhone Duo side bar) it reports its height and may fold into one button.
18
20
  */
19
21
  const HeaderToolkitAction: React.FC<any> = ({
20
22
  tintColor,
@@ -27,9 +29,9 @@ const HeaderToolkitAction: React.FC<any> = ({
27
29
  vertical = false,
28
30
  }) => {
29
31
  const context = useContext<any>(MiniAppContext);
30
- const { toolbarCompressed, toolbarHidden, expandToolbar, setToolbarHeight } =
31
- useDuoBarCompression();
32
- const { statusBarClearance } = useScreenLayout();
32
+ const compression = useDuoBarCompression();
33
+ const { toolbarCompressed, toolbarHidden } = compression;
34
+ const { expandToolbar, setToolbarHeight } = compression;
33
35
  const {
34
36
  isLoading,
35
37
  iconShortcut,
@@ -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
@@ -126,39 +131,6 @@ const HeaderToolkitAction: React.FC<any> = ({
126
131
  );
127
132
  }
128
133
 
129
- // on the status bar row the controls keep the side bar size
130
- if (statusBarClearance) {
131
- return (
132
- <View style={styles.sideBarRow}>
133
- {useShortcut && (
134
- <SideBarButton
135
- disabled={isLoading}
136
- icon={iconShortcut}
137
- onPress={shortcutOnPress}
138
- tintColor={tintColor}
139
- showBadge={showBadge}
140
- />
141
- )}
142
- <SideBarGroup tintColor={tintColor} horizontal>
143
- {useHelpCenter && (
144
- <SideBarGroupItem
145
- accessibilityLabel={'btn_navigation_help_center'}
146
- icon={'help_center'}
147
- tintColor={tintColor}
148
- onPress={onPressHelpCenter}
149
- />
150
- )}
151
- <SideBarGroupItem
152
- accessibilityLabel={'btn_navigation_close'}
153
- icon={closeIcon}
154
- tintColor={tintColor}
155
- onPress={onClose}
156
- />
157
- </SideBarGroup>
158
- </View>
159
- );
160
- }
161
-
162
134
  return (
163
135
  <View style={Styles.row}>
164
136
  {useShortcut && (
@@ -170,15 +142,7 @@ const HeaderToolkitAction: React.FC<any> = ({
170
142
  showBadge={showBadge}
171
143
  />
172
144
  )}
173
- <View
174
- style={[
175
- styles.toolkitContainer,
176
- buttonStyle,
177
- context.appId !== 'vn.momo.helpcenter'
178
- ? styles.toolkitWide
179
- : styles.toolkitNarrow,
180
- ]}
181
- >
145
+ <View style={[styles.toolkitContainer, buttonStyle, toolkitWidthStyle]}>
182
146
  {useHelpCenter && (
183
147
  <>
184
148
  <TouchableOpacity
@@ -187,7 +151,11 @@ const HeaderToolkitAction: React.FC<any> = ({
187
151
  onPress={onPressHelpCenter}
188
152
  hitSlop={{ top: 7, bottom: 7, left: 7, right: 0 }}
189
153
  >
190
- <Icon color={tintColor} source={'help_center'} size={20} />
154
+ <Icon
155
+ color={tintColor}
156
+ source={'help_center'}
157
+ size={HeaderBarMetrics.iconSize}
158
+ />
191
159
  </TouchableOpacity>
192
160
  <View style={[styles.divider, { backgroundColor: tintColor }]} />
193
161
  </>
@@ -198,7 +166,11 @@ const HeaderToolkitAction: React.FC<any> = ({
198
166
  style={styles.toolkitButton}
199
167
  hitSlop={{ top: 7, bottom: 7, left: 0, right: 7 }}
200
168
  >
201
- <Icon color={tintColor} source={closeIcon} size={20} />
169
+ <Icon
170
+ color={tintColor}
171
+ source={closeIcon}
172
+ size={HeaderBarMetrics.iconSize}
173
+ />
202
174
  </TouchableOpacity>
203
175
  </View>
204
176
  </View>
@@ -5,9 +5,13 @@ import { StyleSheet, TouchableOpacity, View, ViewStyle } from 'react-native';
5
5
  import { Colors, Spacing } from '../../Consts';
6
6
  import { Icon } from '../../Icon';
7
7
  import { BadgeDot } from '../../Badge';
8
+ import { HeaderBarMetrics } from '../headerLayout';
9
+
10
+ const { buttonSize, iconSize } = HeaderBarMetrics;
8
11
 
9
12
  /**
10
- * default navigation button used header nav
13
+ * Round 32pt icon button of the header (back, shortcut) with an optional badge
14
+ * dot; switches to a dark fill under a white tint so the icon stays readable.
11
15
  */
12
16
  const NavigationButton: React.FC<NavigationButtonProps> = ({
13
17
  icon,
@@ -34,7 +38,7 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
34
38
  <Icon
35
39
  source={icon}
36
40
  color={tintColor ?? theme.colors.text.default}
37
- size={20}
41
+ size={iconSize}
38
42
  />
39
43
  </TouchableOpacity>
40
44
  {showBadge && <BadgeDot size={'small'} style={styles.badgeDot} />}
@@ -44,12 +48,12 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
44
48
 
45
49
  const styles = StyleSheet.create({
46
50
  container: {
47
- width: 28,
51
+ width: buttonSize,
48
52
  },
49
53
  navigationButton: {
50
- height: 28,
51
- width: 28,
52
- borderRadius: 14,
54
+ height: buttonSize,
55
+ width: buttonSize,
56
+ borderRadius: buttonSize / 2,
53
57
  justifyContent: 'center',
54
58
  alignItems: 'center',
55
59
  backgroundColor: Colors.black_01 + '99',
@@ -1,5 +1,5 @@
1
1
  import { Colors, Radius, Spacing, Styles } from '../../Consts';
2
- import { InputRef, InputSearch } from '../../Input';
2
+ import { InputRef, InputSearch, type InputSearchProps } from '../../Input';
3
3
  import { StyleSheet, TouchableOpacity, View } from 'react-native';
4
4
  import Animated, { useAnimatedStyle } from 'react-native-reanimated';
5
5
  import React, { useContext } from 'react';
@@ -8,8 +8,18 @@ import { ApplicationContext, MiniAppContext } from '../../Context';
8
8
  import { Text } from '../../Text';
9
9
  import { useNormalizedSharedValue } from '../hooks';
10
10
  import { useScreenLayout } from '../../Layout';
11
- import { DuoMetrics } from '../../Layout/utils';
11
+ import { HeaderBarMetrics } from '../headerLayout';
12
12
 
13
+ const { buttonSize, pillWidth, sidePadding, titleGap } = HeaderBarMetrics;
14
+ // help center | home pill and the bar padding right of it
15
+ const TOOLKIT_WIDTH = pillWidth + sidePadding;
16
+ // the iPhone Duo side bar keeps its right header width
17
+ const DUO_TOOLKIT_WIDTH = 73;
18
+
19
+ /**
20
+ * Header search bar whose fill turns from surface to default once scrolled, with
21
+ * a cancel button that goes back (or dismisses the mini app) unless `renderButtons`.
22
+ */
13
23
  const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
14
24
  (
15
25
  {
@@ -24,21 +34,27 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
24
34
  const { theme, navigator } = useContext(ApplicationContext);
25
35
  const context = useContext<any>(MiniAppContext);
26
36
  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;
37
+ let defaultRightWidth = TOOLKIT_WIDTH;
38
+ let searchSize: InputSearchProps['size'] = 'header';
39
+ if (layout.sideBar) {
40
+ defaultRightWidth = DUO_TOOLKIT_WIDTH;
41
+ searchSize = 'default';
42
+ }
29
43
 
30
44
  const sharedValue = useNormalizedSharedValue(animatedValue);
31
- const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
32
- const headerRightWidth = props?.headerRightWidth ?? 73;
45
+ const defaultLeft = buttonSize + sidePadding + titleGap;
46
+ const leftPosition = props?.leftPosition ?? defaultLeft;
47
+ const headerRightWidth = props?.headerRightWidth ?? defaultRightWidth;
33
48
 
34
49
  // iPhone Duo side bar holds back: the search shares the content width with the buttons only
35
- let searchMaxWidth = layout.width - leftPosition - 12 - headerRightWidth;
50
+ const rowWidth = layout.width - leftPosition - sidePadding;
51
+ let searchMaxWidth = rowWidth - headerRightWidth;
36
52
  if (layout.sideBar) {
37
53
  searchMaxWidth = layout.contentWidth - Spacing.M * 2 - headerRightWidth;
38
54
  }
39
- if (layout.statusBarClearance) {
40
- searchMaxWidth -=
41
- layout.statusBarClearance.left + layout.statusBarClearance.right;
55
+ const clearance = layout.statusBarClearance;
56
+ if (clearance) {
57
+ searchMaxWidth -= clearance.left + clearance.right;
42
58
  }
43
59
 
44
60
  const backgroundStyle = useAnimatedStyle(() => {
@@ -76,6 +92,15 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
76
92
 
77
93
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
78
94
 
95
+ let buttons: React.ReactNode = (
96
+ <TouchableOpacity onPress={goBackSafe} style={styles.searchAction}>
97
+ <Text typography={'action_default_bold'}>{buttonText}</Text>
98
+ </TouchableOpacity>
99
+ );
100
+ if (typeof renderButtons === 'function') {
101
+ buttons = renderButtons();
102
+ }
103
+
79
104
  return (
80
105
  <View
81
106
  style={[styles.container, showBaseLineDebug && styles.debugBaseLine]}
@@ -92,18 +117,13 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
92
117
  <InputSearch
93
118
  ref={ref}
94
119
  {...props}
120
+ size={searchSize}
95
121
  backgroundColor={'transparent'}
96
122
  showButtonText={false}
97
123
  />
98
124
  </Animated.View>
99
125
  </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
- )}
126
+ {buttons}
107
127
  </View>
108
128
  );
109
129
  },
@@ -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,
@@ -2,6 +2,10 @@ import { NativeModules } from 'react-native';
2
2
  import { LocalizationObject } from './types';
3
3
  import defaultLanguage from '../Assets/language.json';
4
4
 
5
+ /**
6
+ * String table for kit and feature copy. The language is read once from the
7
+ * native `RNResource` module at construction; Vietnamese is the default.
8
+ */
5
9
  class Localize {
6
10
  private assets: LocalizationObject;
7
11
  private readonly currentLanguage: 'en' | 'vi';
@@ -25,14 +29,17 @@ class Localize {
25
29
  return this.assets;
26
30
  }
27
31
 
32
+ /** Text for `key` in the current language, or the key itself if missing. */
28
33
  translate(key: string) {
29
34
  return this.assets[this.currentLanguage]?.[key] || key;
30
35
  }
31
36
 
37
+ /** Picks the current-language text of a `{ vi, en }` pair, else its JSON. */
32
38
  translateData(data: { vi: string; en: string }) {
33
39
  return data[this.currentLanguage] ?? JSON.stringify(data);
34
40
  }
35
41
 
42
+ /** Merges extra translations; keys that already exist keep their text. */
36
43
  addTranslations(translations: LocalizationObject) {
37
44
  this.assets = {
38
45
  vi: { ...translations?.vi, ...this.assets.vi },
@@ -27,6 +27,10 @@ import { isIPhoneDuo } from './utils';
27
27
  import { useModalLayout } from './modalLayout';
28
28
  import { scheduleOnRN } from 'react-native-worklets';
29
29
 
30
+ /**
31
+ * Screen of the `Modal` route: a bottom sheet when pushed by
32
+ * `showBottomSheet`, otherwise a centered popup.
33
+ */
30
34
  const ModalScreen: React.FC<any> = props => {
31
35
  if (props.route?.params?.isBottomSheet) {
32
36
  return <BottomSheet {...props} />;
@@ -34,6 +38,10 @@ const ModalScreen: React.FC<any> = props => {
34
38
  return <Modal {...props} />;
35
39
  };
36
40
 
41
+ /**
42
+ * Centered popup that fades and scales in over a scrim. On foldables it is
43
+ * centered in the modal placement area, at most a popup wide.
44
+ */
37
45
  const Modal: React.FC<ModalParams> = props => {
38
46
  const { navigator } = useContext(ApplicationContext);
39
47
  const context = useContext<any>(MiniAppContext);
@@ -50,9 +58,7 @@ const Modal: React.FC<ModalParams> = props => {
50
58
  const isDuo = isIPhoneDuo();
51
59
  const { isFoldable, placement, popup, scrimOpacity } = useModalLayout();
52
60
 
53
- /**
54
- * Fire auto_popup_displayed tracking event on mount
55
- */
61
+ // Auto-tracks the popup as displayed on the current screen.
56
62
  useEffect(() => {
57
63
  navigator?.maxApi?.getDataObserver?.('current_screen', (curScreen: any) => {
58
64
  context.autoTracking({
@@ -11,6 +11,10 @@ export const WHITE_LIST = [
11
11
  'vn.momo.ekyc',
12
12
  ];
13
13
 
14
+ /**
15
+ * Screen-level navigation handle given to features; `setOptions` enforces the
16
+ * design system's header rules before applying React Navigation options.
17
+ */
14
18
  class Navigation {
15
19
  instance: NavigationProp<any>;
16
20
  context: any;
@@ -21,39 +25,33 @@ class Navigation {
21
25
  }
22
26
 
23
27
  /**
24
- * navigation options filter by design system rule
25
- * @param params
28
+ * Drops header overrides the design system forbids: any `headerLeft`, and
29
+ * render-function `headerRight`/`headerTitle` outside `WHITE_LIST` apps.
26
30
  */
27
31
  private filterParams = (params: NavigationOptions) => {
32
+ const isWhiteListed = WHITE_LIST.includes(this.context?.appId);
33
+ const rightType = typeof params.headerRight;
34
+ const titleType = typeof params.headerTitle;
35
+ const isTitleRenderer = titleType !== 'string' && titleType !== 'object';
36
+
28
37
  if (params.headerLeft) {
29
38
  console.warn('headerLeft not allow override render by design system!');
30
39
  delete params.headerLeft;
31
40
  }
32
41
 
33
- if (
34
- params.headerRight &&
35
- typeof params.headerRight !== 'object' &&
36
- !WHITE_LIST.includes(this.context?.appId)
37
- ) {
42
+ if (params.headerRight && rightType !== 'object' && !isWhiteListed) {
38
43
  console.warn('headerRight not allow type func render by design system!');
39
44
  delete params.headerRight;
40
45
  }
41
46
 
42
- if (
43
- params.headerTitle &&
44
- !['string', 'object'].includes(typeof params.headerTitle) &&
45
- !WHITE_LIST.includes(this.context?.appId)
46
- ) {
47
+ if (params.headerTitle && isTitleRenderer && !isWhiteListed) {
47
48
  console.warn('headerTitle not allow type func render by design system!');
48
49
  delete params.headerTitle;
49
50
  }
50
51
  return params;
51
52
  };
52
53
 
53
- /**
54
- * navigation setOptions support props NavigationOptions base on react-navigation options
55
- * @param params
56
- */
54
+ /** Applies kit `NavigationOptions` to the screen after `filterParams`. */
57
55
  setOptions = (params: NavigationOptions) => {
58
56
  const options = getOptions(this.filterParams(params));
59
57
  this.instance.setOptions(options as StackNavigationOptions);
@@ -33,6 +33,10 @@ import { ReservedRegionsProvider } from '../Layout/ReservedRegions';
33
33
 
34
34
  const Stack = createStackNavigator();
35
35
 
36
+ /**
37
+ * Root of a mini app: safe area, reserved regions and the merged mini app
38
+ * context around the kit's navigator.
39
+ */
36
40
  const NavigationContainer: React.FC<NavigationContainerProps> = ({
37
41
  screen,
38
42
  theme = defaultTheme,
@@ -92,6 +96,10 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
92
96
  );
93
97
  };
94
98
 
99
+ /**
100
+ * Stack navigator with its `Stack`, `Dialog` and `Modal` routes, under an
101
+ * `ApplicationContext` carrying the navigator, themed fonts and `translate`.
102
+ */
95
103
  const Navigation: React.FC<any> = ({
96
104
  screen,
97
105
  theme = defaultTheme,
@@ -109,9 +117,10 @@ const Navigation: React.FC<any> = ({
109
117
  const syncCanGoBack = useNativeCanGoBack();
110
118
  const isWhiteList = isWhiteListDesignSystem(context);
111
119
  const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
112
- const navigationFontFamily = isWhiteList
113
- ? 'MomoTrustSans-Regular'
114
- : 'SFProText';
120
+ let navigationFontFamily = 'SFProText';
121
+ if (isWhiteList) {
122
+ navigationFontFamily = 'MomoTrustSans-Regular';
123
+ }
115
124
  let headerBackground = context?.designSystemConfig?.config?.headerBar;
116
125
  let headerGradient = theme.colors?.gradient;
117
126
 
@@ -122,15 +131,9 @@ const Navigation: React.FC<any> = ({
122
131
  headerGradient = context?.designSystemConfig?.config?.headerGradient;
123
132
  }
124
133
 
125
- /**
126
- * inject data for navigator
127
- */
128
134
  navigator.current.maxApi = maxApi;
129
135
 
130
- /**
131
- * translate context method
132
- * @param data
133
- */
136
+ /** Text for a localization key, or the current-language side of `{ vi, en }`. */
134
137
  const translate = (data?: string | { vi: string; en: string }) => {
135
138
  if (data && typeof data !== 'string') {
136
139
  return localize?.translateData(data);
@@ -228,12 +231,12 @@ const Navigation: React.FC<any> = ({
228
231
  backgroundColor: theme.colors.background.default,
229
232
  },
230
233
  headerLeft: (props: any) => <HeaderLeft {...props} floating />,
231
- headerBackground: (props: any) => (
232
- <HeaderBackground {...props} />
233
- ),
234
- headerRight: (props: any) => (
235
- <HeaderRight {...props} floating />
236
- ),
234
+ headerBackground: (props: any) => {
235
+ return <HeaderBackground {...props} />;
236
+ },
237
+ headerRight: (props: any) => {
238
+ return <HeaderRight {...props} floating />;
239
+ },
237
240
  headerTintColor: Colors.black_17,
238
241
  ...TransitionPresets.ModalTransition,
239
242
  }}