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