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

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 (145) 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.styles.ts +1 -18
  6. package/Application/BottomTab/BottomTabBar.tsx +72 -57
  7. package/Application/BottomTab/BottomTabInset.tsx +4 -8
  8. package/Application/BottomTab/CurvedContainer.tsx +14 -22
  9. package/Application/BottomTab/CustomBottomTabItem.tsx +15 -15
  10. package/Application/BottomTab/DuoBarCompression.tsx +19 -13
  11. package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
  12. package/Application/BottomTab/DuoTabBar.tsx +13 -23
  13. package/Application/BottomTab/TabBarIcon.tsx +29 -15
  14. package/Application/BottomTab/TabScreen.tsx +5 -1
  15. package/Application/BottomTab/index.tsx +30 -33
  16. package/Application/BottomTab/metrics.ts +26 -46
  17. package/Application/BottomTab/tabItems.ts +1 -1
  18. package/Application/BottomTab/usePressLift.ts +2 -2
  19. package/Application/Components/BackgroundImageView.tsx +0 -4
  20. package/Application/Components/DuoGlassBackground.tsx +4 -5
  21. package/Application/Components/HeaderAnimated.tsx +0 -4
  22. package/Application/Components/HeaderBackground.tsx +0 -4
  23. package/Application/Components/HeaderExtendHeader.tsx +10 -11
  24. package/Application/Components/HeaderLeft.tsx +1 -2
  25. package/Application/Components/HeaderRight.tsx +1 -2
  26. package/Application/Components/HeaderTitle.shared.ts +9 -11
  27. package/Application/Components/HeaderTitle.tsx +3 -4
  28. package/Application/Components/HeaderToolkitAction.tsx +20 -17
  29. package/Application/Components/NavigationButton.tsx +1 -2
  30. package/Application/Components/SearchHeader.tsx +10 -17
  31. package/Application/Components/TitleJourney.tsx +2 -2
  32. package/Application/Components/TitleLocation.tsx +2 -2
  33. package/Application/Components/TitleUser.tsx +2 -2
  34. package/Application/Components/useToolkitActions.tsx +32 -17
  35. package/Application/KitAdoption/AdoptionBadge.tsx +119 -0
  36. package/Application/KitAdoption/AdoptionProbe.tsx +166 -0
  37. package/Application/KitAdoption/AdoptionStore.ts +80 -0
  38. package/Application/KitAdoption/Authorship/utils.ts +137 -0
  39. package/Application/KitAdoption/Slots/utils.ts +68 -0
  40. package/Application/KitAdoption/constants.ts +120 -0
  41. package/Application/KitAdoption/index.ts +12 -0
  42. package/Application/KitAdoption/styles.ts +55 -0
  43. package/Application/KitAdoption/types.ts +61 -0
  44. package/Application/KitAdoption/utils.ts +376 -0
  45. package/Application/Localize.ts +0 -7
  46. package/Application/ModalScreen.tsx +10 -10
  47. package/Application/Navigation.ts +16 -14
  48. package/Application/NavigationContainer.tsx +18 -19
  49. package/Application/Navigator.ts +48 -20
  50. package/Application/ScaleSizeProvider.tsx +6 -13
  51. package/Application/StackScreen.tracking.ts +33 -11
  52. package/Application/StackScreen.tsx +54 -10
  53. package/Application/TooltipPortal.tsx +5 -12
  54. package/Application/WidgetContainer.tsx +4 -10
  55. package/Application/__tests__/KitAdoption.integration.test.tsx +253 -0
  56. package/Application/__tests__/KitAdoption.test.ts +283 -0
  57. package/Application/__tests__/KitAdoptionAuthorship.test.ts +132 -0
  58. package/Application/__tests__/KitAdoptionStore.test.ts +110 -0
  59. package/Application/__tests__/KitAdoptionStructural.test.ts +193 -0
  60. package/Application/__tests__/modalLayout.test.ts +128 -0
  61. package/Application/modalLayout.ts +14 -13
  62. package/Application/navigationOptions.tsx +17 -21
  63. package/Application/types.tabs.ts +1 -0
  64. package/Badge/Badge.tsx +0 -5
  65. package/Badge/BadgeDot.tsx +0 -1
  66. package/Badge/BadgeDotAnimation.tsx +7 -15
  67. package/Badge/BadgeRibbon.tsx +38 -46
  68. package/Badge/Shape.tsx +0 -2
  69. package/Badge/types.ts +2 -2
  70. package/Button/index.tsx +16 -5
  71. package/Button/utils.ts +15 -9
  72. package/CheckBox/index.tsx +0 -4
  73. package/Consts/theme.ts +10 -10
  74. package/Context/index.ts +1 -5
  75. package/Divider/DashDivider.tsx +0 -1
  76. package/Divider/index.tsx +0 -4
  77. package/FoundationList/index.tsx +0 -4
  78. package/Icon/index.tsx +6 -6
  79. package/Icon/types.ts +1 -1
  80. package/IconButton/index.tsx +12 -4
  81. package/Image/index.tsx +7 -9
  82. package/Input/Input.tsx +9 -8
  83. package/Input/InputDropDown.tsx +12 -10
  84. package/Input/InputMoney.tsx +8 -12
  85. package/Input/InputMoneyView.tsx +7 -14
  86. package/Input/InputOTP.tsx +33 -35
  87. package/Input/InputPhoneNumber.tsx +9 -8
  88. package/Input/InputSearch.tsx +6 -4
  89. package/Input/InputTextArea.tsx +0 -4
  90. package/Input/SearchTextTyping.tsx +0 -4
  91. package/Input/SystemTextInput.tsx +2 -7
  92. package/Input/TextTyping.tsx +0 -4
  93. package/Input/common.tsx +12 -26
  94. package/Input/index.tsx +20 -17
  95. package/Input/moneyUtils.ts +4 -10
  96. package/Input/styles.ts +5 -0
  97. package/Input/utils.ts +12 -14
  98. package/Layout/Card.tsx +13 -16
  99. package/Layout/FloatingButton.tsx +0 -4
  100. package/Layout/Item.tsx +2 -6
  101. package/Layout/ItemList.tsx +7 -9
  102. package/Layout/ItemSectionList.tsx +1 -2
  103. package/Layout/ReservedRegions.tsx +25 -25
  104. package/Layout/Screen.tsx +58 -56
  105. package/Layout/Section.tsx +13 -17
  106. package/Layout/TrackingScope.tsx +0 -1
  107. package/Layout/constants.ts +19 -0
  108. package/Layout/screenContent.tsx +2 -4
  109. package/Layout/screenFooter.tsx +18 -28
  110. package/Layout/screenHeader.tsx +31 -34
  111. package/Layout/screenQuickReturn.tsx +20 -17
  112. package/Layout/screenScroll.ts +7 -4
  113. package/Layout/screenTypes.ts +2 -3
  114. package/Layout/types.ts +41 -3
  115. package/Layout/useGridSystem.ts +49 -0
  116. package/Layout/utils.ts +37 -38
  117. package/Loader/DotLoader.tsx +0 -4
  118. package/Loader/Loader.tsx +0 -4
  119. package/Loader/ProgressBar.tsx +0 -1
  120. package/Loader/Spinner.tsx +0 -4
  121. package/Loader/utils.ts +7 -11
  122. package/Pagination/Dot.tsx +0 -4
  123. package/Pagination/PaginationDot.tsx +0 -1
  124. package/Pagination/PaginationNumber.tsx +3 -5
  125. package/Pagination/PaginationScroll.tsx +0 -5
  126. package/Pagination/PaginationWhiteDot.tsx +0 -1
  127. package/Pagination/index.tsx +0 -4
  128. package/Pagination/types.ts +2 -2
  129. package/Popup/PopupNotify.tsx +48 -44
  130. package/Popup/PopupPromotion.tsx +7 -4
  131. package/Popup/types.ts +4 -4
  132. package/Popup/utils.ts +0 -4
  133. package/Radio/index.tsx +0 -4
  134. package/Skeleton/index.tsx +0 -8
  135. package/Switch/index.tsx +0 -1
  136. package/Tag/index.tsx +1 -5
  137. package/Tag/types.ts +3 -3
  138. package/Text/index.tsx +3 -14
  139. package/Text/styles.ts +1 -1
  140. package/Text/types.ts +6 -1
  141. package/Text/utils.ts +4 -20
  142. package/Title/index.tsx +32 -41
  143. package/ViewBoundary/index.tsx +2 -4
  144. package/index.ts +6 -0
  145. package/package.json +1 -1
@@ -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, TabBarMetrics } from './metrics';
6
+ import { NOTCH_CURVES, NOTCH_HALF_WIDTH } from './metrics';
7
7
 
8
8
  type Props = {
9
9
  width: number;
@@ -15,18 +15,16 @@ 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
- /**
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
- */
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
22
20
  const AMBIENT_BLUR = 12;
23
21
  const KEY_BLUR = 16;
24
22
  const KEY_SPREAD = 16;
25
23
  const KEY_OFFSET_Y = 12;
26
24
 
27
25
  /**
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`.
26
+ * rounded container path placed at (`x`, `y`) on the canvas, with the notch dipped into the
27
+ * top edge around `notchCenterX`
30
28
  */
31
29
  const getContainerPath = (
32
30
  width: number,
@@ -45,10 +43,11 @@ const getContainerPath = (
45
43
  if (notchCenterX !== undefined) {
46
44
  const cx = x + notchCenterX;
47
45
  const point = ([dx, dy]: [number, number]) => `${cx + dx} ${top + dy}`;
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('')}`;
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('');
52
51
  }
53
52
  return (
54
53
  `M ${left + r} ${top}` +
@@ -65,8 +64,8 @@ const getContainerPath = (
65
64
  };
66
65
 
67
66
  /**
68
- * Navigation bar container: surface fill with the floating shadow, drawn as an SVG shape
69
- * so the shadow follows the notch.
67
+ * navigation bar container: surface fill with the floating shadow, drawn as a shape so
68
+ * the shadow follows the notch
70
69
  */
71
70
  const CurvedContainer: React.FC<Props> = ({
72
71
  width,
@@ -88,7 +87,6 @@ const CurvedContainer: React.FC<Props> = ({
88
87
  SHADOW_PADDING,
89
88
  notchCenterX,
90
89
  );
91
- const hasKeyShadow = width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD;
92
90
 
93
91
  return (
94
92
  <Svg
@@ -125,7 +123,7 @@ const CurvedContainer: React.FC<Props> = ({
125
123
  fillOpacity={0.12}
126
124
  filter={'url(#ambientShadow)'}
127
125
  />
128
- {hasKeyShadow && (
126
+ {width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD && (
129
127
  <Path
130
128
  d={getContainerPath(
131
129
  width - 2 * KEY_SPREAD,
@@ -139,13 +137,7 @@ const CurvedContainer: React.FC<Props> = ({
139
137
  filter={'url(#keyShadow)'}
140
138
  />
141
139
  )}
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
- />
140
+ <Path d={path} fill={theme.colors.background.surface} />
149
141
  </Svg>
150
142
  );
151
143
  };
@@ -10,10 +10,10 @@ import Animated, {
10
10
  useSharedValue,
11
11
  withTiming,
12
12
  } from 'react-native-reanimated';
13
- import { TabBarIcon } from './TabBarIcon';
13
+ import { FixedScaleText, TabBarIcon } from './TabBarIcon';
14
14
  import { Text } from '../../Text';
15
15
  import { MiniAppContext } from '../../Context';
16
- import { Colors } from '../../Consts';
16
+ import { Colors, Spacing } from '../../Consts';
17
17
  import { TabBarMetrics, TabBarMotion } from './metrics';
18
18
 
19
19
  type IconRenderer = (props: {
@@ -31,12 +31,14 @@ 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;
34
36
  hideLabel?: boolean;
35
37
  };
36
38
 
37
39
  /**
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.
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
40
42
  */
41
43
  const TabItemContent: React.FC<TabItemContentProps> = ({
42
44
  label,
@@ -46,6 +48,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
46
48
  icon,
47
49
  badge,
48
50
  vertical,
51
+ fixedFontScale,
49
52
  hideLabel,
50
53
  }) => {
51
54
  const progress = useSharedValue(focused ? 1 : 0);
@@ -69,9 +72,9 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
69
72
  </Text>
70
73
  );
71
74
 
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}>
75
+ // the active label fades in over the inactive one
76
+ const text = (
77
+ <View style={!vertical && styles.labelBox}>
75
78
  {renderText(inactiveTintColor)}
76
79
  <Animated.View
77
80
  pointerEvents={'none'}
@@ -94,7 +97,8 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
94
97
  renderIcon={icon}
95
98
  />
96
99
  </View>
97
- {!hideLabel && labelNode}
100
+ {!hideLabel &&
101
+ (fixedFontScale ? <FixedScaleText>{text}</FixedScaleText> : text)}
98
102
  </>
99
103
  );
100
104
  };
@@ -108,7 +112,7 @@ type CustomBottomTabItemProps = TabItemContentProps & {
108
112
  };
109
113
 
110
114
  /**
111
- * Horizontal navigation item (.Nav/itemTab), filling an even share of the container.
115
+ * horizontal navigation item (.Nav/itemTab): fills an even share of the container
112
116
  */
113
117
  const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
114
118
  onPress,
@@ -143,7 +147,7 @@ const styles = StyleSheet.create({
143
147
  minWidth: 1,
144
148
  alignItems: 'center',
145
149
  justifyContent: 'center',
146
- gap: TabBarMetrics.itemIconLabelGap,
150
+ gap: Spacing.XXS,
147
151
  },
148
152
  slot: {
149
153
  height: TabBarMetrics.indicatorHeight,
@@ -160,13 +164,9 @@ const styles = StyleSheet.create({
160
164
  paddingVertical: TabBarMetrics.railIndicatorPaddingVertical,
161
165
  paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
162
166
  },
163
- labelSlot: {
164
- height: TabBarMetrics.labelHeight,
167
+ labelBox: {
165
168
  alignSelf: 'stretch',
166
169
  },
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;
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 (
@@ -3,11 +3,7 @@ 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 {
7
- getSystemIndicatorHeight,
8
- hasCenterAction,
9
- HorizontalTabBar,
10
- } from './BottomTabBar';
6
+ import { getSystemIndicatorHeight, HorizontalTabBar } from './BottomTabBar';
11
7
  import { getHorizontalBarWidth, TabBarMetrics } from './metrics';
12
8
  import { getTabItems } from './tabItems';
13
9
  import { DuoRailTabBar } from './DuoRailTabBar';
@@ -20,8 +16,8 @@ type DuoTabBarProps = BottomTabBarProps & {
20
16
  };
21
17
 
22
18
  /**
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.
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
25
21
  */
26
22
  const useDuoTabBarInset = () => {
27
23
  const { isIPhoneDuo, sideBar, insets } = useScreenLayout();
@@ -32,13 +28,12 @@ const useDuoTabBarInset = () => {
32
28
  };
33
29
 
34
30
  /**
35
- * 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
36
32
  */
37
33
  const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
38
34
  const { insets } = useScreenLayout();
39
35
  const { width, height } = useWindowDimensions();
40
36
  const { floatingButton, activeTintColor, inactiveTintColor } = props;
41
- const tabItems = getTabItems(props);
42
37
 
43
38
  return (
44
39
  <View
@@ -49,15 +44,9 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
49
44
  ]}
50
45
  >
51
46
  <HorizontalTabBar
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
- )}
47
+ fixedFontScale
48
+ items={getTabItems(props)}
49
+ width={getHorizontalBarWidth(width, height, insets.left, insets.right)}
61
50
  floatingButton={floatingButton}
62
51
  activeTintColor={activeTintColor}
63
52
  inactiveTintColor={inactiveTintColor}
@@ -67,14 +56,15 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
67
56
  };
68
57
 
69
58
  /**
70
- * 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
71
60
  */
72
61
  const DuoTabBar: React.FC<DuoTabBarProps> = props => {
73
62
  const { sideBar } = useScreenLayout();
74
- if (sideBar) {
75
- return <DuoRailTabBar {...props} />;
76
- }
77
- return <DuoFloatingTabBar {...props} />;
63
+ return sideBar ? (
64
+ <DuoRailTabBar {...props} />
65
+ ) : (
66
+ <DuoFloatingTabBar {...props} />
67
+ );
78
68
  };
79
69
 
80
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`,