@momo-kits/foundation 0.168.1-beta.37 → 0.168.1-beta.39
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 +49 -62
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +14 -12
- package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
- package/Application/BottomTab/DuoTabBar.tsx +8 -10
- 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 +23 -23
- 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 +40 -12
- package/Application/Components/HeaderBackground.tsx +19 -2
- package/Application/Components/HeaderExtendHeader.tsx +51 -37
- package/Application/Components/HeaderLeft.tsx +5 -18
- package/Application/Components/HeaderRight.styles.ts +15 -13
- package/Application/Components/HeaderRight.tsx +2 -1
- package/Application/Components/HeaderTitle.shared.ts +17 -16
- package/Application/Components/HeaderTitle.tsx +4 -3
- package/Application/Components/HeaderToolkitAction.tsx +29 -57
- package/Application/Components/NavigationButton.tsx +10 -6
- package/Application/Components/SearchHeader.tsx +37 -17
- 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/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -26
- package/Application/StackScreen.tsx +17 -35
- package/Application/TooltipPortal.tsx +12 -5
- package/Application/WidgetContainer.tsx +10 -4
- package/Application/headerLayout.ts +158 -0
- package/Application/index.ts +11 -1
- package/Application/modalLayout.ts +13 -14
- package/Application/navigationOptions.tsx +27 -32
- package/Application/types.header.ts +7 -2
- package/Application/types.ts +7 -0
- 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 +22 -8
- 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 +24 -21
- 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 +59 -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 +88 -82
- package/Layout/screenQuickReturn.tsx +17 -20
- package/Layout/screenScroll.ts +4 -7
- package/Layout/screenTypes.ts +13 -4
- 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 +37 -37
- package/Application/__tests__/modalLayout.test.ts +0 -128
|
@@ -28,9 +28,9 @@ type DuoBarCompression = {
|
|
|
28
28
|
|
|
29
29
|
// space kept between the top and the bottom groups (HIG Vertical controls)
|
|
30
30
|
const GROUP_GAP = Spacing.M;
|
|
31
|
+
const BACK_SLOT_HEIGHT = DuoMetrics.controlSize + Spacing.M;
|
|
31
32
|
// Back, then the right toolkit: help + home capsule
|
|
32
|
-
const DEFAULT_TOOLBAR_HEIGHT =
|
|
33
|
-
DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch;
|
|
33
|
+
const DEFAULT_TOOLBAR_HEIGHT = BACK_SLOT_HEIGHT + 2 * DuoMetrics.itemPitch;
|
|
34
34
|
// Back, then the single button the toolbar folds into
|
|
35
35
|
const COMPRESSED_TOOLBAR_HEIGHT = 2 * DuoMetrics.controlSize + Spacing.M;
|
|
36
36
|
|
|
@@ -39,16 +39,12 @@ const NO_COMPRESSION: DuoBarCompression = {
|
|
|
39
39
|
tabBarCompressed: false,
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
const DuoBarCompressionContext =
|
|
43
|
-
createContext<DuoBarCompression>(NO_COMPRESSION);
|
|
42
|
+
const DuoBarCompressionContext = createContext(NO_COMPRESSION);
|
|
44
43
|
|
|
45
44
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* - one fits whole beside the other compressed: one is compressed, tapping it swaps them
|
|
50
|
-
* - otherwise both rest compressed; tapping one opens it for a moment (the rail closes
|
|
51
|
-
* again once a tab is picked)
|
|
45
|
+
* Keeps the Duo side bar's toolbar and tab bar clear of each other (HIG Vertical controls):
|
|
46
|
+
* both stay whole if they fit, else one is compressed and tapping it swaps them, else both
|
|
47
|
+
* rest compressed and tapping one opens it until a tab is picked.
|
|
52
48
|
*/
|
|
53
49
|
const DuoBarCompressionProvider: React.FC<{
|
|
54
50
|
behavior?: DuoBarCompressionBehavior;
|
|
@@ -71,18 +67,16 @@ const DuoBarCompressionProvider: React.FC<{
|
|
|
71
67
|
}
|
|
72
68
|
const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
|
|
73
69
|
const tabBar = getRailHeight(tabCount, hasQr);
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
(hasQr ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
|
|
70
|
+
const qrHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
|
|
71
|
+
const compressedTabBar = DuoMetrics.controlSize + (hasQr ? qrHeight : 0);
|
|
77
72
|
const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
|
|
78
73
|
|
|
79
74
|
if (fits(toolbarHeight, tabBar)) {
|
|
80
75
|
return { ...NO_COMPRESSION, setToolbarHeight };
|
|
81
76
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (canSwap) {
|
|
77
|
+
const canCompressToolbar = fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar);
|
|
78
|
+
const canCompressTabBar = fits(toolbarHeight, compressedTabBar);
|
|
79
|
+
if (canCompressToolbar && canCompressTabBar) {
|
|
86
80
|
return {
|
|
87
81
|
toolbarCompressed: expanded === 'tabBar',
|
|
88
82
|
tabBarCompressed: expanded === 'toolbar',
|
|
@@ -97,8 +91,7 @@ const DuoBarCompressionProvider: React.FC<{
|
|
|
97
91
|
expandTabBar: () => setOpened('tabBar'),
|
|
98
92
|
setToolbarHeight,
|
|
99
93
|
};
|
|
100
|
-
//
|
|
101
|
-
// it closes again once a tab is picked
|
|
94
|
+
// An opened rail shows even if only the group gap is short; picking a tab closes it.
|
|
102
95
|
if (opened === 'tabBar') {
|
|
103
96
|
return {
|
|
104
97
|
...open,
|
|
@@ -122,6 +115,7 @@ const DuoBarCompressionProvider: React.FC<{
|
|
|
122
115
|
);
|
|
123
116
|
};
|
|
124
117
|
|
|
118
|
+
/** Compression state of the Duo side bars; no compression outside the provider. */
|
|
125
119
|
const useDuoBarCompression = () => useContext(DuoBarCompressionContext);
|
|
126
120
|
|
|
127
121
|
export { DuoBarCompressionProvider, useDuoBarCompression };
|
|
@@ -13,7 +13,6 @@ import { TabItemContent } from './CustomBottomTabItem';
|
|
|
13
13
|
import { CurvedContainer } from './CurvedContainer';
|
|
14
14
|
import { TabBarMetrics } from './metrics';
|
|
15
15
|
import { getTabItems, TabItem } from './tabItems';
|
|
16
|
-
import { FixedScaleText } from './TabBarIcon';
|
|
17
16
|
import { usePressLift } from './usePressLift';
|
|
18
17
|
import { ActiveIndicator } from './ActiveIndicator';
|
|
19
18
|
import {
|
|
@@ -24,7 +23,7 @@ import {
|
|
|
24
23
|
import type { DuoTabBarProps } from './DuoTabBar';
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
|
-
* Center action icon: an icon name drawn at the CTA size, or a custom node
|
|
26
|
+
* Center action icon: an icon name drawn at the CTA size, or a custom node.
|
|
28
27
|
*/
|
|
29
28
|
const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
|
|
30
29
|
if (typeof icon === 'string') {
|
|
@@ -40,7 +39,7 @@ const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
|
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
42
|
+
* Vertical container (.Nav/Duo/Item column) with the sliding active indicator.
|
|
44
43
|
*/
|
|
45
44
|
const Rail: React.FC<{
|
|
46
45
|
items: TabItem[];
|
|
@@ -76,7 +75,6 @@ const Rail: React.FC<{
|
|
|
76
75
|
>
|
|
77
76
|
<TabItemContent
|
|
78
77
|
vertical
|
|
79
|
-
fixedFontScale
|
|
80
78
|
label={item.label}
|
|
81
79
|
focused={item.focused}
|
|
82
80
|
icon={item.icon}
|
|
@@ -90,7 +88,7 @@ const Rail: React.FC<{
|
|
|
90
88
|
);
|
|
91
89
|
|
|
92
90
|
/**
|
|
93
|
-
*
|
|
91
|
+
* Center action of the rail: a round button with its label, under the container.
|
|
94
92
|
*/
|
|
95
93
|
const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
96
94
|
floatingButton,
|
|
@@ -116,15 +114,13 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
116
114
|
<CtaIcon icon={floatingButton.icon} />
|
|
117
115
|
</Animated.View>
|
|
118
116
|
<View style={styles.railCtaLabel}>
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
</Text>
|
|
127
|
-
</FixedScaleText>
|
|
117
|
+
<Text
|
|
118
|
+
numberOfLines={1}
|
|
119
|
+
typography={'label_xs_medium'}
|
|
120
|
+
color={theme.colors.text.secondary}
|
|
121
|
+
>
|
|
122
|
+
{floatingButton.label}
|
|
123
|
+
</Text>
|
|
128
124
|
{floatingButton.labelContent && (
|
|
129
125
|
<View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
|
|
130
126
|
{floatingButton.labelContent}
|
|
@@ -136,14 +132,14 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
136
132
|
};
|
|
137
133
|
|
|
138
134
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
135
|
+
* Vertical bar anchored to the bottom of the Duo side bar, center action below it. When the
|
|
136
|
+
* side bar is short it can shrink to one button showing the current tab (HIG "Tab bar
|
|
137
|
+
* compressed"); tapping it expands the rail and compresses the toolbar.
|
|
142
138
|
*/
|
|
143
139
|
const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
144
140
|
const { sideBar } = useScreenLayout();
|
|
145
|
-
const
|
|
146
|
-
|
|
141
|
+
const compression = useDuoBarCompression();
|
|
142
|
+
const { tabBarCompressed, expandTabBar, onTabSelected } = compression;
|
|
147
143
|
const compactLift = usePressLift();
|
|
148
144
|
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
149
145
|
const items = getTabItems(props);
|
|
@@ -153,8 +149,7 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
153
149
|
return null;
|
|
154
150
|
}
|
|
155
151
|
|
|
156
|
-
//
|
|
157
|
-
// from the strip's inner edge
|
|
152
|
+
// Lines up with the side bar header buttons: a 48 lane kept 12 from the inner edge.
|
|
158
153
|
const columnStyle = [
|
|
159
154
|
styles.railColumn,
|
|
160
155
|
{
|
|
@@ -164,9 +159,10 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
164
159
|
},
|
|
165
160
|
sideBar.edge === 'right' ? styles.railColumnRight : styles.railColumnLeft,
|
|
166
161
|
];
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
let cta: React.ReactNode;
|
|
163
|
+
if (floatingButton) {
|
|
164
|
+
cta = <RailCenterAction floatingButton={floatingButton} />;
|
|
165
|
+
}
|
|
170
166
|
|
|
171
167
|
if (!tabBarCompressed) {
|
|
172
168
|
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();
|
|
@@ -44,7 +44,6 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
44
44
|
]}
|
|
45
45
|
>
|
|
46
46
|
<HorizontalTabBar
|
|
47
|
-
fixedFontScale
|
|
48
47
|
items={getTabItems(props)}
|
|
49
48
|
width={getHorizontalBarWidth(width, height, insets.left, insets.right)}
|
|
50
49
|
floatingButton={floatingButton}
|
|
@@ -56,15 +55,14 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
56
55
|
};
|
|
57
56
|
|
|
58
57
|
/**
|
|
59
|
-
* iPhone Duo tab bar: rail in the side bar, or the horizontal bar when unfolded portrait
|
|
58
|
+
* iPhone Duo tab bar: the rail in the side bar, or the horizontal bar when unfolded portrait.
|
|
60
59
|
*/
|
|
61
60
|
const DuoTabBar: React.FC<DuoTabBarProps> = props => {
|
|
62
61
|
const { sideBar } = useScreenLayout();
|
|
63
|
-
|
|
64
|
-
<DuoRailTabBar {...props}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
62
|
+
if (sideBar) {
|
|
63
|
+
return <DuoRailTabBar {...props} />;
|
|
64
|
+
}
|
|
65
|
+
return <DuoFloatingTabBar {...props} />;
|
|
68
66
|
};
|
|
69
67
|
|
|
70
68
|
export type { DuoTabBarProps };
|
|
@@ -7,9 +7,10 @@ import Animated, {
|
|
|
7
7
|
} from 'react-native-reanimated';
|
|
8
8
|
|
|
9
9
|
import { Colors, Spacing } from '../../Consts';
|
|
10
|
-
import { ApplicationContext
|
|
10
|
+
import { ApplicationContext } from '../../Context';
|
|
11
11
|
import { BadgeDot } from '../../Badge';
|
|
12
12
|
import { Text } from '../../Text';
|
|
13
|
+
import ScaleSizeProvider from '../ScaleSizeProvider';
|
|
13
14
|
import { TabBarMetrics, TabBarMotion } from './metrics';
|
|
14
15
|
|
|
15
16
|
type Props = {
|
|
@@ -28,7 +29,8 @@ type Props = {
|
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
+
* Badge value of a tab: undefined hides it, '' is a dot (also 0 when `showDot`), numbers
|
|
33
|
+
* cap at 99+.
|
|
32
34
|
*/
|
|
33
35
|
export const getTabBadge = (
|
|
34
36
|
badgeLabel?: string | number,
|
|
@@ -52,27 +54,8 @@ export const getTabBadge = (
|
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*/
|
|
58
|
-
export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
59
|
-
children,
|
|
60
|
-
}) => {
|
|
61
|
-
const context = useContext<any>(MiniAppContext);
|
|
62
|
-
return (
|
|
63
|
-
<MiniAppContext.Provider
|
|
64
|
-
value={{
|
|
65
|
-
...context,
|
|
66
|
-
fontScale: { useOSScaleRate: false, userScaleRate: 1 },
|
|
67
|
-
}}
|
|
68
|
-
>
|
|
69
|
-
{children}
|
|
70
|
-
</MiniAppContext.Provider>
|
|
71
|
-
);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* numbers use the error color, text the warning color (Figma Badge round numeric / text)
|
|
57
|
+
* Label badge of a tab: numbers use the error color, text the warning color (Figma Badge
|
|
58
|
+
* round numeric / text). The text keeps its design size to fit the 16 height.
|
|
76
59
|
*/
|
|
77
60
|
const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
78
61
|
label,
|
|
@@ -80,19 +63,21 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
80
63
|
}) => {
|
|
81
64
|
const { theme } = useContext(ApplicationContext);
|
|
82
65
|
const isNumber = /^\d+\+?$/.test(label);
|
|
66
|
+
let backgroundColor = theme.colors.warning.primary;
|
|
67
|
+
if (isNumber) {
|
|
68
|
+
backgroundColor = theme.colors.error.primary;
|
|
69
|
+
}
|
|
83
70
|
const badge = (
|
|
84
71
|
<View
|
|
85
72
|
style={[
|
|
86
73
|
styles.badge,
|
|
87
74
|
{
|
|
88
|
-
backgroundColor
|
|
89
|
-
? theme.colors.error.primary
|
|
90
|
-
: theme.colors.warning.primary,
|
|
75
|
+
backgroundColor,
|
|
91
76
|
borderColor: theme.colors.background.surface,
|
|
92
77
|
},
|
|
93
78
|
]}
|
|
94
79
|
>
|
|
95
|
-
<
|
|
80
|
+
<ScaleSizeProvider scaleSizeMaxRate={1}>
|
|
96
81
|
<Text
|
|
97
82
|
numberOfLines={1}
|
|
98
83
|
typography={'action_xxs_bold'}
|
|
@@ -100,7 +85,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
100
85
|
>
|
|
101
86
|
{label}
|
|
102
87
|
</Text>
|
|
103
|
-
</
|
|
88
|
+
</ScaleSizeProvider>
|
|
104
89
|
</View>
|
|
105
90
|
);
|
|
106
91
|
if (vertical) {
|
|
@@ -110,7 +95,8 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
110
95
|
};
|
|
111
96
|
|
|
112
97
|
/**
|
|
113
|
-
* 24
|
|
98
|
+
* Tab icon (24) with its badge; active and inactive icons are stacked and cross-fade on
|
|
99
|
+
* focus.
|
|
114
100
|
*/
|
|
115
101
|
export function TabBarIcon({
|
|
116
102
|
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';
|
|
@@ -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,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,
|
|
@@ -20,17 +19,15 @@ const TabBarMetrics = {
|
|
|
20
19
|
indicatorPaddingHorizontal: Spacing.M,
|
|
21
20
|
indicatorRadius: Radius.XL,
|
|
22
21
|
indicatorHeight: 32,
|
|
23
|
-
|
|
22
|
+
labelMaxScale: 1.2,
|
|
23
|
+
labelHeight: 18,
|
|
24
|
+
railLabelHeight: 14,
|
|
24
25
|
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
26
|
foldableMinShortSide: 600,
|
|
28
27
|
foldableWidthRatio: 0.65,
|
|
29
28
|
ctaSlotWidth: 92,
|
|
30
29
|
ctaSize: 48,
|
|
31
|
-
// the icon sits 10 inside the 48 button (prototype .cta: 28 icon), 12 inside the 52 rail one
|
|
32
30
|
ctaIconSize: 28,
|
|
33
|
-
// the button rises 22 above the container top edge
|
|
34
31
|
ctaLift: 22,
|
|
35
32
|
ctaLabelGap: Spacing.M,
|
|
36
33
|
ctaLabelHeight: 18,
|
|
@@ -51,10 +48,9 @@ const TabBarMetrics = {
|
|
|
51
48
|
};
|
|
52
49
|
|
|
53
50
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* - the center action shrinks while pressed
|
|
51
|
+
* Navigation bar motion (MoMo Navigation Prototype): the active indicator slides to the
|
|
52
|
+
* selected item with a little overshoot, icon and label tints cross-fade, and the center
|
|
53
|
+
* action shrinks while pressed.
|
|
58
54
|
*/
|
|
59
55
|
const TabBarMotion = {
|
|
60
56
|
indicator: { duration: 550, easing: Easing.bezier(0.34, 1.4, 0.5, 1) },
|
|
@@ -85,7 +81,7 @@ const CTA_SHADOW: BoxShadowValue[] = [
|
|
|
85
81
|
];
|
|
86
82
|
|
|
87
83
|
/**
|
|
88
|
-
*
|
|
84
|
+
* Inner light and tint of the active indicator: white 12% from below, primary 12% from above.
|
|
89
85
|
*/
|
|
90
86
|
const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
|
|
91
87
|
{
|
|
@@ -99,9 +95,9 @@ const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
|
|
|
99
95
|
];
|
|
100
96
|
|
|
101
97
|
/**
|
|
102
|
-
*
|
|
98
|
+
* Notch dipped into the container top edge under the center action (Figma "Backgroud" path):
|
|
103
99
|
* 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]
|
|
100
|
+
* each point [dx from the center, dy below the top edge].
|
|
105
101
|
*/
|
|
106
102
|
const NOTCH_HALF_WIDTH = 50.758;
|
|
107
103
|
const NOTCH_CURVES: [number, number][][] = [
|
|
@@ -128,8 +124,8 @@ const NOTCH_CURVES: [number, number][][] = [
|
|
|
128
124
|
];
|
|
129
125
|
|
|
130
126
|
/**
|
|
131
|
-
*
|
|
132
|
-
* window minus the side safe areas and margins
|
|
127
|
+
* Width of the horizontal bar: 65% of the short side on an unfolded foldable, otherwise the
|
|
128
|
+
* window minus the side safe areas and margins.
|
|
133
129
|
*/
|
|
134
130
|
const getHorizontalBarWidth = (
|
|
135
131
|
width: number,
|
|
@@ -145,12 +141,16 @@ const getHorizontalBarWidth = (
|
|
|
145
141
|
};
|
|
146
142
|
|
|
147
143
|
/**
|
|
148
|
-
*
|
|
144
|
+
* Height of the vertical rail container, plus the center action under it.
|
|
149
145
|
*/
|
|
150
|
-
const getRailHeight = (tabCount: number, hasCta: boolean) =>
|
|
151
|
-
tabCount * TabBarMetrics.railItemHeight
|
|
152
|
-
|
|
153
|
-
(hasCta
|
|
146
|
+
const getRailHeight = (tabCount: number, hasCta: boolean) => {
|
|
147
|
+
const itemsHeight = tabCount * TabBarMetrics.railItemHeight;
|
|
148
|
+
let ctaHeight = 0;
|
|
149
|
+
if (hasCta) {
|
|
150
|
+
ctaHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
|
|
151
|
+
}
|
|
152
|
+
return itemsHeight + TabBarMetrics.railPaddingBottom + ctaHeight;
|
|
153
|
+
};
|
|
154
154
|
|
|
155
155
|
export {
|
|
156
156
|
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;
|