@momo-kits/foundation 0.169.7-beta.3 → 0.169.7-beta.4

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 (141) hide show
  1. package/Application/BottomSheet.hooks.ts +8 -8
  2. package/Application/BottomSheet.parts.tsx +34 -44
  3. package/Application/BottomSheet.tsx +37 -40
  4. package/Application/BottomTab/ActiveIndicator.tsx +14 -15
  5. package/Application/BottomTab/BottomTabBar.tsx +62 -49
  6. package/Application/BottomTab/BottomTabInset.tsx +4 -8
  7. package/Application/BottomTab/CurvedContainer.tsx +12 -14
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +19 -13
  10. package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
  11. package/Application/BottomTab/DuoTabBar.tsx +10 -8
  12. package/Application/BottomTab/TabBarIcon.tsx +29 -15
  13. package/Application/BottomTab/TabScreen.tsx +5 -1
  14. package/Application/BottomTab/index.tsx +30 -33
  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 +0 -4
  19. package/Application/Components/DuoGlassBackground.tsx +4 -5
  20. package/Application/Components/HeaderAnimated.tsx +0 -4
  21. package/Application/Components/HeaderBackground.tsx +0 -4
  22. package/Application/Components/HeaderExtendHeader.tsx +10 -11
  23. package/Application/Components/HeaderLeft.tsx +1 -2
  24. package/Application/Components/HeaderRight.tsx +1 -2
  25. package/Application/Components/HeaderTitle.shared.ts +9 -11
  26. package/Application/Components/HeaderTitle.tsx +3 -4
  27. package/Application/Components/HeaderToolkitAction.tsx +20 -17
  28. package/Application/Components/NavigationButton.tsx +1 -2
  29. package/Application/Components/SearchHeader.tsx +10 -17
  30. package/Application/Components/TitleJourney.tsx +2 -2
  31. package/Application/Components/TitleLocation.tsx +2 -2
  32. package/Application/Components/TitleUser.tsx +2 -2
  33. package/Application/Components/useToolkitActions.tsx +32 -17
  34. package/Application/KitAdoption/AdoptionBadge.tsx +119 -0
  35. package/Application/KitAdoption/AdoptionProbe.tsx +166 -0
  36. package/Application/KitAdoption/AdoptionStore.ts +80 -0
  37. package/Application/KitAdoption/Authorship/utils.ts +137 -0
  38. package/Application/KitAdoption/Slots/utils.ts +68 -0
  39. package/Application/KitAdoption/constants.ts +120 -0
  40. package/Application/KitAdoption/index.ts +12 -0
  41. package/Application/KitAdoption/styles.ts +55 -0
  42. package/Application/KitAdoption/types.ts +61 -0
  43. package/Application/KitAdoption/utils.ts +376 -0
  44. package/Application/Localize.ts +0 -7
  45. package/Application/ModalScreen.tsx +10 -10
  46. package/Application/Navigation.ts +16 -14
  47. package/Application/NavigationContainer.tsx +18 -19
  48. package/Application/Navigator.ts +48 -20
  49. package/Application/ScaleSizeProvider.tsx +6 -13
  50. package/Application/StackScreen.tracking.ts +33 -11
  51. package/Application/StackScreen.tsx +54 -10
  52. package/Application/TooltipPortal.tsx +5 -12
  53. package/Application/WidgetContainer.tsx +4 -10
  54. package/Application/__tests__/KitAdoption.integration.test.tsx +253 -0
  55. package/Application/__tests__/KitAdoption.test.ts +283 -0
  56. package/Application/__tests__/KitAdoptionAuthorship.test.ts +132 -0
  57. package/Application/__tests__/KitAdoptionStore.test.ts +110 -0
  58. package/Application/__tests__/KitAdoptionStructural.test.ts +193 -0
  59. package/Application/__tests__/modalLayout.test.ts +128 -0
  60. package/Application/modalLayout.ts +14 -16
  61. package/Application/navigationOptions.tsx +17 -21
  62. package/Badge/Badge.tsx +0 -5
  63. package/Badge/BadgeDot.tsx +0 -1
  64. package/Badge/BadgeDotAnimation.tsx +7 -15
  65. package/Badge/BadgeRibbon.tsx +38 -46
  66. package/Badge/Shape.tsx +0 -2
  67. package/Badge/types.ts +2 -2
  68. package/Button/index.tsx +16 -5
  69. package/Button/utils.ts +15 -9
  70. package/CheckBox/index.tsx +0 -4
  71. package/Consts/theme.ts +10 -10
  72. package/Context/index.ts +1 -5
  73. package/Divider/DashDivider.tsx +0 -1
  74. package/Divider/index.tsx +0 -4
  75. package/FoundationList/index.tsx +0 -4
  76. package/Icon/index.tsx +6 -6
  77. package/Icon/types.ts +1 -1
  78. package/IconButton/index.tsx +12 -4
  79. package/Image/index.tsx +7 -9
  80. package/Input/Input.tsx +9 -8
  81. package/Input/InputDropDown.tsx +12 -10
  82. package/Input/InputMoney.tsx +8 -12
  83. package/Input/InputMoneyView.tsx +7 -14
  84. package/Input/InputOTP.tsx +33 -35
  85. package/Input/InputPhoneNumber.tsx +9 -8
  86. package/Input/InputSearch.tsx +6 -4
  87. package/Input/InputTextArea.tsx +0 -4
  88. package/Input/SearchTextTyping.tsx +0 -4
  89. package/Input/SystemTextInput.tsx +2 -7
  90. package/Input/TextTyping.tsx +0 -4
  91. package/Input/common.tsx +12 -26
  92. package/Input/index.tsx +20 -17
  93. package/Input/moneyUtils.ts +4 -10
  94. package/Input/styles.ts +5 -0
  95. package/Input/utils.ts +12 -14
  96. package/Layout/Card.tsx +4 -7
  97. package/Layout/FloatingButton.tsx +0 -4
  98. package/Layout/Item.tsx +2 -6
  99. package/Layout/ItemList.tsx +7 -9
  100. package/Layout/ItemSectionList.tsx +1 -2
  101. package/Layout/ReservedRegions.tsx +25 -25
  102. package/Layout/Screen.tsx +58 -56
  103. package/Layout/Section.tsx +5 -8
  104. package/Layout/TrackingScope.tsx +0 -1
  105. package/Layout/screenContent.tsx +2 -4
  106. package/Layout/screenFooter.tsx +18 -28
  107. package/Layout/screenHeader.tsx +31 -34
  108. package/Layout/screenQuickReturn.tsx +20 -17
  109. package/Layout/screenScroll.ts +7 -4
  110. package/Layout/screenTypes.ts +2 -3
  111. package/Layout/types.ts +4 -2
  112. package/Layout/utils.ts +37 -31
  113. package/Loader/DotLoader.tsx +0 -4
  114. package/Loader/Loader.tsx +0 -4
  115. package/Loader/ProgressBar.tsx +0 -1
  116. package/Loader/Spinner.tsx +0 -4
  117. package/Loader/utils.ts +7 -11
  118. package/Pagination/Dot.tsx +0 -4
  119. package/Pagination/PaginationDot.tsx +0 -1
  120. package/Pagination/PaginationNumber.tsx +3 -5
  121. package/Pagination/PaginationScroll.tsx +0 -5
  122. package/Pagination/PaginationWhiteDot.tsx +0 -1
  123. package/Pagination/index.tsx +0 -4
  124. package/Pagination/types.ts +2 -2
  125. package/Popup/PopupNotify.tsx +49 -45
  126. package/Popup/PopupPromotion.tsx +7 -4
  127. package/Popup/types.ts +4 -4
  128. package/Popup/utils.ts +4 -11
  129. package/Radio/index.tsx +0 -4
  130. package/Skeleton/index.tsx +0 -8
  131. package/Switch/index.tsx +0 -1
  132. package/Tag/index.tsx +1 -5
  133. package/Tag/types.ts +3 -3
  134. package/Text/index.tsx +3 -14
  135. package/Text/styles.ts +1 -1
  136. package/Text/types.ts +6 -1
  137. package/Text/utils.ts +4 -20
  138. package/Title/index.tsx +32 -41
  139. package/ViewBoundary/index.tsx +2 -4
  140. package/index.ts +6 -0
  141. package/package.json +1 -1
@@ -28,9 +28,9 @@ type DuoBarCompression = {
28
28
 
29
29
  // space kept between the top and the bottom groups (HIG Vertical controls)
30
30
  const GROUP_GAP = Spacing.M;
31
- const BACK_SLOT_HEIGHT = DuoMetrics.controlSize + Spacing.M;
32
31
  // Back, then the right toolkit: help + home capsule
33
- const DEFAULT_TOOLBAR_HEIGHT = BACK_SLOT_HEIGHT + 2 * DuoMetrics.itemPitch;
32
+ const DEFAULT_TOOLBAR_HEIGHT =
33
+ DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch;
34
34
  // Back, then the single button the toolbar folds into
35
35
  const COMPRESSED_TOOLBAR_HEIGHT = 2 * DuoMetrics.controlSize + Spacing.M;
36
36
 
@@ -39,12 +39,16 @@ const NO_COMPRESSION: DuoBarCompression = {
39
39
  tabBarCompressed: false,
40
40
  };
41
41
 
42
- const DuoBarCompressionContext = createContext(NO_COMPRESSION);
42
+ const DuoBarCompressionContext =
43
+ createContext<DuoBarCompression>(NO_COMPRESSION);
43
44
 
44
45
  /**
45
- * Keeps the Duo side bar's toolbar and tab bar clear of each other (HIG Vertical controls):
46
- * both stay whole if they fit, else one is compressed and tapping it swaps them, else both
47
- * rest compressed and tapping one opens it until a tab is picked.
46
+ * keeps the side bar's toolbar and tab bar clear of each other (HIG, Designing for
47
+ * iPhone Duo: Vertical controls):
48
+ * - both fit: both stay whole
49
+ * - one fits whole beside the other compressed: one is compressed, tapping it swaps them
50
+ * - otherwise both rest compressed; tapping one opens it for a moment (the rail closes
51
+ * again once a tab is picked)
48
52
  */
49
53
  const DuoBarCompressionProvider: React.FC<{
50
54
  behavior?: DuoBarCompressionBehavior;
@@ -67,16 +71,18 @@ const DuoBarCompressionProvider: React.FC<{
67
71
  }
68
72
  const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
69
73
  const tabBar = getRailHeight(tabCount, hasQr);
70
- const qrHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
71
- const compressedTabBar = DuoMetrics.controlSize + (hasQr ? qrHeight : 0);
74
+ const compressedTabBar =
75
+ DuoMetrics.controlSize +
76
+ (hasQr ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
72
77
  const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
73
78
 
74
79
  if (fits(toolbarHeight, tabBar)) {
75
80
  return { ...NO_COMPRESSION, setToolbarHeight };
76
81
  }
77
- const canCompressToolbar = fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar);
78
- const canCompressTabBar = fits(toolbarHeight, compressedTabBar);
79
- if (canCompressToolbar && canCompressTabBar) {
82
+ const canSwap =
83
+ fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar) &&
84
+ fits(toolbarHeight, compressedTabBar);
85
+ if (canSwap) {
80
86
  return {
81
87
  toolbarCompressed: expanded === 'tabBar',
82
88
  tabBarCompressed: expanded === 'toolbar',
@@ -91,7 +97,8 @@ const DuoBarCompressionProvider: React.FC<{
91
97
  expandTabBar: () => setOpened('tabBar'),
92
98
  setToolbarHeight,
93
99
  };
94
- // An opened rail shows even if only the group gap is short; picking a tab closes it.
100
+ // an opened rail always shows, even when only the gap between the groups is short:
101
+ // it closes again once a tab is picked
95
102
  if (opened === 'tabBar') {
96
103
  return {
97
104
  ...open,
@@ -115,7 +122,6 @@ const DuoBarCompressionProvider: React.FC<{
115
122
  );
116
123
  };
117
124
 
118
- /** Compression state of the Duo side bars; no compression outside the provider. */
119
125
  const useDuoBarCompression = () => useContext(DuoBarCompressionContext);
120
126
 
121
127
  export { DuoBarCompressionProvider, useDuoBarCompression };
@@ -13,6 +13,7 @@ import { TabItemContent } from './CustomBottomTabItem';
13
13
  import { CurvedContainer } from './CurvedContainer';
14
14
  import { TabBarMetrics } from './metrics';
15
15
  import { getTabItems, TabItem } from './tabItems';
16
+ import { FixedScaleText } from './TabBarIcon';
16
17
  import { usePressLift } from './usePressLift';
17
18
  import { ActiveIndicator } from './ActiveIndicator';
18
19
  import {
@@ -23,7 +24,7 @@ import {
23
24
  import type { DuoTabBarProps } from './DuoTabBar';
24
25
 
25
26
  /**
26
- * Center action icon: an icon name drawn at the CTA size, or a custom node.
27
+ * Center action icon: an icon name drawn at the CTA size, or a custom node
27
28
  */
28
29
  const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
29
30
  if (typeof icon === 'string') {
@@ -39,7 +40,7 @@ const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
39
40
  };
40
41
 
41
42
  /**
42
- * Vertical container (.Nav/Duo/Item column) with the sliding active indicator.
43
+ * vertical container (.Nav/Duo/Item column) with the sliding active indicator
43
44
  */
44
45
  const Rail: React.FC<{
45
46
  items: TabItem[];
@@ -75,6 +76,7 @@ const Rail: React.FC<{
75
76
  >
76
77
  <TabItemContent
77
78
  vertical
79
+ fixedFontScale
78
80
  label={item.label}
79
81
  focused={item.focused}
80
82
  icon={item.icon}
@@ -88,7 +90,7 @@ const Rail: React.FC<{
88
90
  );
89
91
 
90
92
  /**
91
- * Center action of the rail: a round button with its label, under the container.
93
+ * center action of the rail: round button with its label, under the container
92
94
  */
93
95
  const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
94
96
  floatingButton,
@@ -114,13 +116,15 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
114
116
  <CtaIcon icon={floatingButton.icon} />
115
117
  </Animated.View>
116
118
  <View style={styles.railCtaLabel}>
117
- <Text
118
- numberOfLines={1}
119
- typography={'label_xs_medium'}
120
- color={theme.colors.text.secondary}
121
- >
122
- {floatingButton.label}
123
- </Text>
119
+ <FixedScaleText>
120
+ <Text
121
+ numberOfLines={1}
122
+ typography={'label_xs_medium'}
123
+ color={theme.colors.text.secondary}
124
+ >
125
+ {floatingButton.label}
126
+ </Text>
127
+ </FixedScaleText>
124
128
  {floatingButton.labelContent && (
125
129
  <View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
126
130
  {floatingButton.labelContent}
@@ -132,14 +136,14 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
132
136
  };
133
137
 
134
138
  /**
135
- * Vertical bar anchored to the bottom of the Duo side bar, center action below it. When the
136
- * side bar is short it can shrink to one button showing the current tab (HIG "Tab bar
137
- * compressed"); tapping it expands the rail and compresses the toolbar.
139
+ * vertical bar anchored to the bottom of the Duo side bar, center action below it.
140
+ * When the side bar is short, it can shrink to one button showing the current tab
141
+ * (HIG "Tab bar compressed"); tapping it expands the rail and compresses the toolbar
138
142
  */
139
143
  const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
140
144
  const { sideBar } = useScreenLayout();
141
- const compression = useDuoBarCompression();
142
- const { tabBarCompressed, expandTabBar, onTabSelected } = compression;
145
+ const { tabBarCompressed, expandTabBar, onTabSelected } =
146
+ useDuoBarCompression();
143
147
  const compactLift = usePressLift();
144
148
  const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
145
149
  const items = getTabItems(props);
@@ -149,7 +153,8 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
149
153
  return null;
150
154
  }
151
155
 
152
- // Lines up with the side bar header buttons: a 48 lane kept 12 from the inner edge.
156
+ // the rail lines up with the header buttons in the side bar: centered on a 48 lane kept 12
157
+ // from the strip's inner edge
153
158
  const columnStyle = [
154
159
  styles.railColumn,
155
160
  {
@@ -159,10 +164,9 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
159
164
  },
160
165
  sideBar.edge === 'right' ? styles.railColumnRight : styles.railColumnLeft,
161
166
  ];
162
- let cta: React.ReactNode;
163
- if (floatingButton) {
164
- cta = <RailCenterAction floatingButton={floatingButton} />;
165
- }
167
+ const cta = floatingButton && (
168
+ <RailCenterAction floatingButton={floatingButton} />
169
+ );
166
170
 
167
171
  if (!tabBarCompressed) {
168
172
  return (
@@ -16,8 +16,8 @@ type DuoTabBarProps = BottomTabBarProps & {
16
16
  };
17
17
 
18
18
  /**
19
- * Space tab content leaves under the floating horizontal tab bar (phone and unfolded Duo);
20
- * 0 when the Duo rail sits in the side bar.
19
+ * space tab content leaves under the floating horizontal tab bar (phone and unfolded Duo);
20
+ * 0 when the Duo rail sits in the side bar
21
21
  */
22
22
  const useDuoTabBarInset = () => {
23
23
  const { isIPhoneDuo, sideBar, insets } = useScreenLayout();
@@ -28,7 +28,7 @@ const useDuoTabBarInset = () => {
28
28
  };
29
29
 
30
30
  /**
31
- * Horizontal navigation bar of the unfolded display: 65% of the short side, centered.
31
+ * horizontal navigation bar of the unfolded display: 65% of the short side, centered
32
32
  */
33
33
  const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
34
34
  const { insets } = useScreenLayout();
@@ -44,6 +44,7 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
44
44
  ]}
45
45
  >
46
46
  <HorizontalTabBar
47
+ fixedFontScale
47
48
  items={getTabItems(props)}
48
49
  width={getHorizontalBarWidth(width, height, insets.left, insets.right)}
49
50
  floatingButton={floatingButton}
@@ -55,14 +56,15 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
55
56
  };
56
57
 
57
58
  /**
58
- * iPhone Duo tab bar: the rail in the side bar, or the horizontal bar when unfolded portrait.
59
+ * iPhone Duo tab bar: rail in the side bar, or the horizontal bar when unfolded portrait
59
60
  */
60
61
  const DuoTabBar: React.FC<DuoTabBarProps> = props => {
61
62
  const { sideBar } = useScreenLayout();
62
- if (sideBar) {
63
- return <DuoRailTabBar {...props} />;
64
- }
65
- return <DuoFloatingTabBar {...props} />;
63
+ return sideBar ? (
64
+ <DuoRailTabBar {...props} />
65
+ ) : (
66
+ <DuoFloatingTabBar {...props} />
67
+ );
66
68
  };
67
69
 
68
70
  export type { DuoTabBarProps };
@@ -7,10 +7,9 @@ import Animated, {
7
7
  } from 'react-native-reanimated';
8
8
 
9
9
  import { Colors, Spacing } from '../../Consts';
10
- import { ApplicationContext } from '../../Context';
10
+ import { ApplicationContext, MiniAppContext } from '../../Context';
11
11
  import { BadgeDot } from '../../Badge';
12
12
  import { Text } from '../../Text';
13
- import ScaleSizeProvider from '../ScaleSizeProvider';
14
13
  import { TabBarMetrics, TabBarMotion } from './metrics';
15
14
 
16
15
  type Props = {
@@ -29,8 +28,7 @@ type Props = {
29
28
  };
30
29
 
31
30
  /**
32
- * Badge value of a tab: undefined hides it, '' is a dot (also 0 when `showDot`), numbers
33
- * cap at 99+.
31
+ * badge value shown on a tab: undefined hides it, '' is a dot, numbers cap at 99+
34
32
  */
35
33
  export const getTabBadge = (
36
34
  badgeLabel?: string | number,
@@ -54,8 +52,27 @@ export const getTabBadge = (
54
52
  };
55
53
 
56
54
  /**
57
- * Label badge of a tab: numbers use the error color, text the warning color (Figma Badge
58
- * round numeric / text). The text keeps its design size to fit the 16 height.
55
+ * text at its design size: like the system tab bar it doesn't grow with the OS font scale or
56
+ * the wide Duo displays, which Text otherwise scales up to 5pt
57
+ */
58
+ export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
59
+ children,
60
+ }) => {
61
+ const context = useContext<any>(MiniAppContext);
62
+ return (
63
+ <MiniAppContext.Provider
64
+ value={{
65
+ ...context,
66
+ fontScale: { useOSScaleRate: false, userScaleRate: 1 },
67
+ }}
68
+ >
69
+ {children}
70
+ </MiniAppContext.Provider>
71
+ );
72
+ };
73
+
74
+ /**
75
+ * numbers use the error color, text the warning color (Figma Badge round numeric / text)
59
76
  */
60
77
  const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
61
78
  label,
@@ -63,21 +80,19 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
63
80
  }) => {
64
81
  const { theme } = useContext(ApplicationContext);
65
82
  const isNumber = /^\d+\+?$/.test(label);
66
- let backgroundColor = theme.colors.warning.primary;
67
- if (isNumber) {
68
- backgroundColor = theme.colors.error.primary;
69
- }
70
83
  const badge = (
71
84
  <View
72
85
  style={[
73
86
  styles.badge,
74
87
  {
75
- backgroundColor,
88
+ backgroundColor: isNumber
89
+ ? theme.colors.error.primary
90
+ : theme.colors.warning.primary,
76
91
  borderColor: theme.colors.background.surface,
77
92
  },
78
93
  ]}
79
94
  >
80
- <ScaleSizeProvider scaleSizeMaxRate={1}>
95
+ <FixedScaleText>
81
96
  <Text
82
97
  numberOfLines={1}
83
98
  typography={'action_xxs_bold'}
@@ -85,7 +100,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
85
100
  >
86
101
  {label}
87
102
  </Text>
88
- </ScaleSizeProvider>
103
+ </FixedScaleText>
89
104
  </View>
90
105
  );
91
106
  if (vertical) {
@@ -95,8 +110,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
95
110
  };
96
111
 
97
112
  /**
98
- * Tab icon (24) with its badge; active and inactive icons are stacked and cross-fade on
99
- * focus.
113
+ * 24 icon; active and inactive icons are stacked and cross-faded on focus
100
114
  */
101
115
  export function TabBarIcon({
102
116
  focused,
@@ -29,7 +29,8 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
29
29
 
30
30
  const screenName = screen?.name || screen?.type?.name || 'Invalid';
31
31
  const { isBackgroundToForeground } = useAppState();
32
- // Pad clear of the floating bar unless a Screen inside claims the inset to scroll under it.
32
+ // the floating tab bar covers the bottom of the tab: pad the content unless a Screen in it
33
+ // keeps clear of the bar itself (scroll content running under the bar)
33
34
  const { inset: tabBarInset } = useContext(BottomTabInsetContext);
34
35
  const [insetClaimed, setInsetClaimed] = useState(false);
35
36
  const tabInsetValue = useMemo(
@@ -53,6 +54,9 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
53
54
  ...data,
54
55
  });
55
56
 
57
+ /**
58
+ * debug toast
59
+ */
56
60
  navigator?.maxApi?.showToastDebug?.({
57
61
  appId: context.appId,
58
62
  message: `${screenName} screen_navigated`,
@@ -1,5 +1,4 @@
1
1
  import {
2
- BottomTabBarProps,
3
2
  BottomTabNavigationOptions,
4
3
  createBottomTabNavigator,
5
4
  } from '@react-navigation/bottom-tabs';
@@ -14,7 +13,6 @@ import {
14
13
  } from 'react-native';
15
14
  import { Icon } from '../../Icon';
16
15
  import { BottomTabProps } from '../types';
17
- import ScaleSizeProvider from '../ScaleSizeProvider';
18
16
  import BottomTabBar, { hasCenterAction } from './BottomTabBar';
19
17
  import { TabBarMetrics } from './metrics';
20
18
  import { getTabBadge } from './TabBarIcon';
@@ -25,8 +23,6 @@ import TabScreen from './TabScreen';
25
23
  import { ApplicationContext } from '../../Context';
26
24
  import { useScreenLayout } from '../../Layout';
27
25
 
28
- type SceneInterpolator = BottomTabNavigationOptions['sceneStyleInterpolator'];
29
-
30
26
  const Tab = createBottomTabNavigator();
31
27
 
32
28
  // the tab screens slide in index order over 280ms, CSS `ease`
@@ -36,11 +32,12 @@ const TAB_SWITCH_SPEC = {
36
32
  };
37
33
 
38
34
  /**
39
- * Tab slide interpolator. Scene progress is -1 for tabs before the active one and 1 for
40
- * tabs after it, so they wait a full width off screen on their side.
35
+ * scene progress is -1 for tabs before the active one, 1 for tabs after it: they wait a full
36
+ * width off screen on their side
41
37
  */
42
- const getTabSlide = (width: number): SceneInterpolator => {
43
- return ({ current }) => ({
38
+ const getTabSlide =
39
+ (width: number): BottomTabNavigationOptions['sceneStyleInterpolator'] =>
40
+ ({ current }) => ({
44
41
  sceneStyle: {
45
42
  transform: [
46
43
  {
@@ -52,7 +49,6 @@ const getTabSlide = (width: number): SceneInterpolator => {
52
49
  ],
53
50
  },
54
51
  });
55
- };
56
52
 
57
53
  /**
58
54
  * App tab navigator: the floating bar on phones, the rail or floating bar on
@@ -71,11 +67,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
71
67
  const { isIPhoneDuo } = useScreenLayout();
72
68
  const { width: windowWidth } = useWindowDimensions();
73
69
  const barInset = useDuoTabBarInset();
74
- // The center action's button rises above the bar; tab content leaves room for it too.
75
- let tabBarInset = barInset;
76
- if (barInset && hasCenterAction(floatingButton, tabs.length)) {
77
- tabBarInset = barInset + TabBarMetrics.ctaLift;
78
- }
70
+ // the center action's button rises above the bar, tab content leaves room for it too
71
+ const tabBarInset =
72
+ barInset && hasCenterAction(floatingButton, tabs.length)
73
+ ? barInset + TabBarMetrics.ctaLift
74
+ : barInset;
79
75
  const initialIndex = tabs.findIndex(i => i.name === initialRouteName);
80
76
  const activeIndex = React.useRef(initialIndex > 0 ? initialIndex : 0);
81
77
 
@@ -87,15 +83,15 @@ const BottomTab: React.FC<BottomTabProps> = ({
87
83
  });
88
84
  }, [navigation]);
89
85
 
90
- // Focus, not mount/onFocus: popping back from a pushed screen only refocuses BottomTab.
86
+ // Focus, not mount: pushing a screen from a tab leaves BottomTab mounted but blurred, and
87
+ // onFocus only fires on tab changes.
91
88
  useFocusEffect(
92
89
  React.useCallback(() => {
93
90
  NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(
94
91
  activeIndex.current !== 0,
95
92
  );
96
- return () => {
93
+ return () =>
97
94
  NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(false);
98
- };
99
95
  }, []),
100
96
  );
101
97
 
@@ -132,21 +128,6 @@ const BottomTab: React.FC<BottomTabProps> = ({
132
128
  },
133
129
  };
134
130
 
135
- // One cap for the whole bar (labels, CTA, custom content) so it fits the fixed bar and notch.
136
- const renderTabBar = (props: BottomTabBarProps) => {
137
- const TabBar = isIPhoneDuo ? DuoTabBar : BottomTabBar;
138
- return (
139
- <ScaleSizeProvider scaleSizeMaxRate={TabBarMetrics.labelMaxScale}>
140
- <TabBar
141
- {...props}
142
- floatingButton={floatingButton}
143
- inactiveTintColor={theme.colors.text.secondary}
144
- activeTintColor={theme.colors.primary}
145
- />
146
- </ScaleSizeProvider>
147
- );
148
- };
149
-
150
131
  return (
151
132
  <DuoBarCompressionProvider
152
133
  behavior={compressionBehavior}
@@ -164,7 +145,23 @@ const BottomTab: React.FC<BottomTabProps> = ({
164
145
  transitionSpec: TAB_SWITCH_SPEC,
165
146
  sceneStyleInterpolator: getTabSlide(windowWidth),
166
147
  }}
167
- tabBar={renderTabBar}
148
+ tabBar={props =>
149
+ isIPhoneDuo ? (
150
+ <DuoTabBar
151
+ {...props}
152
+ floatingButton={floatingButton}
153
+ inactiveTintColor={theme.colors.text.secondary}
154
+ activeTintColor={theme.colors.primary}
155
+ />
156
+ ) : (
157
+ <BottomTabBar
158
+ {...props}
159
+ floatingButton={floatingButton}
160
+ inactiveTintColor={theme.colors.text.secondary}
161
+ activeTintColor={theme.colors.primary}
162
+ />
163
+ )
164
+ }
168
165
  initialRouteName={initialRouteName}
169
166
  >
170
167
  {tabs.map((item, index) => (
@@ -3,9 +3,10 @@ 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), 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).
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
9
10
  */
10
11
  const TabBarMetrics = {
11
12
  height: 68,
@@ -19,15 +20,17 @@ const TabBarMetrics = {
19
20
  indicatorPaddingHorizontal: Spacing.M,
20
21
  indicatorRadius: Radius.XL,
21
22
  indicatorHeight: 32,
22
- labelMaxScale: 1.2,
23
- labelHeight: 18,
24
- railLabelHeight: 14,
23
+ // home indicator / gesture bar area under the container
25
24
  systemIndicatorHeight: 24,
25
+ // unfolded foldables (short side from 600, the smallest in the spec) use 65% of the short
26
+ // side, centered; phones and folded outer displays fill the window
26
27
  foldableMinShortSide: 600,
27
28
  foldableWidthRatio: 0.65,
28
29
  ctaSlotWidth: 92,
29
30
  ctaSize: 48,
31
+ // the icon sits 10 inside the 48 button (prototype .cta: 28 icon), 12 inside the 52 rail one
30
32
  ctaIconSize: 28,
33
+ // the button rises 22 above the container top edge
31
34
  ctaLift: 22,
32
35
  ctaLabelGap: Spacing.M,
33
36
  ctaLabelHeight: 18,
@@ -48,9 +51,10 @@ const TabBarMetrics = {
48
51
  };
49
52
 
50
53
  /**
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.
54
+ * motion of the navigation bar (MoMo Navigation Prototype):
55
+ * - one active indicator slides to the selected item, overshooting a little
56
+ * - icons cross-fade and the label color follows
57
+ * - the center action shrinks while pressed
54
58
  */
55
59
  const TabBarMotion = {
56
60
  indicator: { duration: 550, easing: Easing.bezier(0.34, 1.4, 0.5, 1) },
@@ -81,7 +85,7 @@ const CTA_SHADOW: BoxShadowValue[] = [
81
85
  ];
82
86
 
83
87
  /**
84
- * Inner light and tint of the active indicator: white 12% from below, primary 12% from above.
88
+ * inner light and tint of the active indicator: white 12% from below, primary 12% from above
85
89
  */
86
90
  const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
87
91
  {
@@ -95,9 +99,9 @@ const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
95
99
  ];
96
100
 
97
101
  /**
98
- * Notch dipped into the container top edge under the center action (Figma "Backgroud" path):
102
+ * notch dipped into the container top edge under the center action (Figma "Backgroud" path):
99
103
  * starts NOTCH_HALF_WIDTH left of the center, then cubic segments as [control1, control2, end],
100
- * each point [dx from the center, dy below the top edge].
104
+ * each point [dx from the center, dy below the top edge]
101
105
  */
102
106
  const NOTCH_HALF_WIDTH = 50.758;
103
107
  const NOTCH_CURVES: [number, number][][] = [
@@ -124,8 +128,8 @@ const NOTCH_CURVES: [number, number][][] = [
124
128
  ];
125
129
 
126
130
  /**
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.
131
+ * width of the horizontal bar: 65% of the short side on an unfolded foldable, otherwise the
132
+ * window minus the side safe areas and margins
129
133
  */
130
134
  const getHorizontalBarWidth = (
131
135
  width: number,
@@ -141,16 +145,12 @@ const getHorizontalBarWidth = (
141
145
  };
142
146
 
143
147
  /**
144
- * Height of the vertical rail container, plus the center action under it.
148
+ * height of the vertical rail container, plus the center action under it
145
149
  */
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
- };
150
+ const getRailHeight = (tabCount: number, hasCta: boolean) =>
151
+ tabCount * TabBarMetrics.railItemHeight +
152
+ TabBarMetrics.railPaddingBottom +
153
+ (hasCta ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
154
154
 
155
155
  export {
156
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
9
10
  const PRESSED_SCALE = 1.1;
10
11
 
11
12
  /**
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.
13
+ * lift for a single button, e.g. the compressed rail
14
14
  */
15
15
  const usePressLift = () => {
16
16
  const reduceMotion = useReducedMotion();
@@ -17,10 +17,6 @@ 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
- */
24
20
  const BackgroundImageView: FC<BackgroundImageViewProps> = ({
25
21
  useShadowHeader = true,
26
22
  headerBackground = null,
@@ -30,7 +30,6 @@ 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;
34
33
 
35
34
  const fadeStyle = useAnimatedStyle(() => ({
36
35
  opacity: interpolate(
@@ -48,7 +47,8 @@ const DuoGlassBackground: React.FC<{
48
47
  StyleSheet.absoluteFill,
49
48
  styles.translucentBar,
50
49
  {
51
- backgroundColor: translucentColor,
50
+ backgroundColor:
51
+ theme.colors.background.surface + TRANSLUCENT_ALPHA,
52
52
  borderBottomColor: theme.colors.border.default,
53
53
  },
54
54
  fadeStyle,
@@ -59,9 +59,8 @@ const DuoGlassBackground: React.FC<{
59
59
  };
60
60
 
61
61
  /**
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.
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
65
64
  */
66
65
  const DuoScrollGradient: React.FC<{
67
66
  animatedValue?: AnimatedCompatValue;
@@ -19,10 +19,6 @@ 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
- */
26
22
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
27
23
  animatedValue,
28
24
  image,
@@ -9,10 +9,6 @@ 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
- */
16
12
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
17
13
  animatedValue,
18
14
  useGradient = true,