@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.
- package/Application/BottomTab/ActiveIndicator.tsx +1 -5
- package/Application/BottomTab/BottomTabBar.styles.ts +1 -18
- package/Application/BottomTab/BottomTabBar.tsx +10 -8
- package/Application/BottomTab/CurvedContainer.tsx +2 -8
- package/Application/BottomTab/CustomBottomTabItem.tsx +2 -2
- package/Application/BottomTab/DuoTabBar.tsx +3 -15
- package/Application/BottomTab/metrics.ts +3 -23
- package/Application/Components/HeaderAnimated.tsx +38 -13
- package/Application/Components/HeaderBackground.tsx +16 -3
- package/Application/Components/HeaderExtendHeader.tsx +41 -28
- package/Application/Components/HeaderLeft.tsx +4 -18
- package/Application/Components/HeaderRight.styles.ts +15 -13
- package/Application/Components/HeaderTitle.shared.ts +10 -11
- package/Application/Components/HeaderToolkitAction.tsx +12 -37
- package/Application/Components/NavigationButton.tsx +9 -6
- package/Application/Components/SearchHeader.tsx +20 -7
- package/Application/StackScreen.tsx +9 -0
- package/Application/headerLayout.ts +165 -0
- package/Application/index.ts +11 -1
- package/Application/navigationOptions.tsx +6 -15
- package/Application/types.header.ts +7 -2
- package/Application/types.tabs.ts +1 -0
- package/Application/types.ts +7 -0
- package/Input/InputSearch.tsx +18 -2
- package/Input/index.tsx +7 -1
- package/Layout/Screen.tsx +3 -0
- package/Layout/screenHeader.tsx +55 -52
- package/Layout/screenTypes.ts +10 -2
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
|
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 -
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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={
|
|
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:
|
|
51
|
+
width: buttonSize,
|
|
49
52
|
},
|
|
50
53
|
navigationButton: {
|
|
51
|
-
height:
|
|
52
|
-
width:
|
|
53
|
-
borderRadius:
|
|
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 {
|
|
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
|
-
|
|
32
|
-
|
|
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
|
|
36
|
-
const
|
|
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
|
-
|
|
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
|
+
};
|
package/Application/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
176
|
-
|
|
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 |
|
|
113
|
-
leftPosition?: 12 |
|
|
117
|
+
headerRightWidth?: 0 | 84 | 124 | number;
|
|
118
|
+
leftPosition?: 12 | 52 | number;
|
|
114
119
|
renderButtons?: () => ReactNode;
|
|
115
120
|
hiddenBack?: boolean;
|
|
116
121
|
}
|
package/Application/types.ts
CHANGED
|
@@ -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
|
}
|
package/Input/InputSearch.tsx
CHANGED
|
@@ -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={[
|
|
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={
|
|
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
|
};
|