@momo-kits/foundation 0.169.7-beta.4 → 0.169.7-beta.5
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/BottomSheet.hooks.ts +8 -8
- package/Application/BottomSheet.parts.tsx +44 -34
- package/Application/BottomSheet.tsx +40 -37
- package/Application/BottomTab/ActiveIndicator.tsx +20 -15
- package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
- package/Application/BottomTab/BottomTabBar.tsx +57 -72
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +22 -14
- package/Application/BottomTab/CustomBottomTabItem.tsx +15 -15
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
- package/Application/BottomTab/DuoTabBar.tsx +23 -13
- package/Application/BottomTab/TabBarIcon.tsx +15 -29
- package/Application/BottomTab/TabScreen.tsx +1 -5
- package/Application/BottomTab/index.tsx +33 -30
- package/Application/BottomTab/metrics.ts +46 -26
- package/Application/BottomTab/tabItems.ts +1 -1
- package/Application/BottomTab/usePressLift.ts +2 -2
- package/Application/Components/BackgroundImageView.tsx +4 -0
- package/Application/Components/DuoGlassBackground.tsx +5 -4
- package/Application/Components/HeaderAnimated.tsx +4 -0
- package/Application/Components/HeaderBackground.tsx +4 -0
- package/Application/Components/HeaderExtendHeader.tsx +11 -10
- package/Application/Components/HeaderLeft.tsx +2 -1
- package/Application/Components/HeaderRight.tsx +2 -1
- package/Application/Components/HeaderTitle.shared.ts +11 -9
- package/Application/Components/HeaderTitle.tsx +4 -3
- package/Application/Components/HeaderToolkitAction.tsx +17 -20
- package/Application/Components/NavigationButton.tsx +2 -1
- package/Application/Components/SearchHeader.tsx +17 -10
- package/Application/Components/TitleJourney.tsx +2 -2
- package/Application/Components/TitleLocation.tsx +2 -2
- package/Application/Components/TitleUser.tsx +2 -2
- package/Application/Components/useToolkitActions.tsx +17 -32
- package/Application/Localize.ts +7 -0
- package/Application/ModalScreen.tsx +10 -10
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -18
- package/Application/Navigator.ts +20 -48
- package/Application/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -33
- package/Application/StackScreen.tsx +10 -54
- package/Application/TooltipPortal.tsx +12 -5
- package/Application/WidgetContainer.tsx +10 -4
- package/Application/modalLayout.ts +13 -14
- package/Application/navigationOptions.tsx +21 -17
- package/Application/types.tabs.ts +0 -1
- package/Badge/Badge.tsx +5 -0
- package/Badge/BadgeDot.tsx +1 -0
- package/Badge/BadgeDotAnimation.tsx +15 -7
- package/Badge/BadgeRibbon.tsx +46 -38
- package/Badge/Shape.tsx +2 -0
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +5 -16
- package/Button/utils.ts +9 -15
- package/CheckBox/index.tsx +4 -0
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +5 -1
- package/Divider/DashDivider.tsx +1 -0
- package/Divider/index.tsx +4 -0
- package/FoundationList/index.tsx +4 -0
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +4 -12
- package/Image/index.tsx +9 -7
- package/Input/Input.tsx +8 -9
- package/Input/InputDropDown.tsx +10 -12
- package/Input/InputMoney.tsx +12 -8
- package/Input/InputMoneyView.tsx +14 -7
- package/Input/InputOTP.tsx +35 -33
- package/Input/InputPhoneNumber.tsx +8 -9
- package/Input/InputSearch.tsx +4 -6
- package/Input/InputTextArea.tsx +4 -0
- package/Input/SearchTextTyping.tsx +4 -0
- package/Input/SystemTextInput.tsx +7 -2
- package/Input/TextTyping.tsx +4 -0
- package/Input/common.tsx +26 -12
- package/Input/index.tsx +17 -20
- package/Input/moneyUtils.ts +10 -4
- package/Input/styles.ts +0 -5
- package/Input/utils.ts +14 -12
- package/Layout/Card.tsx +7 -4
- package/Layout/FloatingButton.tsx +4 -0
- package/Layout/Item.tsx +6 -2
- package/Layout/ItemList.tsx +9 -7
- package/Layout/ItemSectionList.tsx +2 -1
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +56 -58
- package/Layout/Section.tsx +8 -5
- package/Layout/TrackingScope.tsx +1 -0
- package/Layout/screenContent.tsx +4 -2
- package/Layout/screenFooter.tsx +28 -18
- package/Layout/screenHeader.tsx +34 -31
- package/Layout/screenQuickReturn.tsx +17 -20
- package/Layout/screenScroll.ts +4 -7
- package/Layout/screenTypes.ts +3 -2
- package/Layout/types.ts +2 -4
- package/Layout/utils.ts +31 -37
- package/Loader/DotLoader.tsx +4 -0
- package/Loader/Loader.tsx +4 -0
- package/Loader/ProgressBar.tsx +1 -0
- package/Loader/Spinner.tsx +4 -0
- package/Loader/utils.ts +11 -7
- package/Pagination/Dot.tsx +4 -0
- package/Pagination/PaginationDot.tsx +1 -0
- package/Pagination/PaginationNumber.tsx +5 -3
- package/Pagination/PaginationScroll.tsx +5 -0
- package/Pagination/PaginationWhiteDot.tsx +1 -0
- package/Pagination/index.tsx +4 -0
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +44 -48
- package/Popup/PopupPromotion.tsx +4 -7
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +4 -0
- package/Radio/index.tsx +4 -0
- package/Skeleton/index.tsx +8 -0
- package/Switch/index.tsx +1 -0
- package/Tag/index.tsx +5 -1
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +14 -3
- package/Text/styles.ts +1 -1
- package/Text/types.ts +1 -6
- package/Text/utils.ts +20 -4
- package/Title/index.tsx +41 -32
- package/ViewBoundary/index.tsx +4 -2
- package/index.ts +0 -6
- package/package.json +1 -1
- package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
- package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/Authorship/utils.ts +0 -137
- package/Application/KitAdoption/Slots/utils.ts +0 -68
- package/Application/KitAdoption/constants.ts +0 -120
- package/Application/KitAdoption/index.ts +0 -12
- package/Application/KitAdoption/styles.ts +0 -55
- package/Application/KitAdoption/types.ts +0 -61
- package/Application/KitAdoption/utils.ts +0 -376
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
- package/Application/__tests__/KitAdoption.test.ts +0 -283
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
- package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
- package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
- package/Application/__tests__/modalLayout.test.ts +0 -128
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BottomTabBarProps,
|
|
2
3
|
BottomTabNavigationOptions,
|
|
3
4
|
createBottomTabNavigator,
|
|
4
5
|
} from '@react-navigation/bottom-tabs';
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
} from 'react-native';
|
|
14
15
|
import { Icon } from '../../Icon';
|
|
15
16
|
import { BottomTabProps } from '../types';
|
|
17
|
+
import ScaleSizeProvider from '../ScaleSizeProvider';
|
|
16
18
|
import BottomTabBar, { hasCenterAction } from './BottomTabBar';
|
|
17
19
|
import { TabBarMetrics } from './metrics';
|
|
18
20
|
import { getTabBadge } from './TabBarIcon';
|
|
@@ -23,6 +25,8 @@ import TabScreen from './TabScreen';
|
|
|
23
25
|
import { ApplicationContext } from '../../Context';
|
|
24
26
|
import { useScreenLayout } from '../../Layout';
|
|
25
27
|
|
|
28
|
+
type SceneInterpolator = BottomTabNavigationOptions['sceneStyleInterpolator'];
|
|
29
|
+
|
|
26
30
|
const Tab = createBottomTabNavigator();
|
|
27
31
|
|
|
28
32
|
// the tab screens slide in index order over 280ms, CSS `ease`
|
|
@@ -32,12 +36,11 @@ const TAB_SWITCH_SPEC = {
|
|
|
32
36
|
};
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
36
|
-
* width off screen on their side
|
|
39
|
+
* Tab slide interpolator. Scene progress is -1 for tabs before the active one and 1 for
|
|
40
|
+
* tabs after it, so they wait a full width off screen on their side.
|
|
37
41
|
*/
|
|
38
|
-
const getTabSlide =
|
|
39
|
-
(
|
|
40
|
-
({ current }) => ({
|
|
42
|
+
const getTabSlide = (width: number): SceneInterpolator => {
|
|
43
|
+
return ({ current }) => ({
|
|
41
44
|
sceneStyle: {
|
|
42
45
|
transform: [
|
|
43
46
|
{
|
|
@@ -49,6 +52,7 @@ const getTabSlide =
|
|
|
49
52
|
],
|
|
50
53
|
},
|
|
51
54
|
});
|
|
55
|
+
};
|
|
52
56
|
|
|
53
57
|
/**
|
|
54
58
|
* App tab navigator: the floating bar on phones, the rail or floating bar on
|
|
@@ -67,11 +71,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
67
71
|
const { isIPhoneDuo } = useScreenLayout();
|
|
68
72
|
const { width: windowWidth } = useWindowDimensions();
|
|
69
73
|
const barInset = useDuoTabBarInset();
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
// The center action's button rises above the bar; tab content leaves room for it too.
|
|
75
|
+
let tabBarInset = barInset;
|
|
76
|
+
if (barInset && hasCenterAction(floatingButton, tabs.length)) {
|
|
77
|
+
tabBarInset = barInset + TabBarMetrics.ctaLift;
|
|
78
|
+
}
|
|
75
79
|
const initialIndex = tabs.findIndex(i => i.name === initialRouteName);
|
|
76
80
|
const activeIndex = React.useRef(initialIndex > 0 ? initialIndex : 0);
|
|
77
81
|
|
|
@@ -83,15 +87,15 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
83
87
|
});
|
|
84
88
|
}, [navigation]);
|
|
85
89
|
|
|
86
|
-
// Focus, not mount:
|
|
87
|
-
// onFocus only fires on tab changes.
|
|
90
|
+
// Focus, not mount/onFocus: popping back from a pushed screen only refocuses BottomTab.
|
|
88
91
|
useFocusEffect(
|
|
89
92
|
React.useCallback(() => {
|
|
90
93
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(
|
|
91
94
|
activeIndex.current !== 0,
|
|
92
95
|
);
|
|
93
|
-
return () =>
|
|
96
|
+
return () => {
|
|
94
97
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(false);
|
|
98
|
+
};
|
|
95
99
|
}, []),
|
|
96
100
|
);
|
|
97
101
|
|
|
@@ -128,6 +132,21 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
128
132
|
},
|
|
129
133
|
};
|
|
130
134
|
|
|
135
|
+
// One cap for the whole bar (labels, CTA, custom content) so it fits the fixed bar and notch.
|
|
136
|
+
const renderTabBar = (props: BottomTabBarProps) => {
|
|
137
|
+
const TabBar = isIPhoneDuo ? DuoTabBar : BottomTabBar;
|
|
138
|
+
return (
|
|
139
|
+
<ScaleSizeProvider scaleSizeMaxRate={TabBarMetrics.labelMaxScale}>
|
|
140
|
+
<TabBar
|
|
141
|
+
{...props}
|
|
142
|
+
floatingButton={floatingButton}
|
|
143
|
+
inactiveTintColor={theme.colors.text.secondary}
|
|
144
|
+
activeTintColor={theme.colors.primary}
|
|
145
|
+
/>
|
|
146
|
+
</ScaleSizeProvider>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
131
150
|
return (
|
|
132
151
|
<DuoBarCompressionProvider
|
|
133
152
|
behavior={compressionBehavior}
|
|
@@ -145,23 +164,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
145
164
|
transitionSpec: TAB_SWITCH_SPEC,
|
|
146
165
|
sceneStyleInterpolator: getTabSlide(windowWidth),
|
|
147
166
|
}}
|
|
148
|
-
tabBar={
|
|
149
|
-
isIPhoneDuo ? (
|
|
150
|
-
<DuoTabBar
|
|
151
|
-
{...props}
|
|
152
|
-
floatingButton={floatingButton}
|
|
153
|
-
inactiveTintColor={theme.colors.text.secondary}
|
|
154
|
-
activeTintColor={theme.colors.primary}
|
|
155
|
-
/>
|
|
156
|
-
) : (
|
|
157
|
-
<BottomTabBar
|
|
158
|
-
{...props}
|
|
159
|
-
floatingButton={floatingButton}
|
|
160
|
-
inactiveTintColor={theme.colors.text.secondary}
|
|
161
|
-
activeTintColor={theme.colors.primary}
|
|
162
|
-
/>
|
|
163
|
-
)
|
|
164
|
-
}
|
|
167
|
+
tabBar={renderTabBar}
|
|
165
168
|
initialRouteName={initialRouteName}
|
|
166
169
|
>
|
|
167
170
|
{tabs.map((item, index) => (
|
|
@@ -3,34 +3,41 @@ import { Easing } from 'react-native-reanimated';
|
|
|
3
3
|
import { Colors, Radius, Spacing } from '../../Consts';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Navigation bar measurements (Figma "Specs Navigation", DS-984),
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - vertical: iPhone Duo rail, center action under the container
|
|
6
|
+
* Navigation bar measurements (Figma "Specs Navigation", DS-984), in sync with momo-native-kits
|
|
7
|
+
* `TabBarMetrics`: the horizontal floating bar (center action in a notch) and the vertical
|
|
8
|
+
* iPhone Duo rail (center action under the container).
|
|
10
9
|
*/
|
|
11
10
|
const TabBarMetrics = {
|
|
12
|
-
height:
|
|
11
|
+
height: 60,
|
|
13
12
|
margin: Spacing.S,
|
|
14
|
-
paddingVertical: Spacing.
|
|
13
|
+
paddingVertical: Spacing.XS,
|
|
15
14
|
paddingHorizontal: Spacing.M,
|
|
16
|
-
itemGap:
|
|
15
|
+
itemGap: 1,
|
|
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,
|
|
17
23
|
iconSize: 24,
|
|
18
24
|
// active indicator behind the icon: 24 icon + XS·M padding
|
|
19
25
|
indicatorPaddingVertical: Spacing.XS,
|
|
20
26
|
indicatorPaddingHorizontal: Spacing.M,
|
|
21
27
|
indicatorRadius: Radius.XL,
|
|
22
28
|
indicatorHeight: 32,
|
|
23
|
-
|
|
29
|
+
labelMaxScale: 1.2,
|
|
30
|
+
labelHeight: 18,
|
|
31
|
+
railLabelHeight: 14,
|
|
24
32
|
systemIndicatorHeight: 24,
|
|
25
|
-
// unfolded foldables (short side from 600, the smallest in the spec) use 65% of the short
|
|
26
|
-
// side, centered; phones and folded outer displays fill the window
|
|
27
33
|
foldableMinShortSide: 600,
|
|
28
34
|
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,
|
|
29
38
|
ctaSlotWidth: 92,
|
|
30
39
|
ctaSize: 48,
|
|
31
|
-
// the icon sits 10 inside the 48 button (prototype .cta: 28 icon), 12 inside the 52 rail one
|
|
32
40
|
ctaIconSize: 28,
|
|
33
|
-
// the button rises 22 above the container top edge
|
|
34
41
|
ctaLift: 22,
|
|
35
42
|
ctaLabelGap: Spacing.M,
|
|
36
43
|
ctaLabelHeight: 18,
|
|
@@ -51,10 +58,9 @@ const TabBarMetrics = {
|
|
|
51
58
|
};
|
|
52
59
|
|
|
53
60
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* - the center action shrinks while pressed
|
|
61
|
+
* Navigation bar motion (MoMo Navigation Prototype): the active indicator slides to the
|
|
62
|
+
* selected item with a little overshoot, icon and label tints cross-fade, and the center
|
|
63
|
+
* action shrinks while pressed.
|
|
58
64
|
*/
|
|
59
65
|
const TabBarMotion = {
|
|
60
66
|
indicator: { duration: 550, easing: Easing.bezier(0.34, 1.4, 0.5, 1) },
|
|
@@ -85,7 +91,7 @@ const CTA_SHADOW: BoxShadowValue[] = [
|
|
|
85
91
|
];
|
|
86
92
|
|
|
87
93
|
/**
|
|
88
|
-
*
|
|
94
|
+
* Inner light and tint of the active indicator: white 12% from below, primary 12% from above.
|
|
89
95
|
*/
|
|
90
96
|
const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
|
|
91
97
|
{
|
|
@@ -99,9 +105,9 @@ const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
|
|
|
99
105
|
];
|
|
100
106
|
|
|
101
107
|
/**
|
|
102
|
-
*
|
|
108
|
+
* Notch dipped into the container top edge under the center action (Figma "Backgroud" path):
|
|
103
109
|
* starts NOTCH_HALF_WIDTH left of the center, then cubic segments as [control1, control2, end],
|
|
104
|
-
* each point [dx from the center, dy below the top edge]
|
|
110
|
+
* each point [dx from the center, dy below the top edge].
|
|
105
111
|
*/
|
|
106
112
|
const NOTCH_HALF_WIDTH = 50.758;
|
|
107
113
|
const NOTCH_CURVES: [number, number][][] = [
|
|
@@ -128,15 +134,25 @@ const NOTCH_CURVES: [number, number][][] = [
|
|
|
128
134
|
];
|
|
129
135
|
|
|
130
136
|
/**
|
|
131
|
-
*
|
|
132
|
-
* window minus the side safe areas and margins
|
|
137
|
+
* Width of the horizontal bar: 65% of the short side on an unfolded foldable, otherwise the
|
|
138
|
+
* window minus the side safe areas and margins.
|
|
133
139
|
*/
|
|
134
140
|
const getHorizontalBarWidth = (
|
|
135
141
|
width: number,
|
|
136
142
|
height: number,
|
|
137
143
|
insetLeft: number,
|
|
138
144
|
insetRight: number,
|
|
145
|
+
itemCount: number,
|
|
146
|
+
hasCta: boolean,
|
|
139
147
|
) => {
|
|
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
|
+
}
|
|
140
156
|
const shortSide = Math.min(width, height);
|
|
141
157
|
if (shortSide >= TabBarMetrics.foldableMinShortSide) {
|
|
142
158
|
return shortSide * TabBarMetrics.foldableWidthRatio;
|
|
@@ -145,12 +161,16 @@ const getHorizontalBarWidth = (
|
|
|
145
161
|
};
|
|
146
162
|
|
|
147
163
|
/**
|
|
148
|
-
*
|
|
164
|
+
* Height of the vertical rail container, plus the center action under it.
|
|
149
165
|
*/
|
|
150
|
-
const getRailHeight = (tabCount: number, hasCta: boolean) =>
|
|
151
|
-
tabCount * TabBarMetrics.railItemHeight
|
|
152
|
-
|
|
153
|
-
(hasCta
|
|
166
|
+
const getRailHeight = (tabCount: number, hasCta: boolean) => {
|
|
167
|
+
const itemsHeight = tabCount * TabBarMetrics.railItemHeight;
|
|
168
|
+
let ctaHeight = 0;
|
|
169
|
+
if (hasCta) {
|
|
170
|
+
ctaHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
|
|
171
|
+
}
|
|
172
|
+
return itemsHeight + TabBarMetrics.railPaddingBottom + ctaHeight;
|
|
173
|
+
};
|
|
154
174
|
|
|
155
175
|
export {
|
|
156
176
|
TabBarMetrics,
|
|
@@ -2,7 +2,7 @@ import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
|
|
|
2
2
|
import { CommonActions, Route } from '@react-navigation/native';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Navigation items of the tab bar, with the press handlers of the system tab bar.
|
|
6
6
|
*/
|
|
7
7
|
const getTabItems = ({ state, descriptors, navigation }: BottomTabBarProps) => {
|
|
8
8
|
return state.routes.map((route: Route<string>, index: number) => {
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
} from 'react-native-reanimated';
|
|
7
7
|
|
|
8
8
|
const SPRING = { damping: 18, stiffness: 220, mass: 0.8 };
|
|
9
|
-
// the button grows by a tenth while held
|
|
10
9
|
const PRESSED_SCALE = 1.1;
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
* lift for a single button
|
|
12
|
+
* Press lift for a single button (e.g. the compressed rail): it scales up while held,
|
|
13
|
+
* unless the OS asks for reduced motion.
|
|
14
14
|
*/
|
|
15
15
|
const usePressLift = () => {
|
|
16
16
|
const reduceMotion = useReducedMotion();
|
|
@@ -17,6 +17,10 @@ type BackgroundImageViewProps = {
|
|
|
17
17
|
animatedValue: SharedValue<number>;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Header surface (shadow or divider) and image, fading in over the first 52pt of
|
|
22
|
+
* scroll. iOS keeps the surface out of the clipped view so its shadow isn't cut.
|
|
23
|
+
*/
|
|
20
24
|
const BackgroundImageView: FC<BackgroundImageViewProps> = ({
|
|
21
25
|
useShadowHeader = true,
|
|
22
26
|
headerBackground = null,
|
|
@@ -30,6 +30,7 @@ const DuoGlassBackground: React.FC<{
|
|
|
30
30
|
const { headerHeight } = useScreenLayout();
|
|
31
31
|
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
32
32
|
const fadeStart = Math.max(bannerBottom - headerHeight, 0);
|
|
33
|
+
const translucentColor = theme.colors.background.surface + TRANSLUCENT_ALPHA;
|
|
33
34
|
|
|
34
35
|
const fadeStyle = useAnimatedStyle(() => ({
|
|
35
36
|
opacity: interpolate(
|
|
@@ -47,8 +48,7 @@ const DuoGlassBackground: React.FC<{
|
|
|
47
48
|
StyleSheet.absoluteFill,
|
|
48
49
|
styles.translucentBar,
|
|
49
50
|
{
|
|
50
|
-
backgroundColor:
|
|
51
|
-
theme.colors.background.surface + TRANSLUCENT_ALPHA,
|
|
51
|
+
backgroundColor: translucentColor,
|
|
52
52
|
borderBottomColor: theme.colors.border.default,
|
|
53
53
|
},
|
|
54
54
|
fadeStyle,
|
|
@@ -59,8 +59,9 @@ const DuoGlassBackground: React.FC<{
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
* header
|
|
62
|
+
* Header gradient (and theme image) drawn under the content, fading out on scroll:
|
|
63
|
+
* header-high, or 52pt taller and collapsing when `extended`. `followScroll`
|
|
64
|
+
* scrolls it away with the content instead of fading it.
|
|
64
65
|
*/
|
|
65
66
|
const DuoScrollGradient: React.FC<{
|
|
66
67
|
animatedValue?: AnimatedCompatValue;
|
|
@@ -19,6 +19,10 @@ const ASPECT_RATIOS: Record<
|
|
|
19
19
|
'1/1': 1 / 1,
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Parallax header image (or `children`): stretches when pulled down and fades out
|
|
24
|
+
* as the content scrolls up.
|
|
25
|
+
*/
|
|
22
26
|
const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
23
27
|
animatedValue,
|
|
24
28
|
image,
|
|
@@ -9,6 +9,10 @@ import { Image } from '../../Image';
|
|
|
9
9
|
import BackgroundImageView from './BackgroundImageView';
|
|
10
10
|
import { useNormalizedSharedValue } from '../hooks';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Header background: surface and image that fade in on scroll, with an optional
|
|
14
|
+
* gradient (custom or theme) on top. Outlined when `showBaseLineDebug` is on.
|
|
15
|
+
*/
|
|
12
16
|
const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
13
17
|
animatedValue,
|
|
14
18
|
useGradient = true,
|
|
@@ -23,6 +23,10 @@ const SCREEN_PADDING = 12;
|
|
|
23
23
|
const BACK_WIDTH = 28;
|
|
24
24
|
const TOOLKIT_WIDTH = 73;
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Extended header background: a gradient that collapses on scroll and, with
|
|
28
|
+
* `inputSearchProps`, a full-width search that slides into the bar beside back/toolkit.
|
|
29
|
+
*/
|
|
26
30
|
const HeaderExtendHeader: React.FC<{
|
|
27
31
|
headerType?: HeaderType;
|
|
28
32
|
animatedValue: AnimatedCompatValue;
|
|
@@ -64,18 +68,15 @@ const HeaderExtendHeader: React.FC<{
|
|
|
64
68
|
const contentLeft = layout.insetLeft + SCREEN_PADDING;
|
|
65
69
|
const contentRight = layout.insetLeft + layout.contentWidth;
|
|
66
70
|
// collapsed on the status bar row, the search stays between its clusters
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
const
|
|
71
|
+
const clearanceLeft = layout.statusBarClearance?.left ?? 0;
|
|
72
|
+
const clearanceRight = layout.statusBarClearance?.right ?? 0;
|
|
73
|
+
const collapsedLeft = layout.insetLeft + leftPosition + clearanceLeft;
|
|
74
|
+
const collapsedRight = contentRight - clearanceRight - headerRightWidth;
|
|
70
75
|
const searchWidth = layout.contentWidth - SCREEN_PADDING * 2;
|
|
71
|
-
const collapsedSearchWidth =
|
|
72
|
-
collapsedRight - collapsedLeft - SCREEN_PADDING - headerRightWidth;
|
|
76
|
+
const collapsedSearchWidth = collapsedRight - collapsedLeft - SCREEN_PADDING;
|
|
73
77
|
// a row taller than barHeight (iPhone Duo) centers the collapsed search on the title
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
layout.headerStatusBarHeight -
|
|
77
|
-
HeaderMetrics.barHeight) /
|
|
78
|
-
2;
|
|
78
|
+
const titleRowHeight = layout.headerHeight - layout.headerStatusBarHeight;
|
|
79
|
+
const collapsedRaise = (titleRowHeight - HeaderMetrics.barHeight) / 2;
|
|
79
80
|
|
|
80
81
|
let useShadowHeader = useShadowHeaderProp;
|
|
81
82
|
if (inputSearchProps && Platform.OS === 'android') {
|
|
@@ -10,7 +10,8 @@ import { useScreenLayout, useSideBarItemStyle } from '../../Layout/utils';
|
|
|
10
10
|
import { useSideBarCounterStyle } from './DuoQuickReturnHeader';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Header back button, also bound to the Android hardware back. `preventBack`
|
|
14
|
+
* confirms in a popup first; the iPhone Duo layouts use the 48pt side bar button.
|
|
14
15
|
*/
|
|
15
16
|
const HeaderLeft: React.FC<HeaderBackProps> = ({
|
|
16
17
|
tintColor,
|
|
@@ -9,7 +9,8 @@ import { HeaderToolkitAction } from './HeaderToolkitAction';
|
|
|
9
9
|
import { styles } from './HeaderRight.styles';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Header right slot: a custom `headerRight` render function, the onboarding skip
|
|
13
|
+
* button, or the toolkit (stacked vertically in the floating iPhone Duo side bar).
|
|
13
14
|
*/
|
|
14
15
|
const HeaderRight: React.FC<any> = props => {
|
|
15
16
|
const { translate } = useContext(ApplicationContext);
|
|
@@ -4,6 +4,10 @@ import { Spacing } from '../../Consts';
|
|
|
4
4
|
import { isWhiteListDesignSystem } from '../utils';
|
|
5
5
|
import { useScreenLayout } from '../../Layout';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Header title font: MoMoTrustDisplay for design-system whitelisted mini apps,
|
|
9
|
+
* SFProText otherwise.
|
|
10
|
+
*/
|
|
7
11
|
const useHeaderFontFamily = (): string => {
|
|
8
12
|
const context = useContext<any>(MiniAppContext);
|
|
9
13
|
return isWhiteListDesignSystem(context) ? 'MoMoTrustDisplay' : 'SFProText';
|
|
@@ -20,24 +24,22 @@ const HEADER_ACTIONS_WIDTH = 172;
|
|
|
20
24
|
const STATUS_BAR_ROW_ACTIONS_EXTRA = 55;
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
|
-
*
|
|
27
|
+
* Max header title width. The iPhone Duo side bar takes back and toolkit out of
|
|
28
|
+
* the bar, so the title gets the content width; otherwise it shares the bar with them.
|
|
24
29
|
*/
|
|
25
30
|
const useTitleMaxWidth = () => {
|
|
26
|
-
const
|
|
27
|
-
|
|
31
|
+
const layout = useScreenLayout();
|
|
32
|
+
const { width, contentWidth, sideBar, statusBarClearance } = layout;
|
|
28
33
|
let clearance = 0;
|
|
34
|
+
let rowActionsExtra = 0;
|
|
29
35
|
if (statusBarClearance) {
|
|
30
36
|
clearance = statusBarClearance.left + statusBarClearance.right;
|
|
37
|
+
rowActionsExtra = STATUS_BAR_ROW_ACTIONS_EXTRA;
|
|
31
38
|
}
|
|
32
39
|
if (sideBar) {
|
|
33
40
|
return contentWidth - Spacing.M * 2 - clearance;
|
|
34
41
|
}
|
|
35
|
-
return
|
|
36
|
-
width -
|
|
37
|
-
HEADER_ACTIONS_WIDTH -
|
|
38
|
-
(statusBarClearance ? STATUS_BAR_ROW_ACTIONS_EXTRA : 0) -
|
|
39
|
-
clearance
|
|
40
|
-
);
|
|
42
|
+
return width - HEADER_ACTIONS_WIDTH - rowActionsExtra - clearance;
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
export { useHeaderFontFamily, HEADER_ACTIONS_WIDTH, useTitleMaxWidth };
|
|
@@ -33,7 +33,8 @@ type HeaderTitleExtraProps = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* Default navigation header title. With `animatedValue` its opacity follows the
|
|
37
|
+
* scroll through `interpolate` (0 to 1 over the first 200pt by default).
|
|
37
38
|
*/
|
|
38
39
|
const HeaderTitle: React.FC<
|
|
39
40
|
HeaderTitleExtraProps & { [key: string]: any }
|
|
@@ -100,8 +101,8 @@ const HeaderTitle: React.FC<
|
|
|
100
101
|
};
|
|
101
102
|
|
|
102
103
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
104
|
+
* Header title picked by `type` (user, location or journey) with `data` as its
|
|
105
|
+
* props; an empty view for an unknown type.
|
|
105
106
|
*/
|
|
106
107
|
const TitleCustom: React.FC<any> = ({ type, data, ...props }) => {
|
|
107
108
|
switch (type) {
|
|
@@ -14,7 +14,8 @@ const CLOSE_ICON = '16_navigation_close_circle';
|
|
|
14
14
|
const HOME_ICON = '16_basic_home';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Header toolkit
|
|
17
|
+
* Header toolkit: optional shortcut plus the help center / close capsule. When
|
|
18
|
+
* `vertical` (iPhone Duo side bar) it reports its height and may fold into one button.
|
|
18
19
|
*/
|
|
19
20
|
const HeaderToolkitAction: React.FC<any> = ({
|
|
20
21
|
tintColor,
|
|
@@ -27,8 +28,9 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
27
28
|
vertical = false,
|
|
28
29
|
}) => {
|
|
29
30
|
const context = useContext<any>(MiniAppContext);
|
|
30
|
-
const
|
|
31
|
-
|
|
31
|
+
const compression = useDuoBarCompression();
|
|
32
|
+
const { toolbarCompressed, toolbarHidden } = compression;
|
|
33
|
+
const { expandToolbar, setToolbarHeight } = compression;
|
|
32
34
|
const { statusBarClearance } = useScreenLayout();
|
|
33
35
|
const {
|
|
34
36
|
isLoading,
|
|
@@ -56,18 +58,22 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
56
58
|
|
|
57
59
|
const useHelpCenter = context.appId !== 'vn.momo.helpcenter';
|
|
58
60
|
const closeIcon = useCloseIcon ? CLOSE_ICON : HOME_ICON;
|
|
61
|
+
let toolkitWidthStyle: ViewStyle = styles.toolkitNarrow;
|
|
62
|
+
if (useHelpCenter) {
|
|
63
|
+
toolkitWidthStyle = styles.toolkitWide;
|
|
64
|
+
}
|
|
59
65
|
|
|
60
66
|
const groupCount = useHelpCenter ? 2 : 1;
|
|
61
67
|
let capsuleHeight = DuoMetrics.controlSize;
|
|
62
68
|
if (groupCount > 1) {
|
|
63
69
|
capsuleHeight = groupCount * DuoMetrics.itemPitch;
|
|
64
70
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Spacing.M
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
const backSlotHeight = DuoMetrics.controlSize + Spacing.M;
|
|
72
|
+
let shortcutHeight = 0;
|
|
73
|
+
if (useShortcut) {
|
|
74
|
+
shortcutHeight = DuoMetrics.controlSize + Spacing.M;
|
|
75
|
+
}
|
|
76
|
+
const fullToolbarHeight = backSlotHeight + shortcutHeight + capsuleHeight;
|
|
71
77
|
|
|
72
78
|
useEffect(() => {
|
|
73
79
|
if (vertical) {
|
|
@@ -80,8 +86,7 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
80
86
|
return null;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
// HIG "Toolbar compressed":
|
|
84
|
-
// toolbar and compresses the tab bar instead. Not the useMore icon, to tell them apart
|
|
89
|
+
// HIG "Toolbar compressed": a menu icon (not the "more" one) that expands the toolbar
|
|
85
90
|
if (vertical && toolbarCompressed) {
|
|
86
91
|
return (
|
|
87
92
|
<SideBarButton
|
|
@@ -170,15 +175,7 @@ const HeaderToolkitAction: React.FC<any> = ({
|
|
|
170
175
|
showBadge={showBadge}
|
|
171
176
|
/>
|
|
172
177
|
)}
|
|
173
|
-
<View
|
|
174
|
-
style={[
|
|
175
|
-
styles.toolkitContainer,
|
|
176
|
-
buttonStyle,
|
|
177
|
-
context.appId !== 'vn.momo.helpcenter'
|
|
178
|
-
? styles.toolkitWide
|
|
179
|
-
: styles.toolkitNarrow,
|
|
180
|
-
]}
|
|
181
|
-
>
|
|
178
|
+
<View style={[styles.toolkitContainer, buttonStyle, toolkitWidthStyle]}>
|
|
182
179
|
{useHelpCenter && (
|
|
183
180
|
<>
|
|
184
181
|
<TouchableOpacity
|
|
@@ -7,7 +7,8 @@ import { Icon } from '../../Icon';
|
|
|
7
7
|
import { BadgeDot } from '../../Badge';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Round 28pt icon button of the header (back, shortcut) with an optional badge
|
|
11
|
+
* dot; switches to a dark fill under a white tint so the icon stays readable.
|
|
11
12
|
*/
|
|
12
13
|
const NavigationButton: React.FC<NavigationButtonProps> = ({
|
|
13
14
|
icon,
|
|
@@ -10,6 +10,10 @@ import { useNormalizedSharedValue } from '../hooks';
|
|
|
10
10
|
import { useScreenLayout } from '../../Layout';
|
|
11
11
|
import { DuoMetrics } from '../../Layout/utils';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Header search bar whose fill turns from surface to default once scrolled, with
|
|
15
|
+
* a cancel button that goes back (or dismisses the mini app) unless `renderButtons`.
|
|
16
|
+
*/
|
|
13
17
|
const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
14
18
|
(
|
|
15
19
|
{
|
|
@@ -36,9 +40,9 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
36
40
|
if (layout.sideBar) {
|
|
37
41
|
searchMaxWidth = layout.contentWidth - Spacing.M * 2 - headerRightWidth;
|
|
38
42
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
const clearance = layout.statusBarClearance;
|
|
44
|
+
if (clearance) {
|
|
45
|
+
searchMaxWidth -= clearance.left + clearance.right;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
const backgroundStyle = useAnimatedStyle(() => {
|
|
@@ -76,6 +80,15 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
76
80
|
|
|
77
81
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
78
82
|
|
|
83
|
+
let buttons: React.ReactNode = (
|
|
84
|
+
<TouchableOpacity onPress={goBackSafe} style={styles.searchAction}>
|
|
85
|
+
<Text typography={'action_default_bold'}>{buttonText}</Text>
|
|
86
|
+
</TouchableOpacity>
|
|
87
|
+
);
|
|
88
|
+
if (typeof renderButtons === 'function') {
|
|
89
|
+
buttons = renderButtons();
|
|
90
|
+
}
|
|
91
|
+
|
|
79
92
|
return (
|
|
80
93
|
<View
|
|
81
94
|
style={[styles.container, showBaseLineDebug && styles.debugBaseLine]}
|
|
@@ -97,13 +110,7 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
97
110
|
/>
|
|
98
111
|
</Animated.View>
|
|
99
112
|
</View>
|
|
100
|
-
{
|
|
101
|
-
renderButtons()
|
|
102
|
-
) : (
|
|
103
|
-
<TouchableOpacity onPress={goBackSafe} style={styles.searchAction}>
|
|
104
|
-
<Text typography={'action_default_bold'}>{buttonText}</Text>
|
|
105
|
-
</TouchableOpacity>
|
|
106
|
-
)}
|
|
113
|
+
{buttons}
|
|
107
114
|
</View>
|
|
108
115
|
);
|
|
109
116
|
},
|
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
} from './HeaderTitle.shared';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Header
|
|
18
|
-
*
|
|
17
|
+
* Header title for a journey: `start` and `end` around an icon, with an optional
|
|
18
|
+
* description below; a shimmer while `isLoading`.
|
|
19
19
|
*/
|
|
20
20
|
const TitleJourney: React.FC<TitleJourneyProps> = ({
|
|
21
21
|
start,
|
|
@@ -9,8 +9,8 @@ import { styles } from './HeaderTitle.styles';
|
|
|
9
9
|
import { useHeaderFontFamily, useTitleMaxWidth } from './HeaderTitle.shared';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Header
|
|
13
|
-
*
|
|
12
|
+
* Header title showing a location with a dropdown chevron and an optional
|
|
13
|
+
* description above it; a shimmer while `isLoading`.
|
|
14
14
|
*/
|
|
15
15
|
const TitleLocation: React.FC<TitleLocationProps> = ({
|
|
16
16
|
tintColor,
|
|
@@ -10,8 +10,8 @@ import { styles } from './HeaderTitle.styles';
|
|
|
10
10
|
import { useHeaderFontFamily, useTitleMaxWidth } from './HeaderTitle.shared';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Header user
|
|
14
|
-
*
|
|
13
|
+
* Header title for a user or group: avatar (1 to 4 images, a `+N` stack, or name
|
|
14
|
+
* initials), title with verified icons and subtitle; a shimmer while `isLoading`.
|
|
15
15
|
*/
|
|
16
16
|
const TitleUser: React.FC<TitleUserProps> = ({
|
|
17
17
|
title,
|