@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
@@ -51,8 +51,10 @@ export const useBottomSheetLayout = () => {
51
51
  };
52
52
  };
53
53
 
54
- // KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
55
- // so lift it here and shrink maxHeight by the same amount instead
54
+ /**
55
+ * Keyboard lift of the sheet on iPhone Duo, where KeyboardAvoidingView pushes
56
+ * the whole sheet past the top; the sheet lifts by it and shrinks its maxHeight.
57
+ */
56
58
  export const useDuoKeyboardLift = (
57
59
  isDuo: boolean,
58
60
  useKeyboardAvoidingView: boolean,
@@ -136,6 +138,10 @@ type SheetDismissParams = {
136
138
  action: MutableRefObject<undefined | string>;
137
139
  };
138
140
 
141
+ /**
142
+ * Close handlers of a bottom sheet. A non-forced dismiss (scrim tap, hardware
143
+ * back) is ignored while `barrierDismissible` is set.
144
+ */
139
145
  export const useSheetDismiss = ({
140
146
  barrierDismissible,
141
147
  closeAnimation,
@@ -143,9 +149,6 @@ export const useSheetDismiss = ({
143
149
  leftOptions,
144
150
  action,
145
151
  }: SheetDismissParams) => {
146
- /**
147
- * handler dismiss
148
- */
149
152
  const onDismiss = useCallback(
150
153
  (force = false, callback?: () => void) => {
151
154
  if (barrierDismissible && !force) {
@@ -161,9 +164,6 @@ export const useSheetDismiss = ({
161
164
  [barrierDismissible, closeAnimation, navigator],
162
165
  );
163
166
 
164
- /**
165
- * on request close
166
- */
167
167
  const onRequestClose = useCallback(
168
168
  (callback?: () => void) => {
169
169
  onDismiss(true, callback);
@@ -34,42 +34,52 @@ export const renderSheetHeader = ({
34
34
  iconBack,
35
35
  options,
36
36
  leftOptions,
37
- }: SheetHeaderParams) => (
38
- <View {...panHandlers}>
39
- <View style={{ height: INDICATOR_HEIGHT }}>
40
- <View style={styles.indicator} />
41
- </View>
42
- <View
43
- style={[
44
- styles.header,
45
- useDivider && styles.headerDivider,
46
- useDivider && {
47
- borderBottomColor: theme.colors.border.default,
48
- },
49
- ]}
50
- >
51
- {showBack ? (
52
- <ModalIconButton icon={iconBack} onPress={onPressBack} />
53
- ) : null}
37
+ }: SheetHeaderParams) => {
38
+ let backButton: React.ReactNode = null;
39
+ if (showBack) {
40
+ backButton = <ModalIconButton icon={iconBack} onPress={onPressBack} />;
41
+ }
54
42
 
55
- {options.header ? (
56
- <View style={Styles.flex}>{options.header}</View>
57
- ) : (
58
- <>
59
- {!leftOptions ? <View style={styles.iconButtonSpace} /> : null}
60
- <Text
61
- style={[Styles.flex, styles.title]}
62
- numberOfLines={1}
63
- typography="header_default_bold"
64
- >
65
- {options.title ?? ''}
66
- </Text>
67
- </>
68
- )}
69
- <ModalIconButton icon="navigation_close" onPress={onPressClose} />
43
+ let headerContent: React.ReactNode;
44
+ if (options.header) {
45
+ headerContent = <View style={Styles.flex}>{options.header}</View>;
46
+ } else {
47
+ headerContent = (
48
+ <>
49
+ {!leftOptions ? <View style={styles.iconButtonSpace} /> : null}
50
+ <Text
51
+ style={[Styles.flex, styles.title]}
52
+ numberOfLines={1}
53
+ typography="header_default_bold"
54
+ >
55
+ {options.title ?? ''}
56
+ </Text>
57
+ </>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <View {...panHandlers}>
63
+ <View style={{ height: INDICATOR_HEIGHT }}>
64
+ <View style={styles.indicator} />
65
+ </View>
66
+ <View
67
+ style={[
68
+ styles.header,
69
+ useDivider && styles.headerDivider,
70
+ useDivider && {
71
+ borderBottomColor: theme.colors.border.default,
72
+ },
73
+ ]}
74
+ >
75
+ {backButton}
76
+
77
+ {headerContent}
78
+ <ModalIconButton icon="navigation_close" onPress={onPressClose} />
79
+ </View>
70
80
  </View>
71
- </View>
72
- );
81
+ );
82
+ };
73
83
 
74
84
  /**
75
85
  * Footer pinned under the sheet content, `null` when the sheet has none.
@@ -18,7 +18,6 @@ import Animated, {
18
18
  } from 'react-native-reanimated';
19
19
  import { scheduleOnRN } from 'react-native-worklets';
20
20
  import { DUO_ORIENTATIONS, styles } from './BottomSheet.styles';
21
- import { AdoptionProbe } from './KitAdoption';
22
21
  import {
23
22
  useBottomSheetLayout,
24
23
  useDuoKeyboardLift,
@@ -31,6 +30,10 @@ import {
31
30
  renderSheetHeader,
32
31
  } from './BottomSheet.parts';
33
32
 
33
+ /**
34
+ * Bottom sheet route of the modal stack: slides the given screen up under a
35
+ * drag handle and header, and reports how it was closed through `onDismiss`.
36
+ */
34
37
  const BottomSheet: React.FC<BottomSheetParams> = props => {
35
38
  const { theme, navigator } = useContext(ApplicationContext);
36
39
  const context = useContext<any>(MiniAppContext);
@@ -99,8 +102,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
99
102
  const panResponder = useRef(
100
103
  PanResponder.create({
101
104
  onStartShouldSetPanResponder: () => draggable,
102
- onMoveShouldSetPanResponder: (_, gestureState) =>
103
- draggable && gestureState.dy > 0,
105
+ onMoveShouldSetPanResponder: (_, gestureState) => {
106
+ return draggable && gestureState.dy > 0;
107
+ },
104
108
  onPanResponderMove: (_, gestureState) => {
105
109
  if (gestureState.dy > 0) {
106
110
  translateY.value = gestureState.dy;
@@ -132,10 +136,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
132
136
  backgroundColor = theme.colors.background.surface;
133
137
  }
134
138
 
135
- /**
136
- * emit dismiss event, once: folding or rotating iPhone Duo changes the
137
- * window height while the sheet stays open
138
- */
139
+ // Mount only, so folding or rotating iPhone Duo doesn't emit a dismiss.
139
140
  useEffect(() => {
140
141
  openAnimation();
141
142
  return () => {
@@ -143,14 +144,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
143
144
  };
144
145
  }, [openAnimation]);
145
146
 
146
- const { onDismiss, onRequestClose, onPressBack, onPressClose } =
147
- useSheetDismiss({
148
- barrierDismissible,
149
- closeAnimation,
150
- navigator,
151
- leftOptions,
152
- action,
153
- });
147
+ const handlers = useSheetDismiss({
148
+ barrierDismissible,
149
+ closeAnimation,
150
+ navigator,
151
+ leftOptions,
152
+ action,
153
+ });
154
+ const { onDismiss, onRequestClose, onPressBack, onPressClose } = handlers;
154
155
 
155
156
  const showBack = !!leftOptions?.onPressIconLeft;
156
157
  const iconBack = leftOptions?.iconLeft ?? 'ic_back';
@@ -174,18 +175,31 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
174
175
  );
175
176
 
176
177
  const screen = (
177
- <AdoptionProbe
178
- screenName={Screen?.name || 'Invalid'}
179
- navigation={props.navigation}
180
- >
181
- <Screen
182
- {...props}
183
- {...props.route.params}
184
- onRequestClose={onRequestClose}
185
- />
186
- </AdoptionProbe>
178
+ <Screen
179
+ {...props}
180
+ {...props.route.params}
181
+ onRequestClose={onRequestClose}
182
+ />
187
183
  );
188
184
 
185
+ let content: React.ReactNode;
186
+ if (isFoldable) {
187
+ // Content shrinks so a tall sheet keeps its header and footer.
188
+ content = (
189
+ <View style={styles.shrink}>
190
+ {renderSheetHeader(headerParams)}
191
+ <View style={styles.shrink}>{screen}</View>
192
+ </View>
193
+ );
194
+ } else {
195
+ content = (
196
+ <>
197
+ {renderSheetHeader(headerParams)}
198
+ {screen}
199
+ </>
200
+ );
201
+ }
202
+
189
203
  return (
190
204
  <Container
191
205
  transparent
@@ -231,18 +245,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
231
245
  }),
232
246
  ]}
233
247
  >
234
- {isFoldable ? (
235
- // content shrinks so a tall sheet keeps its header and footer
236
- <View style={styles.shrink}>
237
- {renderSheetHeader(headerParams)}
238
- <View style={styles.shrink}>{screen}</View>
239
- </View>
240
- ) : (
241
- <>
242
- {renderSheetHeader(headerParams)}
243
- {screen}
244
- </>
245
- )}
248
+ {content}
246
249
  {renderSheetFooter({ footerComponent, theme })}
247
250
  {useBottomInset && (
248
251
  <View
@@ -5,6 +5,7 @@ import Animated, {
5
5
  useSharedValue,
6
6
  withTiming,
7
7
  } from 'react-native-reanimated';
8
+ import { defaultTheme } from '../../Consts';
8
9
  import { ApplicationContext } from '../../Context';
9
10
  import { getIndicatorShadow, TabBarMotion } from './metrics';
10
11
 
@@ -19,8 +20,8 @@ type Props = {
19
20
  };
20
21
 
21
22
  /**
22
- * one active indicator per bar (Background/tonal with its inner shadows) sliding to the
23
- * selected item; it lands in place on the first measure and slides from then on
23
+ * The single active indicator of a bar (Background/tonal with inner shadows) that slides
24
+ * to the selected item. It lands in place on the first measure and slides from then on.
24
25
  */
25
26
  const ActiveIndicator: React.FC<Props> = ({
26
27
  axis,
@@ -49,18 +50,19 @@ const ActiveIndicator: React.FC<Props> = ({
49
50
  position.value = withTiming(offset, TabBarMotion.indicator);
50
51
  }, [offset, extent, position, size, visible]);
51
52
 
52
- const animatedStyle = useAnimatedStyle(() =>
53
- axis === 'x'
54
- ? {
55
- opacity: visible.value,
56
- width: size.value,
57
- transform: [{ translateX: position.value }],
58
- }
59
- : {
60
- opacity: visible.value,
61
- transform: [{ translateY: position.value }],
62
- },
63
- );
53
+ const animatedStyle = useAnimatedStyle(() => {
54
+ if (axis === 'x') {
55
+ return {
56
+ opacity: visible.value,
57
+ width: size.value,
58
+ transform: [{ translateX: position.value }],
59
+ };
60
+ }
61
+ return {
62
+ opacity: visible.value,
63
+ transform: [{ translateY: position.value }],
64
+ };
65
+ });
64
66
 
65
67
  return (
66
68
  <Animated.View
@@ -69,7 +71,10 @@ const ActiveIndicator: React.FC<Props> = ({
69
71
  styles.indicator,
70
72
  {
71
73
  borderRadius: radius,
72
- backgroundColor: theme.colors.background.tonal,
74
+ // a mini app's own theme may lack `tonal`; without the fallback the indicator is transparent
75
+ backgroundColor:
76
+ theme.colors.background.tonal ??
77
+ defaultTheme.colors.background.tonal,
73
78
  boxShadow: getIndicatorShadow(theme.colors.primary),
74
79
  },
75
80
  style,
@@ -2,6 +2,20 @@ import { StyleSheet } from 'react-native';
2
2
  import { Colors, Spacing } from '../../Consts';
3
3
  import { CTA_SHADOW, TabBarMetrics } from './metrics';
4
4
 
5
+ const ITEM_HEIGHT =
6
+ TabBarMetrics.indicatorHeight +
7
+ TabBarMetrics.itemIconLabelGap +
8
+ TabBarMetrics.labelHeight;
9
+ const CONTENT_HEIGHT = TabBarMetrics.height - 2 * TabBarMetrics.paddingVertical;
10
+ // the items are centered in the content, so their label starts this far below the content top
11
+ const ITEM_LABEL_TOP =
12
+ (CONTENT_HEIGHT - ITEM_HEIGHT) / 2 +
13
+ TabBarMetrics.indicatorHeight +
14
+ TabBarMetrics.itemIconLabelGap;
15
+ // the center action column is placed so its label starts on the same line as the tab labels
16
+ const CTA_TOP =
17
+ ITEM_LABEL_TOP - TabBarMetrics.ctaLabelGap - TabBarMetrics.ctaSize;
18
+
5
19
  export const styles = StyleSheet.create({
6
20
  bar: {
7
21
  position: 'absolute',
@@ -37,7 +51,7 @@ export const styles = StyleSheet.create({
37
51
  position: 'absolute',
38
52
  left: 0,
39
53
  right: 0,
40
- top: -TabBarMetrics.ctaLift - TabBarMetrics.paddingVertical,
54
+ top: CTA_TOP,
41
55
  alignItems: 'center',
42
56
  gap: TabBarMetrics.ctaLabelGap,
43
57
  },
@@ -46,6 +60,9 @@ export const styles = StyleSheet.create({
46
60
  width: TabBarMetrics.ctaSize,
47
61
  height: TabBarMetrics.ctaSize,
48
62
  borderRadius: TabBarMetrics.ctaSize / 2,
63
+ // Figma Icon Button border: white 60% hairline
64
+ borderWidth: 1,
65
+ borderColor: `rgba(255, 255, 255, ${TabBarMetrics.ctaBorderOpacity})`,
49
66
  alignItems: 'center',
50
67
  justifyContent: 'center',
51
68
  },
@@ -17,7 +17,6 @@ import { CurvedContainer } from './CurvedContainer';
17
17
  import { ActiveIndicator } from './ActiveIndicator';
18
18
  import { getHorizontalBarWidth, TabBarMetrics, TabBarMotion } from './metrics';
19
19
  import { getTabItems, TabItem } from './tabItems';
20
- import { FixedScaleText } from './TabBarIcon';
21
20
  import { styles } from './BottomTabBar.styles';
22
21
 
23
22
  type Props = BottomTabBarProps & {
@@ -27,24 +26,28 @@ type Props = BottomTabBarProps & {
27
26
  };
28
27
 
29
28
  /**
30
- * the center action needs a tab on each side: Special type takes 2 or 4 tabs
29
+ * Whether the bar shows the center action. It needs a tab on each side, so the
30
+ * Special type takes 2 or 4 tabs.
31
31
  */
32
- const hasCenterAction = (floatingButton: unknown, tabCount: number) =>
33
- !!floatingButton && tabCount % 2 === 0;
32
+ const hasCenterAction = (floatingButton: unknown, tabCount: number) => {
33
+ return !!floatingButton && tabCount % 2 === 0;
34
+ };
34
35
 
35
36
  /**
36
- * gap under the container: the home indicator / gesture bar area
37
+ * Gap under the bar container for the home indicator / gesture bar area, at
38
+ * least `Spacing.S`; iOS caps it at the indicator height.
37
39
  */
38
- const getSystemIndicatorHeight = (bottomInset: number) =>
39
- Math.max(
40
- Platform.OS === 'ios'
41
- ? Math.min(bottomInset, TabBarMetrics.systemIndicatorHeight)
42
- : bottomInset,
43
- Spacing.S,
44
- );
40
+ const getSystemIndicatorHeight = (bottomInset: number) => {
41
+ let height = bottomInset;
42
+ if (Platform.OS === 'ios') {
43
+ height = Math.min(bottomInset, TabBarMetrics.systemIndicatorHeight);
44
+ }
45
+ return Math.max(height, Spacing.S);
46
+ };
45
47
 
46
48
  /**
47
- * center action press: the button shrinks while held; the label stays plain text
49
+ * Press feedback of the center action: the button shrinks while held, the label
50
+ * stays still.
48
51
  */
49
52
  const useCenterActionPress = () => {
50
53
  const scale = useSharedValue(1);
@@ -64,26 +67,27 @@ const useCenterActionPress = () => {
64
67
  };
65
68
 
66
69
  /**
67
- * center action (.Nav/CTA): button rising out of the notch, label under it
70
+ * Center action (.Nav/CTA): a button rising out of the notch, its label below
71
+ * (growing downward when scaled).
68
72
  */
69
- const CenterAction: React.FC<{
70
- floatingButton: FloatingButtonProps;
71
- fixedFontScale?: boolean;
72
- }> = ({ floatingButton, fixedFontScale }) => {
73
+ const CenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
74
+ floatingButton,
75
+ }) => {
73
76
  const { theme } = useContext(ApplicationContext);
74
77
  const context = useContext<any>(MiniAppContext);
75
78
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
76
79
  const { icon, label, onPress, labelContent } = floatingButton;
77
80
  const { buttonStyle, onPressIn, onPressOut } = useCenterActionPress();
78
- const labelText = (
79
- <Text
80
- numberOfLines={1}
81
- typography={'label_s_medium'}
82
- color={theme.colors.text.secondary}
83
- >
84
- {label}
85
- </Text>
86
- );
81
+ let iconNode = icon;
82
+ if (typeof icon === 'string') {
83
+ iconNode = (
84
+ <Icon
85
+ source={icon}
86
+ size={TabBarMetrics.ctaIconSize}
87
+ color={Colors.black_01}
88
+ />
89
+ );
90
+ }
87
91
 
88
92
  return (
89
93
  <View style={[styles.ctaSlot, showBaseLineDebug && styles.debugBaseLine]}>
@@ -102,22 +106,16 @@ const CenterAction: React.FC<{
102
106
  buttonStyle,
103
107
  ]}
104
108
  >
105
- {typeof icon === 'string' ? (
106
- <Icon
107
- source={icon}
108
- size={TabBarMetrics.ctaIconSize}
109
- color={Colors.black_01}
110
- />
111
- ) : (
112
- icon
113
- )}
109
+ {iconNode}
114
110
  </Animated.View>
115
111
  <View style={styles.ctaLabel}>
116
- {fixedFontScale ? (
117
- <FixedScaleText>{labelText}</FixedScaleText>
118
- ) : (
119
- labelText
120
- )}
112
+ <Text
113
+ numberOfLines={1}
114
+ typography={'label_s_medium'}
115
+ color={theme.colors.text.secondary}
116
+ >
117
+ {label}
118
+ </Text>
121
119
  {labelContent && (
122
120
  <View pointerEvents={'none'} style={styles.ctaLabelOverlay}>
123
121
  {labelContent}
@@ -130,8 +128,8 @@ const CenterAction: React.FC<{
130
128
  };
131
129
 
132
130
  /**
133
- * horizontal navigation bar: floating container, items split evenly, Special type puts
134
- * the center action in a notch in the middle
131
+ * Horizontal navigation bar: a floating container with evenly split items; the
132
+ * Special type puts the center action in a notch in the middle.
135
133
  */
136
134
  const HorizontalTabBar: React.FC<{
137
135
  items: TabItem[];
@@ -139,23 +137,15 @@ const HorizontalTabBar: React.FC<{
139
137
  activeTintColor: string;
140
138
  inactiveTintColor: string;
141
139
  floatingButton?: FloatingButtonProps;
142
- fixedFontScale?: boolean;
143
- }> = ({
144
- items,
145
- width,
146
- activeTintColor,
147
- inactiveTintColor,
148
- floatingButton,
149
- fixedFontScale,
150
- }) => {
140
+ }> = ({ items, width, activeTintColor, inactiveTintColor, floatingButton }) => {
151
141
  const withCta = hasCenterAction(floatingButton, items.length);
152
142
  const [layouts, setLayouts] = useState<
153
143
  Record<string, { x: number; y: number; width: number }>
154
144
  >({});
155
145
  const selected = layouts[items.find(item => item.focused)?.route.key ?? ''];
156
146
 
157
- const renderItems = (data: TabItem[]) =>
158
- data.map(item => (
147
+ const renderItems = (data: TabItem[]) => {
148
+ return data.map(item => (
159
149
  <CustomBottomTabItem
160
150
  key={item.route.key}
161
151
  onLayout={e => {
@@ -173,9 +163,9 @@ const HorizontalTabBar: React.FC<{
173
163
  onLongPress={item.onLongPress}
174
164
  activeTintColor={activeTintColor}
175
165
  inactiveTintColor={inactiveTintColor}
176
- fixedFontScale={fixedFontScale}
177
166
  />
178
167
  ));
168
+ };
179
169
 
180
170
  const renderContent = () => {
181
171
  if (withCta && floatingButton) {
@@ -183,10 +173,7 @@ const HorizontalTabBar: React.FC<{
183
173
  return (
184
174
  <>
185
175
  {renderItems(items.slice(0, middle))}
186
- <CenterAction
187
- floatingButton={floatingButton}
188
- fixedFontScale={fixedFontScale}
189
- />
176
+ <CenterAction floatingButton={floatingButton} />
190
177
  {renderItems(items.slice(middle))}
191
178
  </>
192
179
  );
@@ -194,21 +181,17 @@ const HorizontalTabBar: React.FC<{
194
181
  return renderItems(items);
195
182
  };
196
183
 
197
- const customContainer = withCta && floatingButton?.container;
198
-
199
184
  return (
200
185
  <View
201
186
  style={[styles.container, { width }]}
202
187
  accessibilityLabel={'bottom_tab_bar'}
203
188
  >
204
- {customContainer || (
205
- <CurvedContainer
206
- width={width}
207
- height={TabBarMetrics.height}
208
- radius={TabBarMetrics.height / 2}
209
- notchCenterX={withCta ? width / 2 : undefined}
210
- />
211
- )}
189
+ <CurvedContainer
190
+ width={width}
191
+ height={TabBarMetrics.height}
192
+ radius={TabBarMetrics.height / 2}
193
+ notchCenterX={withCta ? width / 2 : undefined}
194
+ />
212
195
  <View style={styles.content}>
213
196
  <ActiveIndicator
214
197
  axis={'x'}
@@ -227,9 +210,9 @@ const HorizontalTabBar: React.FC<{
227
210
  };
228
211
 
229
212
  /**
230
- * phone tab bar: the horizontal navigation bar floating over the tab content above the home
231
- * indicator, so the content shows around it and through the notch. The transparent strip
232
- * above it keeps the rising center action touchable
213
+ * Phone tab bar: the horizontal bar floats over the tab content above the home indicator,
214
+ * so content shows around it and through the notch. The transparent strip above it keeps
215
+ * the rising center action touchable.
233
216
  */
234
217
  export default function BottomTabBar(props: Props) {
235
218
  const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
@@ -241,6 +224,8 @@ export default function BottomTabBar(props: Props) {
241
224
  window.height,
242
225
  insets.left,
243
226
  insets.right,
227
+ state.routes.length,
228
+ withCta,
244
229
  );
245
230
 
246
231
  return (
@@ -1,11 +1,15 @@
1
1
  import { createContext } from 'react';
2
2
 
3
3
  type BottomTabInset = {
4
- // space the floating tab bar takes over the bottom of a tab's content (bar + home
5
- // indicator area, plus the rising center action when shown); 0 on the iPhone Duo rail
4
+ /**
5
+ * Space the floating tab bar takes over the bottom of a tab's content (bar + home
6
+ * indicator area, plus the rising center action when shown); 0 on the iPhone Duo rail.
7
+ */
6
8
  inset: number;
7
- // a Screen in the tab keeps its content clear of the bar itself (inside its scroll view,
8
- // or above the bar when static), so the tab stops padding its content
9
+ /**
10
+ * Called by a Screen in the tab that keeps its content clear of the bar itself (inside
11
+ * its scroll view, or above the bar when static), so the tab stops padding its content.
12
+ */
9
13
  claim?: () => void;
10
14
  };
11
15