@momo-kits/foundation 0.169.7-beta.8 → 0.169.7

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.
@@ -1,5 +1,4 @@
1
- import { StyleSheet, View } from 'react-native';
2
- import LinearGradient from 'react-native-linear-gradient';
1
+ import { StyleSheet } from 'react-native';
3
2
  import Animated, {
4
3
  Extrapolation,
5
4
  interpolate,
@@ -10,7 +9,6 @@ import React from 'react';
10
9
  import { Image } from '../../Image';
11
10
  import { Colors } from '../../Consts';
12
11
  import { useNormalizedSharedValue } from '../hooks';
13
- import { HeaderBarMetrics, useHeaderLayout } from '../headerLayout';
14
12
 
15
13
  const ASPECT_RATIOS: Record<
16
14
  NonNullable<HeaderAnimatedProps['aspectRatio']>,
@@ -23,19 +21,16 @@ const ASPECT_RATIOS: Record<
23
21
 
24
22
  /**
25
23
  * Parallax header image (or `children`): stretches when pulled down and fades out
26
- * as the content scrolls up. Wide windows keep it at phone size, centered.
24
+ * as the content scrolls up.
27
25
  */
28
26
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
29
27
  animatedValue,
30
28
  image,
31
29
  useScale = true,
32
30
  aspectRatio = '16/9',
33
- bannerBackgroundColor,
34
31
  children,
35
32
  }) => {
36
33
  const sharedValue = useNormalizedSharedValue(animatedValue);
37
- const { isWide, bannerWidth } = useHeaderLayout();
38
- const ratio = ASPECT_RATIOS[aspectRatio];
39
34
  const animatedStyle = useAnimatedStyle(() => {
40
35
  let scale = 1;
41
36
  if (useScale) {
@@ -64,36 +59,22 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
64
59
  };
65
60
  });
66
61
 
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
-
86
62
  return (
87
63
  <Animated.View
88
- style={[styles.container, { height: bannerWidth / ratio }, animatedStyle]}
64
+ style={[
65
+ styles.container,
66
+ { aspectRatio: ASPECT_RATIOS[aspectRatio] },
67
+ animatedStyle,
68
+ ]}
89
69
  >
90
- {!!bannerBackgroundColor && (
91
- <LinearGradient
92
- colors={[bannerBackgroundColor, bannerBackgroundColor + '00']}
93
- style={StyleSheet.absoluteFill}
70
+ {children ?? (
71
+ <Image
72
+ source={{
73
+ uri: image,
74
+ }}
75
+ style={styles.full}
94
76
  />
95
77
  )}
96
- <View style={styles.phoneBanner}>{content}</View>
97
78
  </Animated.View>
98
79
  );
99
80
  };
@@ -109,11 +90,6 @@ const styles = StyleSheet.create({
109
90
  alignSelf: 'center',
110
91
  },
111
92
  full: { width: '100%', height: '100%' },
112
- phoneBanner: {
113
- width: HeaderBarMetrics.bannerWidth,
114
- height: '100%',
115
- alignSelf: 'center',
116
- },
117
93
  });
118
94
 
119
95
  export { HeaderAnimated };
@@ -8,12 +8,10 @@ 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';
12
11
 
13
12
  /**
14
13
  * Header background: surface and image that fade in on scroll, with an optional
15
- * gradient (custom or theme) on top, at phone height on wide windows. Outlined
16
- * when `showBaseLineDebug` is on.
14
+ * gradient (custom or theme) on top. Outlined when `showBaseLineDebug` is on.
17
15
  */
18
16
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
19
17
  animatedValue,
@@ -27,7 +25,6 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
27
25
  const gradientColor = customGradientColor ?? theme.colors.gradient;
28
26
  const headerImage = headerBackground ?? theme.assets?.headerBackground;
29
27
  const sharedValue = useNormalizedSharedValue(animatedValue);
30
- const { isWide } = useHeaderLayout();
31
28
 
32
29
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
33
30
 
@@ -41,19 +38,14 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
41
38
  />
42
39
  <View style={styles.gradientContainer}>
43
40
  {useGradient && !!gradientColor && (
44
- <Animated.View
45
- style={[styles.extendedHeader, isWide && styles.phoneHeight]}
46
- >
41
+ <Animated.View style={styles.extendedHeader}>
47
42
  <LinearGradient
48
43
  colors={[gradientColor, gradientColor + '00']}
49
44
  style={StyleSheet.absoluteFill}
50
45
  >
51
46
  {!!headerImage && (
52
47
  <Image
53
- style={[
54
- styles.headerBackground,
55
- isWide && styles.phoneHeight,
56
- ]}
48
+ style={styles.headerBackground}
57
49
  source={{ uri: headerImage }}
58
50
  loading={false}
59
51
  />
@@ -83,11 +75,6 @@ const styles = StyleSheet.create({
83
75
  position: 'absolute',
84
76
  aspectRatio: 375 / 154,
85
77
  },
86
- // wide windows keep the phone gradient height instead of scaling with the width
87
- phoneHeight: {
88
- aspectRatio: undefined,
89
- height: HeaderBarMetrics.gradientHeight,
90
- },
91
78
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
92
79
  });
93
80
 
@@ -17,16 +17,11 @@ import BackgroundImageView from './BackgroundImageView';
17
17
  import { type AnimatedCompatValue } from '../utils';
18
18
  import { useNormalizedSharedValue } from '../hooks';
19
19
  import { useScreenLayout } from '../../Layout';
20
- import { HeaderMetrics } from '../../Layout/utils';
21
- import {
22
- HeaderBarMetrics,
23
- type HeaderContentLayout,
24
- useHeaderLayout,
25
- } from '../headerLayout';
20
+ import { DuoMetrics, HeaderMetrics } from '../../Layout/utils';
26
21
 
27
- const { buttonSize, pillWidth, sidePadding, titleGap } = HeaderBarMetrics;
28
- const SCREEN_PADDING = sidePadding;
29
- const TOOLKIT_WIDTH = pillWidth + sidePadding;
22
+ const SCREEN_PADDING = 12;
23
+ const BACK_WIDTH = 28;
24
+ const TOOLKIT_WIDTH = 73;
30
25
 
31
26
  /**
32
27
  * Extended header background: a gradient that collapses on scroll and, with
@@ -43,7 +38,6 @@ const HeaderExtendHeader: React.FC<{
43
38
  useShadowHeader?: boolean;
44
39
  gradientColor?: string;
45
40
  headerBackground?: string;
46
- contentLayout?: HeaderContentLayout;
47
41
  }> = ({
48
42
  headerType = 'default',
49
43
  animatedValue,
@@ -54,7 +48,6 @@ const HeaderExtendHeader: React.FC<{
54
48
  useShadowHeader: useShadowHeaderProp = true,
55
49
  gradientColor: customGradientColor,
56
50
  headerBackground: customBackground,
57
- contentLayout,
58
51
  }) => {
59
52
  const { theme } = useContext(ApplicationContext);
60
53
  const context = useContext<any>(MiniAppContext);
@@ -62,32 +55,29 @@ const HeaderExtendHeader: React.FC<{
62
55
  const gradientColor = customGradientColor ?? theme.colors.gradient;
63
56
  const headerBackground = customBackground ?? theme.assets?.headerBackground;
64
57
  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;
68
58
 
69
- const backLeft = buttonSize + sidePadding + titleGap;
70
- const defaultLeft = layout.sideBar ? SCREEN_PADDING : backLeft;
59
+ // 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;
71
65
  const leftPosition = inputSearchProps?.leftPosition || defaultLeft;
72
- const defaultRightWidth = layout.sideBar ? 0 : TOOLKIT_WIDTH;
66
+ const defaultRightWidth = layout.sideBar ? 0 : toolkitWidth;
73
67
  const headerRightWidth = customHeaderRightWidth ?? defaultRightWidth;
74
- const contentLeft = layout.insetLeft + SCREEN_PADDING + frameShift;
75
- const contentRight = layout.insetLeft + layout.contentWidth - frameShift;
68
+ const contentLeft = layout.insetLeft + SCREEN_PADDING;
69
+ const contentRight = layout.insetLeft + layout.contentWidth;
76
70
  // collapsed on the status bar row, the search stays between its clusters
77
71
  const clearanceLeft = layout.statusBarClearance?.left ?? 0;
78
72
  const clearanceRight = layout.statusBarClearance?.right ?? 0;
79
- const searchLeft = leftPosition + clearanceLeft + frameShift;
80
- const collapsedLeft = layout.insetLeft + searchLeft;
73
+ const collapsedLeft = layout.insetLeft + leftPosition + clearanceLeft;
81
74
  const collapsedRight = contentRight - clearanceRight - headerRightWidth;
82
- const searchWidth = contentRight - contentLeft - SCREEN_PADDING;
75
+ const searchWidth = layout.contentWidth - SCREEN_PADDING * 2;
83
76
  const collapsedSearchWidth = collapsedRight - collapsedLeft - SCREEN_PADDING;
84
77
  // a row taller than barHeight (iPhone Duo) centers the collapsed search on the title
85
78
  const titleRowHeight = layout.headerHeight - layout.headerStatusBarHeight;
86
79
  const collapsedRaise = (titleRowHeight - HeaderMetrics.barHeight) / 2;
87
80
 
88
- // the iPhone Duo side bar keeps the content search size
89
- const searchSize = layout.sideBar ? 'default' : 'header';
90
-
91
81
  let useShadowHeader = useShadowHeaderProp;
92
82
  if (inputSearchProps && Platform.OS === 'android') {
93
83
  useShadowHeader = false;
@@ -179,7 +169,6 @@ const HeaderExtendHeader: React.FC<{
179
169
  <Animated.View
180
170
  style={[
181
171
  styles.extendedHeader,
182
- headerLayout.isWide && styles.phoneHeight,
183
172
  // fade out within the header box, as DuoScrollGradient does
184
173
  !!layout.sideBar && styles.fillHeaderBox,
185
174
  gradientOpacityStyle,
@@ -191,10 +180,7 @@ const HeaderExtendHeader: React.FC<{
191
180
  >
192
181
  {!!theme.assets?.headerBackground && (
193
182
  <Image
194
- style={[
195
- styles.headerBackground,
196
- headerLayout.isWide && styles.phoneHeight,
197
- ]}
183
+ style={styles.headerBackground}
198
184
  source={{ uri: theme.assets?.headerBackground }}
199
185
  loading={false}
200
186
  />
@@ -203,7 +189,9 @@ const HeaderExtendHeader: React.FC<{
203
189
  </Animated.View>
204
190
  )}
205
191
  </Animated.View>
206
- <Animated.View style={[styles.searchContainer, heightStyle]}>
192
+ <Animated.View
193
+ style={[styles.searchContainer, heightStyle]}
194
+ >
207
195
  <Animated.View
208
196
  style={[{ marginVertical: Spacing.S }, searchTranslateStyle]}
209
197
  >
@@ -213,7 +201,6 @@ const HeaderExtendHeader: React.FC<{
213
201
  <InputSearch
214
202
  {...inputSearchProps}
215
203
  ref={inputSearchRef}
216
- size={searchSize}
217
204
  backgroundColor={'transparent'}
218
205
  showButtonText={false}
219
206
  />
@@ -249,10 +236,7 @@ const HeaderExtendHeader: React.FC<{
249
236
  >
250
237
  {!!headerBackground && (
251
238
  <Image
252
- style={[
253
- styles.headerBackground,
254
- headerLayout.isWide && styles.phoneHeight,
255
- ]}
239
+ style={styles.headerBackground}
256
240
  source={{ uri: headerBackground }}
257
241
  loading={false}
258
242
  />
@@ -280,11 +264,6 @@ const styles = StyleSheet.create({
280
264
  position: 'absolute',
281
265
  width: '100%',
282
266
  },
283
- // wide windows keep the phone gradient height instead of scaling with the width
284
- phoneHeight: {
285
- aspectRatio: undefined,
286
- height: HeaderBarMetrics.gradientHeight,
287
- },
288
267
  fillHeaderBox: {
289
268
  aspectRatio: undefined,
290
269
  height: '100%',
@@ -294,6 +273,14 @@ const styles = StyleSheet.create({
294
273
  position: 'absolute',
295
274
  overflow: 'hidden',
296
275
  },
276
+ verifiedDot: {
277
+ width: 4,
278
+ height: 4,
279
+ borderRadius: 2,
280
+ backgroundColor: Colors.green_03,
281
+ position: 'absolute',
282
+ alignSelf: 'center',
283
+ },
297
284
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
298
285
  container: {
299
286
  zIndex: 0,
@@ -6,13 +6,12 @@ import { PopupNotify } from '../../Popup';
6
6
  import { NavigationButton } from './NavigationButton';
7
7
  import { SideBarButton } from './SideBarButton';
8
8
  import Animated from 'react-native-reanimated';
9
- import { useSideBarItemStyle } from '../../Layout/utils';
9
+ import { useScreenLayout, useSideBarItemStyle } from '../../Layout/utils';
10
10
  import { useSideBarCounterStyle } from './DuoQuickReturnHeader';
11
- import { HeaderBarMetrics } from '../headerLayout';
12
11
 
13
12
  /**
14
13
  * Header back button, also bound to the Android hardware back. `preventBack`
15
- * confirms in a popup first; the iPhone Duo side bar uses the 48pt side bar button.
14
+ * confirms in a popup first; the iPhone Duo layouts use the 48pt side bar button.
16
15
  */
17
16
  const HeaderLeft: React.FC<HeaderBackProps> = ({
18
17
  tintColor,
@@ -25,6 +24,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
25
24
  const { navigator } = useContext(ApplicationContext);
26
25
  const sideBarStyle = useSideBarItemStyle('left', 0);
27
26
  const counterStyle = useSideBarCounterStyle();
27
+ const { statusBarClearance } = useScreenLayout();
28
28
 
29
29
  const goBack = useCallback(() => {
30
30
  const canGoBack = navigator?.ref?.current?.canGoBack?.();
@@ -97,6 +97,20 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
97
97
  );
98
98
  }
99
99
 
100
+ // on the status bar row the controls keep the side bar size
101
+ if (statusBarClearance) {
102
+ return (
103
+ <View style={styles.headerLeft}>
104
+ <SideBarButton
105
+ accessibilityLabel="btn_navigation_back"
106
+ icon="arrow-back"
107
+ tintColor={tintColor}
108
+ onPress={goBackSafe}
109
+ />
110
+ </View>
111
+ );
112
+ }
113
+
100
114
  return (
101
115
  <View style={styles.headerLeft}>
102
116
  <NavigationButton
@@ -111,7 +125,7 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
111
125
 
112
126
  const styles = StyleSheet.create({
113
127
  headerLeft: {
114
- marginLeft: HeaderBarMetrics.sidePadding,
128
+ marginLeft: 12,
115
129
  },
116
130
  });
117
131
 
@@ -1,14 +1,11 @@
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;
6
3
 
7
4
  export const styles = StyleSheet.create({
8
5
  navigationButton: {
9
- height: buttonSize,
10
- width: buttonSize,
11
- borderRadius: buttonSize / 2,
6
+ height: 28,
7
+ width: 28,
8
+ borderRadius: 14,
12
9
  justifyContent: 'center',
13
10
  alignItems: 'center',
14
11
  backgroundColor: Colors.black_01 + '99',
@@ -48,11 +45,11 @@ export const styles = StyleSheet.create({
48
45
  flexDirection: 'row',
49
46
  justifyContent: 'center',
50
47
  alignItems: 'center',
51
- paddingRight: sidePadding,
48
+ paddingRight: Spacing.M,
52
49
  },
53
50
  toolkitContainer: {
54
- height: pillHeight,
55
- borderRadius: pillHeight / 2,
51
+ height: 28,
52
+ borderRadius: 14,
56
53
  justifyContent: 'center',
57
54
  alignItems: 'center',
58
55
  flexDirection: 'row',
@@ -62,10 +59,7 @@ export const styles = StyleSheet.create({
62
59
  marginLeft: Spacing.S,
63
60
  },
64
61
  toolkitButton: {
65
- flex: 1,
66
- height: '100%',
67
- justifyContent: 'center',
68
- alignItems: 'center',
62
+ padding: 4,
69
63
  },
70
64
  divider: {
71
65
  width: 0.5,
@@ -74,6 +68,10 @@ export const styles = StyleSheet.create({
74
68
  sideBarColumn: {
75
69
  gap: Spacing.M,
76
70
  },
71
+ sideBarRow: {
72
+ flexDirection: 'row',
73
+ gap: Spacing.M,
74
+ },
77
75
  headerLeft: {
78
76
  marginLeft: 12,
79
77
  },
@@ -138,9 +136,9 @@ export const styles = StyleSheet.create({
138
136
  marginRight: Spacing.M,
139
137
  },
140
138
  toolkitWide: {
141
- width: pillWidth,
139
+ width: 65,
142
140
  },
143
141
  toolkitNarrow: {
144
- width: buttonSize,
142
+ width: 28,
145
143
  },
146
144
  });
@@ -3,9 +3,6 @@ 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;
9
6
 
10
7
  /**
11
8
  * Header title font: MoMoTrustDisplay for design-system whitelisted mini apps,
@@ -17,28 +14,32 @@ const useHeaderFontFamily = (): string => {
17
14
  };
18
15
 
19
16
  /**
20
- * side paddings, back button, title gaps and right cluster sharing the
21
- * navigation bar with the title
17
+ * back button and toolkit sharing the navigation bar with the title
18
+ */
19
+ const HEADER_ACTIONS_WIDTH = 172;
20
+
21
+ /**
22
+ * extra width the 48pt back and toolkit take on the iPhone Duo status bar row
22
23
  */
23
- const HEADER_ACTIONS_WIDTH =
24
- sidePadding * 2 + buttonSize + titleGap * 2 + clusterWidth;
24
+ const STATUS_BAR_ROW_ACTIONS_EXTRA = 55;
25
25
 
26
26
  /**
27
27
  * Max header title width. The iPhone Duo side bar takes back and toolkit out of
28
- * the bar, so the title gets the content width; otherwise it shares the bar with
29
- * them, minus the status bar clusters on the iPhone Duo inner portrait row.
28
+ * the bar, so the title gets the content width; otherwise it shares the bar with them.
30
29
  */
31
30
  const useTitleMaxWidth = () => {
32
31
  const layout = useScreenLayout();
33
32
  const { width, contentWidth, sideBar, statusBarClearance } = layout;
34
33
  let clearance = 0;
34
+ let rowActionsExtra = 0;
35
35
  if (statusBarClearance) {
36
36
  clearance = statusBarClearance.left + statusBarClearance.right;
37
+ rowActionsExtra = STATUS_BAR_ROW_ACTIONS_EXTRA;
37
38
  }
38
39
  if (sideBar) {
39
40
  return contentWidth - Spacing.M * 2 - clearance;
40
41
  }
41
- return width - HEADER_ACTIONS_WIDTH - clearance;
42
+ return width - HEADER_ACTIONS_WIDTH - rowActionsExtra - clearance;
42
43
  };
43
44
 
44
45
  export { useHeaderFontFamily, HEADER_ACTIONS_WIDTH, useTitleMaxWidth };
@@ -4,12 +4,11 @@ import { NavigationButton } from './NavigationButton';
4
4
  import { MiniAppContext } from '../../Context';
5
5
  import { Colors, Spacing, Styles } from '../../Consts';
6
6
  import { Icon } from '../../Icon';
7
- import { DuoMetrics } from '../../Layout/utils';
7
+ import { DuoMetrics, useScreenLayout } from '../../Layout/utils';
8
8
  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';
13
12
 
14
13
  const CLOSE_ICON = '16_navigation_close_circle';
15
14
  const HOME_ICON = '16_basic_home';
@@ -32,6 +31,7 @@ const HeaderToolkitAction: React.FC<any> = ({
32
31
  const compression = useDuoBarCompression();
33
32
  const { toolbarCompressed, toolbarHidden } = compression;
34
33
  const { expandToolbar, setToolbarHeight } = compression;
34
+ const { statusBarClearance } = useScreenLayout();
35
35
  const {
36
36
  isLoading,
37
37
  iconShortcut,
@@ -131,6 +131,39 @@ const HeaderToolkitAction: React.FC<any> = ({
131
131
  );
132
132
  }
133
133
 
134
+ // on the status bar row the controls keep the side bar size
135
+ if (statusBarClearance) {
136
+ return (
137
+ <View style={styles.sideBarRow}>
138
+ {useShortcut && (
139
+ <SideBarButton
140
+ disabled={isLoading}
141
+ icon={iconShortcut}
142
+ onPress={shortcutOnPress}
143
+ tintColor={tintColor}
144
+ showBadge={showBadge}
145
+ />
146
+ )}
147
+ <SideBarGroup tintColor={tintColor} horizontal>
148
+ {useHelpCenter && (
149
+ <SideBarGroupItem
150
+ accessibilityLabel={'btn_navigation_help_center'}
151
+ icon={'help_center'}
152
+ tintColor={tintColor}
153
+ onPress={onPressHelpCenter}
154
+ />
155
+ )}
156
+ <SideBarGroupItem
157
+ accessibilityLabel={'btn_navigation_close'}
158
+ icon={closeIcon}
159
+ tintColor={tintColor}
160
+ onPress={onClose}
161
+ />
162
+ </SideBarGroup>
163
+ </View>
164
+ );
165
+ }
166
+
134
167
  return (
135
168
  <View style={Styles.row}>
136
169
  {useShortcut && (
@@ -151,11 +184,7 @@ const HeaderToolkitAction: React.FC<any> = ({
151
184
  onPress={onPressHelpCenter}
152
185
  hitSlop={{ top: 7, bottom: 7, left: 7, right: 0 }}
153
186
  >
154
- <Icon
155
- color={tintColor}
156
- source={'help_center'}
157
- size={HeaderBarMetrics.iconSize}
158
- />
187
+ <Icon color={tintColor} source={'help_center'} size={20} />
159
188
  </TouchableOpacity>
160
189
  <View style={[styles.divider, { backgroundColor: tintColor }]} />
161
190
  </>
@@ -166,11 +195,7 @@ const HeaderToolkitAction: React.FC<any> = ({
166
195
  style={styles.toolkitButton}
167
196
  hitSlop={{ top: 7, bottom: 7, left: 0, right: 7 }}
168
197
  >
169
- <Icon
170
- color={tintColor}
171
- source={closeIcon}
172
- size={HeaderBarMetrics.iconSize}
173
- />
198
+ <Icon color={tintColor} source={closeIcon} size={20} />
174
199
  </TouchableOpacity>
175
200
  </View>
176
201
  </View>
@@ -5,12 +5,9 @@ import { StyleSheet, TouchableOpacity, View, ViewStyle } from 'react-native';
5
5
  import { Colors, Spacing } from '../../Consts';
6
6
  import { Icon } from '../../Icon';
7
7
  import { BadgeDot } from '../../Badge';
8
- import { HeaderBarMetrics } from '../headerLayout';
9
-
10
- const { buttonSize, iconSize } = HeaderBarMetrics;
11
8
 
12
9
  /**
13
- * Round 32pt icon button of the header (back, shortcut) with an optional badge
10
+ * Round 28pt icon button of the header (back, shortcut) with an optional badge
14
11
  * dot; switches to a dark fill under a white tint so the icon stays readable.
15
12
  */
16
13
  const NavigationButton: React.FC<NavigationButtonProps> = ({
@@ -38,7 +35,7 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
38
35
  <Icon
39
36
  source={icon}
40
37
  color={tintColor ?? theme.colors.text.default}
41
- size={iconSize}
38
+ size={20}
42
39
  />
43
40
  </TouchableOpacity>
44
41
  {showBadge && <BadgeDot size={'small'} style={styles.badgeDot} />}
@@ -48,12 +45,12 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
48
45
 
49
46
  const styles = StyleSheet.create({
50
47
  container: {
51
- width: buttonSize,
48
+ width: 28,
52
49
  },
53
50
  navigationButton: {
54
- height: buttonSize,
55
- width: buttonSize,
56
- borderRadius: buttonSize / 2,
51
+ height: 28,
52
+ width: 28,
53
+ borderRadius: 14,
57
54
  justifyContent: 'center',
58
55
  alignItems: 'center',
59
56
  backgroundColor: Colors.black_01 + '99',
@@ -1,5 +1,5 @@
1
1
  import { Colors, Radius, Spacing, Styles } from '../../Consts';
2
- import { InputRef, InputSearch, type InputSearchProps } from '../../Input';
2
+ import { InputRef, InputSearch } from '../../Input';
3
3
  import { StyleSheet, TouchableOpacity, View } from 'react-native';
4
4
  import Animated, { useAnimatedStyle } from 'react-native-reanimated';
5
5
  import React, { useContext } from 'react';
@@ -8,13 +8,7 @@ import { ApplicationContext, MiniAppContext } from '../../Context';
8
8
  import { Text } from '../../Text';
9
9
  import { useNormalizedSharedValue } from '../hooks';
10
10
  import { useScreenLayout } from '../../Layout';
11
- import { HeaderBarMetrics } from '../headerLayout';
12
-
13
- const { buttonSize, pillWidth, sidePadding, titleGap } = HeaderBarMetrics;
14
- // help center | home pill and the bar padding right of it
15
- const TOOLKIT_WIDTH = pillWidth + sidePadding;
16
- // the iPhone Duo side bar keeps its right header width
17
- const DUO_TOOLKIT_WIDTH = 73;
11
+ import { DuoMetrics } from '../../Layout/utils';
18
12
 
19
13
  /**
20
14
  * Header search bar whose fill turns from surface to default once scrolled, with
@@ -34,21 +28,15 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
34
28
  const { theme, navigator } = useContext(ApplicationContext);
35
29
  const context = useContext<any>(MiniAppContext);
36
30
  const layout = useScreenLayout();
37
- let defaultRightWidth = TOOLKIT_WIDTH;
38
- let searchSize: InputSearchProps['size'] = 'header';
39
- if (layout.sideBar) {
40
- defaultRightWidth = DUO_TOOLKIT_WIDTH;
41
- searchSize = 'default';
42
- }
31
+ // the iPhone Duo status bar row keeps a side bar sized back button
32
+ const BACK_WIDTH = layout.statusBarClearance ? DuoMetrics.controlSize : 28;
43
33
 
44
34
  const sharedValue = useNormalizedSharedValue(animatedValue);
45
- const defaultLeft = buttonSize + sidePadding + titleGap;
46
- const leftPosition = props?.leftPosition ?? defaultLeft;
47
- const headerRightWidth = props?.headerRightWidth ?? defaultRightWidth;
35
+ const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
36
+ const headerRightWidth = props?.headerRightWidth ?? 73;
48
37
 
49
38
  // iPhone Duo side bar holds back: the search shares the content width with the buttons only
50
- const rowWidth = layout.width - leftPosition - sidePadding;
51
- let searchMaxWidth = rowWidth - headerRightWidth;
39
+ let searchMaxWidth = layout.width - leftPosition - 12 - headerRightWidth;
52
40
  if (layout.sideBar) {
53
41
  searchMaxWidth = layout.contentWidth - Spacing.M * 2 - headerRightWidth;
54
42
  }
@@ -117,7 +105,6 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
117
105
  <InputSearch
118
106
  ref={ref}
119
107
  {...props}
120
- size={searchSize}
121
108
  backgroundColor={'transparent'}
122
109
  showButtonText={false}
123
110
  />
@@ -8,7 +8,6 @@ import { SwipeBackHaptic } from './Components/SwipeBackHaptic';
8
8
  import { useAppState } from './hooks';
9
9
  import { createScreenTracking } from './StackScreen.tracking';
10
10
  import { TooltipPortalHost, TooltipPortalProvider } from './TooltipPortal';
11
- import { useHeaderFrameOptions } from './headerLayout';
12
11
 
13
12
  const runAfterInteractions = InteractionManager.runAfterInteractions;
14
13
 
@@ -52,7 +51,6 @@ const StackScreen: React.FC<any> = props => {
52
51
  const navigation = useRef(new Navigation(props.navigation, context)).current;
53
52
  const heightHeader = useHeaderHeight();
54
53
  const { isBackgroundToForeground } = useAppState();
55
- const headerFrameOptions = useHeaderFrameOptions(options?.contentLayout);
56
54
 
57
55
  const data = {
58
56
  ...initialParams,
@@ -71,13 +69,6 @@ const StackScreen: React.FC<any> = props => {
71
69
  }
72
70
  }, [navigation, options]);
73
71
 
74
- // re-applied on fold, unfold and rotation, apart from the other options
75
- useLayoutEffect(() => {
76
- if (headerFrameOptions) {
77
- navigation.instance.setOptions(headerFrameOptions);
78
- }
79
- }, [navigation, headerFrameOptions]);
80
-
81
72
  useEffect(() => {
82
73
  let focusScreen: any;
83
74
  let onFocusApp: any;
@@ -26,13 +26,6 @@ import {
26
26
  type ModalLayout,
27
27
  type ModalRect,
28
28
  } from './modalLayout';
29
- import {
30
- HeaderBarMetrics,
31
- getHeaderLayout,
32
- useHeaderLayout,
33
- type HeaderContentLayout,
34
- type HeaderLayout,
35
- } from './headerLayout';
36
29
 
37
30
  export {
38
31
  NavigationContainer,
@@ -48,9 +41,6 @@ export {
48
41
  useTooltipPortal,
49
42
  getModalLayout,
50
43
  useModalLayout,
51
- HeaderBarMetrics,
52
- getHeaderLayout,
53
- useHeaderLayout,
54
44
  };
55
45
 
56
- export type { ModalLayout, ModalRect, HeaderContentLayout, HeaderLayout };
46
+ export type { ModalLayout, ModalRect };
@@ -8,12 +8,11 @@ import {
8
8
  TransitionPresets,
9
9
  } from '@react-navigation/stack';
10
10
  import type { SharedValue } from 'react-native-reanimated';
11
- import { Colors } from '../Consts';
11
+ import { Colors, Spacing } from '../Consts';
12
12
  import { HeaderBackground } from './Components/HeaderBackground';
13
13
  import { HeaderLeft } from './Components/HeaderLeft';
14
14
  import { HeaderRight } from './Components/HeaderRight';
15
15
  import { HeaderTitle, TitleCustom } from './Components/HeaderTitle';
16
- import { HeaderBarMetrics, TITLE_CONTAINER_RESET } from './headerLayout';
17
16
  import type { HeaderTitleProps, NavigationOptions } from './types';
18
17
 
19
18
  const CARD_SHADOW_OPACITY = 0.3;
@@ -56,8 +55,13 @@ const getStackOptions = (): StackNavigationOptions => {
56
55
  headerTitleAlign: 'left',
57
56
  headerTitle: props => <HeaderTitle {...props} />,
58
57
  headerTitleContainerStyle: {
59
- ...TITLE_CONTAINER_RESET,
60
- left: HeaderBarMetrics.titleGap,
58
+ marginLeft: 0,
59
+ marginEnd: 0,
60
+ marginStart: 0,
61
+ marginHorizontal: 0,
62
+ marginVertical: 0,
63
+ margin: 0,
64
+ left: Spacing.S,
61
65
  },
62
66
  headerBackground: (props: any) => <HeaderBackground {...props} />,
63
67
  headerLeft: (props: any) => <HeaderLeft {...props} floating />,
@@ -168,8 +172,13 @@ const getOptions = (
168
172
  if (params.hiddenBack) {
169
173
  headerLeft = null;
170
174
  options.headerTitleContainerStyle = {
171
- ...TITLE_CONTAINER_RESET,
172
- left: HeaderBarMetrics.sidePadding,
175
+ marginLeft: 0,
176
+ marginEnd: 0,
177
+ marginStart: 0,
178
+ marginHorizontal: 0,
179
+ marginVertical: 0,
180
+ margin: 0,
181
+ left: Spacing.M,
173
182
  };
174
183
  }
175
184
  options = {
@@ -98,11 +98,6 @@ export interface HeaderAnimatedProps extends ViewProps {
98
98
  image: string;
99
99
  useScale?: boolean;
100
100
  aspectRatio?: '16/9' | '3/2' | '1/1';
101
- /**
102
- * Optional. On wide windows the image keeps its phone width, centered; this
103
- * color fills the sides and fades to transparent downward. No fill when unset.
104
- */
105
- bannerBackgroundColor?: string;
106
101
  }
107
102
 
108
103
  export type AnimatedHeader = {
@@ -114,8 +109,8 @@ export type AnimatedHeader = {
114
109
  export interface SearchHeaderProps extends InputSearchProps {
115
110
  ref?: React.RefObject<InputRef>;
116
111
  animatedValue?: Animated.Value | SharedValue<number>;
117
- headerRightWidth?: 0 | 84 | 124 | number;
118
- leftPosition?: 12 | 52 | number;
112
+ headerRightWidth?: 0 | 74 | 110 | number;
113
+ leftPosition?: 12 | 48 | number;
119
114
  renderButtons?: () => ReactNode;
120
115
  hiddenBack?: boolean;
121
116
  }
@@ -9,7 +9,6 @@ import type {
9
9
  OnBoarding,
10
10
  } from './types.header';
11
11
  import type { LocalizeProps, Theme } from './types.theme';
12
- import type { HeaderContentLayout } from './headerLayout';
13
12
 
14
13
  export * from './types.theme';
15
14
  export * from './types.header';
@@ -129,10 +128,4 @@ export interface NavigationOptions
129
128
  hiddenBack?: boolean;
130
129
  headerTitle?: HeaderTitleProps | string;
131
130
  headerRight?: OnBoarding | HeaderRightToolkit | any;
132
- /**
133
- * Optional. `stretch` centers back, title and the right cluster in a 600pt
134
- * frame on wide windows (foldable inner display, tablet); the background stays
135
- * full width. Defaults to `grid`, the header row at the window edges.
136
- */
137
- contentLayout?: HeaderContentLayout;
138
131
  }
@@ -13,7 +13,6 @@ import {
13
13
  View,
14
14
  } from 'react-native';
15
15
  import { useComponentId } from '../Application';
16
- import { HeaderBarMetrics } from '../Application/headerLayout';
17
16
  import {
18
17
  ApplicationContext,
19
18
  ComponentContext,
@@ -27,8 +26,6 @@ import styles from './styles';
27
26
  import { Styles } from '../Consts';
28
27
  import SystemTextInput from './SystemTextInput';
29
28
 
30
- const SEARCH_ICON_SIZE = 24;
31
-
32
29
  /**
33
30
  * Search field with search icon, clear icon, optional trailing icon and a
34
31
  * cancel button; with `typingData` it shows an animated placeholder instead.
@@ -52,7 +49,6 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
52
49
  backgroundColor,
53
50
  onPress,
54
51
  typingData,
55
- size = 'default',
56
52
  ...props
57
53
  },
58
54
  ref,
@@ -63,13 +59,6 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
63
59
  const searchFontFamily = exportFontFamily('regular');
64
60
 
65
61
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
66
- const isHeaderSize = size === 'header';
67
- const inputHeightStyle = isHeaderSize && {
68
- height: HeaderBarMetrics.searchHeight,
69
- };
70
- const searchIconSize = isHeaderSize
71
- ? HeaderBarMetrics.iconSize
72
- : SEARCH_ICON_SIZE;
73
62
 
74
63
  const [focused, setFocused] = useState(false);
75
64
  const [internalText, setInternalText] = useState(defaultValue || '');
@@ -125,11 +114,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
125
114
  if (typingData && typingData?.length > 0) {
126
115
  return (
127
116
  <TouchableOpacity
128
- style={[
129
- styles.searchInput,
130
- styles.searchInputTyping,
131
- inputHeightStyle,
132
- ]}
117
+ style={[styles.searchInput, styles.searchInputTyping]}
133
118
  onPress={onPress}
134
119
  activeOpacity={onPress ? 0.6 : 1}
135
120
  >
@@ -147,7 +132,6 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
147
132
  ref={inputRef}
148
133
  style={[
149
134
  styles.searchInput,
150
- inputHeightStyle,
151
135
  {
152
136
  fontSize: scaledFontSize,
153
137
  color: theme.colors.text.default,
@@ -241,7 +225,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
241
225
  >
242
226
  <Icon
243
227
  source={'navigation_search'}
244
- size={searchIconSize}
228
+ size={24}
245
229
  color={theme.colors.text.hint}
246
230
  />
247
231
  {renderInputView()}
package/Input/index.tsx CHANGED
@@ -194,12 +194,6 @@ export interface InputSearchProps extends TextInputProps {
194
194
  */
195
195
  typingData?: string[];
196
196
 
197
- /**
198
- * Optional. `header` is the 32pt field with a 20pt search icon that sits in the
199
- * navigation header. Defaults to `default`, the 36pt field for content.
200
- */
201
- size?: 'default' | 'header';
202
-
203
197
  /**
204
198
  * Optional. Called when the field is pressed while showing `typingData`.
205
199
  */
@@ -373,4 +367,4 @@ export {
373
367
  InputPhoneNumber,
374
368
  };
375
369
 
376
- export { checkValidPhoneNumber };
370
+ export {checkValidPhoneNumber};
package/Layout/Screen.tsx CHANGED
@@ -67,7 +67,6 @@ const Screen = forwardRef(
67
67
  headerBackground,
68
68
  gradientColor,
69
69
  trackingParams,
70
- contentLayout,
71
70
  }: ScreenProps,
72
71
  ref: any,
73
72
  ) => {
@@ -145,7 +144,6 @@ const Screen = forwardRef(
145
144
  gradientColor,
146
145
  headerBackground,
147
146
  layoutOffset,
148
- contentLayout,
149
147
  });
150
148
 
151
149
  // mutates the content node so callers keep its methods plus the header setters
@@ -225,7 +223,6 @@ const Screen = forwardRef(
225
223
  useShadowHeader={useShadowHeader}
226
224
  gradientColor={gradientColor}
227
225
  headerBackground={headerBackground}
228
- contentLayout={contentLayout}
229
226
  />
230
227
  );
231
228
  };
@@ -1,7 +1,6 @@
1
1
  import { useCallback, useEffect, useRef } from 'react';
2
2
  import { StatusBar, StatusBarStyle } from 'react-native';
3
3
  import { StackNavigationOptions } from '@react-navigation/stack';
4
- import type { SharedValue } from 'react-native-reanimated';
5
4
  import Navigation from '../Application/Navigation';
6
5
  import { AnimatedHeader, SearchHeaderProps } from '../Application/types';
7
6
  import { exportHeaderTitle } from '../Application/navigationOptions';
@@ -13,11 +12,6 @@ import {
13
12
  SearchHeader,
14
13
  } from '../Application';
15
14
  import { DuoGlassBackground } from '../Application/Components/DuoGlassBackground';
16
- import {
17
- type HeaderContentLayout,
18
- useHeaderFrameOptions,
19
- useHeaderLayout,
20
- } from '../Application/headerLayout';
21
15
  import { getHeaderMargin } from './utils';
22
16
  import { ANIMATED_BANNER_HEIGHT } from './screenTypes';
23
17
  import { HeaderType } from './types';
@@ -35,31 +29,6 @@ type ScreenHeaderParams = {
35
29
  gradientColor?: string;
36
30
  headerBackground?: string;
37
31
  layoutOffset: number;
38
- contentLayout?: HeaderContentLayout;
39
- };
40
-
41
- /**
42
- * Transparent header for an `inputSearchProps` Screen: its title fades out over the
43
- * first 50pt of scroll while the search collapses into the bar.
44
- */
45
- const getSearchTitleOptions = (
46
- sharedValue: SharedValue<number>,
47
- ): StackNavigationOptions => {
48
- return {
49
- headerShown: true,
50
- headerTransparent: true,
51
- headerBackground: () => null,
52
- headerTitle: (props: any) => (
53
- <HeaderTitle
54
- {...props}
55
- interpolate={{
56
- inputRange: [0, 50],
57
- outputRange: [1, 0],
58
- }}
59
- animatedValue={sharedValue}
60
- />
61
- ),
62
- };
63
32
  };
64
33
 
65
34
  /**
@@ -79,12 +48,9 @@ export const useScreenHeader = ({
79
48
  gradientColor,
80
49
  headerBackground,
81
50
  layoutOffset,
82
- contentLayout,
83
51
  }: ScreenHeaderParams) => {
84
52
  const currentTint = useRef<string | undefined>(undefined);
85
53
  const appliedHeaderMode = useRef<string | undefined>(undefined);
86
- const headerLayout = useHeaderLayout(contentLayout);
87
- const frameOptions = useHeaderFrameOptions(contentLayout);
88
54
 
89
55
  // keepTitle leaves a title the screen set itself when the same type is re-applied
90
56
  const setHeaderType = useCallback(
@@ -113,7 +79,21 @@ export const useScreenHeader = ({
113
79
  headerTitle: (props: any) => <HeaderTitle {...props} />,
114
80
  };
115
81
  if (inputSearchProps) {
116
- options = getSearchTitleOptions(sharedValue);
82
+ options = {
83
+ headerShown: true,
84
+ headerTransparent: true,
85
+ headerBackground: () => null,
86
+ headerTitle: (props: any) => (
87
+ <HeaderTitle
88
+ {...props}
89
+ interpolate={{
90
+ inputRange: [0, 50],
91
+ outputRange: [1, 0],
92
+ }}
93
+ animatedValue={sharedValue}
94
+ />
95
+ ),
96
+ };
117
97
  }
118
98
  break;
119
99
 
@@ -147,7 +127,21 @@ export const useScreenHeader = ({
147
127
  headerTitle: (props: any) => <HeaderTitle {...props} />,
148
128
  };
149
129
  if (inputSearchProps) {
150
- options = getSearchTitleOptions(sharedValue);
130
+ options = {
131
+ headerShown: true,
132
+ headerTransparent: true,
133
+ headerBackground: () => null,
134
+ headerTitle: (props: any) => (
135
+ <HeaderTitle
136
+ {...props}
137
+ interpolate={{
138
+ inputRange: [0, 50],
139
+ outputRange: [1, 0],
140
+ }}
141
+ animatedValue={sharedValue}
142
+ />
143
+ ),
144
+ };
151
145
  }
152
146
  }
153
147
 
@@ -155,11 +149,7 @@ export const useScreenHeader = ({
155
149
  delete options.headerTitle;
156
150
  }
157
151
 
158
- navigation?.instance?.setOptions({
159
- header: undefined,
160
- ...frameOptions,
161
- ...options,
162
- });
152
+ navigation?.instance?.setOptions({ header: undefined, ...options });
163
153
  },
164
154
  [
165
155
  gradientColor,
@@ -170,7 +160,6 @@ export const useScreenHeader = ({
170
160
  useShadowHeader,
171
161
  hasSideBar,
172
162
  quickReturnOptions,
173
- frameOptions,
174
163
  ],
175
164
  );
176
165
 
@@ -218,7 +207,7 @@ export const useScreenHeader = ({
218
207
  };
219
208
  }
220
209
 
221
- navigation?.instance?.setOptions({ ...frameOptions, ...options });
210
+ navigation?.instance?.setOptions(options);
222
211
  },
223
212
  [
224
213
  gradientColor,
@@ -229,23 +218,32 @@ export const useScreenHeader = ({
229
218
  hasSideBar,
230
219
  layoutOffset,
231
220
  quickReturnOptions,
232
- frameOptions,
233
221
  ],
234
222
  );
235
223
 
236
224
  const setAnimatedSearch = useCallback(() => {
237
- const options = getSearchTitleOptions(sharedValue);
238
- navigation?.instance?.setOptions({
239
- header: undefined,
240
- ...frameOptions,
241
- ...options,
242
- });
243
- }, [navigation?.instance, sharedValue, frameOptions]);
225
+ const options: StackNavigationOptions = {
226
+ headerShown: true,
227
+ headerTransparent: true,
228
+ headerBackground: () => null,
229
+ headerTitle: (props: any) => (
230
+ <HeaderTitle
231
+ {...props}
232
+ interpolate={{
233
+ inputRange: [0, 50],
234
+ outputRange: [1, 0],
235
+ }}
236
+ animatedValue={sharedValue}
237
+ />
238
+ ),
239
+ };
240
+
241
+ navigation?.instance?.setOptions({ header: undefined, ...options });
242
+ }, [navigation?.instance, sharedValue]);
244
243
 
245
- // the search field spans the content (or the stretch frame), clear of the side bar
246
- // and status bar clusters
244
+ // the search field spans the content, clear of the side bar and status bar clusters
247
245
  const setSearchHeader = (params: SearchHeaderProps) => {
248
- let searchWidth = headerLayout.frameWidth;
246
+ let searchWidth = layout.width - 24;
249
247
  let searchLeft = Spacing.M;
250
248
  if (layout.sideBar) {
251
249
  searchWidth = layout.contentWidth - Spacing.M * 2;
@@ -256,7 +254,6 @@ export const useScreenHeader = ({
256
254
  searchWidth -= clearance.left + clearance.right;
257
255
  }
258
256
  const options: StackNavigationOptions = {
259
- ...frameOptions,
260
257
  headerRight: undefined,
261
258
  headerTitleContainerStyle: {
262
259
  width: searchWidth,
@@ -9,7 +9,6 @@ import {
9
9
  import { FloatingButtonProps } from './FloatingButton';
10
10
  import { HeaderType } from './types';
11
11
  import { InputRef } from '../Input';
12
- import type { HeaderContentLayout } from '../Application/headerLayout';
13
12
 
14
13
  export const ANIMATED_BANNER_HEIGHT = 210;
15
14
 
@@ -78,17 +77,10 @@ export interface ScreenProps extends ViewProps {
78
77
  layoutOffset?: -8 | -24 | -56 | number;
79
78
 
80
79
  /**
81
- * Optional. Width of the right header. Defaults to the toolkit width (84), or 0 when
80
+ * Optional. Width of the right header. Defaults to the toolkit width (73), or 0 when
82
81
  * iPhone Duo floats the toolkit into its side bar.
83
82
  */
84
- headerRightWidth?: 0 | 84 | 124 | number;
85
-
86
- /**
87
- * Optional. `stretch` centers back, title or search and the right cluster of the
88
- * header in a 600pt frame on wide windows (foldable inner display, tablet); the
89
- * background and status bar stay full width. Defaults to `grid`.
90
- */
91
- contentLayout?: HeaderContentLayout;
83
+ headerRightWidth?: 0 | 73 | 110 | number;
92
84
 
93
85
  /**
94
86
  * Optional. Props for the floating action button.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.169.7-beta.8",
3
+ "version": "0.169.7",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},
@@ -1,158 +0,0 @@
1
- import { useMemo } from 'react';
2
- import { useWindowDimensions, type ViewStyle } from 'react-native';
3
- import type { StackNavigationOptions } from '@react-navigation/stack';
4
- import { Spacing } from '../Consts';
5
- import { getHeaderMargin } from '../Layout/utils';
6
- import { FOLDABLE_MIN_WIDTH } from './modalLayout';
7
- import { isIPhoneDuo } from './utils';
8
-
9
- // Top navigation spec, shared with the Compose / SwiftUI kits
10
- const HEADER_FRAME_MAX_WIDTH = 600;
11
-
12
- /**
13
- * Top navigation geometry on every device
14
- * - buttonSize / iconSize: round back and shortcut buttons, icons inside them and the pill
15
- * - pillWidth / pillHeight: help center | home capsule
16
- * - clusterWidth: shortcut, gap and pill on the right of the bar
17
- * - searchHeight: search field inside the header
18
- * - titleGap: space between back and the title, and the title and the right cluster
19
- * - sidePadding: bar edge to back and to the right cluster
20
- * - gradientHeight: header gradient, kept at the phone height on wide windows
21
- * - bannerWidth: animated banner image, kept at the phone width on wide windows
22
- */
23
- const HeaderBarMetrics = {
24
- buttonSize: 32,
25
- iconSize: 20,
26
- pillWidth: 72,
27
- pillHeight: 32,
28
- clusterWidth: 112,
29
- searchHeight: 32,
30
- titleGap: Spacing.S,
31
- sidePadding: Spacing.M,
32
- gradientHeight: 154,
33
- bannerWidth: 375,
34
- };
35
-
36
- /**
37
- * `grid` keeps the header row at the bar edges, `stretch` centers it in a 600pt
38
- * frame on wide windows. Background and status bar always span the window.
39
- */
40
- type HeaderContentLayout = 'grid' | 'stretch';
41
-
42
- type HeaderLayout = typeof HeaderBarMetrics & {
43
- /**
44
- * Window whose short side is at least 600 (Galaxy Z Fold / Pixel Fold inner
45
- * display, tablets), iPhone Duo excluded. Phones, landscape included, are not.
46
- */
47
- isWide: boolean;
48
- /**
49
- * Window edge to back and to the right cluster: `sidePadding`, or centering the
50
- * row in the 600pt frame with `stretch` on a wide window.
51
- */
52
- frameInset: number;
53
- /**
54
- * Header row width between the two `frameInset`s.
55
- */
56
- frameWidth: number;
57
- };
58
-
59
- type HeaderLayoutInput = {
60
- width: number;
61
- height: number;
62
- isDuo: boolean;
63
- contentLayout?: HeaderContentLayout;
64
- };
65
-
66
- /**
67
- * Header geometry for a window, pure so it can be tested without a device.
68
- */
69
- const getHeaderLayout = ({
70
- width,
71
- height,
72
- isDuo,
73
- contentLayout = 'grid',
74
- }: HeaderLayoutInput): HeaderLayout => {
75
- const { sidePadding } = HeaderBarMetrics;
76
- const isWide = !isDuo && Math.min(width, height) >= FOLDABLE_MIN_WIDTH;
77
- let frameInset = sidePadding;
78
- if (isWide && contentLayout === 'stretch') {
79
- const centered = (width - HEADER_FRAME_MAX_WIDTH) / 2;
80
- frameInset = Math.max(sidePadding, centered);
81
- }
82
- return {
83
- ...HeaderBarMetrics,
84
- isWide,
85
- frameInset,
86
- frameWidth: width - frameInset * 2,
87
- };
88
- };
89
-
90
- /**
91
- * Widest a title can be between back and the right cluster of the header row.
92
- */
93
- const getTitleBudget = (layout: HeaderLayout) => {
94
- const { buttonSize, titleGap, clusterWidth } = layout;
95
- return layout.frameWidth - buttonSize - titleGap * 2 - clusterWidth;
96
- };
97
-
98
- /**
99
- * Header geometry for the current window; follows fold, unfold and rotation.
100
- */
101
- const useHeaderLayout = (contentLayout?: HeaderContentLayout): HeaderLayout => {
102
- const { width, height } = useWindowDimensions();
103
- const isDuo = isIPhoneDuo();
104
- return useMemo(() => {
105
- return getHeaderLayout({ width, height, isDuo, contentLayout });
106
- }, [width, height, isDuo, contentLayout]);
107
- };
108
-
109
- const TITLE_CONTAINER_RESET: ViewStyle = {
110
- marginLeft: 0,
111
- marginEnd: 0,
112
- marginStart: 0,
113
- marginHorizontal: 0,
114
- marginVertical: 0,
115
- margin: 0,
116
- };
117
-
118
- /**
119
- * React Navigation container styles that keep back, title and the right cluster
120
- * in the `stretch` frame. `undefined` for `grid`, and on iPhone Duo, which keeps
121
- * its own header row.
122
- */
123
- const useHeaderFrameOptions = (
124
- contentLayout?: HeaderContentLayout,
125
- ): StackNavigationOptions | undefined => {
126
- const layout = useHeaderLayout(contentLayout);
127
- const { width } = useWindowDimensions();
128
- const isDuo = isIPhoneDuo();
129
- return useMemo(() => {
130
- if (contentLayout !== 'stretch' || isDuo) {
131
- return undefined;
132
- }
133
- // the header content row already sits this far in on large iOS windows
134
- const shift = layout.frameInset - layout.sidePadding;
135
- const containerShift = Math.max(0, shift - getHeaderMargin(width));
136
- return {
137
- headerLeftContainerStyle: { paddingLeft: containerShift },
138
- headerRightContainerStyle: { paddingRight: containerShift },
139
- headerTitleContainerStyle: {
140
- ...TITLE_CONTAINER_RESET,
141
- left: layout.titleGap,
142
- maxWidth: getTitleBudget(layout),
143
- },
144
- };
145
- }, [contentLayout, isDuo, layout, width]);
146
- };
147
-
148
- export type { HeaderContentLayout, HeaderLayout };
149
-
150
- export {
151
- HeaderBarMetrics,
152
- HEADER_FRAME_MAX_WIDTH,
153
- TITLE_CONTAINER_RESET,
154
- getHeaderLayout,
155
- getTitleBudget,
156
- useHeaderLayout,
157
- useHeaderFrameOptions,
158
- };