@momo-kits/foundation 0.169.6 → 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 (143) 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.tsx +62 -49
  6. package/Application/BottomTab/BottomTabInset.tsx +4 -8
  7. package/Application/BottomTab/CurvedContainer.tsx +12 -14
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +19 -13
  10. package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
  11. package/Application/BottomTab/DuoTabBar.tsx +10 -8
  12. package/Application/BottomTab/TabBarIcon.tsx +29 -15
  13. package/Application/BottomTab/TabScreen.tsx +5 -1
  14. package/Application/BottomTab/index.tsx +30 -33
  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 +0 -4
  19. package/Application/Components/DuoGlassBackground.tsx +4 -5
  20. package/Application/Components/HeaderAnimated.tsx +0 -4
  21. package/Application/Components/HeaderBackground.tsx +0 -4
  22. package/Application/Components/HeaderExtendHeader.tsx +10 -11
  23. package/Application/Components/HeaderLeft.tsx +1 -2
  24. package/Application/Components/HeaderRight.tsx +1 -2
  25. package/Application/Components/HeaderTitle.shared.ts +9 -11
  26. package/Application/Components/HeaderTitle.tsx +3 -4
  27. package/Application/Components/HeaderToolkitAction.tsx +20 -17
  28. package/Application/Components/NavigationButton.tsx +1 -2
  29. package/Application/Components/SearchHeader.tsx +10 -17
  30. package/Application/Components/TitleJourney.tsx +2 -2
  31. package/Application/Components/TitleLocation.tsx +2 -2
  32. package/Application/Components/TitleUser.tsx +2 -2
  33. package/Application/Components/useToolkitActions.tsx +32 -17
  34. package/Application/KitAdoption/AdoptionBadge.tsx +119 -0
  35. package/Application/KitAdoption/AdoptionProbe.tsx +166 -0
  36. package/Application/KitAdoption/AdoptionStore.ts +80 -0
  37. package/Application/KitAdoption/Authorship/utils.ts +137 -0
  38. package/Application/KitAdoption/Slots/utils.ts +68 -0
  39. package/Application/KitAdoption/constants.ts +120 -0
  40. package/Application/KitAdoption/index.ts +12 -0
  41. package/Application/KitAdoption/styles.ts +55 -0
  42. package/Application/KitAdoption/types.ts +61 -0
  43. package/Application/KitAdoption/utils.ts +376 -0
  44. package/Application/Localize.ts +0 -7
  45. package/Application/ModalScreen.tsx +10 -10
  46. package/Application/Navigation.ts +16 -14
  47. package/Application/NavigationContainer.tsx +18 -19
  48. package/Application/Navigator.ts +48 -20
  49. package/Application/ScaleSizeProvider.tsx +6 -13
  50. package/Application/StackScreen.tracking.ts +33 -11
  51. package/Application/StackScreen.tsx +54 -10
  52. package/Application/TooltipPortal.tsx +5 -12
  53. package/Application/WidgetContainer.tsx +4 -10
  54. package/Application/__tests__/KitAdoption.integration.test.tsx +253 -0
  55. package/Application/__tests__/KitAdoption.test.ts +283 -0
  56. package/Application/__tests__/KitAdoptionAuthorship.test.ts +132 -0
  57. package/Application/__tests__/KitAdoptionStore.test.ts +110 -0
  58. package/Application/__tests__/KitAdoptionStructural.test.ts +193 -0
  59. package/Application/__tests__/modalLayout.test.ts +128 -0
  60. package/Application/modalLayout.ts +14 -13
  61. package/Application/navigationOptions.tsx +17 -21
  62. package/Badge/Badge.tsx +0 -5
  63. package/Badge/BadgeDot.tsx +0 -1
  64. package/Badge/BadgeDotAnimation.tsx +7 -15
  65. package/Badge/BadgeRibbon.tsx +38 -46
  66. package/Badge/Shape.tsx +0 -2
  67. package/Badge/types.ts +2 -2
  68. package/Button/index.tsx +16 -5
  69. package/Button/utils.ts +15 -9
  70. package/CheckBox/index.tsx +0 -4
  71. package/Consts/theme.ts +10 -10
  72. package/Context/index.ts +1 -5
  73. package/Divider/DashDivider.tsx +0 -1
  74. package/Divider/index.tsx +0 -4
  75. package/FoundationList/index.tsx +0 -4
  76. package/Icon/index.tsx +6 -6
  77. package/Icon/types.ts +1 -1
  78. package/IconButton/index.tsx +12 -4
  79. package/Image/index.tsx +7 -9
  80. package/Input/Input.tsx +9 -8
  81. package/Input/InputDropDown.tsx +12 -10
  82. package/Input/InputMoney.tsx +8 -12
  83. package/Input/InputMoneyView.tsx +7 -14
  84. package/Input/InputOTP.tsx +33 -35
  85. package/Input/InputPhoneNumber.tsx +9 -8
  86. package/Input/InputSearch.tsx +6 -4
  87. package/Input/InputTextArea.tsx +0 -4
  88. package/Input/SearchTextTyping.tsx +0 -4
  89. package/Input/SystemTextInput.tsx +2 -7
  90. package/Input/TextTyping.tsx +0 -4
  91. package/Input/common.tsx +12 -26
  92. package/Input/index.tsx +20 -17
  93. package/Input/moneyUtils.ts +4 -10
  94. package/Input/styles.ts +5 -0
  95. package/Input/utils.ts +12 -14
  96. package/Layout/Card.tsx +13 -16
  97. package/Layout/FloatingButton.tsx +0 -4
  98. package/Layout/Item.tsx +2 -6
  99. package/Layout/ItemList.tsx +7 -9
  100. package/Layout/ItemSectionList.tsx +1 -2
  101. package/Layout/ReservedRegions.tsx +25 -25
  102. package/Layout/Screen.tsx +58 -56
  103. package/Layout/Section.tsx +13 -17
  104. package/Layout/TrackingScope.tsx +0 -1
  105. package/Layout/constants.ts +19 -0
  106. package/Layout/screenContent.tsx +2 -4
  107. package/Layout/screenFooter.tsx +18 -28
  108. package/Layout/screenHeader.tsx +31 -34
  109. package/Layout/screenQuickReturn.tsx +20 -17
  110. package/Layout/screenScroll.ts +7 -4
  111. package/Layout/screenTypes.ts +2 -3
  112. package/Layout/types.ts +41 -3
  113. package/Layout/useGridSystem.ts +49 -0
  114. package/Layout/utils.ts +37 -38
  115. package/Loader/DotLoader.tsx +0 -4
  116. package/Loader/Loader.tsx +0 -4
  117. package/Loader/ProgressBar.tsx +0 -1
  118. package/Loader/Spinner.tsx +0 -4
  119. package/Loader/utils.ts +7 -11
  120. package/Pagination/Dot.tsx +0 -4
  121. package/Pagination/PaginationDot.tsx +0 -1
  122. package/Pagination/PaginationNumber.tsx +3 -5
  123. package/Pagination/PaginationScroll.tsx +0 -5
  124. package/Pagination/PaginationWhiteDot.tsx +0 -1
  125. package/Pagination/index.tsx +0 -4
  126. package/Pagination/types.ts +2 -2
  127. package/Popup/PopupNotify.tsx +48 -44
  128. package/Popup/PopupPromotion.tsx +7 -4
  129. package/Popup/types.ts +4 -4
  130. package/Popup/utils.ts +0 -4
  131. package/Radio/index.tsx +0 -4
  132. package/Skeleton/index.tsx +0 -8
  133. package/Switch/index.tsx +0 -1
  134. package/Tag/index.tsx +1 -5
  135. package/Tag/types.ts +3 -3
  136. package/Text/index.tsx +3 -14
  137. package/Text/styles.ts +1 -1
  138. package/Text/types.ts +6 -1
  139. package/Text/utils.ts +4 -20
  140. package/Title/index.tsx +32 -41
  141. package/ViewBoundary/index.tsx +2 -4
  142. package/index.ts +6 -0
  143. package/package.json +1 -1
@@ -51,10 +51,8 @@ export const useBottomSheetLayout = () => {
51
51
  };
52
52
  };
53
53
 
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
- */
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
58
56
  export const useDuoKeyboardLift = (
59
57
  isDuo: boolean,
60
58
  useKeyboardAvoidingView: boolean,
@@ -138,10 +136,6 @@ type SheetDismissParams = {
138
136
  action: MutableRefObject<undefined | string>;
139
137
  };
140
138
 
141
- /**
142
- * Close handlers of a bottom sheet. A non-forced dismiss (scrim tap, hardware
143
- * back) is ignored while `barrierDismissible` is set.
144
- */
145
139
  export const useSheetDismiss = ({
146
140
  barrierDismissible,
147
141
  closeAnimation,
@@ -149,6 +143,9 @@ export const useSheetDismiss = ({
149
143
  leftOptions,
150
144
  action,
151
145
  }: SheetDismissParams) => {
146
+ /**
147
+ * handler dismiss
148
+ */
152
149
  const onDismiss = useCallback(
153
150
  (force = false, callback?: () => void) => {
154
151
  if (barrierDismissible && !force) {
@@ -164,6 +161,9 @@ export const useSheetDismiss = ({
164
161
  [barrierDismissible, closeAnimation, navigator],
165
162
  );
166
163
 
164
+ /**
165
+ * on request close
166
+ */
167
167
  const onRequestClose = useCallback(
168
168
  (callback?: () => void) => {
169
169
  onDismiss(true, callback);
@@ -34,52 +34,42 @@ export const renderSheetHeader = ({
34
34
  iconBack,
35
35
  options,
36
36
  leftOptions,
37
- }: SheetHeaderParams) => {
38
- let backButton: React.ReactNode = null;
39
- if (showBack) {
40
- backButton = <ModalIconButton icon={iconBack} onPress={onPressBack} />;
41
- }
42
-
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}
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}
76
54
 
77
- {headerContent}
78
- <ModalIconButton icon="navigation_close" onPress={onPressClose} />
79
- </View>
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} />
80
70
  </View>
81
- );
82
- };
71
+ </View>
72
+ );
83
73
 
84
74
  /**
85
75
  * Footer pinned under the sheet content, `null` when the sheet has none.
@@ -18,6 +18,7 @@ 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';
21
22
  import {
22
23
  useBottomSheetLayout,
23
24
  useDuoKeyboardLift,
@@ -30,10 +31,6 @@ import {
30
31
  renderSheetHeader,
31
32
  } from './BottomSheet.parts';
32
33
 
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
- */
37
34
  const BottomSheet: React.FC<BottomSheetParams> = props => {
38
35
  const { theme, navigator } = useContext(ApplicationContext);
39
36
  const context = useContext<any>(MiniAppContext);
@@ -102,9 +99,8 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
102
99
  const panResponder = useRef(
103
100
  PanResponder.create({
104
101
  onStartShouldSetPanResponder: () => draggable,
105
- onMoveShouldSetPanResponder: (_, gestureState) => {
106
- return draggable && gestureState.dy > 0;
107
- },
102
+ onMoveShouldSetPanResponder: (_, gestureState) =>
103
+ draggable && gestureState.dy > 0,
108
104
  onPanResponderMove: (_, gestureState) => {
109
105
  if (gestureState.dy > 0) {
110
106
  translateY.value = gestureState.dy;
@@ -136,7 +132,10 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
136
132
  backgroundColor = theme.colors.background.surface;
137
133
  }
138
134
 
139
- // Mount only, so folding or rotating iPhone Duo doesn't emit a dismiss.
135
+ /**
136
+ * emit dismiss event, once: folding or rotating iPhone Duo changes the
137
+ * window height while the sheet stays open
138
+ */
140
139
  useEffect(() => {
141
140
  openAnimation();
142
141
  return () => {
@@ -144,14 +143,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
144
143
  };
145
144
  }, [openAnimation]);
146
145
 
147
- const handlers = useSheetDismiss({
148
- barrierDismissible,
149
- closeAnimation,
150
- navigator,
151
- leftOptions,
152
- action,
153
- });
154
- const { onDismiss, onRequestClose, onPressBack, onPressClose } = handlers;
146
+ const { onDismiss, onRequestClose, onPressBack, onPressClose } =
147
+ useSheetDismiss({
148
+ barrierDismissible,
149
+ closeAnimation,
150
+ navigator,
151
+ leftOptions,
152
+ action,
153
+ });
155
154
 
156
155
  const showBack = !!leftOptions?.onPressIconLeft;
157
156
  const iconBack = leftOptions?.iconLeft ?? 'ic_back';
@@ -175,31 +174,18 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
175
174
  );
176
175
 
177
176
  const screen = (
178
- <Screen
179
- {...props}
180
- {...props.route.params}
181
- onRequestClose={onRequestClose}
182
- />
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>
183
187
  );
184
188
 
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
-
203
189
  return (
204
190
  <Container
205
191
  transparent
@@ -245,7 +231,18 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
245
231
  }),
246
232
  ]}
247
233
  >
248
- {content}
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
+ )}
249
246
  {renderSheetFooter({ footerComponent, theme })}
250
247
  {useBottomInset && (
251
248
  <View
@@ -19,8 +19,8 @@ type Props = {
19
19
  };
20
20
 
21
21
  /**
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.
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
24
24
  */
25
25
  const ActiveIndicator: React.FC<Props> = ({
26
26
  axis,
@@ -49,19 +49,18 @@ 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
- 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
- });
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
+ );
65
64
 
66
65
  return (
67
66
  <Animated.View
@@ -17,6 +17,7 @@ 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';
20
21
  import { styles } from './BottomTabBar.styles';
21
22
 
22
23
  type Props = BottomTabBarProps & {
@@ -26,28 +27,24 @@ type Props = BottomTabBarProps & {
26
27
  };
27
28
 
28
29
  /**
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.
30
+ * the center action needs a tab on each side: Special type takes 2 or 4 tabs
31
31
  */
32
- const hasCenterAction = (floatingButton: unknown, tabCount: number) => {
33
- return !!floatingButton && tabCount % 2 === 0;
34
- };
32
+ const hasCenterAction = (floatingButton: unknown, tabCount: number) =>
33
+ !!floatingButton && tabCount % 2 === 0;
35
34
 
36
35
  /**
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.
36
+ * gap under the container: the home indicator / gesture bar area
39
37
  */
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
- };
38
+ const getSystemIndicatorHeight = (bottomInset: number) =>
39
+ Math.max(
40
+ Platform.OS === 'ios'
41
+ ? Math.min(bottomInset, TabBarMetrics.systemIndicatorHeight)
42
+ : bottomInset,
43
+ Spacing.S,
44
+ );
47
45
 
48
46
  /**
49
- * Press feedback of the center action: the button shrinks while held, the label
50
- * stays still.
47
+ * center action press: the button shrinks while held; the label stays plain text
51
48
  */
52
49
  const useCenterActionPress = () => {
53
50
  const scale = useSharedValue(1);
@@ -67,27 +64,26 @@ const useCenterActionPress = () => {
67
64
  };
68
65
 
69
66
  /**
70
- * Center action (.Nav/CTA): a button rising out of the notch, its label below
71
- * (growing downward when scaled).
67
+ * center action (.Nav/CTA): button rising out of the notch, label under it
72
68
  */
73
- const CenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
74
- floatingButton,
75
- }) => {
69
+ const CenterAction: React.FC<{
70
+ floatingButton: FloatingButtonProps;
71
+ fixedFontScale?: boolean;
72
+ }> = ({ floatingButton, fixedFontScale }) => {
76
73
  const { theme } = useContext(ApplicationContext);
77
74
  const context = useContext<any>(MiniAppContext);
78
75
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
79
76
  const { icon, label, onPress, labelContent } = floatingButton;
80
77
  const { buttonStyle, onPressIn, onPressOut } = useCenterActionPress();
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
- }
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
+ );
91
87
 
92
88
  return (
93
89
  <View style={[styles.ctaSlot, showBaseLineDebug && styles.debugBaseLine]}>
@@ -106,16 +102,22 @@ const CenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
106
102
  buttonStyle,
107
103
  ]}
108
104
  >
109
- {iconNode}
105
+ {typeof icon === 'string' ? (
106
+ <Icon
107
+ source={icon}
108
+ size={TabBarMetrics.ctaIconSize}
109
+ color={Colors.black_01}
110
+ />
111
+ ) : (
112
+ icon
113
+ )}
110
114
  </Animated.View>
111
115
  <View style={styles.ctaLabel}>
112
- <Text
113
- numberOfLines={1}
114
- typography={'label_s_medium'}
115
- color={theme.colors.text.secondary}
116
- >
117
- {label}
118
- </Text>
116
+ {fixedFontScale ? (
117
+ <FixedScaleText>{labelText}</FixedScaleText>
118
+ ) : (
119
+ labelText
120
+ )}
119
121
  {labelContent && (
120
122
  <View pointerEvents={'none'} style={styles.ctaLabelOverlay}>
121
123
  {labelContent}
@@ -128,8 +130,8 @@ const CenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
128
130
  };
129
131
 
130
132
  /**
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.
133
+ * horizontal navigation bar: floating container, items split evenly, Special type puts
134
+ * the center action in a notch in the middle
133
135
  */
134
136
  const HorizontalTabBar: React.FC<{
135
137
  items: TabItem[];
@@ -137,15 +139,23 @@ const HorizontalTabBar: React.FC<{
137
139
  activeTintColor: string;
138
140
  inactiveTintColor: string;
139
141
  floatingButton?: FloatingButtonProps;
140
- }> = ({ items, width, activeTintColor, inactiveTintColor, floatingButton }) => {
142
+ fixedFontScale?: boolean;
143
+ }> = ({
144
+ items,
145
+ width,
146
+ activeTintColor,
147
+ inactiveTintColor,
148
+ floatingButton,
149
+ fixedFontScale,
150
+ }) => {
141
151
  const withCta = hasCenterAction(floatingButton, items.length);
142
152
  const [layouts, setLayouts] = useState<
143
153
  Record<string, { x: number; y: number; width: number }>
144
154
  >({});
145
155
  const selected = layouts[items.find(item => item.focused)?.route.key ?? ''];
146
156
 
147
- const renderItems = (data: TabItem[]) => {
148
- return data.map(item => (
157
+ const renderItems = (data: TabItem[]) =>
158
+ data.map(item => (
149
159
  <CustomBottomTabItem
150
160
  key={item.route.key}
151
161
  onLayout={e => {
@@ -163,9 +173,9 @@ const HorizontalTabBar: React.FC<{
163
173
  onLongPress={item.onLongPress}
164
174
  activeTintColor={activeTintColor}
165
175
  inactiveTintColor={inactiveTintColor}
176
+ fixedFontScale={fixedFontScale}
166
177
  />
167
178
  ));
168
- };
169
179
 
170
180
  const renderContent = () => {
171
181
  if (withCta && floatingButton) {
@@ -173,7 +183,10 @@ const HorizontalTabBar: React.FC<{
173
183
  return (
174
184
  <>
175
185
  {renderItems(items.slice(0, middle))}
176
- <CenterAction floatingButton={floatingButton} />
186
+ <CenterAction
187
+ floatingButton={floatingButton}
188
+ fixedFontScale={fixedFontScale}
189
+ />
177
190
  {renderItems(items.slice(middle))}
178
191
  </>
179
192
  );
@@ -214,9 +227,9 @@ const HorizontalTabBar: React.FC<{
214
227
  };
215
228
 
216
229
  /**
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.
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
220
233
  */
221
234
  export default function BottomTabBar(props: Props) {
222
235
  const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
@@ -1,15 +1,11 @@
1
1
  import { createContext } from 'react';
2
2
 
3
3
  type BottomTabInset = {
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
- */
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
8
6
  inset: number;
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
- */
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
13
9
  claim?: () => void;
14
10
  };
15
11
 
@@ -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,
@@ -10,7 +10,7 @@ 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
16
  import { Colors, Spacing } from '../../Consts';
@@ -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,
@@ -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
  },