@momo-kits/foundation 0.169.7 → 0.169.9-beta.1

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.
@@ -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,32 +17,28 @@ const useHeaderFontFamily = (): string => {
14
17
  };
15
18
 
16
19
  /**
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
20
+ * side paddings, back button, title gaps and right cluster sharing the
21
+ * navigation bar with the title
23
22
  */
24
- const STATUS_BAR_ROW_ACTIONS_EXTRA = 55;
23
+ const HEADER_ACTIONS_WIDTH =
24
+ sidePadding * 2 + buttonSize + titleGap * 2 + clusterWidth;
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 them.
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.
29
30
  */
30
31
  const useTitleMaxWidth = () => {
31
32
  const layout = useScreenLayout();
32
33
  const { width, contentWidth, sideBar, statusBarClearance } = layout;
33
34
  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;
38
37
  }
39
38
  if (sideBar) {
40
39
  return contentWidth - Spacing.M * 2 - clearance;
41
40
  }
42
- return width - HEADER_ACTIONS_WIDTH - rowActionsExtra - clearance;
41
+ return width - HEADER_ACTIONS_WIDTH - clearance;
43
42
  };
44
43
 
45
44
  export { useHeaderFontFamily, HEADER_ACTIONS_WIDTH, useTitleMaxWidth };
@@ -4,11 +4,12 @@ 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, useScreenLayout } from '../../Layout/utils';
7
+ import { DuoMetrics } 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';
12
13
 
13
14
  const CLOSE_ICON = '16_navigation_close_circle';
14
15
  const HOME_ICON = '16_basic_home';
@@ -31,7 +32,6 @@ const HeaderToolkitAction: React.FC<any> = ({
31
32
  const compression = useDuoBarCompression();
32
33
  const { toolbarCompressed, toolbarHidden } = compression;
33
34
  const { expandToolbar, setToolbarHeight } = compression;
34
- const { statusBarClearance } = useScreenLayout();
35
35
  const {
36
36
  isLoading,
37
37
  iconShortcut,
@@ -131,39 +131,6 @@ 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
-
167
134
  return (
168
135
  <View style={Styles.row}>
169
136
  {useShortcut && (
@@ -184,7 +151,11 @@ const HeaderToolkitAction: React.FC<any> = ({
184
151
  onPress={onPressHelpCenter}
185
152
  hitSlop={{ top: 7, bottom: 7, left: 7, right: 0 }}
186
153
  >
187
- <Icon color={tintColor} source={'help_center'} size={20} />
154
+ <Icon
155
+ color={tintColor}
156
+ source={'help_center'}
157
+ size={HeaderBarMetrics.iconSize}
158
+ />
188
159
  </TouchableOpacity>
189
160
  <View style={[styles.divider, { backgroundColor: tintColor }]} />
190
161
  </>
@@ -195,7 +166,11 @@ const HeaderToolkitAction: React.FC<any> = ({
195
166
  style={styles.toolkitButton}
196
167
  hitSlop={{ top: 7, bottom: 7, left: 0, right: 7 }}
197
168
  >
198
- <Icon color={tintColor} source={closeIcon} size={20} />
169
+ <Icon
170
+ color={tintColor}
171
+ source={closeIcon}
172
+ size={HeaderBarMetrics.iconSize}
173
+ />
199
174
  </TouchableOpacity>
200
175
  </View>
201
176
  </View>
@@ -5,9 +5,12 @@ 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;
8
11
 
9
12
  /**
10
- * Round 28pt icon button of the header (back, shortcut) with an optional badge
13
+ * Round 32pt icon button of the header (back, shortcut) with an optional badge
11
14
  * dot; switches to a dark fill under a white tint so the icon stays readable.
12
15
  */
13
16
  const NavigationButton: React.FC<NavigationButtonProps> = ({
@@ -35,7 +38,7 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
35
38
  <Icon
36
39
  source={icon}
37
40
  color={tintColor ?? theme.colors.text.default}
38
- size={20}
41
+ size={iconSize}
39
42
  />
40
43
  </TouchableOpacity>
41
44
  {showBadge && <BadgeDot size={'small'} style={styles.badgeDot} />}
@@ -45,12 +48,12 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
45
48
 
46
49
  const styles = StyleSheet.create({
47
50
  container: {
48
- width: 28,
51
+ width: buttonSize,
49
52
  },
50
53
  navigationButton: {
51
- height: 28,
52
- width: 28,
53
- borderRadius: 14,
54
+ height: buttonSize,
55
+ width: buttonSize,
56
+ borderRadius: buttonSize / 2,
54
57
  justifyContent: 'center',
55
58
  alignItems: 'center',
56
59
  backgroundColor: Colors.black_01 + '99',
@@ -1,5 +1,5 @@
1
1
  import { Colors, Radius, Spacing, Styles } from '../../Consts';
2
- import { InputRef, InputSearch } from '../../Input';
2
+ import { InputRef, InputSearch, type InputSearchProps } 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,7 +8,13 @@ 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 { DuoMetrics } from '../../Layout/utils';
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;
12
18
 
13
19
  /**
14
20
  * Header search bar whose fill turns from surface to default once scrolled, with
@@ -28,15 +34,21 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
28
34
  const { theme, navigator } = useContext(ApplicationContext);
29
35
  const context = useContext<any>(MiniAppContext);
30
36
  const layout = useScreenLayout();
31
- // the iPhone Duo status bar row keeps a side bar sized back button
32
- const BACK_WIDTH = layout.statusBarClearance ? DuoMetrics.controlSize : 28;
37
+ let defaultRightWidth = TOOLKIT_WIDTH;
38
+ let searchSize: InputSearchProps['size'] = 'header';
39
+ if (layout.sideBar) {
40
+ defaultRightWidth = DUO_TOOLKIT_WIDTH;
41
+ searchSize = 'default';
42
+ }
33
43
 
34
44
  const sharedValue = useNormalizedSharedValue(animatedValue);
35
- const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
36
- const headerRightWidth = props?.headerRightWidth ?? 73;
45
+ const defaultLeft = buttonSize + sidePadding + titleGap;
46
+ const leftPosition = props?.leftPosition ?? defaultLeft;
47
+ const headerRightWidth = props?.headerRightWidth ?? defaultRightWidth;
37
48
 
38
49
  // iPhone Duo side bar holds back: the search shares the content width with the buttons only
39
- let searchMaxWidth = layout.width - leftPosition - 12 - headerRightWidth;
50
+ const rowWidth = layout.width - leftPosition - sidePadding;
51
+ let searchMaxWidth = rowWidth - headerRightWidth;
40
52
  if (layout.sideBar) {
41
53
  searchMaxWidth = layout.contentWidth - Spacing.M * 2 - headerRightWidth;
42
54
  }
@@ -105,6 +117,7 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
105
117
  <InputSearch
106
118
  ref={ref}
107
119
  {...props}
120
+ size={searchSize}
108
121
  backgroundColor={'transparent'}
109
122
  showButtonText={false}
110
123
  />
@@ -8,6 +8,7 @@ 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';
11
12
 
12
13
  const runAfterInteractions = InteractionManager.runAfterInteractions;
13
14
 
@@ -51,6 +52,7 @@ const StackScreen: React.FC<any> = props => {
51
52
  const navigation = useRef(new Navigation(props.navigation, context)).current;
52
53
  const heightHeader = useHeaderHeight();
53
54
  const { isBackgroundToForeground } = useAppState();
55
+ const headerFrameOptions = useHeaderFrameOptions(options?.contentLayout);
54
56
 
55
57
  const data = {
56
58
  ...initialParams,
@@ -69,6 +71,13 @@ const StackScreen: React.FC<any> = props => {
69
71
  }
70
72
  }, [navigation, options]);
71
73
 
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
+
72
81
  useEffect(() => {
73
82
  let focusScreen: any;
74
83
  let onFocusApp: any;
@@ -0,0 +1,165 @@
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
+ * Banner kept at `bannerWidth`, centered: wide windows plus the iPhone Duo
50
+ * inner display, portrait and landscape.
51
+ */
52
+ isBannerCentered: boolean;
53
+ /**
54
+ * Window edge to back and to the right cluster: `sidePadding`, or centering the
55
+ * row in the 600pt frame with `stretch` on a wide window.
56
+ */
57
+ frameInset: number;
58
+ /**
59
+ * Header row width between the two `frameInset`s.
60
+ */
61
+ frameWidth: number;
62
+ };
63
+
64
+ type HeaderLayoutInput = {
65
+ width: number;
66
+ height: number;
67
+ isDuo: boolean;
68
+ contentLayout?: HeaderContentLayout;
69
+ };
70
+
71
+ /**
72
+ * Header geometry for a window, pure so it can be tested without a device.
73
+ */
74
+ const getHeaderLayout = ({
75
+ width,
76
+ height,
77
+ isDuo,
78
+ contentLayout = 'grid',
79
+ }: HeaderLayoutInput): HeaderLayout => {
80
+ const { sidePadding } = HeaderBarMetrics;
81
+ const isLargeWindow = Math.min(width, height) >= FOLDABLE_MIN_WIDTH;
82
+ const isWide = !isDuo && isLargeWindow;
83
+ let frameInset = sidePadding;
84
+ if (isWide && contentLayout === 'stretch') {
85
+ const centered = (width - HEADER_FRAME_MAX_WIDTH) / 2;
86
+ frameInset = Math.max(sidePadding, centered);
87
+ }
88
+ return {
89
+ ...HeaderBarMetrics,
90
+ isWide,
91
+ isBannerCentered: isLargeWindow,
92
+ frameInset,
93
+ frameWidth: width - frameInset * 2,
94
+ };
95
+ };
96
+
97
+ /**
98
+ * Widest a title can be between back and the right cluster of the header row.
99
+ */
100
+ const getTitleBudget = (layout: HeaderLayout) => {
101
+ const { buttonSize, titleGap, clusterWidth } = layout;
102
+ return layout.frameWidth - buttonSize - titleGap * 2 - clusterWidth;
103
+ };
104
+
105
+ /**
106
+ * Header geometry for the current window; follows fold, unfold and rotation.
107
+ */
108
+ const useHeaderLayout = (contentLayout?: HeaderContentLayout): HeaderLayout => {
109
+ const { width, height } = useWindowDimensions();
110
+ const isDuo = isIPhoneDuo();
111
+ return useMemo(() => {
112
+ return getHeaderLayout({ width, height, isDuo, contentLayout });
113
+ }, [width, height, isDuo, contentLayout]);
114
+ };
115
+
116
+ const TITLE_CONTAINER_RESET: ViewStyle = {
117
+ marginLeft: 0,
118
+ marginEnd: 0,
119
+ marginStart: 0,
120
+ marginHorizontal: 0,
121
+ marginVertical: 0,
122
+ margin: 0,
123
+ };
124
+
125
+ /**
126
+ * React Navigation container styles that keep back, title and the right cluster
127
+ * in the `stretch` frame. `undefined` for `grid`, and on iPhone Duo, which keeps
128
+ * its own header row.
129
+ */
130
+ const useHeaderFrameOptions = (
131
+ contentLayout?: HeaderContentLayout,
132
+ ): StackNavigationOptions | undefined => {
133
+ const layout = useHeaderLayout(contentLayout);
134
+ const { width } = useWindowDimensions();
135
+ const isDuo = isIPhoneDuo();
136
+ return useMemo(() => {
137
+ if (contentLayout !== 'stretch' || isDuo) {
138
+ return undefined;
139
+ }
140
+ // the header content row already sits this far in on large iOS windows
141
+ const shift = layout.frameInset - layout.sidePadding;
142
+ const containerShift = Math.max(0, shift - getHeaderMargin(width));
143
+ return {
144
+ headerLeftContainerStyle: { paddingLeft: containerShift },
145
+ headerRightContainerStyle: { paddingRight: containerShift },
146
+ headerTitleContainerStyle: {
147
+ ...TITLE_CONTAINER_RESET,
148
+ left: layout.titleGap,
149
+ maxWidth: getTitleBudget(layout),
150
+ },
151
+ };
152
+ }, [contentLayout, isDuo, layout, width]);
153
+ };
154
+
155
+ export type { HeaderContentLayout, HeaderLayout };
156
+
157
+ export {
158
+ HeaderBarMetrics,
159
+ HEADER_FRAME_MAX_WIDTH,
160
+ TITLE_CONTAINER_RESET,
161
+ getHeaderLayout,
162
+ getTitleBudget,
163
+ useHeaderLayout,
164
+ useHeaderFrameOptions,
165
+ };
@@ -26,6 +26,13 @@ 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';
29
36
 
30
37
  export {
31
38
  NavigationContainer,
@@ -41,6 +48,9 @@ export {
41
48
  useTooltipPortal,
42
49
  getModalLayout,
43
50
  useModalLayout,
51
+ HeaderBarMetrics,
52
+ getHeaderLayout,
53
+ useHeaderLayout,
44
54
  };
45
55
 
46
- export type { ModalLayout, ModalRect };
56
+ export type { ModalLayout, ModalRect, HeaderContentLayout, HeaderLayout };
@@ -8,11 +8,12 @@ import {
8
8
  TransitionPresets,
9
9
  } from '@react-navigation/stack';
10
10
  import type { SharedValue } from 'react-native-reanimated';
11
- import { Colors, Spacing } from '../Consts';
11
+ import { Colors } 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';
16
17
  import type { HeaderTitleProps, NavigationOptions } from './types';
17
18
 
18
19
  const CARD_SHADOW_OPACITY = 0.3;
@@ -55,13 +56,8 @@ const getStackOptions = (): StackNavigationOptions => {
55
56
  headerTitleAlign: 'left',
56
57
  headerTitle: props => <HeaderTitle {...props} />,
57
58
  headerTitleContainerStyle: {
58
- marginLeft: 0,
59
- marginEnd: 0,
60
- marginStart: 0,
61
- marginHorizontal: 0,
62
- marginVertical: 0,
63
- margin: 0,
64
- left: Spacing.S,
59
+ ...TITLE_CONTAINER_RESET,
60
+ left: HeaderBarMetrics.titleGap,
65
61
  },
66
62
  headerBackground: (props: any) => <HeaderBackground {...props} />,
67
63
  headerLeft: (props: any) => <HeaderLeft {...props} floating />,
@@ -172,13 +168,8 @@ const getOptions = (
172
168
  if (params.hiddenBack) {
173
169
  headerLeft = null;
174
170
  options.headerTitleContainerStyle = {
175
- marginLeft: 0,
176
- marginEnd: 0,
177
- marginStart: 0,
178
- marginHorizontal: 0,
179
- marginVertical: 0,
180
- margin: 0,
181
- left: Spacing.M,
171
+ ...TITLE_CONTAINER_RESET,
172
+ left: HeaderBarMetrics.sidePadding,
182
173
  };
183
174
  }
184
175
  options = {
@@ -98,6 +98,11 @@ 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;
101
106
  }
102
107
 
103
108
  export type AnimatedHeader = {
@@ -109,8 +114,8 @@ export type AnimatedHeader = {
109
114
  export interface SearchHeaderProps extends InputSearchProps {
110
115
  ref?: React.RefObject<InputRef>;
111
116
  animatedValue?: Animated.Value | SharedValue<number>;
112
- headerRightWidth?: 0 | 74 | 110 | number;
113
- leftPosition?: 12 | 48 | number;
117
+ headerRightWidth?: 0 | 84 | 124 | number;
118
+ leftPosition?: 12 | 52 | number;
114
119
  renderButtons?: () => ReactNode;
115
120
  hiddenBack?: boolean;
116
121
  }
@@ -36,6 +36,7 @@ export type BottomTabProps = {
36
36
  };
37
37
 
38
38
  export type FloatingButtonProps = {
39
+ container?: React.ReactNode;
39
40
  icon: string | React.ReactNode;
40
41
  label: string;
41
42
  onPress: () => void;
@@ -9,6 +9,7 @@ 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';
12
13
 
13
14
  export * from './types.theme';
14
15
  export * from './types.header';
@@ -128,4 +129,10 @@ export interface NavigationOptions
128
129
  hiddenBack?: boolean;
129
130
  headerTitle?: HeaderTitleProps | string;
130
131
  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;
131
138
  }
@@ -13,6 +13,7 @@ import {
13
13
  View,
14
14
  } from 'react-native';
15
15
  import { useComponentId } from '../Application';
16
+ import { HeaderBarMetrics } from '../Application/headerLayout';
16
17
  import {
17
18
  ApplicationContext,
18
19
  ComponentContext,
@@ -26,6 +27,8 @@ import styles from './styles';
26
27
  import { Styles } from '../Consts';
27
28
  import SystemTextInput from './SystemTextInput';
28
29
 
30
+ const SEARCH_ICON_SIZE = 24;
31
+
29
32
  /**
30
33
  * Search field with search icon, clear icon, optional trailing icon and a
31
34
  * cancel button; with `typingData` it shows an animated placeholder instead.
@@ -49,6 +52,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
49
52
  backgroundColor,
50
53
  onPress,
51
54
  typingData,
55
+ size = 'default',
52
56
  ...props
53
57
  },
54
58
  ref,
@@ -59,6 +63,13 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
59
63
  const searchFontFamily = exportFontFamily('regular');
60
64
 
61
65
  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;
62
73
 
63
74
  const [focused, setFocused] = useState(false);
64
75
  const [internalText, setInternalText] = useState(defaultValue || '');
@@ -114,7 +125,11 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
114
125
  if (typingData && typingData?.length > 0) {
115
126
  return (
116
127
  <TouchableOpacity
117
- style={[styles.searchInput, styles.searchInputTyping]}
128
+ style={[
129
+ styles.searchInput,
130
+ styles.searchInputTyping,
131
+ inputHeightStyle,
132
+ ]}
118
133
  onPress={onPress}
119
134
  activeOpacity={onPress ? 0.6 : 1}
120
135
  >
@@ -132,6 +147,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
132
147
  ref={inputRef}
133
148
  style={[
134
149
  styles.searchInput,
150
+ inputHeightStyle,
135
151
  {
136
152
  fontSize: scaledFontSize,
137
153
  color: theme.colors.text.default,
@@ -225,7 +241,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
225
241
  >
226
242
  <Icon
227
243
  source={'navigation_search'}
228
- size={24}
244
+ size={searchIconSize}
229
245
  color={theme.colors.text.hint}
230
246
  />
231
247
  {renderInputView()}
package/Input/index.tsx CHANGED
@@ -194,6 +194,12 @@ 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
+
197
203
  /**
198
204
  * Optional. Called when the field is pressed while showing `typingData`.
199
205
  */
@@ -367,4 +373,4 @@ export {
367
373
  InputPhoneNumber,
368
374
  };
369
375
 
370
- export {checkValidPhoneNumber};
376
+ export { checkValidPhoneNumber };
package/Layout/Screen.tsx CHANGED
@@ -67,6 +67,7 @@ const Screen = forwardRef(
67
67
  headerBackground,
68
68
  gradientColor,
69
69
  trackingParams,
70
+ contentLayout,
70
71
  }: ScreenProps,
71
72
  ref: any,
72
73
  ) => {
@@ -144,6 +145,7 @@ const Screen = forwardRef(
144
145
  gradientColor,
145
146
  headerBackground,
146
147
  layoutOffset,
148
+ contentLayout,
147
149
  });
148
150
 
149
151
  // mutates the content node so callers keep its methods plus the header setters
@@ -223,6 +225,7 @@ const Screen = forwardRef(
223
225
  useShadowHeader={useShadowHeader}
224
226
  gradientColor={gradientColor}
225
227
  headerBackground={headerBackground}
228
+ contentLayout={contentLayout}
226
229
  />
227
230
  );
228
231
  };