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

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 (146) 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 +15 -14
  5. package/Application/BottomTab/BottomTabBar.tsx +49 -62
  6. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  7. package/Application/BottomTab/CurvedContainer.tsx +14 -12
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  10. package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
  11. package/Application/BottomTab/DuoTabBar.tsx +8 -10
  12. package/Application/BottomTab/TabBarIcon.tsx +15 -29
  13. package/Application/BottomTab/TabScreen.tsx +1 -5
  14. package/Application/BottomTab/index.tsx +33 -30
  15. package/Application/BottomTab/metrics.ts +23 -23
  16. package/Application/BottomTab/tabItems.ts +1 -1
  17. package/Application/BottomTab/usePressLift.ts +2 -2
  18. package/Application/Components/BackgroundImageView.tsx +4 -0
  19. package/Application/Components/DuoGlassBackground.tsx +5 -4
  20. package/Application/Components/HeaderAnimated.tsx +40 -12
  21. package/Application/Components/HeaderBackground.tsx +19 -2
  22. package/Application/Components/HeaderExtendHeader.tsx +56 -34
  23. package/Application/Components/HeaderLeft.tsx +4 -2
  24. package/Application/Components/HeaderRight.styles.ts +15 -9
  25. package/Application/Components/HeaderRight.tsx +2 -1
  26. package/Application/Components/HeaderTitle.shared.ts +19 -12
  27. package/Application/Components/HeaderTitle.tsx +4 -3
  28. package/Application/Components/HeaderToolkitAction.tsx +28 -22
  29. package/Application/Components/NavigationButton.tsx +10 -6
  30. package/Application/Components/SearchHeader.tsx +41 -15
  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 +19 -54
  43. package/Application/TooltipPortal.tsx +12 -5
  44. package/Application/WidgetContainer.tsx +10 -4
  45. package/Application/headerLayout.ts +158 -0
  46. package/Application/index.ts +11 -1
  47. package/Application/modalLayout.ts +13 -14
  48. package/Application/navigationOptions.tsx +27 -32
  49. package/Application/types.header.ts +7 -2
  50. package/Application/types.ts +7 -0
  51. package/Badge/Badge.tsx +5 -0
  52. package/Badge/BadgeDot.tsx +1 -0
  53. package/Badge/BadgeDotAnimation.tsx +15 -7
  54. package/Badge/BadgeRibbon.tsx +46 -38
  55. package/Badge/Shape.tsx +2 -0
  56. package/Badge/types.ts +2 -2
  57. package/Button/index.tsx +5 -16
  58. package/Button/utils.ts +9 -15
  59. package/CheckBox/index.tsx +4 -0
  60. package/Consts/theme.ts +10 -10
  61. package/Context/index.ts +5 -1
  62. package/Divider/DashDivider.tsx +1 -0
  63. package/Divider/index.tsx +4 -0
  64. package/FoundationList/index.tsx +4 -0
  65. package/Icon/index.tsx +6 -6
  66. package/Icon/types.ts +1 -1
  67. package/IconButton/index.tsx +4 -12
  68. package/Image/index.tsx +9 -7
  69. package/Input/Input.tsx +8 -9
  70. package/Input/InputDropDown.tsx +10 -12
  71. package/Input/InputMoney.tsx +12 -8
  72. package/Input/InputMoneyView.tsx +14 -7
  73. package/Input/InputOTP.tsx +35 -33
  74. package/Input/InputPhoneNumber.tsx +8 -9
  75. package/Input/InputSearch.tsx +22 -8
  76. package/Input/InputTextArea.tsx +4 -0
  77. package/Input/SearchTextTyping.tsx +4 -0
  78. package/Input/SystemTextInput.tsx +7 -2
  79. package/Input/TextTyping.tsx +4 -0
  80. package/Input/common.tsx +26 -12
  81. package/Input/index.tsx +24 -21
  82. package/Input/moneyUtils.ts +10 -4
  83. package/Input/styles.ts +0 -5
  84. package/Input/utils.ts +14 -12
  85. package/Layout/Card.tsx +7 -4
  86. package/Layout/FloatingButton.tsx +4 -0
  87. package/Layout/Item.tsx +6 -2
  88. package/Layout/ItemList.tsx +9 -7
  89. package/Layout/ItemSectionList.tsx +2 -1
  90. package/Layout/ReservedRegions.tsx +25 -25
  91. package/Layout/Screen.tsx +59 -58
  92. package/Layout/Section.tsx +8 -5
  93. package/Layout/TrackingScope.tsx +1 -0
  94. package/Layout/screenContent.tsx +4 -2
  95. package/Layout/screenFooter.tsx +28 -18
  96. package/Layout/screenHeader.tsx +88 -82
  97. package/Layout/screenQuickReturn.tsx +17 -20
  98. package/Layout/screenScroll.ts +4 -7
  99. package/Layout/screenTypes.ts +13 -4
  100. package/Layout/types.ts +2 -4
  101. package/Layout/utils.ts +31 -37
  102. package/Loader/DotLoader.tsx +4 -0
  103. package/Loader/Loader.tsx +4 -0
  104. package/Loader/ProgressBar.tsx +1 -0
  105. package/Loader/Spinner.tsx +4 -0
  106. package/Loader/utils.ts +11 -7
  107. package/Pagination/Dot.tsx +4 -0
  108. package/Pagination/PaginationDot.tsx +1 -0
  109. package/Pagination/PaginationNumber.tsx +5 -3
  110. package/Pagination/PaginationScroll.tsx +5 -0
  111. package/Pagination/PaginationWhiteDot.tsx +1 -0
  112. package/Pagination/index.tsx +4 -0
  113. package/Pagination/types.ts +2 -2
  114. package/Popup/PopupNotify.tsx +44 -48
  115. package/Popup/PopupPromotion.tsx +4 -7
  116. package/Popup/types.ts +4 -4
  117. package/Popup/utils.ts +4 -0
  118. package/Radio/index.tsx +4 -0
  119. package/Skeleton/index.tsx +8 -0
  120. package/Switch/index.tsx +1 -0
  121. package/Tag/index.tsx +5 -1
  122. package/Tag/types.ts +3 -3
  123. package/Text/index.tsx +14 -3
  124. package/Text/styles.ts +1 -1
  125. package/Text/types.ts +1 -6
  126. package/Text/utils.ts +20 -4
  127. package/Title/index.tsx +41 -32
  128. package/ViewBoundary/index.tsx +4 -2
  129. package/index.ts +0 -6
  130. package/package.json +1 -1
  131. package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
  132. package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
  133. package/Application/KitAdoption/AdoptionStore.ts +0 -80
  134. package/Application/KitAdoption/Authorship/utils.ts +0 -137
  135. package/Application/KitAdoption/Slots/utils.ts +0 -68
  136. package/Application/KitAdoption/constants.ts +0 -120
  137. package/Application/KitAdoption/index.ts +0 -12
  138. package/Application/KitAdoption/styles.ts +0 -55
  139. package/Application/KitAdoption/types.ts +0 -61
  140. package/Application/KitAdoption/utils.ts +0 -376
  141. package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
  142. package/Application/__tests__/KitAdoption.test.ts +0 -283
  143. package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
  144. package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
  145. package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
  146. 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
@@ -19,8 +19,8 @@ type Props = {
19
19
  };
20
20
 
21
21
  /**
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
22
+ * The single active indicator of a bar (Background/tonal with inner shadows) that slides
23
+ * to the selected item. It lands in place on the first measure and slides from then on.
24
24
  */
25
25
  const ActiveIndicator: React.FC<Props> = ({
26
26
  axis,
@@ -49,18 +49,19 @@ const ActiveIndicator: React.FC<Props> = ({
49
49
  position.value = withTiming(offset, TabBarMotion.indicator);
50
50
  }, [offset, extent, position, size, visible]);
51
51
 
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
- );
52
+ const animatedStyle = useAnimatedStyle(() => {
53
+ if (axis === 'x') {
54
+ return {
55
+ opacity: visible.value,
56
+ width: size.value,
57
+ transform: [{ translateX: position.value }],
58
+ };
59
+ }
60
+ return {
61
+ opacity: visible.value,
62
+ transform: [{ translateY: position.value }],
63
+ };
64
+ });
64
65
 
65
66
  return (
66
67
  <Animated.View
@@ -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
  );
@@ -227,9 +214,9 @@ const HorizontalTabBar: React.FC<{
227
214
  };
228
215
 
229
216
  /**
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
217
+ * Phone tab bar: the horizontal bar floats over the tab content above the home indicator,
218
+ * so content shows around it and through the notch. The transparent strip above it keeps
219
+ * the rising center action touchable.
233
220
  */
234
221
  export default function BottomTabBar(props: Props) {
235
222
  const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
@@ -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
 
@@ -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,
@@ -10,7 +10,7 @@ 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
16
  import { Colors, Spacing } from '../../Consts';
@@ -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,
@@ -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
  },