@momo-kits/foundation 0.168.1-rn862.1 → 0.169.2

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 (109) hide show
  1. package/Application/BottomSheet.hooks.ts +195 -0
  2. package/Application/BottomSheet.parts.tsx +147 -0
  3. package/Application/BottomSheet.styles.ts +43 -0
  4. package/Application/BottomSheet.tsx +76 -279
  5. package/Application/BottomTab/ActiveIndicator.tsx +90 -0
  6. package/Application/BottomTab/BottomTabBar.styles.ts +65 -0
  7. package/Application/BottomTab/BottomTabBar.tsx +229 -276
  8. package/Application/BottomTab/BottomTabInset.tsx +14 -0
  9. package/Application/BottomTab/CurvedContainer.tsx +153 -0
  10. package/Application/BottomTab/CustomBottomTabItem.tsx +132 -108
  11. package/Application/BottomTab/DuoBarCompression.tsx +4 -12
  12. package/Application/BottomTab/DuoRailTabBar.tsx +227 -0
  13. package/Application/BottomTab/DuoTabBar.styles.ts +81 -0
  14. package/Application/BottomTab/DuoTabBar.tsx +29 -495
  15. package/Application/BottomTab/TabBarIcon.tsx +160 -72
  16. package/Application/BottomTab/TabScreen.tsx +164 -0
  17. package/Application/BottomTab/index.tsx +84 -351
  18. package/Application/BottomTab/metrics.ts +164 -0
  19. package/Application/BottomTab/tabItems.ts +46 -0
  20. package/Application/BottomTab/usePressLift.ts +32 -0
  21. package/Application/Components/DuoGlassBackground.tsx +7 -9
  22. package/Application/Components/HeaderAnimated.tsx +1 -1
  23. package/Application/Components/HeaderBackground.tsx +1 -1
  24. package/Application/Components/HeaderExtendHeader.tsx +17 -11
  25. package/Application/Components/HeaderRight.styles.ts +144 -0
  26. package/Application/Components/HeaderRight.tsx +6 -528
  27. package/Application/Components/HeaderTitle.shared.ts +43 -0
  28. package/Application/Components/HeaderTitle.styles.ts +125 -0
  29. package/Application/Components/HeaderTitle.tsx +12 -576
  30. package/Application/Components/HeaderToolkitAction.tsx +208 -0
  31. package/Application/Components/NavigationButton.tsx +9 -6
  32. package/Application/Components/SearchHeader.tsx +1 -1
  33. package/Application/Components/SwipeBackHaptic.tsx +95 -0
  34. package/Application/Components/TitleJourney.tsx +110 -0
  35. package/Application/Components/TitleLocation.tsx +73 -0
  36. package/Application/Components/TitleUser.tsx +304 -0
  37. package/Application/Components/useToolkitActions.tsx +233 -0
  38. package/Application/ModalIconButton.tsx +66 -0
  39. package/Application/ModalScreen.tsx +39 -18
  40. package/Application/Navigation.ts +1 -1
  41. package/Application/NavigationContainer.tsx +7 -8
  42. package/Application/StackScreen.tracking.ts +158 -0
  43. package/Application/StackScreen.tsx +10 -137
  44. package/Application/WidgetContainer.tsx +10 -3
  45. package/Application/__tests__/modalLayout.test.ts +128 -0
  46. package/Application/hooks.ts +137 -0
  47. package/Application/index.ts +13 -1
  48. package/Application/modalLayout.ts +177 -0
  49. package/Application/navigationOptions.tsx +217 -0
  50. package/Application/types.header.ts +116 -0
  51. package/Application/types.tabs.ts +48 -0
  52. package/Application/types.theme.ts +87 -0
  53. package/Application/types.ts +12 -244
  54. package/Application/utils.ts +107 -0
  55. package/Badge/BadgeRibbon.tsx +3 -3
  56. package/Badge/styles.ts +2 -0
  57. package/Button/index.tsx +16 -136
  58. package/Button/styles.ts +1 -1
  59. package/Button/utils.ts +139 -0
  60. package/CheckBox/index.tsx +1 -6
  61. package/Divider/DashDivider.tsx +10 -11
  62. package/Divider/index.tsx +18 -8
  63. package/Image/index.tsx +1 -46
  64. package/Input/Input.tsx +7 -8
  65. package/Input/InputDropDown.tsx +3 -4
  66. package/Input/InputMoney.tsx +38 -164
  67. package/Input/InputMoneyView.tsx +187 -0
  68. package/Input/InputOTP.tsx +1 -1
  69. package/Input/InputPhoneNumber.tsx +5 -1
  70. package/Input/InputSearch.tsx +2 -108
  71. package/Input/InputTextArea.tsx +1 -5
  72. package/Input/SearchTextTyping.tsx +104 -0
  73. package/Input/common.tsx +4 -4
  74. package/Input/moneyUtils.ts +17 -0
  75. package/Input/styles.ts +26 -2
  76. package/Layout/FloatingButton.tsx +22 -21
  77. package/Layout/ReservedRegions.tsx +21 -4
  78. package/Layout/Screen.tsx +74 -670
  79. package/Layout/Section.tsx +1 -2
  80. package/Layout/index.ts +10 -4
  81. package/Layout/layoutMetrics.ts +58 -0
  82. package/Layout/layoutTypes.ts +86 -0
  83. package/Layout/screenContent.tsx +90 -0
  84. package/Layout/screenFooter.tsx +53 -0
  85. package/Layout/screenHeader.tsx +315 -0
  86. package/Layout/screenQuickReturn.tsx +67 -0
  87. package/Layout/screenScroll.ts +110 -0
  88. package/Layout/screenTypes.ts +134 -0
  89. package/Layout/styles.ts +2 -0
  90. package/Layout/utils.ts +10 -144
  91. package/Loader/DotLoader.tsx +7 -3
  92. package/Loader/ProgressBar.tsx +2 -6
  93. package/Loader/Spinner.tsx +7 -3
  94. package/Loader/styles.ts +4 -0
  95. package/Popup/PopupNotify.tsx +70 -64
  96. package/Popup/PopupPromotion.tsx +5 -4
  97. package/Popup/types.ts +7 -0
  98. package/Popup/utils.ts +46 -24
  99. package/Radio/index.tsx +2 -10
  100. package/Radio/styles.ts +6 -0
  101. package/Skeleton/index.tsx +1 -9
  102. package/Skeleton/styles.ts +1 -0
  103. package/Tag/index.tsx +3 -3
  104. package/Text/index.tsx +11 -2
  105. package/Title/index.tsx +2 -9
  106. package/Title/styles.ts +6 -0
  107. package/package.json +21 -22
  108. package/Application/device.ts +0 -29
  109. package/Application/utils.tsx +0 -497
@@ -0,0 +1,153 @@
1
+ import React, { useContext } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import Svg, { Defs, FeGaussianBlur, Filter, Path } from 'react-native-svg';
4
+ import { Colors } from '../../Consts';
5
+ import { ApplicationContext } from '../../Context';
6
+ import { NOTCH_CURVES, NOTCH_HALF_WIDTH } from './metrics';
7
+
8
+ type Props = {
9
+ width: number;
10
+ height: number;
11
+ radius: number;
12
+ // x of the center action, the top edge is cut around it
13
+ notchCenterX?: number;
14
+ };
15
+
16
+ // room for the floating shadow around the shape: the key shadow reaches 12 + 3 × 16 below it
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
20
+ const AMBIENT_BLUR = 12;
21
+ const KEY_BLUR = 16;
22
+ const KEY_SPREAD = 16;
23
+ const KEY_OFFSET_Y = 12;
24
+
25
+ /**
26
+ * rounded container path placed at (`x`, `y`) on the canvas, with the notch dipped into the
27
+ * top edge around `notchCenterX`
28
+ */
29
+ const getContainerPath = (
30
+ width: number,
31
+ height: number,
32
+ radius: number,
33
+ x: number,
34
+ y: number,
35
+ notchCenterX?: number,
36
+ ) => {
37
+ const r = Math.max(Math.min(radius, height / 2, width / 2), 0);
38
+ const left = x;
39
+ const top = y;
40
+ const right = x + width;
41
+ const bottom = y + height;
42
+ let notch = '';
43
+ if (notchCenterX !== undefined) {
44
+ const cx = x + notchCenterX;
45
+ 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('');
51
+ }
52
+ return (
53
+ `M ${left + r} ${top}` +
54
+ notch +
55
+ ` L ${right - r} ${top}` +
56
+ ` A ${r} ${r} 0 0 1 ${right} ${top + r}` +
57
+ ` L ${right} ${bottom - r}` +
58
+ ` A ${r} ${r} 0 0 1 ${right - r} ${bottom}` +
59
+ ` L ${left + r} ${bottom}` +
60
+ ` A ${r} ${r} 0 0 1 ${left} ${bottom - r}` +
61
+ ` L ${left} ${top + r}` +
62
+ ` A ${r} ${r} 0 0 1 ${left + r} ${top} Z`
63
+ );
64
+ };
65
+
66
+ /**
67
+ * navigation bar container: surface fill with the floating shadow, drawn as a shape so
68
+ * the shadow follows the notch
69
+ */
70
+ const CurvedContainer: React.FC<Props> = ({
71
+ width,
72
+ height,
73
+ radius,
74
+ notchCenterX,
75
+ }) => {
76
+ const { theme } = useContext(ApplicationContext);
77
+ if (width <= 0 || height <= 0) {
78
+ return null;
79
+ }
80
+ const canvasWidth = width + 2 * SHADOW_PADDING;
81
+ const canvasHeight = height + 2 * SHADOW_PADDING;
82
+ const path = getContainerPath(
83
+ width,
84
+ height,
85
+ radius,
86
+ SHADOW_PADDING,
87
+ SHADOW_PADDING,
88
+ notchCenterX,
89
+ );
90
+
91
+ return (
92
+ <Svg
93
+ pointerEvents={'none'}
94
+ width={canvasWidth}
95
+ height={canvasHeight}
96
+ style={styles.canvas}
97
+ >
98
+ <Defs>
99
+ <Filter
100
+ id={'ambientShadow'}
101
+ filterUnits={'userSpaceOnUse'}
102
+ x={0}
103
+ y={0}
104
+ width={canvasWidth}
105
+ height={canvasHeight}
106
+ >
107
+ <FeGaussianBlur in={'SourceAlpha'} stdDeviation={AMBIENT_BLUR} />
108
+ </Filter>
109
+ <Filter
110
+ id={'keyShadow'}
111
+ filterUnits={'userSpaceOnUse'}
112
+ x={0}
113
+ y={0}
114
+ width={canvasWidth}
115
+ height={canvasHeight}
116
+ >
117
+ <FeGaussianBlur in={'SourceAlpha'} stdDeviation={KEY_BLUR} />
118
+ </Filter>
119
+ </Defs>
120
+ <Path
121
+ d={path}
122
+ fill={Colors.black_20}
123
+ fillOpacity={0.12}
124
+ filter={'url(#ambientShadow)'}
125
+ />
126
+ {width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD && (
127
+ <Path
128
+ d={getContainerPath(
129
+ width - 2 * KEY_SPREAD,
130
+ height - 2 * KEY_SPREAD,
131
+ radius - KEY_SPREAD,
132
+ SHADOW_PADDING + KEY_SPREAD,
133
+ SHADOW_PADDING + KEY_SPREAD + KEY_OFFSET_Y,
134
+ )}
135
+ fill={Colors.black_20}
136
+ fillOpacity={0.04}
137
+ filter={'url(#keyShadow)'}
138
+ />
139
+ )}
140
+ <Path d={path} fill={theme.colors.background.surface} />
141
+ </Svg>
142
+ );
143
+ };
144
+
145
+ const styles = StyleSheet.create({
146
+ canvas: {
147
+ position: 'absolute',
148
+ left: -SHADOW_PADDING,
149
+ top: -SHADOW_PADDING,
150
+ },
151
+ });
152
+
153
+ export { CurvedContainer };
@@ -1,136 +1,142 @@
1
- import React, { useContext } from 'react';
2
- import { TouchableOpacity, StyleSheet } from 'react-native';
1
+ import React, { useContext, useEffect } from 'react';
3
2
  import {
4
- BottomTabBarButtonProps,
5
- BottomTabNavigationOptions,
6
- } from '@react-navigation/bottom-tabs';
7
- import { Route } from '@react-navigation/native';
8
- import { TabBarIcon } from './TabBarIcon';
3
+ LayoutChangeEvent,
4
+ StyleSheet,
5
+ TouchableOpacity,
6
+ View,
7
+ } from 'react-native';
8
+ import Animated, {
9
+ useAnimatedStyle,
10
+ useSharedValue,
11
+ withTiming,
12
+ } from 'react-native-reanimated';
13
+ import { FixedScaleText, TabBarIcon } from './TabBarIcon';
9
14
  import { Text } from '../../Text';
10
15
  import { MiniAppContext } from '../../Context';
11
- import { Colors } from '../../Consts';
16
+ import { Colors, Spacing } from '../../Consts';
17
+ import { TabBarMetrics, TabBarMotion } from './metrics';
12
18
 
13
- type LabelType = NonNullable<BottomTabNavigationOptions['tabBarLabel']>;
19
+ type IconRenderer = (props: {
20
+ focused: boolean;
21
+ color: string;
22
+ size: number;
23
+ }) => React.ReactNode;
14
24
 
15
- interface CustomBottomTabItemProps {
16
- route: Route<string>;
25
+ type TabItemContentProps = {
26
+ label: string;
17
27
  focused: boolean;
18
- label: LabelType;
19
- onPress: () => void;
20
- onLongPress: () => void;
21
28
  activeTintColor: string;
22
29
  inactiveTintColor: string;
23
- activeBackgroundColor?: string;
24
- inactiveBackgroundColor?: string;
25
- icon: any;
30
+ icon?: IconRenderer;
26
31
  badge?: string | number;
27
- showLabel?: boolean;
28
- labelStyle?: any;
29
- iconStyle?: any;
30
- style?: any;
31
- badgeStyle?: any;
32
- allowFontScaling?: boolean;
33
- button?: (props: BottomTabBarButtonProps) => React.ReactNode;
34
- /**
35
- * The accessibility label for the tab.
36
- */
37
- accessibilityLabel?: string;
38
- /**
39
- * An unique ID for testing for the tab.
40
- */
41
- testID?: string;
42
- }
32
+ // rail item (.Nav/Duo/Item) instead of the horizontal one (.Nav/itemTab)
33
+ vertical?: boolean;
34
+ // keep the label at its design size, the Duo displays otherwise scale it up
35
+ fixedFontScale?: boolean;
36
+ hideLabel?: boolean;
37
+ };
43
38
 
44
- const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
45
- route,
46
- focused,
39
+ /**
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
42
+ */
43
+ const TabItemContent: React.FC<TabItemContentProps> = ({
47
44
  label,
48
- onPress,
49
- onLongPress,
45
+ focused,
50
46
  activeTintColor,
51
47
  inactiveTintColor,
52
- activeBackgroundColor,
53
- inactiveBackgroundColor,
54
48
  icon,
55
49
  badge,
56
- showLabel,
57
- labelStyle,
58
- iconStyle,
59
- style,
60
- badgeStyle,
61
- allowFontScaling,
50
+ vertical,
51
+ fixedFontScale,
52
+ hideLabel,
62
53
  }) => {
63
- const context = useContext<any>(MiniAppContext);
64
- const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
54
+ const progress = useSharedValue(focused ? 1 : 0);
65
55
 
66
- const renderIcon = () => {
67
- if (icon === undefined) {
68
- return null;
69
- }
56
+ useEffect(() => {
57
+ progress.value = withTiming(focused ? 1 : 0, TabBarMotion.tint);
58
+ }, [focused, progress]);
70
59
 
71
- const activeOpacity = focused ? 1 : 0;
72
- const inactiveOpacity = focused ? 0 : 1;
60
+ const activeLabelStyle = useAnimatedStyle(() => ({
61
+ opacity: progress.value,
62
+ }));
73
63
 
74
- return (
75
- <TabBarIcon
76
- route={route}
77
- badge={badge}
78
- badgeStyle={badgeStyle}
79
- activeOpacity={activeOpacity}
80
- allowFontScaling={allowFontScaling}
81
- inactiveOpacity={inactiveOpacity}
82
- activeTintColor={activeTintColor}
83
- inactiveTintColor={inactiveTintColor}
84
- renderIcon={icon}
85
- style={iconStyle}
86
- />
87
- );
88
- };
89
-
90
- const renderLabel = ({ focused }: { focused: boolean }) => {
91
- if (showLabel === false) {
92
- return null;
93
- }
64
+ const renderText = (color: string) => (
65
+ <Text
66
+ numberOfLines={1}
67
+ typography={vertical ? 'label_xs_medium' : 'label_s_medium'}
68
+ color={color}
69
+ style={!vertical && styles.label}
70
+ >
71
+ {label}
72
+ </Text>
73
+ );
94
74
 
95
- const color = focused ? activeTintColor : inactiveTintColor;
75
+ // the active label fades in over the inactive one
76
+ const text = (
77
+ <View style={!vertical && styles.labelBox}>
78
+ {renderText(inactiveTintColor)}
79
+ <Animated.View
80
+ pointerEvents={'none'}
81
+ style={[StyleSheet.absoluteFill, activeLabelStyle]}
82
+ >
83
+ {renderText(activeTintColor)}
84
+ </Animated.View>
85
+ </View>
86
+ );
96
87
 
97
- if (typeof label === 'string') {
98
- return (
99
- <Text
100
- numberOfLines={1}
101
- style={[styles.label, labelStyle]}
102
- color={focused ? activeTintColor : inactiveTintColor}
103
- typography={focused ? 'label_xs_medium' : 'description_xs_regular'}
104
- allowFontScaling={allowFontScaling}
105
- >
106
- {label}
107
- </Text>
108
- );
109
- }
88
+ return (
89
+ <>
90
+ <View style={vertical ? styles.railSlot : styles.slot}>
91
+ <TabBarIcon
92
+ focused={focused}
93
+ badge={badge}
94
+ vertical={vertical}
95
+ activeTintColor={activeTintColor}
96
+ inactiveTintColor={inactiveTintColor}
97
+ renderIcon={icon}
98
+ />
99
+ </View>
100
+ {!hideLabel &&
101
+ (fixedFontScale ? <FixedScaleText>{text}</FixedScaleText> : text)}
102
+ </>
103
+ );
104
+ };
110
105
 
111
- return label({
112
- focused,
113
- color,
114
- children: route.name,
115
- position: 'below-icon',
116
- });
117
- };
106
+ type CustomBottomTabItemProps = TabItemContentProps & {
107
+ onPress: () => void;
108
+ onLongPress: () => void;
109
+ onLayout?: (e: LayoutChangeEvent) => void;
110
+ accessibilityLabel?: string;
111
+ testID?: string;
112
+ };
118
113
 
119
- const background = focused ? activeBackgroundColor : inactiveBackgroundColor;
114
+ /**
115
+ * horizontal navigation item (.Nav/itemTab): fills an even share of the container
116
+ */
117
+ const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
118
+ onPress,
119
+ onLongPress,
120
+ onLayout,
121
+ accessibilityLabel,
122
+ testID,
123
+ ...content
124
+ }) => {
125
+ const context = useContext<any>(MiniAppContext);
126
+ const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
120
127
 
121
128
  return (
122
129
  <TouchableOpacity
130
+ accessibilityRole={'tab'}
131
+ accessibilityLabel={accessibilityLabel ?? content.label}
132
+ accessibilityState={{ selected: content.focused }}
133
+ testID={testID}
123
134
  onPress={onPress}
124
135
  onLongPress={onLongPress}
125
- style={[
126
- styles.container,
127
- { backgroundColor: background },
128
- style,
129
- showBaseLineDebug && styles.debugBaseLine,
130
- ]}
136
+ onLayout={onLayout}
137
+ style={[styles.container, showBaseLineDebug && styles.debugBaseLine]}
131
138
  >
132
- {renderIcon()}
133
- {renderLabel({ focused })}
139
+ <TabItemContent {...content} />
134
140
  </TouchableOpacity>
135
141
  );
136
142
  };
@@ -138,16 +144,34 @@ const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
138
144
  const styles = StyleSheet.create({
139
145
  container: {
140
146
  flex: 1,
147
+ minWidth: 1,
148
+ alignItems: 'center',
149
+ justifyContent: 'center',
150
+ gap: Spacing.XXS,
151
+ },
152
+ slot: {
153
+ height: TabBarMetrics.indicatorHeight,
154
+ alignSelf: 'stretch',
141
155
  alignItems: 'center',
142
156
  justifyContent: 'center',
143
- paddingVertical: 8,
144
- bottom: -2,
157
+ paddingVertical: TabBarMetrics.indicatorPaddingVertical,
158
+ paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
159
+ },
160
+ railSlot: {
161
+ height: TabBarMetrics.railIndicatorHeight,
162
+ alignItems: 'center',
163
+ justifyContent: 'center',
164
+ paddingVertical: TabBarMetrics.railIndicatorPaddingVertical,
165
+ paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
166
+ },
167
+ labelBox: {
168
+ alignSelf: 'stretch',
145
169
  },
146
170
  label: {
147
- fontSize: 12,
148
- fontWeight: '600',
171
+ textAlign: 'center',
149
172
  },
150
173
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
151
174
  });
152
175
 
176
+ export { TabItemContent };
153
177
  export default CustomBottomTabItem;
@@ -8,6 +8,7 @@ import React, {
8
8
  import { Spacing } from '../../Consts';
9
9
  import { useScreenLayout } from '../../Layout';
10
10
  import { DuoMetrics } from '../../Layout/utils';
11
+ import { getRailHeight, TabBarMetrics } from './metrics';
11
12
 
12
13
  export type DuoBarCompressionBehavior = 'toolbar' | 'tabBar';
13
14
 
@@ -25,8 +26,6 @@ type DuoBarCompression = {
25
26
  setToolbarHeight?: (height: number) => void;
26
27
  };
27
28
 
28
- const RAIL_PADDING = 6;
29
- const RAIL_GAP = Spacing.XXS;
30
29
  // space kept between the top and the bottom groups (HIG Vertical controls)
31
30
  const GROUP_GAP = Spacing.M;
32
31
  // Back, then the right toolkit: help + home capsule
@@ -43,15 +42,6 @@ const NO_COMPRESSION: DuoBarCompression = {
43
42
  const DuoBarCompressionContext =
44
43
  createContext<DuoBarCompression>(NO_COMPRESSION);
45
44
 
46
- /**
47
- * height of the vertical tab rail, plus the QR button under it
48
- */
49
- const getRailHeight = (tabCount: number, hasQr: boolean) =>
50
- 2 * RAIL_PADDING +
51
- tabCount * DuoMetrics.controlSize +
52
- Math.max(tabCount - 1, 0) * RAIL_GAP +
53
- (hasQr ? Spacing.M + DuoMetrics.controlSize : 0);
54
-
55
45
  /**
56
46
  * keeps the side bar's toolbar and tab bar clear of each other (HIG, Designing for
57
47
  * iPhone Duo: Vertical controls):
@@ -81,7 +71,9 @@ const DuoBarCompressionProvider: React.FC<{
81
71
  }
82
72
  const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
83
73
  const tabBar = getRailHeight(tabCount, hasQr);
84
- const compressedTabBar = getRailHeight(1, hasQr) - 2 * RAIL_PADDING;
74
+ const compressedTabBar =
75
+ DuoMetrics.controlSize +
76
+ (hasQr ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
85
77
  const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
86
78
 
87
79
  if (fits(toolbarHeight, tabBar)) {