@momo-kits/foundation 0.169.7-beta.5 → 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.
@@ -5,7 +5,6 @@ import Animated, {
5
5
  useSharedValue,
6
6
  withTiming,
7
7
  } from 'react-native-reanimated';
8
- import { defaultTheme } from '../../Consts';
9
8
  import { ApplicationContext } from '../../Context';
10
9
  import { getIndicatorShadow, TabBarMotion } from './metrics';
11
10
 
@@ -71,10 +70,7 @@ const ActiveIndicator: React.FC<Props> = ({
71
70
  styles.indicator,
72
71
  {
73
72
  borderRadius: radius,
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
+ backgroundColor: theme.colors.background.tonal,
78
74
  boxShadow: getIndicatorShadow(theme.colors.primary),
79
75
  },
80
76
  style,
@@ -2,20 +2,6 @@ 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
-
19
5
  export const styles = StyleSheet.create({
20
6
  bar: {
21
7
  position: 'absolute',
@@ -51,7 +37,7 @@ export const styles = StyleSheet.create({
51
37
  position: 'absolute',
52
38
  left: 0,
53
39
  right: 0,
54
- top: CTA_TOP,
40
+ top: -TabBarMetrics.ctaLift - TabBarMetrics.paddingVertical,
55
41
  alignItems: 'center',
56
42
  gap: TabBarMetrics.ctaLabelGap,
57
43
  },
@@ -60,9 +46,6 @@ export const styles = StyleSheet.create({
60
46
  width: TabBarMetrics.ctaSize,
61
47
  height: TabBarMetrics.ctaSize,
62
48
  borderRadius: TabBarMetrics.ctaSize / 2,
63
- // Figma Icon Button border: white 60% hairline
64
- borderWidth: 1,
65
- borderColor: `rgba(255, 255, 255, ${TabBarMetrics.ctaBorderOpacity})`,
66
49
  alignItems: 'center',
67
50
  justifyContent: 'center',
68
51
  },
@@ -181,17 +181,21 @@ const HorizontalTabBar: React.FC<{
181
181
  return renderItems(items);
182
182
  };
183
183
 
184
+ const customContainer = withCta && floatingButton?.container;
185
+
184
186
  return (
185
187
  <View
186
188
  style={[styles.container, { width }]}
187
189
  accessibilityLabel={'bottom_tab_bar'}
188
190
  >
189
- <CurvedContainer
190
- width={width}
191
- height={TabBarMetrics.height}
192
- radius={TabBarMetrics.height / 2}
193
- notchCenterX={withCta ? width / 2 : undefined}
194
- />
191
+ {customContainer || (
192
+ <CurvedContainer
193
+ width={width}
194
+ height={TabBarMetrics.height}
195
+ radius={TabBarMetrics.height / 2}
196
+ notchCenterX={withCta ? width / 2 : undefined}
197
+ />
198
+ )}
195
199
  <View style={styles.content}>
196
200
  <ActiveIndicator
197
201
  axis={'x'}
@@ -224,8 +228,6 @@ export default function BottomTabBar(props: Props) {
224
228
  window.height,
225
229
  insets.left,
226
230
  insets.right,
227
- state.routes.length,
228
- withCta,
229
231
  );
230
232
 
231
233
  return (
@@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native';
3
3
  import Svg, { Defs, FeGaussianBlur, Filter, Path } from 'react-native-svg';
4
4
  import { Colors } from '../../Consts';
5
5
  import { ApplicationContext } from '../../Context';
6
- import { NOTCH_CURVES, NOTCH_HALF_WIDTH, TabBarMetrics } from './metrics';
6
+ import { NOTCH_CURVES, NOTCH_HALF_WIDTH } from './metrics';
7
7
 
8
8
  type Props = {
9
9
  width: number;
@@ -139,13 +139,7 @@ const CurvedContainer: React.FC<Props> = ({
139
139
  filter={'url(#keyShadow)'}
140
140
  />
141
141
  )}
142
- <Path
143
- d={path}
144
- fill={theme.colors.background.surface}
145
- fillOpacity={TabBarMetrics.backgroundOpacity}
146
- stroke={theme.colors.border.default}
147
- strokeWidth={1}
148
- />
142
+ <Path d={path} fill={theme.colors.background.surface} />
149
143
  </Svg>
150
144
  );
151
145
  };
@@ -13,7 +13,7 @@ import Animated, {
13
13
  import { TabBarIcon } from './TabBarIcon';
14
14
  import { Text } from '../../Text';
15
15
  import { MiniAppContext } from '../../Context';
16
- import { Colors } from '../../Consts';
16
+ import { Colors, Spacing } from '../../Consts';
17
17
  import { TabBarMetrics, TabBarMotion } from './metrics';
18
18
 
19
19
  type IconRenderer = (props: {
@@ -143,7 +143,7 @@ const styles = StyleSheet.create({
143
143
  minWidth: 1,
144
144
  alignItems: 'center',
145
145
  justifyContent: 'center',
146
- gap: TabBarMetrics.itemIconLabelGap,
146
+ gap: Spacing.XXS,
147
147
  },
148
148
  slot: {
149
149
  height: TabBarMetrics.indicatorHeight,
@@ -3,11 +3,7 @@ import React from 'react';
3
3
  import { useWindowDimensions, View } from 'react-native';
4
4
  import { useScreenLayout } from '../../Layout/utils';
5
5
  import { FloatingButtonProps } from '../types';
6
- import {
7
- getSystemIndicatorHeight,
8
- hasCenterAction,
9
- HorizontalTabBar,
10
- } from './BottomTabBar';
6
+ import { getSystemIndicatorHeight, HorizontalTabBar } from './BottomTabBar';
11
7
  import { getHorizontalBarWidth, TabBarMetrics } from './metrics';
12
8
  import { getTabItems } from './tabItems';
13
9
  import { DuoRailTabBar } from './DuoRailTabBar';
@@ -38,7 +34,6 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
38
34
  const { insets } = useScreenLayout();
39
35
  const { width, height } = useWindowDimensions();
40
36
  const { floatingButton, activeTintColor, inactiveTintColor } = props;
41
- const tabItems = getTabItems(props);
42
37
 
43
38
  return (
44
39
  <View
@@ -49,15 +44,8 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
49
44
  ]}
50
45
  >
51
46
  <HorizontalTabBar
52
- items={tabItems}
53
- width={getHorizontalBarWidth(
54
- width,
55
- height,
56
- insets.left,
57
- insets.right,
58
- tabItems.length,
59
- hasCenterAction(floatingButton, tabItems.length),
60
- )}
47
+ items={getTabItems(props)}
48
+ width={getHorizontalBarWidth(width, height, insets.left, insets.right)}
61
49
  floatingButton={floatingButton}
62
50
  activeTintColor={activeTintColor}
63
51
  inactiveTintColor={inactiveTintColor}
@@ -8,18 +8,11 @@ import { Colors, Radius, Spacing } from '../../Consts';
8
8
  * iPhone Duo rail (center action under the container).
9
9
  */
10
10
  const TabBarMetrics = {
11
- height: 60,
11
+ height: 68,
12
12
  margin: Spacing.S,
13
- paddingVertical: Spacing.XS,
13
+ paddingVertical: Spacing.S,
14
14
  paddingHorizontal: Spacing.M,
15
- itemGap: 1,
16
- // gap between the icon slot and the label inside a tab (Figma: none)
17
- itemIconLabelGap: 0,
18
- // .Nav/Background: no backdrop blur, so use the Figma Blur=Off spec (opacity 95%, no blur) plus the 1px
19
- // border — nearly solid, matching the frosted sample; surface keeps dark-mode support
20
- backgroundOpacity: 0.95,
21
- // white 60% hairline around the center action button (Figma Icon Button border)
22
- ctaBorderOpacity: 0.6,
15
+ itemGap: Spacing.XS,
23
16
  iconSize: 24,
24
17
  // active indicator behind the icon: 24 icon + XS·M padding
25
18
  indicatorPaddingVertical: Spacing.XS,
@@ -32,9 +25,6 @@ const TabBarMetrics = {
32
25
  systemIndicatorHeight: 24,
33
26
  foldableMinShortSide: 600,
34
27
  foldableWidthRatio: 0.65,
35
- // a 2-tab bar doesn't stretch to the full width: each item is this fixed width and the bar is
36
- // sized to its content, centered (Figma Navigation · 2 Tab: item 100, bar ~225)
37
- twoTabItemWidth: 100,
38
28
  ctaSlotWidth: 92,
39
29
  ctaSize: 48,
40
30
  ctaIconSize: 28,
@@ -142,17 +132,7 @@ const getHorizontalBarWidth = (
142
132
  height: number,
143
133
  insetLeft: number,
144
134
  insetRight: number,
145
- itemCount: number,
146
- hasCta: boolean,
147
135
  ) => {
148
- // 2 tabs (no center action) stay content-sized and centered instead of filling the window
149
- if (itemCount === 2 && !hasCta) {
150
- return (
151
- TabBarMetrics.twoTabItemWidth * 2 +
152
- TabBarMetrics.paddingHorizontal * 2 +
153
- TabBarMetrics.itemGap
154
- );
155
- }
156
136
  const shortSide = Math.min(width, height);
157
137
  if (shortSide >= TabBarMetrics.foldableMinShortSide) {
158
138
  return shortSide * TabBarMetrics.foldableWidthRatio;
@@ -1,4 +1,5 @@
1
- import { StyleSheet } from 'react-native';
1
+ import { StyleSheet, View } from 'react-native';
2
+ import LinearGradient from 'react-native-linear-gradient';
2
3
  import Animated, {
3
4
  Extrapolation,
4
5
  interpolate,
@@ -9,6 +10,7 @@ import React from 'react';
9
10
  import { Image } from '../../Image';
10
11
  import { Colors } from '../../Consts';
11
12
  import { useNormalizedSharedValue } from '../hooks';
13
+ import { HeaderBarMetrics, useHeaderLayout } from '../headerLayout';
12
14
 
13
15
  const ASPECT_RATIOS: Record<
14
16
  NonNullable<HeaderAnimatedProps['aspectRatio']>,
@@ -21,16 +23,19 @@ const ASPECT_RATIOS: Record<
21
23
 
22
24
  /**
23
25
  * Parallax header image (or `children`): stretches when pulled down and fades out
24
- * as the content scrolls up.
26
+ * as the content scrolls up. Wide windows keep it at phone size, centered.
25
27
  */
26
28
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
27
29
  animatedValue,
28
30
  image,
29
31
  useScale = true,
30
32
  aspectRatio = '16/9',
33
+ bannerBackgroundColor,
31
34
  children,
32
35
  }) => {
33
36
  const sharedValue = useNormalizedSharedValue(animatedValue);
37
+ const { isWide, bannerWidth } = useHeaderLayout();
38
+ const ratio = ASPECT_RATIOS[aspectRatio];
34
39
  const animatedStyle = useAnimatedStyle(() => {
35
40
  let scale = 1;
36
41
  if (useScale) {
@@ -59,22 +64,36 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
59
64
  };
60
65
  });
61
66
 
67
+ const content = children ?? (
68
+ <Image
69
+ source={{
70
+ uri: image,
71
+ }}
72
+ style={styles.full}
73
+ />
74
+ );
75
+
76
+ if (!isWide) {
77
+ return (
78
+ <Animated.View
79
+ style={[styles.container, { aspectRatio: ratio }, animatedStyle]}
80
+ >
81
+ {content}
82
+ </Animated.View>
83
+ );
84
+ }
85
+
62
86
  return (
63
87
  <Animated.View
64
- style={[
65
- styles.container,
66
- { aspectRatio: ASPECT_RATIOS[aspectRatio] },
67
- animatedStyle,
68
- ]}
88
+ style={[styles.container, { height: bannerWidth / ratio }, animatedStyle]}
69
89
  >
70
- {children ?? (
71
- <Image
72
- source={{
73
- uri: image,
74
- }}
75
- style={styles.full}
90
+ {!!bannerBackgroundColor && (
91
+ <LinearGradient
92
+ colors={[bannerBackgroundColor, bannerBackgroundColor + '00']}
93
+ style={StyleSheet.absoluteFill}
76
94
  />
77
95
  )}
96
+ <View style={styles.phoneBanner}>{content}</View>
78
97
  </Animated.View>
79
98
  );
80
99
  };
@@ -90,6 +109,11 @@ const styles = StyleSheet.create({
90
109
  alignSelf: 'center',
91
110
  },
92
111
  full: { width: '100%', height: '100%' },
112
+ phoneBanner: {
113
+ width: HeaderBarMetrics.bannerWidth,
114
+ height: '100%',
115
+ alignSelf: 'center',
116
+ },
93
117
  });
94
118
 
95
119
  export { HeaderAnimated };
@@ -8,10 +8,12 @@ import { Colors, Styles } from '../../Consts';
8
8
  import { Image } from '../../Image';
9
9
  import BackgroundImageView from './BackgroundImageView';
10
10
  import { useNormalizedSharedValue } from '../hooks';
11
+ import { HeaderBarMetrics, useHeaderLayout } from '../headerLayout';
11
12
 
12
13
  /**
13
14
  * Header background: surface and image that fade in on scroll, with an optional
14
- * gradient (custom or theme) on top. Outlined when `showBaseLineDebug` is on.
15
+ * gradient (custom or theme) on top, at phone height on wide windows. Outlined
16
+ * when `showBaseLineDebug` is on.
15
17
  */
16
18
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
17
19
  animatedValue,
@@ -25,6 +27,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
25
27
  const gradientColor = customGradientColor ?? theme.colors.gradient;
26
28
  const headerImage = headerBackground ?? theme.assets?.headerBackground;
27
29
  const sharedValue = useNormalizedSharedValue(animatedValue);
30
+ const { isWide } = useHeaderLayout();
28
31
 
29
32
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
30
33
 
@@ -38,14 +41,19 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
38
41
  />
39
42
  <View style={styles.gradientContainer}>
40
43
  {useGradient && !!gradientColor && (
41
- <Animated.View style={styles.extendedHeader}>
44
+ <Animated.View
45
+ style={[styles.extendedHeader, isWide && styles.phoneHeight]}
46
+ >
42
47
  <LinearGradient
43
48
  colors={[gradientColor, gradientColor + '00']}
44
49
  style={StyleSheet.absoluteFill}
45
50
  >
46
51
  {!!headerImage && (
47
52
  <Image
48
- style={styles.headerBackground}
53
+ style={[
54
+ styles.headerBackground,
55
+ isWide && styles.phoneHeight,
56
+ ]}
49
57
  source={{ uri: headerImage }}
50
58
  loading={false}
51
59
  />
@@ -75,6 +83,11 @@ const styles = StyleSheet.create({
75
83
  position: 'absolute',
76
84
  aspectRatio: 375 / 154,
77
85
  },
86
+ // wide windows keep the phone gradient height instead of scaling with the width
87
+ phoneHeight: {
88
+ aspectRatio: undefined,
89
+ height: HeaderBarMetrics.gradientHeight,
90
+ },
78
91
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
79
92
  });
80
93
 
@@ -18,10 +18,15 @@ import { type AnimatedCompatValue } from '../utils';
18
18
  import { useNormalizedSharedValue } from '../hooks';
19
19
  import { useScreenLayout } from '../../Layout';
20
20
  import { DuoMetrics, HeaderMetrics } from '../../Layout/utils';
21
+ import {
22
+ HeaderBarMetrics,
23
+ type HeaderContentLayout,
24
+ useHeaderLayout,
25
+ } from '../headerLayout';
21
26
 
22
- const SCREEN_PADDING = 12;
23
- const BACK_WIDTH = 28;
24
- const TOOLKIT_WIDTH = 73;
27
+ const { buttonSize, pillWidth, sidePadding, titleGap } = HeaderBarMetrics;
28
+ const SCREEN_PADDING = sidePadding;
29
+ const TOOLKIT_WIDTH = pillWidth + sidePadding;
25
30
 
26
31
  /**
27
32
  * Extended header background: a gradient that collapses on scroll and, with
@@ -38,6 +43,7 @@ const HeaderExtendHeader: React.FC<{
38
43
  useShadowHeader?: boolean;
39
44
  gradientColor?: string;
40
45
  headerBackground?: string;
46
+ contentLayout?: HeaderContentLayout;
41
47
  }> = ({
42
48
  headerType = 'default',
43
49
  animatedValue,
@@ -48,6 +54,7 @@ const HeaderExtendHeader: React.FC<{
48
54
  useShadowHeader: useShadowHeaderProp = true,
49
55
  gradientColor: customGradientColor,
50
56
  headerBackground: customBackground,
57
+ contentLayout,
51
58
  }) => {
52
59
  const { theme } = useContext(ApplicationContext);
53
60
  const context = useContext<any>(MiniAppContext);
@@ -55,29 +62,40 @@ const HeaderExtendHeader: React.FC<{
55
62
  const gradientColor = customGradientColor ?? theme.colors.gradient;
56
63
  const headerBackground = customBackground ?? theme.assets?.headerBackground;
57
64
  const layout = useScreenLayout();
65
+ const headerLayout = useHeaderLayout(contentLayout);
66
+ // the stretch frame pulls the search in from both bar edges
67
+ const frameShift = headerLayout.frameInset - sidePadding;
58
68
 
69
+ let backWidth = buttonSize;
70
+ let toolkitWidth = TOOLKIT_WIDTH;
59
71
  // the iPhone Duo status bar row keeps side bar sized back and toolkit
60
- const isStatusBarRow = !!layout.statusBarClearance;
61
- const duoToolkitWidth = DuoMetrics.itemPitch * 2 + SCREEN_PADDING;
62
- const backWidth = isStatusBarRow ? DuoMetrics.controlSize : BACK_WIDTH;
63
- const toolkitWidth = isStatusBarRow ? duoToolkitWidth : TOOLKIT_WIDTH;
64
- const defaultLeft = layout.sideBar ? SCREEN_PADDING : backWidth + 20;
72
+ if (layout.statusBarClearance) {
73
+ backWidth = DuoMetrics.controlSize;
74
+ toolkitWidth = DuoMetrics.itemPitch * 2 + SCREEN_PADDING;
75
+ }
76
+ const backLeft = backWidth + sidePadding + titleGap;
77
+ const defaultLeft = layout.sideBar ? SCREEN_PADDING : backLeft;
65
78
  const leftPosition = inputSearchProps?.leftPosition || defaultLeft;
66
79
  const defaultRightWidth = layout.sideBar ? 0 : toolkitWidth;
67
80
  const headerRightWidth = customHeaderRightWidth ?? defaultRightWidth;
68
- const contentLeft = layout.insetLeft + SCREEN_PADDING;
69
- const contentRight = layout.insetLeft + layout.contentWidth;
81
+ const contentLeft = layout.insetLeft + SCREEN_PADDING + frameShift;
82
+ const contentRight = layout.insetLeft + layout.contentWidth - frameShift;
70
83
  // collapsed on the status bar row, the search stays between its clusters
71
84
  const clearanceLeft = layout.statusBarClearance?.left ?? 0;
72
85
  const clearanceRight = layout.statusBarClearance?.right ?? 0;
73
- const collapsedLeft = layout.insetLeft + leftPosition + clearanceLeft;
86
+ const searchLeft = leftPosition + clearanceLeft + frameShift;
87
+ const collapsedLeft = layout.insetLeft + searchLeft;
74
88
  const collapsedRight = contentRight - clearanceRight - headerRightWidth;
75
- const searchWidth = layout.contentWidth - SCREEN_PADDING * 2;
89
+ const searchWidth = contentRight - contentLeft - SCREEN_PADDING;
76
90
  const collapsedSearchWidth = collapsedRight - collapsedLeft - SCREEN_PADDING;
77
91
  // a row taller than barHeight (iPhone Duo) centers the collapsed search on the title
78
92
  const titleRowHeight = layout.headerHeight - layout.headerStatusBarHeight;
79
93
  const collapsedRaise = (titleRowHeight - HeaderMetrics.barHeight) / 2;
80
94
 
95
+ // the iPhone Duo side bar and status bar row keep the content search size
96
+ const isDuoRow = !!layout.sideBar || !!layout.statusBarClearance;
97
+ const searchSize = isDuoRow ? 'default' : 'header';
98
+
81
99
  let useShadowHeader = useShadowHeaderProp;
82
100
  if (inputSearchProps && Platform.OS === 'android') {
83
101
  useShadowHeader = false;
@@ -169,6 +187,7 @@ const HeaderExtendHeader: React.FC<{
169
187
  <Animated.View
170
188
  style={[
171
189
  styles.extendedHeader,
190
+ headerLayout.isWide && styles.phoneHeight,
172
191
  // fade out within the header box, as DuoScrollGradient does
173
192
  !!layout.sideBar && styles.fillHeaderBox,
174
193
  gradientOpacityStyle,
@@ -180,7 +199,10 @@ const HeaderExtendHeader: React.FC<{
180
199
  >
181
200
  {!!theme.assets?.headerBackground && (
182
201
  <Image
183
- style={styles.headerBackground}
202
+ style={[
203
+ styles.headerBackground,
204
+ headerLayout.isWide && styles.phoneHeight,
205
+ ]}
184
206
  source={{ uri: theme.assets?.headerBackground }}
185
207
  loading={false}
186
208
  />
@@ -189,9 +211,7 @@ const HeaderExtendHeader: React.FC<{
189
211
  </Animated.View>
190
212
  )}
191
213
  </Animated.View>
192
- <Animated.View
193
- style={[styles.searchContainer, heightStyle]}
194
- >
214
+ <Animated.View style={[styles.searchContainer, heightStyle]}>
195
215
  <Animated.View
196
216
  style={[{ marginVertical: Spacing.S }, searchTranslateStyle]}
197
217
  >
@@ -201,6 +221,7 @@ const HeaderExtendHeader: React.FC<{
201
221
  <InputSearch
202
222
  {...inputSearchProps}
203
223
  ref={inputSearchRef}
224
+ size={searchSize}
204
225
  backgroundColor={'transparent'}
205
226
  showButtonText={false}
206
227
  />
@@ -236,7 +257,10 @@ const HeaderExtendHeader: React.FC<{
236
257
  >
237
258
  {!!headerBackground && (
238
259
  <Image
239
- style={styles.headerBackground}
260
+ style={[
261
+ styles.headerBackground,
262
+ headerLayout.isWide && styles.phoneHeight,
263
+ ]}
240
264
  source={{ uri: headerBackground }}
241
265
  loading={false}
242
266
  />
@@ -264,6 +288,11 @@ const styles = StyleSheet.create({
264
288
  position: 'absolute',
265
289
  width: '100%',
266
290
  },
291
+ // wide windows keep the phone gradient height instead of scaling with the width
292
+ phoneHeight: {
293
+ aspectRatio: undefined,
294
+ height: HeaderBarMetrics.gradientHeight,
295
+ },
267
296
  fillHeaderBox: {
268
297
  aspectRatio: undefined,
269
298
  height: '100%',
@@ -273,14 +302,6 @@ const styles = StyleSheet.create({
273
302
  position: 'absolute',
274
303
  overflow: 'hidden',
275
304
  },
276
- verifiedDot: {
277
- width: 4,
278
- height: 4,
279
- borderRadius: 2,
280
- backgroundColor: Colors.green_03,
281
- position: 'absolute',
282
- alignSelf: 'center',
283
- },
284
305
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
285
306
  container: {
286
307
  zIndex: 0,
@@ -8,6 +8,7 @@ import { SideBarButton } from './SideBarButton';
8
8
  import Animated from 'react-native-reanimated';
9
9
  import { useScreenLayout, useSideBarItemStyle } from '../../Layout/utils';
10
10
  import { useSideBarCounterStyle } from './DuoQuickReturnHeader';
11
+ import { HeaderBarMetrics } from '../headerLayout';
11
12
 
12
13
  /**
13
14
  * Header back button, also bound to the Android hardware back. `preventBack`
@@ -125,7 +126,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
125
126
 
126
127
  const styles = StyleSheet.create({
127
128
  headerLeft: {
128
- marginLeft: 12,
129
+ marginLeft: HeaderBarMetrics.sidePadding,
129
130
  },
130
131
  });
131
132
 
@@ -1,11 +1,14 @@
1
1
  import { Platform, StyleSheet } from 'react-native';
2
2
  import { Colors, Spacing } from '../../Consts';
3
+ import { HeaderBarMetrics } from '../headerLayout';
4
+
5
+ const { buttonSize, pillWidth, pillHeight, sidePadding } = HeaderBarMetrics;
3
6
 
4
7
  export const styles = StyleSheet.create({
5
8
  navigationButton: {
6
- height: 28,
7
- width: 28,
8
- borderRadius: 14,
9
+ height: buttonSize,
10
+ width: buttonSize,
11
+ borderRadius: buttonSize / 2,
9
12
  justifyContent: 'center',
10
13
  alignItems: 'center',
11
14
  backgroundColor: Colors.black_01 + '99',
@@ -45,11 +48,11 @@ export const styles = StyleSheet.create({
45
48
  flexDirection: 'row',
46
49
  justifyContent: 'center',
47
50
  alignItems: 'center',
48
- paddingRight: Spacing.M,
51
+ paddingRight: sidePadding,
49
52
  },
50
53
  toolkitContainer: {
51
- height: 28,
52
- borderRadius: 14,
54
+ height: pillHeight,
55
+ borderRadius: pillHeight / 2,
53
56
  justifyContent: 'center',
54
57
  alignItems: 'center',
55
58
  flexDirection: 'row',
@@ -59,7 +62,10 @@ export const styles = StyleSheet.create({
59
62
  marginLeft: Spacing.S,
60
63
  },
61
64
  toolkitButton: {
62
- padding: 4,
65
+ flex: 1,
66
+ height: '100%',
67
+ justifyContent: 'center',
68
+ alignItems: 'center',
63
69
  },
64
70
  divider: {
65
71
  width: 0.5,
@@ -136,9 +142,9 @@ export const styles = StyleSheet.create({
136
142
  marginRight: Spacing.M,
137
143
  },
138
144
  toolkitWide: {
139
- width: 65,
145
+ width: pillWidth,
140
146
  },
141
147
  toolkitNarrow: {
142
- width: 28,
148
+ width: buttonSize,
143
149
  },
144
150
  });
@@ -3,6 +3,9 @@ import { MiniAppContext } from '../../Context';
3
3
  import { Spacing } from '../../Consts';
4
4
  import { isWhiteListDesignSystem } from '../utils';
5
5
  import { useScreenLayout } from '../../Layout';
6
+ import { HeaderBarMetrics } from '../headerLayout';
7
+
8
+ const { sidePadding, buttonSize, titleGap, clusterWidth } = HeaderBarMetrics;
6
9
 
7
10
  /**
8
11
  * Header title font: MoMoTrustDisplay for design-system whitelisted mini apps,
@@ -14,14 +17,16 @@ const useHeaderFontFamily = (): string => {
14
17
  };
15
18
 
16
19
  /**
17
- * back button and toolkit sharing the navigation bar with the title
20
+ * side paddings, back button, title gaps and right cluster sharing the
21
+ * navigation bar with the title
18
22
  */
19
- const HEADER_ACTIONS_WIDTH = 172;
23
+ const HEADER_ACTIONS_WIDTH =
24
+ sidePadding * 2 + buttonSize + titleGap * 2 + clusterWidth;
20
25
 
21
26
  /**
22
27
  * extra width the 48pt back and toolkit take on the iPhone Duo status bar row
23
28
  */
24
- const STATUS_BAR_ROW_ACTIONS_EXTRA = 55;
29
+ const STATUS_BAR_ROW_ACTIONS_EXTRA = 43;
25
30
 
26
31
  /**
27
32
  * Max header title width. The iPhone Duo side bar takes back and toolkit out of
@@ -9,6 +9,7 @@ import { SideBarButton, SideBarGroup, SideBarGroupItem } from './SideBarButton';
9
9
  import { useDuoBarCompression } from '../BottomTab/DuoBarCompression';
10
10
  import { styles } from './HeaderRight.styles';
11
11
  import { useToolkitActions } from './useToolkitActions';
12
+ import { HeaderBarMetrics } from '../headerLayout';
12
13
 
13
14
  const CLOSE_ICON = '16_navigation_close_circle';
14
15
  const HOME_ICON = '16_basic_home';
@@ -184,7 +185,11 @@ const HeaderToolkitAction: React.FC<any> = ({
184
185
  onPress={onPressHelpCenter}
185
186
  hitSlop={{ top: 7, bottom: 7, left: 7, right: 0 }}
186
187
  >
187
- <Icon color={tintColor} source={'help_center'} size={20} />
188
+ <Icon
189
+ color={tintColor}
190
+ source={'help_center'}
191
+ size={HeaderBarMetrics.iconSize}
192
+ />
188
193
  </TouchableOpacity>
189
194
  <View style={[styles.divider, { backgroundColor: tintColor }]} />
190
195
  </>
@@ -195,7 +200,11 @@ const HeaderToolkitAction: React.FC<any> = ({
195
200
  style={styles.toolkitButton}
196
201
  hitSlop={{ top: 7, bottom: 7, left: 0, right: 7 }}
197
202
  >
198
- <Icon color={tintColor} source={closeIcon} size={20} />
203
+ <Icon
204
+ color={tintColor}
205
+ source={closeIcon}
206
+ size={HeaderBarMetrics.iconSize}
207
+ />
199
208
  </TouchableOpacity>
200
209
  </View>
201
210
  </View>