@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.
- package/Application/Components/HeaderAnimated.tsx +13 -37
- package/Application/Components/HeaderBackground.tsx +3 -16
- package/Application/Components/HeaderExtendHeader.tsx +28 -41
- package/Application/Components/HeaderLeft.tsx +18 -4
- package/Application/Components/HeaderRight.styles.ts +13 -15
- package/Application/Components/HeaderTitle.shared.ts +11 -10
- package/Application/Components/HeaderToolkitAction.tsx +37 -12
- package/Application/Components/NavigationButton.tsx +6 -9
- package/Application/Components/SearchHeader.tsx +7 -20
- package/Application/StackScreen.tsx +0 -9
- package/Application/index.ts +1 -11
- package/Application/navigationOptions.tsx +15 -6
- package/Application/types.header.ts +2 -7
- package/Application/types.ts +0 -7
- package/Input/InputSearch.tsx +2 -18
- package/Input/index.tsx +1 -7
- package/Layout/Screen.tsx +0 -3
- package/Layout/screenHeader.tsx +52 -55
- package/Layout/screenTypes.ts +2 -10
- package/package.json +1 -1
- package/Application/headerLayout.ts +0 -158
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { StyleSheet
|
|
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.
|
|
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={[
|
|
64
|
+
style={[
|
|
65
|
+
styles.container,
|
|
66
|
+
{ aspectRatio: ASPECT_RATIOS[aspectRatio] },
|
|
67
|
+
animatedStyle,
|
|
68
|
+
]}
|
|
89
69
|
>
|
|
90
|
-
{
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
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
|
|
28
|
-
const
|
|
29
|
-
const TOOLKIT_WIDTH =
|
|
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
|
-
|
|
70
|
-
const
|
|
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 :
|
|
66
|
+
const defaultRightWidth = layout.sideBar ? 0 : toolkitWidth;
|
|
73
67
|
const headerRightWidth = customHeaderRightWidth ?? defaultRightWidth;
|
|
74
|
-
const contentLeft = layout.insetLeft + SCREEN_PADDING
|
|
75
|
-
const contentRight = layout.insetLeft + layout.contentWidth
|
|
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
|
|
80
|
-
const collapsedLeft = layout.insetLeft + searchLeft;
|
|
73
|
+
const collapsedLeft = layout.insetLeft + leftPosition + clearanceLeft;
|
|
81
74
|
const collapsedRight = contentRight - clearanceRight - headerRightWidth;
|
|
82
|
-
const searchWidth =
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
10
|
-
width:
|
|
11
|
-
borderRadius:
|
|
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:
|
|
48
|
+
paddingRight: Spacing.M,
|
|
52
49
|
},
|
|
53
50
|
toolkitContainer: {
|
|
54
|
-
height:
|
|
55
|
-
borderRadius:
|
|
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
|
-
|
|
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:
|
|
139
|
+
width: 65,
|
|
142
140
|
},
|
|
143
141
|
toolkitNarrow: {
|
|
144
|
-
width:
|
|
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
|
-
*
|
|
21
|
-
|
|
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
|
|
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
|
|
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={
|
|
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:
|
|
48
|
+
width: 28,
|
|
52
49
|
},
|
|
53
50
|
navigationButton: {
|
|
54
|
-
height:
|
|
55
|
-
width:
|
|
56
|
-
borderRadius:
|
|
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
|
|
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 {
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|
46
|
-
const
|
|
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
|
-
|
|
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;
|
package/Application/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
172
|
-
|
|
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 |
|
|
118
|
-
leftPosition?: 12 |
|
|
112
|
+
headerRightWidth?: 0 | 74 | 110 | number;
|
|
113
|
+
leftPosition?: 12 | 48 | number;
|
|
119
114
|
renderButtons?: () => ReactNode;
|
|
120
115
|
hiddenBack?: boolean;
|
|
121
116
|
}
|
package/Application/types.ts
CHANGED
|
@@ -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
|
}
|
package/Input/InputSearch.tsx
CHANGED
|
@@ -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={
|
|
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 {
|
|
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
|
};
|
package/Layout/screenHeader.tsx
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
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 =
|
|
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,
|
package/Layout/screenTypes.ts
CHANGED
|
@@ -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 (
|
|
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 |
|
|
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,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
|
-
};
|