@momo-kits/foundation 0.169.6-beta.1 → 0.169.7-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) 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 +48 -49
  6. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  7. package/Application/BottomTab/CurvedContainer.tsx +14 -12
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +12 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  10. package/Application/BottomTab/DuoRailTabBar.tsx +20 -20
  11. package/Application/BottomTab/DuoTabBar.tsx +8 -9
  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 +22 -25
  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 +4 -0
  21. package/Application/Components/HeaderBackground.tsx +4 -0
  22. package/Application/Components/HeaderExtendHeader.tsx +11 -10
  23. package/Application/Components/HeaderLeft.tsx +2 -1
  24. package/Application/Components/HeaderRight.tsx +2 -1
  25. package/Application/Components/HeaderTitle.shared.ts +11 -9
  26. package/Application/Components/HeaderTitle.tsx +4 -3
  27. package/Application/Components/HeaderToolkitAction.tsx +17 -20
  28. package/Application/Components/NavigationButton.tsx +2 -1
  29. package/Application/Components/SearchHeader.tsx +17 -10
  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 +17 -32
  34. package/Application/Localize.ts +7 -0
  35. package/Application/ModalScreen.tsx +9 -3
  36. package/Application/Navigation.ts +14 -16
  37. package/Application/NavigationContainer.tsx +19 -16
  38. package/Application/Navigator.ts +20 -48
  39. package/Application/ScaleSizeProvider.tsx +13 -6
  40. package/Application/StackScreen.tracking.ts +11 -26
  41. package/Application/StackScreen.tsx +8 -35
  42. package/Application/TooltipPortal.tsx +12 -5
  43. package/Application/WidgetContainer.tsx +10 -4
  44. package/Application/modalLayout.ts +13 -14
  45. package/Application/navigationOptions.tsx +21 -17
  46. package/Badge/Badge.tsx +5 -0
  47. package/Badge/BadgeDot.tsx +1 -0
  48. package/Badge/BadgeDotAnimation.tsx +15 -7
  49. package/Badge/BadgeRibbon.tsx +46 -38
  50. package/Badge/Shape.tsx +2 -0
  51. package/Badge/types.ts +2 -2
  52. package/Button/index.tsx +5 -16
  53. package/Button/utils.ts +9 -15
  54. package/CheckBox/index.tsx +4 -0
  55. package/Consts/theme.ts +10 -10
  56. package/Context/index.ts +5 -1
  57. package/Divider/DashDivider.tsx +1 -0
  58. package/Divider/index.tsx +4 -0
  59. package/FoundationList/index.tsx +4 -0
  60. package/Icon/index.tsx +6 -6
  61. package/Icon/types.ts +1 -1
  62. package/IconButton/index.tsx +4 -12
  63. package/Image/index.tsx +9 -7
  64. package/Input/Input.tsx +8 -9
  65. package/Input/InputDropDown.tsx +10 -12
  66. package/Input/InputMoney.tsx +12 -8
  67. package/Input/InputMoneyView.tsx +14 -7
  68. package/Input/InputOTP.tsx +35 -33
  69. package/Input/InputPhoneNumber.tsx +8 -9
  70. package/Input/InputSearch.tsx +4 -6
  71. package/Input/InputTextArea.tsx +4 -0
  72. package/Input/SearchTextTyping.tsx +4 -0
  73. package/Input/SystemTextInput.tsx +7 -2
  74. package/Input/TextTyping.tsx +4 -0
  75. package/Input/common.tsx +26 -12
  76. package/Input/index.tsx +17 -20
  77. package/Input/moneyUtils.ts +10 -4
  78. package/Input/styles.ts +0 -5
  79. package/Input/utils.ts +14 -12
  80. package/Layout/Card.tsx +7 -4
  81. package/Layout/FloatingButton.tsx +4 -0
  82. package/Layout/Item.tsx +6 -2
  83. package/Layout/ItemList.tsx +9 -7
  84. package/Layout/ItemSectionList.tsx +2 -1
  85. package/Layout/ReservedRegions.tsx +25 -25
  86. package/Layout/Screen.tsx +56 -58
  87. package/Layout/Section.tsx +8 -5
  88. package/Layout/TrackingScope.tsx +1 -0
  89. package/Layout/screenContent.tsx +4 -2
  90. package/Layout/screenFooter.tsx +28 -18
  91. package/Layout/screenHeader.tsx +34 -31
  92. package/Layout/screenQuickReturn.tsx +17 -20
  93. package/Layout/screenScroll.ts +4 -7
  94. package/Layout/screenTypes.ts +3 -2
  95. package/Layout/types.ts +2 -4
  96. package/Layout/utils.ts +31 -37
  97. package/Loader/DotLoader.tsx +4 -0
  98. package/Loader/Loader.tsx +4 -0
  99. package/Loader/ProgressBar.tsx +1 -0
  100. package/Loader/Spinner.tsx +4 -0
  101. package/Loader/utils.ts +11 -7
  102. package/Pagination/Dot.tsx +4 -0
  103. package/Pagination/PaginationDot.tsx +1 -0
  104. package/Pagination/PaginationNumber.tsx +5 -3
  105. package/Pagination/PaginationScroll.tsx +5 -0
  106. package/Pagination/PaginationWhiteDot.tsx +1 -0
  107. package/Pagination/index.tsx +4 -0
  108. package/Pagination/types.ts +2 -2
  109. package/Popup/PopupNotify.tsx +44 -48
  110. package/Popup/PopupPromotion.tsx +4 -7
  111. package/Popup/types.ts +4 -4
  112. package/Popup/utils.ts +4 -0
  113. package/Radio/index.tsx +4 -0
  114. package/Skeleton/index.tsx +8 -0
  115. package/Switch/index.tsx +1 -0
  116. package/Tag/index.tsx +5 -1
  117. package/Tag/types.ts +3 -3
  118. package/Text/index.tsx +14 -3
  119. package/Text/styles.ts +1 -1
  120. package/Text/types.ts +1 -6
  121. package/Text/utils.ts +20 -4
  122. package/Title/index.tsx +41 -32
  123. package/ViewBoundary/index.tsx +4 -2
  124. package/index.ts +0 -6
  125. package/package.json +1 -1
  126. package/Application/__tests__/modalLayout.test.ts +0 -128
@@ -23,7 +23,7 @@ import {
23
23
  import type { DuoTabBarProps } from './DuoTabBar';
24
24
 
25
25
  /**
26
- * Center action icon: an icon name drawn at the CTA size, or a custom node
26
+ * Center action icon: an icon name drawn at the CTA size, or a custom node.
27
27
  */
28
28
  const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
29
29
  if (typeof icon === 'string') {
@@ -39,7 +39,7 @@ const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
39
39
  };
40
40
 
41
41
  /**
42
- * vertical container (.Nav/Duo/Item column) with the sliding active indicator
42
+ * Vertical container (.Nav/Duo/Item column) with the sliding active indicator.
43
43
  */
44
44
  const Rail: React.FC<{
45
45
  items: TabItem[];
@@ -88,7 +88,7 @@ const Rail: React.FC<{
88
88
  );
89
89
 
90
90
  /**
91
- * center action of the rail: round button with its label, under the container
91
+ * Center action of the rail: a round button with its label, under the container.
92
92
  */
93
93
  const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
94
94
  floatingButton,
@@ -114,13 +114,13 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
114
114
  <CtaIcon icon={floatingButton.icon} />
115
115
  </Animated.View>
116
116
  <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>
117
+ <Text
118
+ numberOfLines={1}
119
+ typography={'label_xs_medium'}
120
+ color={theme.colors.text.secondary}
121
+ >
122
+ {floatingButton.label}
123
+ </Text>
124
124
  {floatingButton.labelContent && (
125
125
  <View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
126
126
  {floatingButton.labelContent}
@@ -132,14 +132,14 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
132
132
  };
133
133
 
134
134
  /**
135
- * vertical bar anchored to the bottom of the Duo side bar, center action below it.
136
- * When the side bar is short, it can shrink to one button showing the current tab
137
- * (HIG "Tab bar compressed"); tapping it expands the rail and compresses the toolbar
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.
138
138
  */
139
139
  const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
140
140
  const { sideBar } = useScreenLayout();
141
- const { tabBarCompressed, expandTabBar, onTabSelected } =
142
- useDuoBarCompression();
141
+ const compression = useDuoBarCompression();
142
+ const { tabBarCompressed, expandTabBar, onTabSelected } = compression;
143
143
  const compactLift = usePressLift();
144
144
  const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
145
145
  const items = getTabItems(props);
@@ -149,8 +149,7 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
149
149
  return null;
150
150
  }
151
151
 
152
- // the rail lines up with the header buttons in the side bar: centered on a 48 lane kept 12
153
- // from the strip's inner edge
152
+ // Lines up with the side bar header buttons: a 48 lane kept 12 from the inner edge.
154
153
  const columnStyle = [
155
154
  styles.railColumn,
156
155
  {
@@ -160,9 +159,10 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
160
159
  },
161
160
  sideBar.edge === 'right' ? styles.railColumnRight : styles.railColumnLeft,
162
161
  ];
163
- const cta = floatingButton && (
164
- <RailCenterAction floatingButton={floatingButton} />
165
- );
162
+ let cta: React.ReactNode;
163
+ if (floatingButton) {
164
+ cta = <RailCenterAction floatingButton={floatingButton} />;
165
+ }
166
166
 
167
167
  if (!tabBarCompressed) {
168
168
  return (
@@ -20,8 +20,8 @@ type DuoTabBarProps = BottomTabBarProps & {
20
20
  };
21
21
 
22
22
  /**
23
- * space tab content leaves under the floating horizontal tab bar (phone and unfolded Duo);
24
- * 0 when the Duo rail sits in the side bar
23
+ * Space tab content leaves under the floating horizontal tab bar (phone and unfolded Duo);
24
+ * 0 when the Duo rail sits in the side bar.
25
25
  */
26
26
  const useDuoTabBarInset = () => {
27
27
  const { isIPhoneDuo, sideBar, insets } = useScreenLayout();
@@ -32,7 +32,7 @@ const useDuoTabBarInset = () => {
32
32
  };
33
33
 
34
34
  /**
35
- * horizontal navigation bar of the unfolded display: 65% of the short side, centered
35
+ * Horizontal navigation bar of the unfolded display: 65% of the short side, centered.
36
36
  */
37
37
  const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
38
38
  const { insets } = useScreenLayout();
@@ -67,15 +67,14 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
67
67
  };
68
68
 
69
69
  /**
70
- * iPhone Duo tab bar: rail in the side bar, or the horizontal bar when unfolded portrait
70
+ * iPhone Duo tab bar: the rail in the side bar, or the horizontal bar when unfolded portrait.
71
71
  */
72
72
  const DuoTabBar: React.FC<DuoTabBarProps> = props => {
73
73
  const { sideBar } = useScreenLayout();
74
- return sideBar ? (
75
- <DuoRailTabBar {...props} />
76
- ) : (
77
- <DuoFloatingTabBar {...props} />
78
- );
74
+ if (sideBar) {
75
+ return <DuoRailTabBar {...props} />;
76
+ }
77
+ return <DuoFloatingTabBar {...props} />;
79
78
  };
80
79
 
81
80
  export type { DuoTabBarProps };
@@ -7,9 +7,10 @@ import Animated, {
7
7
  } from 'react-native-reanimated';
8
8
 
9
9
  import { Colors, Spacing } from '../../Consts';
10
- import { ApplicationContext, MiniAppContext } from '../../Context';
10
+ import { ApplicationContext } from '../../Context';
11
11
  import { BadgeDot } from '../../Badge';
12
12
  import { Text } from '../../Text';
13
+ import ScaleSizeProvider from '../ScaleSizeProvider';
13
14
  import { TabBarMetrics, TabBarMotion } from './metrics';
14
15
 
15
16
  type Props = {
@@ -28,7 +29,8 @@ type Props = {
28
29
  };
29
30
 
30
31
  /**
31
- * badge value shown on a tab: undefined hides it, '' is a dot, numbers cap at 99+
32
+ * Badge value of a tab: undefined hides it, '' is a dot (also 0 when `showDot`), numbers
33
+ * cap at 99+.
32
34
  */
33
35
  export const getTabBadge = (
34
36
  badgeLabel?: string | number,
@@ -52,27 +54,8 @@ export const getTabBadge = (
52
54
  };
53
55
 
54
56
  /**
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)
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.
76
59
  */
77
60
  const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
78
61
  label,
@@ -80,19 +63,21 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
80
63
  }) => {
81
64
  const { theme } = useContext(ApplicationContext);
82
65
  const isNumber = /^\d+\+?$/.test(label);
66
+ let backgroundColor = theme.colors.warning.primary;
67
+ if (isNumber) {
68
+ backgroundColor = theme.colors.error.primary;
69
+ }
83
70
  const badge = (
84
71
  <View
85
72
  style={[
86
73
  styles.badge,
87
74
  {
88
- backgroundColor: isNumber
89
- ? theme.colors.error.primary
90
- : theme.colors.warning.primary,
75
+ backgroundColor,
91
76
  borderColor: theme.colors.background.surface,
92
77
  },
93
78
  ]}
94
79
  >
95
- <FixedScaleText>
80
+ <ScaleSizeProvider scaleSizeMaxRate={1}>
96
81
  <Text
97
82
  numberOfLines={1}
98
83
  typography={'action_xxs_bold'}
@@ -100,7 +85,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
100
85
  >
101
86
  {label}
102
87
  </Text>
103
- </FixedScaleText>
88
+ </ScaleSizeProvider>
104
89
  </View>
105
90
  );
106
91
  if (vertical) {
@@ -110,7 +95,8 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
110
95
  };
111
96
 
112
97
  /**
113
- * 24 icon; active and inactive icons are stacked and cross-faded on focus
98
+ * Tab icon (24) with its badge; active and inactive icons are stacked and cross-fade on
99
+ * focus.
114
100
  */
115
101
  export function TabBarIcon({
116
102
  focused,
@@ -29,8 +29,7 @@ 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
- // 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)
32
+ // Pad clear of the floating bar unless a Screen inside claims the inset to scroll under it.
34
33
  const { inset: tabBarInset } = useContext(BottomTabInsetContext);
35
34
  const [insetClaimed, setInsetClaimed] = useState(false);
36
35
  const tabInsetValue = useMemo(
@@ -54,9 +53,6 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
54
53
  ...data,
55
54
  });
56
55
 
57
- /**
58
- * debug toast
59
- */
60
56
  navigator?.maxApi?.showToastDebug?.({
61
57
  appId: context.appId,
62
58
  message: `${screenName} screen_navigated`,
@@ -1,4 +1,5 @@
1
1
  import {
2
+ BottomTabBarProps,
2
3
  BottomTabNavigationOptions,
3
4
  createBottomTabNavigator,
4
5
  } from '@react-navigation/bottom-tabs';
@@ -13,6 +14,7 @@ import {
13
14
  } from 'react-native';
14
15
  import { Icon } from '../../Icon';
15
16
  import { BottomTabProps } from '../types';
17
+ import ScaleSizeProvider from '../ScaleSizeProvider';
16
18
  import BottomTabBar, { hasCenterAction } from './BottomTabBar';
17
19
  import { TabBarMetrics } from './metrics';
18
20
  import { getTabBadge } from './TabBarIcon';
@@ -23,6 +25,8 @@ import TabScreen from './TabScreen';
23
25
  import { ApplicationContext } from '../../Context';
24
26
  import { useScreenLayout } from '../../Layout';
25
27
 
28
+ type SceneInterpolator = BottomTabNavigationOptions['sceneStyleInterpolator'];
29
+
26
30
  const Tab = createBottomTabNavigator();
27
31
 
28
32
  // the tab screens slide in index order over 280ms, CSS `ease`
@@ -32,12 +36,11 @@ const TAB_SWITCH_SPEC = {
32
36
  };
33
37
 
34
38
  /**
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
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.
37
41
  */
38
- const getTabSlide =
39
- (width: number): BottomTabNavigationOptions['sceneStyleInterpolator'] =>
40
- ({ current }) => ({
42
+ const getTabSlide = (width: number): SceneInterpolator => {
43
+ return ({ current }) => ({
41
44
  sceneStyle: {
42
45
  transform: [
43
46
  {
@@ -49,6 +52,7 @@ const getTabSlide =
49
52
  ],
50
53
  },
51
54
  });
55
+ };
52
56
 
53
57
  /**
54
58
  * App tab navigator: the floating bar on phones, the rail or floating bar on
@@ -67,11 +71,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
67
71
  const { isIPhoneDuo } = useScreenLayout();
68
72
  const { width: windowWidth } = useWindowDimensions();
69
73
  const barInset = useDuoTabBarInset();
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;
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
+ }
75
79
  const initialIndex = tabs.findIndex(i => i.name === initialRouteName);
76
80
  const activeIndex = React.useRef(initialIndex > 0 ? initialIndex : 0);
77
81
 
@@ -83,15 +87,15 @@ const BottomTab: React.FC<BottomTabProps> = ({
83
87
  });
84
88
  }, [navigation]);
85
89
 
86
- // Focus, not mount: pushing a screen from a tab leaves BottomTab mounted but blurred, and
87
- // onFocus only fires on tab changes.
90
+ // Focus, not mount/onFocus: popping back from a pushed screen only refocuses BottomTab.
88
91
  useFocusEffect(
89
92
  React.useCallback(() => {
90
93
  NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(
91
94
  activeIndex.current !== 0,
92
95
  );
93
- return () =>
96
+ return () => {
94
97
  NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(false);
98
+ };
95
99
  }, []),
96
100
  );
97
101
 
@@ -128,6 +132,21 @@ const BottomTab: React.FC<BottomTabProps> = ({
128
132
  },
129
133
  };
130
134
 
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
+
131
150
  return (
132
151
  <DuoBarCompressionProvider
133
152
  behavior={compressionBehavior}
@@ -145,23 +164,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
145
164
  transitionSpec: TAB_SWITCH_SPEC,
146
165
  sceneStyleInterpolator: getTabSlide(windowWidth),
147
166
  }}
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
- }
167
+ tabBar={renderTabBar}
165
168
  initialRouteName={initialRouteName}
166
169
  >
167
170
  {tabs.map((item, index) => (
@@ -3,10 +3,9 @@ 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
11
  height: 60,
@@ -27,13 +26,10 @@ const TabBarMetrics = {
27
26
  indicatorPaddingHorizontal: Spacing.M,
28
27
  indicatorRadius: Radius.XL,
29
28
  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
29
+ labelMaxScale: 1.2,
32
30
  labelHeight: 18,
33
- // home indicator / gesture bar area under the container
31
+ railLabelHeight: 14,
34
32
  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
33
  foldableMinShortSide: 600,
38
34
  foldableWidthRatio: 0.65,
39
35
  // a 2-tab bar doesn't stretch to the full width: each item is this fixed width and the bar is
@@ -41,9 +37,7 @@ const TabBarMetrics = {
41
37
  twoTabItemWidth: 100,
42
38
  ctaSlotWidth: 92,
43
39
  ctaSize: 48,
44
- // the icon sits 10 inside the 48 button (prototype .cta: 28 icon), 12 inside the 52 rail one
45
40
  ctaIconSize: 28,
46
- // the button rises 22 above the container top edge
47
41
  ctaLift: 22,
48
42
  ctaLabelGap: Spacing.M,
49
43
  ctaLabelHeight: 18,
@@ -64,10 +58,9 @@ const TabBarMetrics = {
64
58
  };
65
59
 
66
60
  /**
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
61
+ * Navigation bar motion (MoMo Navigation Prototype): the active indicator slides to the
62
+ * selected item with a little overshoot, icon and label tints cross-fade, and the center
63
+ * action shrinks while pressed.
71
64
  */
72
65
  const TabBarMotion = {
73
66
  indicator: { duration: 550, easing: Easing.bezier(0.34, 1.4, 0.5, 1) },
@@ -98,7 +91,7 @@ const CTA_SHADOW: BoxShadowValue[] = [
98
91
  ];
99
92
 
100
93
  /**
101
- * inner light and tint of the active indicator: white 12% from below, primary 12% from above
94
+ * Inner light and tint of the active indicator: white 12% from below, primary 12% from above.
102
95
  */
103
96
  const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
104
97
  {
@@ -112,9 +105,9 @@ const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
112
105
  ];
113
106
 
114
107
  /**
115
- * notch dipped into the container top edge under the center action (Figma "Backgroud" path):
108
+ * Notch dipped into the container top edge under the center action (Figma "Backgroud" path):
116
109
  * 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]
110
+ * each point [dx from the center, dy below the top edge].
118
111
  */
119
112
  const NOTCH_HALF_WIDTH = 50.758;
120
113
  const NOTCH_CURVES: [number, number][][] = [
@@ -141,8 +134,8 @@ const NOTCH_CURVES: [number, number][][] = [
141
134
  ];
142
135
 
143
136
  /**
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
137
+ * Width of the horizontal bar: 65% of the short side on an unfolded foldable, otherwise the
138
+ * window minus the side safe areas and margins.
146
139
  */
147
140
  const getHorizontalBarWidth = (
148
141
  width: number,
@@ -168,12 +161,16 @@ const getHorizontalBarWidth = (
168
161
  };
169
162
 
170
163
  /**
171
- * height of the vertical rail container, plus the center action under it
164
+ * Height of the vertical rail container, plus the center action under it.
172
165
  */
173
- const getRailHeight = (tabCount: number, hasCta: boolean) =>
174
- tabCount * TabBarMetrics.railItemHeight +
175
- TabBarMetrics.railPaddingBottom +
176
- (hasCta ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
166
+ const getRailHeight = (tabCount: number, hasCta: boolean) => {
167
+ const itemsHeight = tabCount * TabBarMetrics.railItemHeight;
168
+ let ctaHeight = 0;
169
+ if (hasCta) {
170
+ ctaHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
171
+ }
172
+ return itemsHeight + TabBarMetrics.railPaddingBottom + ctaHeight;
173
+ };
177
174
 
178
175
  export {
179
176
  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);