@momo-kits/foundation 0.169.4 → 0.169.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 +35 -26
- package/Application/BottomTab/ActiveIndicator.tsx +15 -14
- package/Application/BottomTab/BottomTabBar.tsx +43 -34
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +14 -12
- package/Application/BottomTab/CustomBottomTabItem.tsx +10 -6
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +13 -13
- package/Application/BottomTab/DuoTabBar.tsx +8 -9
- package/Application/BottomTab/TabBarIcon.tsx +13 -8
- package/Application/BottomTab/TabScreen.tsx +1 -5
- package/Application/BottomTab/index.tsx +38 -30
- package/Application/BottomTab/metrics.ts +21 -20
- 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 +9 -3
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -16
- package/Application/Navigator.ts +20 -48
- package/Application/StackScreen.tracking.ts +11 -26
- package/Application/StackScreen.tsx +8 -35
- package/Application/TooltipPortal.tsx +12 -5
- package/Application/WidgetContainer.tsx +10 -4
- package/Application/modalLayout.ts +11 -12
- package/Application/navigationOptions.tsx +21 -17
- 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 +1 -0
- 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 +39 -39
- 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 +9 -0
- package/Title/index.tsx +41 -32
- package/ViewBoundary/index.tsx +4 -2
- package/index.ts +0 -6
- package/package.json +1 -1
- package/Application/__tests__/modalLayout.test.ts +0 -128
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
import type { DuoTabBarProps } from './DuoTabBar';
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Center action icon: an icon name drawn at the CTA size, or a custom node
|
|
27
|
+
* Center action icon: an icon name drawn at the CTA size, or a custom node.
|
|
28
28
|
*/
|
|
29
29
|
const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
|
|
30
30
|
if (typeof icon === 'string') {
|
|
@@ -40,7 +40,7 @@ const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Vertical container (.Nav/Duo/Item column) with the sliding active indicator.
|
|
44
44
|
*/
|
|
45
45
|
const Rail: React.FC<{
|
|
46
46
|
items: TabItem[];
|
|
@@ -90,7 +90,7 @@ const Rail: React.FC<{
|
|
|
90
90
|
);
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
93
|
+
* Center action of the rail: a round button with its label, under the container.
|
|
94
94
|
*/
|
|
95
95
|
const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
96
96
|
floatingButton,
|
|
@@ -136,14 +136,14 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
136
136
|
};
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
139
|
+
* Vertical bar anchored to the bottom of the Duo side bar, center action below it. When the
|
|
140
|
+
* side bar is short it can shrink to one button showing the current tab (HIG "Tab bar
|
|
141
|
+
* compressed"); tapping it expands the rail and compresses the toolbar.
|
|
142
142
|
*/
|
|
143
143
|
const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
144
144
|
const { sideBar } = useScreenLayout();
|
|
145
|
-
const
|
|
146
|
-
|
|
145
|
+
const compression = useDuoBarCompression();
|
|
146
|
+
const { tabBarCompressed, expandTabBar, onTabSelected } = compression;
|
|
147
147
|
const compactLift = usePressLift();
|
|
148
148
|
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
149
149
|
const items = getTabItems(props);
|
|
@@ -153,8 +153,7 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
153
153
|
return null;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
//
|
|
157
|
-
// from the strip's inner edge
|
|
156
|
+
// Lines up with the side bar header buttons: a 48 lane kept 12 from the inner edge.
|
|
158
157
|
const columnStyle = [
|
|
159
158
|
styles.railColumn,
|
|
160
159
|
{
|
|
@@ -164,9 +163,10 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
164
163
|
},
|
|
165
164
|
sideBar.edge === 'right' ? styles.railColumnRight : styles.railColumnLeft,
|
|
166
165
|
];
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
let cta: React.ReactNode;
|
|
167
|
+
if (floatingButton) {
|
|
168
|
+
cta = <RailCenterAction floatingButton={floatingButton} />;
|
|
169
|
+
}
|
|
170
170
|
|
|
171
171
|
if (!tabBarCompressed) {
|
|
172
172
|
return (
|
|
@@ -16,8 +16,8 @@ type DuoTabBarProps = BottomTabBarProps & {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
* 0 when the Duo rail sits in the side bar
|
|
19
|
+
* Space tab content leaves under the floating horizontal tab bar (phone and unfolded Duo);
|
|
20
|
+
* 0 when the Duo rail sits in the side bar.
|
|
21
21
|
*/
|
|
22
22
|
const useDuoTabBarInset = () => {
|
|
23
23
|
const { isIPhoneDuo, sideBar, insets } = useScreenLayout();
|
|
@@ -28,7 +28,7 @@ const useDuoTabBarInset = () => {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Horizontal navigation bar of the unfolded display: 65% of the short side, centered.
|
|
32
32
|
*/
|
|
33
33
|
const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
34
34
|
const { insets } = useScreenLayout();
|
|
@@ -56,15 +56,14 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* iPhone Duo tab bar: rail in the side bar, or the horizontal bar when unfolded portrait
|
|
59
|
+
* iPhone Duo tab bar: the rail in the side bar, or the horizontal bar when unfolded portrait.
|
|
60
60
|
*/
|
|
61
61
|
const DuoTabBar: React.FC<DuoTabBarProps> = props => {
|
|
62
62
|
const { sideBar } = useScreenLayout();
|
|
63
|
-
|
|
64
|
-
<DuoRailTabBar {...props}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
63
|
+
if (sideBar) {
|
|
64
|
+
return <DuoRailTabBar {...props} />;
|
|
65
|
+
}
|
|
66
|
+
return <DuoFloatingTabBar {...props} />;
|
|
68
67
|
};
|
|
69
68
|
|
|
70
69
|
export type { DuoTabBarProps };
|
|
@@ -28,7 +28,8 @@ type Props = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Badge value of a tab: undefined hides it, '' is a dot (also 0 when `showDot`), numbers
|
|
32
|
+
* cap at 99+.
|
|
32
33
|
*/
|
|
33
34
|
export const getTabBadge = (
|
|
34
35
|
badgeLabel?: string | number,
|
|
@@ -52,8 +53,8 @@ export const getTabBadge = (
|
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
* text at its design size: like the system tab bar it doesn't grow with the OS font
|
|
56
|
-
* the wide Duo displays, which Text otherwise scales up to 5pt
|
|
56
|
+
* Keeps text at its design size: like the system tab bar it doesn't grow with the OS font
|
|
57
|
+
* scale or the wide Duo displays, which Text otherwise scales up to 5pt.
|
|
57
58
|
*/
|
|
58
59
|
export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
59
60
|
children,
|
|
@@ -72,7 +73,8 @@ export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
|
-
* numbers use the error color, text the warning color (Figma Badge
|
|
76
|
+
* Label badge of a tab: numbers use the error color, text the warning color (Figma Badge
|
|
77
|
+
* round numeric / text).
|
|
76
78
|
*/
|
|
77
79
|
const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
78
80
|
label,
|
|
@@ -80,14 +82,16 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
80
82
|
}) => {
|
|
81
83
|
const { theme } = useContext(ApplicationContext);
|
|
82
84
|
const isNumber = /^\d+\+?$/.test(label);
|
|
85
|
+
let backgroundColor = theme.colors.warning.primary;
|
|
86
|
+
if (isNumber) {
|
|
87
|
+
backgroundColor = theme.colors.error.primary;
|
|
88
|
+
}
|
|
83
89
|
const badge = (
|
|
84
90
|
<View
|
|
85
91
|
style={[
|
|
86
92
|
styles.badge,
|
|
87
93
|
{
|
|
88
|
-
backgroundColor
|
|
89
|
-
? theme.colors.error.primary
|
|
90
|
-
: theme.colors.warning.primary,
|
|
94
|
+
backgroundColor,
|
|
91
95
|
borderColor: theme.colors.background.surface,
|
|
92
96
|
},
|
|
93
97
|
]}
|
|
@@ -110,7 +114,8 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
110
114
|
};
|
|
111
115
|
|
|
112
116
|
/**
|
|
113
|
-
* 24
|
|
117
|
+
* Tab icon (24) with its badge; active and inactive icons are stacked and cross-fade on
|
|
118
|
+
* focus.
|
|
114
119
|
*/
|
|
115
120
|
export function TabBarIcon({
|
|
116
121
|
focused,
|
|
@@ -29,8 +29,7 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
29
29
|
|
|
30
30
|
const screenName = screen?.name || screen?.type?.name || 'Invalid';
|
|
31
31
|
const { isBackgroundToForeground } = useAppState();
|
|
32
|
-
// the floating
|
|
33
|
-
// keeps clear of the bar itself (scroll content running under the bar)
|
|
32
|
+
// Pad clear of the floating bar unless a Screen inside claims the inset to scroll under it.
|
|
34
33
|
const { inset: tabBarInset } = useContext(BottomTabInsetContext);
|
|
35
34
|
const [insetClaimed, setInsetClaimed] = useState(false);
|
|
36
35
|
const tabInsetValue = useMemo(
|
|
@@ -54,9 +53,6 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
54
53
|
...data,
|
|
55
54
|
});
|
|
56
55
|
|
|
57
|
-
/**
|
|
58
|
-
* debug toast
|
|
59
|
-
*/
|
|
60
56
|
navigator?.maxApi?.showToastDebug?.({
|
|
61
57
|
appId: context.appId,
|
|
62
58
|
message: `${screenName} screen_navigated`,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BottomTabBarProps,
|
|
2
3
|
BottomTabNavigationOptions,
|
|
3
4
|
createBottomTabNavigator,
|
|
4
5
|
} from '@react-navigation/bottom-tabs';
|
|
@@ -23,6 +24,8 @@ import TabScreen from './TabScreen';
|
|
|
23
24
|
import { ApplicationContext } from '../../Context';
|
|
24
25
|
import { useScreenLayout } from '../../Layout';
|
|
25
26
|
|
|
27
|
+
type SceneInterpolator = BottomTabNavigationOptions['sceneStyleInterpolator'];
|
|
28
|
+
|
|
26
29
|
const Tab = createBottomTabNavigator();
|
|
27
30
|
|
|
28
31
|
// the tab screens slide in index order over 280ms, CSS `ease`
|
|
@@ -32,12 +35,11 @@ const TAB_SWITCH_SPEC = {
|
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
36
|
-
* width off screen on their side
|
|
38
|
+
* Tab slide interpolator. Scene progress is -1 for tabs before the active one and 1 for
|
|
39
|
+
* tabs after it, so they wait a full width off screen on their side.
|
|
37
40
|
*/
|
|
38
|
-
const getTabSlide =
|
|
39
|
-
(
|
|
40
|
-
({ current }) => ({
|
|
41
|
+
const getTabSlide = (width: number): SceneInterpolator => {
|
|
42
|
+
return ({ current }) => ({
|
|
41
43
|
sceneStyle: {
|
|
42
44
|
transform: [
|
|
43
45
|
{
|
|
@@ -49,6 +51,7 @@ const getTabSlide =
|
|
|
49
51
|
],
|
|
50
52
|
},
|
|
51
53
|
});
|
|
54
|
+
};
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* App tab navigator: the floating bar on phones, the rail or floating bar on
|
|
@@ -67,11 +70,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
67
70
|
const { isIPhoneDuo } = useScreenLayout();
|
|
68
71
|
const { width: windowWidth } = useWindowDimensions();
|
|
69
72
|
const barInset = useDuoTabBarInset();
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
// The center action's button rises above the bar; tab content leaves room for it too.
|
|
74
|
+
let tabBarInset = barInset;
|
|
75
|
+
if (barInset && hasCenterAction(floatingButton, tabs.length)) {
|
|
76
|
+
tabBarInset = barInset + TabBarMetrics.ctaLift;
|
|
77
|
+
}
|
|
75
78
|
const initialIndex = tabs.findIndex(i => i.name === initialRouteName);
|
|
76
79
|
const activeIndex = React.useRef(initialIndex > 0 ? initialIndex : 0);
|
|
77
80
|
|
|
@@ -83,15 +86,15 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
83
86
|
});
|
|
84
87
|
}, [navigation]);
|
|
85
88
|
|
|
86
|
-
// Focus, not mount:
|
|
87
|
-
// onFocus only fires on tab changes.
|
|
89
|
+
// Focus, not mount/onFocus: popping back from a pushed screen only refocuses BottomTab.
|
|
88
90
|
useFocusEffect(
|
|
89
91
|
React.useCallback(() => {
|
|
90
92
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(
|
|
91
93
|
activeIndex.current !== 0,
|
|
92
94
|
);
|
|
93
|
-
return () =>
|
|
95
|
+
return () => {
|
|
94
96
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(false);
|
|
97
|
+
};
|
|
95
98
|
}, []),
|
|
96
99
|
);
|
|
97
100
|
|
|
@@ -128,6 +131,27 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
128
131
|
},
|
|
129
132
|
};
|
|
130
133
|
|
|
134
|
+
const renderTabBar = (props: BottomTabBarProps) => {
|
|
135
|
+
if (isIPhoneDuo) {
|
|
136
|
+
return (
|
|
137
|
+
<DuoTabBar
|
|
138
|
+
{...props}
|
|
139
|
+
floatingButton={floatingButton}
|
|
140
|
+
inactiveTintColor={theme.colors.text.secondary}
|
|
141
|
+
activeTintColor={theme.colors.primary}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return (
|
|
146
|
+
<BottomTabBar
|
|
147
|
+
{...props}
|
|
148
|
+
floatingButton={floatingButton}
|
|
149
|
+
inactiveTintColor={theme.colors.text.secondary}
|
|
150
|
+
activeTintColor={theme.colors.primary}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
131
155
|
return (
|
|
132
156
|
<DuoBarCompressionProvider
|
|
133
157
|
behavior={compressionBehavior}
|
|
@@ -145,23 +169,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
145
169
|
transitionSpec: TAB_SWITCH_SPEC,
|
|
146
170
|
sceneStyleInterpolator: getTabSlide(windowWidth),
|
|
147
171
|
}}
|
|
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
|
-
}
|
|
172
|
+
tabBar={renderTabBar}
|
|
165
173
|
initialRouteName={initialRouteName}
|
|
166
174
|
>
|
|
167
175
|
{tabs.map((item, index) => (
|
|
@@ -3,10 +3,9 @@ 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
11
|
height: 68,
|
|
@@ -22,8 +21,7 @@ const TabBarMetrics = {
|
|
|
22
21
|
indicatorHeight: 32,
|
|
23
22
|
// home indicator / gesture bar area under the container
|
|
24
23
|
systemIndicatorHeight: 24,
|
|
25
|
-
//
|
|
26
|
-
// side, centered; phones and folded outer displays fill the window
|
|
24
|
+
// Unfolded foldables (short side from 600) use 65% of it, centered; others fill the window
|
|
27
25
|
foldableMinShortSide: 600,
|
|
28
26
|
foldableWidthRatio: 0.65,
|
|
29
27
|
ctaSlotWidth: 92,
|
|
@@ -51,10 +49,9 @@ const TabBarMetrics = {
|
|
|
51
49
|
};
|
|
52
50
|
|
|
53
51
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* - the center action shrinks while pressed
|
|
52
|
+
* Navigation bar motion (MoMo Navigation Prototype): the active indicator slides to the
|
|
53
|
+
* selected item with a little overshoot, icon and label tints cross-fade, and the center
|
|
54
|
+
* action shrinks while pressed.
|
|
58
55
|
*/
|
|
59
56
|
const TabBarMotion = {
|
|
60
57
|
indicator: { duration: 550, easing: Easing.bezier(0.34, 1.4, 0.5, 1) },
|
|
@@ -85,7 +82,7 @@ const CTA_SHADOW: BoxShadowValue[] = [
|
|
|
85
82
|
];
|
|
86
83
|
|
|
87
84
|
/**
|
|
88
|
-
*
|
|
85
|
+
* Inner light and tint of the active indicator: white 12% from below, primary 12% from above.
|
|
89
86
|
*/
|
|
90
87
|
const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
|
|
91
88
|
{
|
|
@@ -99,9 +96,9 @@ const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
|
|
|
99
96
|
];
|
|
100
97
|
|
|
101
98
|
/**
|
|
102
|
-
*
|
|
99
|
+
* Notch dipped into the container top edge under the center action (Figma "Backgroud" path):
|
|
103
100
|
* 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]
|
|
101
|
+
* each point [dx from the center, dy below the top edge].
|
|
105
102
|
*/
|
|
106
103
|
const NOTCH_HALF_WIDTH = 50.758;
|
|
107
104
|
const NOTCH_CURVES: [number, number][][] = [
|
|
@@ -128,8 +125,8 @@ const NOTCH_CURVES: [number, number][][] = [
|
|
|
128
125
|
];
|
|
129
126
|
|
|
130
127
|
/**
|
|
131
|
-
*
|
|
132
|
-
* window minus the side safe areas and margins
|
|
128
|
+
* Width of the horizontal bar: 65% of the short side on an unfolded foldable, otherwise the
|
|
129
|
+
* window minus the side safe areas and margins.
|
|
133
130
|
*/
|
|
134
131
|
const getHorizontalBarWidth = (
|
|
135
132
|
width: number,
|
|
@@ -145,12 +142,16 @@ const getHorizontalBarWidth = (
|
|
|
145
142
|
};
|
|
146
143
|
|
|
147
144
|
/**
|
|
148
|
-
*
|
|
145
|
+
* Height of the vertical rail container, plus the center action under it.
|
|
149
146
|
*/
|
|
150
|
-
const getRailHeight = (tabCount: number, hasCta: boolean) =>
|
|
151
|
-
tabCount * TabBarMetrics.railItemHeight
|
|
152
|
-
|
|
153
|
-
(hasCta
|
|
147
|
+
const getRailHeight = (tabCount: number, hasCta: boolean) => {
|
|
148
|
+
const itemsHeight = tabCount * TabBarMetrics.railItemHeight;
|
|
149
|
+
let ctaHeight = 0;
|
|
150
|
+
if (hasCta) {
|
|
151
|
+
ctaHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
|
|
152
|
+
}
|
|
153
|
+
return itemsHeight + TabBarMetrics.railPaddingBottom + ctaHeight;
|
|
154
|
+
};
|
|
154
155
|
|
|
155
156
|
export {
|
|
156
157
|
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) {
|