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

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 (143) hide show
  1. package/Application/BottomSheet.hooks.ts +8 -8
  2. package/Application/BottomSheet.parts.tsx +44 -34
  3. package/Application/BottomSheet.tsx +40 -37
  4. package/Application/BottomTab/ActiveIndicator.tsx +20 -15
  5. package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
  6. package/Application/BottomTab/BottomTabBar.tsx +57 -72
  7. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  8. package/Application/BottomTab/CurvedContainer.tsx +22 -14
  9. package/Application/BottomTab/CustomBottomTabItem.tsx +15 -15
  10. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  11. package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
  12. package/Application/BottomTab/DuoTabBar.tsx +23 -13
  13. package/Application/BottomTab/TabBarIcon.tsx +15 -29
  14. package/Application/BottomTab/TabScreen.tsx +1 -5
  15. package/Application/BottomTab/index.tsx +33 -30
  16. package/Application/BottomTab/metrics.ts +46 -26
  17. package/Application/BottomTab/tabItems.ts +1 -1
  18. package/Application/BottomTab/usePressLift.ts +2 -2
  19. package/Application/Components/BackgroundImageView.tsx +4 -0
  20. package/Application/Components/DuoGlassBackground.tsx +5 -4
  21. package/Application/Components/HeaderAnimated.tsx +4 -0
  22. package/Application/Components/HeaderBackground.tsx +4 -0
  23. package/Application/Components/HeaderExtendHeader.tsx +11 -10
  24. package/Application/Components/HeaderLeft.tsx +2 -1
  25. package/Application/Components/HeaderRight.tsx +2 -1
  26. package/Application/Components/HeaderTitle.shared.ts +11 -9
  27. package/Application/Components/HeaderTitle.tsx +4 -3
  28. package/Application/Components/HeaderToolkitAction.tsx +17 -20
  29. package/Application/Components/NavigationButton.tsx +2 -1
  30. package/Application/Components/SearchHeader.tsx +17 -10
  31. package/Application/Components/TitleJourney.tsx +2 -2
  32. package/Application/Components/TitleLocation.tsx +2 -2
  33. package/Application/Components/TitleUser.tsx +2 -2
  34. package/Application/Components/useToolkitActions.tsx +17 -32
  35. package/Application/Localize.ts +7 -0
  36. package/Application/ModalScreen.tsx +10 -10
  37. package/Application/Navigation.ts +14 -16
  38. package/Application/NavigationContainer.tsx +19 -18
  39. package/Application/Navigator.ts +20 -48
  40. package/Application/ScaleSizeProvider.tsx +13 -6
  41. package/Application/StackScreen.tracking.ts +11 -33
  42. package/Application/StackScreen.tsx +10 -54
  43. package/Application/TooltipPortal.tsx +12 -5
  44. package/Application/WidgetContainer.tsx +10 -4
  45. package/Application/modalLayout.ts +13 -14
  46. package/Application/navigationOptions.tsx +21 -17
  47. package/Application/types.tabs.ts +0 -1
  48. package/Badge/Badge.tsx +5 -0
  49. package/Badge/BadgeDot.tsx +1 -0
  50. package/Badge/BadgeDotAnimation.tsx +15 -7
  51. package/Badge/BadgeRibbon.tsx +46 -38
  52. package/Badge/Shape.tsx +2 -0
  53. package/Badge/types.ts +2 -2
  54. package/Button/index.tsx +5 -16
  55. package/Button/utils.ts +9 -15
  56. package/CheckBox/index.tsx +4 -0
  57. package/Consts/theme.ts +10 -10
  58. package/Context/index.ts +5 -1
  59. package/Divider/DashDivider.tsx +1 -0
  60. package/Divider/index.tsx +4 -0
  61. package/FoundationList/index.tsx +4 -0
  62. package/Icon/index.tsx +6 -6
  63. package/Icon/types.ts +1 -1
  64. package/IconButton/index.tsx +4 -12
  65. package/Image/index.tsx +9 -7
  66. package/Input/Input.tsx +8 -9
  67. package/Input/InputDropDown.tsx +10 -12
  68. package/Input/InputMoney.tsx +12 -8
  69. package/Input/InputMoneyView.tsx +14 -7
  70. package/Input/InputOTP.tsx +35 -33
  71. package/Input/InputPhoneNumber.tsx +8 -9
  72. package/Input/InputSearch.tsx +4 -6
  73. package/Input/InputTextArea.tsx +4 -0
  74. package/Input/SearchTextTyping.tsx +4 -0
  75. package/Input/SystemTextInput.tsx +7 -2
  76. package/Input/TextTyping.tsx +4 -0
  77. package/Input/common.tsx +26 -12
  78. package/Input/index.tsx +17 -20
  79. package/Input/moneyUtils.ts +10 -4
  80. package/Input/styles.ts +0 -5
  81. package/Input/utils.ts +14 -12
  82. package/Layout/Card.tsx +7 -4
  83. package/Layout/FloatingButton.tsx +4 -0
  84. package/Layout/Item.tsx +6 -2
  85. package/Layout/ItemList.tsx +9 -7
  86. package/Layout/ItemSectionList.tsx +2 -1
  87. package/Layout/ReservedRegions.tsx +25 -25
  88. package/Layout/Screen.tsx +56 -58
  89. package/Layout/Section.tsx +8 -5
  90. package/Layout/TrackingScope.tsx +1 -0
  91. package/Layout/screenContent.tsx +4 -2
  92. package/Layout/screenFooter.tsx +28 -18
  93. package/Layout/screenHeader.tsx +34 -31
  94. package/Layout/screenQuickReturn.tsx +17 -20
  95. package/Layout/screenScroll.ts +4 -7
  96. package/Layout/screenTypes.ts +3 -2
  97. package/Layout/types.ts +2 -4
  98. package/Layout/utils.ts +31 -37
  99. package/Loader/DotLoader.tsx +4 -0
  100. package/Loader/Loader.tsx +4 -0
  101. package/Loader/ProgressBar.tsx +1 -0
  102. package/Loader/Spinner.tsx +4 -0
  103. package/Loader/utils.ts +11 -7
  104. package/Pagination/Dot.tsx +4 -0
  105. package/Pagination/PaginationDot.tsx +1 -0
  106. package/Pagination/PaginationNumber.tsx +5 -3
  107. package/Pagination/PaginationScroll.tsx +5 -0
  108. package/Pagination/PaginationWhiteDot.tsx +1 -0
  109. package/Pagination/index.tsx +4 -0
  110. package/Pagination/types.ts +2 -2
  111. package/Popup/PopupNotify.tsx +44 -48
  112. package/Popup/PopupPromotion.tsx +4 -7
  113. package/Popup/types.ts +4 -4
  114. package/Popup/utils.ts +4 -0
  115. package/Radio/index.tsx +4 -0
  116. package/Skeleton/index.tsx +8 -0
  117. package/Switch/index.tsx +1 -0
  118. package/Tag/index.tsx +5 -1
  119. package/Tag/types.ts +3 -3
  120. package/Text/index.tsx +14 -3
  121. package/Text/styles.ts +1 -1
  122. package/Text/types.ts +1 -6
  123. package/Text/utils.ts +20 -4
  124. package/Title/index.tsx +41 -32
  125. package/ViewBoundary/index.tsx +4 -2
  126. package/index.ts +0 -6
  127. package/package.json +1 -1
  128. package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
  129. package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
  130. package/Application/KitAdoption/AdoptionStore.ts +0 -80
  131. package/Application/KitAdoption/Authorship/utils.ts +0 -137
  132. package/Application/KitAdoption/Slots/utils.ts +0 -68
  133. package/Application/KitAdoption/constants.ts +0 -120
  134. package/Application/KitAdoption/index.ts +0 -12
  135. package/Application/KitAdoption/styles.ts +0 -55
  136. package/Application/KitAdoption/types.ts +0 -61
  137. package/Application/KitAdoption/utils.ts +0 -376
  138. package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
  139. package/Application/__tests__/KitAdoption.test.ts +0 -283
  140. package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
  141. package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
  142. package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
  143. package/Application/__tests__/modalLayout.test.ts +0 -128
@@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native';
3
3
  import Svg, { Defs, FeGaussianBlur, Filter, Path } from 'react-native-svg';
4
4
  import { Colors } from '../../Consts';
5
5
  import { ApplicationContext } from '../../Context';
6
- import { NOTCH_CURVES, NOTCH_HALF_WIDTH } from './metrics';
6
+ import { NOTCH_CURVES, NOTCH_HALF_WIDTH, TabBarMetrics } from './metrics';
7
7
 
8
8
  type Props = {
9
9
  width: number;
@@ -15,16 +15,18 @@ type Props = {
15
15
 
16
16
  // room for the floating shadow around the shape: the key shadow reaches 12 + 3 × 16 below it
17
17
  const SHADOW_PADDING = 64;
18
- // Figma Shadow/floating: 12% blur 24, plus 4% blur 32 spread -16 offset 12; svg blur takes
19
- // half the CSS blur radius, the spread shrinks the shape the key shadow is cast from
18
+ /**
19
+ * Figma Shadow/floating: 12% blur 24, plus 4% blur 32 spread -16 offset 12. SVG blur takes
20
+ * half the CSS blur radius; the spread shrinks the shape the key shadow is cast from.
21
+ */
20
22
  const AMBIENT_BLUR = 12;
21
23
  const KEY_BLUR = 16;
22
24
  const KEY_SPREAD = 16;
23
25
  const KEY_OFFSET_Y = 12;
24
26
 
25
27
  /**
26
- * rounded container path placed at (`x`, `y`) on the canvas, with the notch dipped into the
27
- * top edge around `notchCenterX`
28
+ * SVG path of the rounded container placed at (`x`, `y`) on the canvas, with the notch
29
+ * dipped into the top edge around `notchCenterX`.
28
30
  */
29
31
  const getContainerPath = (
30
32
  width: number,
@@ -43,11 +45,10 @@ const getContainerPath = (
43
45
  if (notchCenterX !== undefined) {
44
46
  const cx = x + notchCenterX;
45
47
  const point = ([dx, dy]: [number, number]) => `${cx + dx} ${top + dy}`;
46
- notch =
47
- ` L ${cx - NOTCH_HALF_WIDTH} ${top}` +
48
- NOTCH_CURVES.map(
49
- ([c1, c2, end]) => ` C ${point(c1)} ${point(c2)} ${point(end)}`,
50
- ).join('');
48
+ const curves = NOTCH_CURVES.map(([c1, c2, end]) => {
49
+ return ` C ${point(c1)} ${point(c2)} ${point(end)}`;
50
+ });
51
+ notch = ` L ${cx - NOTCH_HALF_WIDTH} ${top}${curves.join('')}`;
51
52
  }
52
53
  return (
53
54
  `M ${left + r} ${top}` +
@@ -64,8 +65,8 @@ const getContainerPath = (
64
65
  };
65
66
 
66
67
  /**
67
- * navigation bar container: surface fill with the floating shadow, drawn as a shape so
68
- * the shadow follows the notch
68
+ * Navigation bar container: surface fill with the floating shadow, drawn as an SVG shape
69
+ * so the shadow follows the notch.
69
70
  */
70
71
  const CurvedContainer: React.FC<Props> = ({
71
72
  width,
@@ -87,6 +88,7 @@ const CurvedContainer: React.FC<Props> = ({
87
88
  SHADOW_PADDING,
88
89
  notchCenterX,
89
90
  );
91
+ const hasKeyShadow = width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD;
90
92
 
91
93
  return (
92
94
  <Svg
@@ -123,7 +125,7 @@ const CurvedContainer: React.FC<Props> = ({
123
125
  fillOpacity={0.12}
124
126
  filter={'url(#ambientShadow)'}
125
127
  />
126
- {width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD && (
128
+ {hasKeyShadow && (
127
129
  <Path
128
130
  d={getContainerPath(
129
131
  width - 2 * KEY_SPREAD,
@@ -137,7 +139,13 @@ const CurvedContainer: React.FC<Props> = ({
137
139
  filter={'url(#keyShadow)'}
138
140
  />
139
141
  )}
140
- <Path d={path} fill={theme.colors.background.surface} />
142
+ <Path
143
+ d={path}
144
+ fill={theme.colors.background.surface}
145
+ fillOpacity={TabBarMetrics.backgroundOpacity}
146
+ stroke={theme.colors.border.default}
147
+ strokeWidth={1}
148
+ />
141
149
  </Svg>
142
150
  );
143
151
  };
@@ -10,10 +10,10 @@ import Animated, {
10
10
  useSharedValue,
11
11
  withTiming,
12
12
  } from 'react-native-reanimated';
13
- import { FixedScaleText, TabBarIcon } from './TabBarIcon';
13
+ import { TabBarIcon } from './TabBarIcon';
14
14
  import { Text } from '../../Text';
15
15
  import { MiniAppContext } from '../../Context';
16
- import { Colors, Spacing } from '../../Consts';
16
+ import { Colors } from '../../Consts';
17
17
  import { TabBarMetrics, TabBarMotion } from './metrics';
18
18
 
19
19
  type IconRenderer = (props: {
@@ -31,14 +31,12 @@ type TabItemContentProps = {
31
31
  badge?: string | number;
32
32
  // rail item (.Nav/Duo/Item) instead of the horizontal one (.Nav/itemTab)
33
33
  vertical?: boolean;
34
- // keep the label at its design size, the Duo displays otherwise scale it up
35
- fixedFontScale?: boolean;
36
34
  hideLabel?: boolean;
37
35
  };
38
36
 
39
37
  /**
40
- * icon in its indicator slot, label under it. The bar slides one shared active indicator
41
- * behind the slots; the icon and label tint cross-fade on focus
38
+ * Tab item body: the icon in its indicator slot, the label in a fixed slot under it so a
39
+ * scaled label grows down. The bar slides one shared active indicator behind the icon slots.
42
40
  */
43
41
  const TabItemContent: React.FC<TabItemContentProps> = ({
44
42
  label,
@@ -48,7 +46,6 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
48
46
  icon,
49
47
  badge,
50
48
  vertical,
51
- fixedFontScale,
52
49
  hideLabel,
53
50
  }) => {
54
51
  const progress = useSharedValue(focused ? 1 : 0);
@@ -72,9 +69,9 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
72
69
  </Text>
73
70
  );
74
71
 
75
- // the active label fades in over the inactive one
76
- const text = (
77
- <View style={!vertical && styles.labelBox}>
72
+ // Active label fades in over the inactive one; a scaled label overflows down.
73
+ const labelNode = (
74
+ <View style={vertical ? styles.railLabelSlot : styles.labelSlot}>
78
75
  {renderText(inactiveTintColor)}
79
76
  <Animated.View
80
77
  pointerEvents={'none'}
@@ -97,8 +94,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
97
94
  renderIcon={icon}
98
95
  />
99
96
  </View>
100
- {!hideLabel &&
101
- (fixedFontScale ? <FixedScaleText>{text}</FixedScaleText> : text)}
97
+ {!hideLabel && labelNode}
102
98
  </>
103
99
  );
104
100
  };
@@ -112,7 +108,7 @@ type CustomBottomTabItemProps = TabItemContentProps & {
112
108
  };
113
109
 
114
110
  /**
115
- * horizontal navigation item (.Nav/itemTab): fills an even share of the container
111
+ * Horizontal navigation item (.Nav/itemTab), filling an even share of the container.
116
112
  */
117
113
  const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
118
114
  onPress,
@@ -147,7 +143,7 @@ const styles = StyleSheet.create({
147
143
  minWidth: 1,
148
144
  alignItems: 'center',
149
145
  justifyContent: 'center',
150
- gap: Spacing.XXS,
146
+ gap: TabBarMetrics.itemIconLabelGap,
151
147
  },
152
148
  slot: {
153
149
  height: TabBarMetrics.indicatorHeight,
@@ -164,9 +160,13 @@ const styles = StyleSheet.create({
164
160
  paddingVertical: TabBarMetrics.railIndicatorPaddingVertical,
165
161
  paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
166
162
  },
167
- labelBox: {
163
+ labelSlot: {
164
+ height: TabBarMetrics.labelHeight,
168
165
  alignSelf: 'stretch',
169
166
  },
167
+ railLabelSlot: {
168
+ height: TabBarMetrics.railLabelHeight,
169
+ },
170
170
  label: {
171
171
  textAlign: 'center',
172
172
  },
@@ -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;
31
32
  // Back, then the right toolkit: help + home capsule
32
- const DEFAULT_TOOLBAR_HEIGHT =
33
- DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch;
33
+ const DEFAULT_TOOLBAR_HEIGHT = BACK_SLOT_HEIGHT + 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,16 +39,12 @@ const NO_COMPRESSION: DuoBarCompression = {
39
39
  tabBarCompressed: false,
40
40
  };
41
41
 
42
- const DuoBarCompressionContext =
43
- createContext<DuoBarCompression>(NO_COMPRESSION);
42
+ const DuoBarCompressionContext = createContext(NO_COMPRESSION);
44
43
 
45
44
  /**
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)
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.
52
48
  */
53
49
  const DuoBarCompressionProvider: React.FC<{
54
50
  behavior?: DuoBarCompressionBehavior;
@@ -71,18 +67,16 @@ const DuoBarCompressionProvider: React.FC<{
71
67
  }
72
68
  const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
73
69
  const tabBar = getRailHeight(tabCount, hasQr);
74
- const compressedTabBar =
75
- DuoMetrics.controlSize +
76
- (hasQr ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
70
+ const qrHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
71
+ const compressedTabBar = DuoMetrics.controlSize + (hasQr ? qrHeight : 0);
77
72
  const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
78
73
 
79
74
  if (fits(toolbarHeight, tabBar)) {
80
75
  return { ...NO_COMPRESSION, setToolbarHeight };
81
76
  }
82
- const canSwap =
83
- fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar) &&
84
- fits(toolbarHeight, compressedTabBar);
85
- if (canSwap) {
77
+ const canCompressToolbar = fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar);
78
+ const canCompressTabBar = fits(toolbarHeight, compressedTabBar);
79
+ if (canCompressToolbar && canCompressTabBar) {
86
80
  return {
87
81
  toolbarCompressed: expanded === 'tabBar',
88
82
  tabBarCompressed: expanded === 'toolbar',
@@ -97,8 +91,7 @@ const DuoBarCompressionProvider: React.FC<{
97
91
  expandTabBar: () => setOpened('tabBar'),
98
92
  setToolbarHeight,
99
93
  };
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
94
+ // An opened rail shows even if only the group gap is short; picking a tab closes it.
102
95
  if (opened === 'tabBar') {
103
96
  return {
104
97
  ...open,
@@ -122,6 +115,7 @@ const DuoBarCompressionProvider: React.FC<{
122
115
  );
123
116
  };
124
117
 
118
+ /** Compression state of the Duo side bars; no compression outside the provider. */
125
119
  const useDuoBarCompression = () => useContext(DuoBarCompressionContext);
126
120
 
127
121
  export { DuoBarCompressionProvider, useDuoBarCompression };
@@ -13,7 +13,6 @@ 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';
17
16
  import { usePressLift } from './usePressLift';
18
17
  import { ActiveIndicator } from './ActiveIndicator';
19
18
  import {
@@ -24,7 +23,7 @@ import {
24
23
  import type { DuoTabBarProps } from './DuoTabBar';
25
24
 
26
25
  /**
27
- * 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.
28
27
  */
29
28
  const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
30
29
  if (typeof icon === 'string') {
@@ -40,7 +39,7 @@ const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
40
39
  };
41
40
 
42
41
  /**
43
- * vertical container (.Nav/Duo/Item column) with the sliding active indicator
42
+ * Vertical container (.Nav/Duo/Item column) with the sliding active indicator.
44
43
  */
45
44
  const Rail: React.FC<{
46
45
  items: TabItem[];
@@ -76,7 +75,6 @@ const Rail: React.FC<{
76
75
  >
77
76
  <TabItemContent
78
77
  vertical
79
- fixedFontScale
80
78
  label={item.label}
81
79
  focused={item.focused}
82
80
  icon={item.icon}
@@ -90,7 +88,7 @@ const Rail: React.FC<{
90
88
  );
91
89
 
92
90
  /**
93
- * 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.
94
92
  */
95
93
  const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
96
94
  floatingButton,
@@ -116,15 +114,13 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
116
114
  <CtaIcon icon={floatingButton.icon} />
117
115
  </Animated.View>
118
116
  <View style={styles.railCtaLabel}>
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>
117
+ <Text
118
+ numberOfLines={1}
119
+ typography={'label_xs_medium'}
120
+ color={theme.colors.text.secondary}
121
+ >
122
+ {floatingButton.label}
123
+ </Text>
128
124
  {floatingButton.labelContent && (
129
125
  <View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
130
126
  {floatingButton.labelContent}
@@ -136,14 +132,14 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
136
132
  };
137
133
 
138
134
  /**
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
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.
142
138
  */
143
139
  const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
144
140
  const { sideBar } = useScreenLayout();
145
- const { tabBarCompressed, expandTabBar, onTabSelected } =
146
- useDuoBarCompression();
141
+ const compression = useDuoBarCompression();
142
+ const { tabBarCompressed, expandTabBar, onTabSelected } = compression;
147
143
  const compactLift = usePressLift();
148
144
  const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
149
145
  const items = getTabItems(props);
@@ -153,8 +149,7 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
153
149
  return null;
154
150
  }
155
151
 
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
152
+ // Lines up with the side bar header buttons: a 48 lane kept 12 from the inner edge.
158
153
  const columnStyle = [
159
154
  styles.railColumn,
160
155
  {
@@ -164,9 +159,10 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
164
159
  },
165
160
  sideBar.edge === 'right' ? styles.railColumnRight : styles.railColumnLeft,
166
161
  ];
167
- const cta = floatingButton && (
168
- <RailCenterAction floatingButton={floatingButton} />
169
- );
162
+ let cta: React.ReactNode;
163
+ if (floatingButton) {
164
+ cta = <RailCenterAction floatingButton={floatingButton} />;
165
+ }
170
166
 
171
167
  if (!tabBarCompressed) {
172
168
  return (
@@ -3,7 +3,11 @@ import React from 'react';
3
3
  import { useWindowDimensions, View } from 'react-native';
4
4
  import { useScreenLayout } from '../../Layout/utils';
5
5
  import { FloatingButtonProps } from '../types';
6
- import { getSystemIndicatorHeight, HorizontalTabBar } from './BottomTabBar';
6
+ import {
7
+ getSystemIndicatorHeight,
8
+ hasCenterAction,
9
+ HorizontalTabBar,
10
+ } from './BottomTabBar';
7
11
  import { getHorizontalBarWidth, TabBarMetrics } from './metrics';
8
12
  import { getTabItems } from './tabItems';
9
13
  import { DuoRailTabBar } from './DuoRailTabBar';
@@ -16,8 +20,8 @@ type DuoTabBarProps = BottomTabBarProps & {
16
20
  };
17
21
 
18
22
  /**
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
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.
21
25
  */
22
26
  const useDuoTabBarInset = () => {
23
27
  const { isIPhoneDuo, sideBar, insets } = useScreenLayout();
@@ -28,12 +32,13 @@ const useDuoTabBarInset = () => {
28
32
  };
29
33
 
30
34
  /**
31
- * 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.
32
36
  */
33
37
  const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
34
38
  const { insets } = useScreenLayout();
35
39
  const { width, height } = useWindowDimensions();
36
40
  const { floatingButton, activeTintColor, inactiveTintColor } = props;
41
+ const tabItems = getTabItems(props);
37
42
 
38
43
  return (
39
44
  <View
@@ -44,9 +49,15 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
44
49
  ]}
45
50
  >
46
51
  <HorizontalTabBar
47
- fixedFontScale
48
- items={getTabItems(props)}
49
- width={getHorizontalBarWidth(width, height, insets.left, insets.right)}
52
+ items={tabItems}
53
+ width={getHorizontalBarWidth(
54
+ width,
55
+ height,
56
+ insets.left,
57
+ insets.right,
58
+ tabItems.length,
59
+ hasCenterAction(floatingButton, tabItems.length),
60
+ )}
50
61
  floatingButton={floatingButton}
51
62
  activeTintColor={activeTintColor}
52
63
  inactiveTintColor={inactiveTintColor}
@@ -56,15 +67,14 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
56
67
  };
57
68
 
58
69
  /**
59
- * 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.
60
71
  */
61
72
  const DuoTabBar: React.FC<DuoTabBarProps> = props => {
62
73
  const { sideBar } = useScreenLayout();
63
- return sideBar ? (
64
- <DuoRailTabBar {...props} />
65
- ) : (
66
- <DuoFloatingTabBar {...props} />
67
- );
74
+ if (sideBar) {
75
+ return <DuoRailTabBar {...props} />;
76
+ }
77
+ return <DuoFloatingTabBar {...props} />;
68
78
  };
69
79
 
70
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`,