@momo-kits/foundation 0.169.7-beta.3 → 0.169.7-beta.4
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 +34 -44
- package/Application/BottomSheet.tsx +37 -40
- package/Application/BottomTab/ActiveIndicator.tsx +14 -15
- package/Application/BottomTab/BottomTabBar.tsx +62 -49
- package/Application/BottomTab/BottomTabInset.tsx +4 -8
- package/Application/BottomTab/CurvedContainer.tsx +12 -14
- package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
- package/Application/BottomTab/DuoBarCompression.tsx +19 -13
- package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
- package/Application/BottomTab/DuoTabBar.tsx +10 -8
- package/Application/BottomTab/TabBarIcon.tsx +29 -15
- package/Application/BottomTab/TabScreen.tsx +5 -1
- package/Application/BottomTab/index.tsx +30 -33
- 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 +0 -4
- package/Application/Components/DuoGlassBackground.tsx +4 -5
- package/Application/Components/HeaderAnimated.tsx +0 -4
- package/Application/Components/HeaderBackground.tsx +0 -4
- package/Application/Components/HeaderExtendHeader.tsx +10 -11
- package/Application/Components/HeaderLeft.tsx +1 -2
- package/Application/Components/HeaderRight.tsx +1 -2
- package/Application/Components/HeaderTitle.shared.ts +9 -11
- package/Application/Components/HeaderTitle.tsx +3 -4
- package/Application/Components/HeaderToolkitAction.tsx +20 -17
- package/Application/Components/NavigationButton.tsx +1 -2
- package/Application/Components/SearchHeader.tsx +10 -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 +32 -17
- package/Application/KitAdoption/AdoptionBadge.tsx +119 -0
- package/Application/KitAdoption/AdoptionProbe.tsx +166 -0
- package/Application/KitAdoption/AdoptionStore.ts +80 -0
- package/Application/KitAdoption/Authorship/utils.ts +137 -0
- package/Application/KitAdoption/Slots/utils.ts +68 -0
- package/Application/KitAdoption/constants.ts +120 -0
- package/Application/KitAdoption/index.ts +12 -0
- package/Application/KitAdoption/styles.ts +55 -0
- package/Application/KitAdoption/types.ts +61 -0
- package/Application/KitAdoption/utils.ts +376 -0
- package/Application/Localize.ts +0 -7
- package/Application/ModalScreen.tsx +10 -10
- package/Application/Navigation.ts +16 -14
- package/Application/NavigationContainer.tsx +18 -19
- package/Application/Navigator.ts +48 -20
- package/Application/ScaleSizeProvider.tsx +6 -13
- package/Application/StackScreen.tracking.ts +33 -11
- package/Application/StackScreen.tsx +54 -10
- package/Application/TooltipPortal.tsx +5 -12
- package/Application/WidgetContainer.tsx +4 -10
- package/Application/__tests__/KitAdoption.integration.test.tsx +253 -0
- package/Application/__tests__/KitAdoption.test.ts +283 -0
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +132 -0
- package/Application/__tests__/KitAdoptionStore.test.ts +110 -0
- package/Application/__tests__/KitAdoptionStructural.test.ts +193 -0
- package/Application/__tests__/modalLayout.test.ts +128 -0
- package/Application/modalLayout.ts +14 -16
- package/Application/navigationOptions.tsx +17 -21
- package/Badge/Badge.tsx +0 -5
- package/Badge/BadgeDot.tsx +0 -1
- package/Badge/BadgeDotAnimation.tsx +7 -15
- package/Badge/BadgeRibbon.tsx +38 -46
- package/Badge/Shape.tsx +0 -2
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +16 -5
- package/Button/utils.ts +15 -9
- package/CheckBox/index.tsx +0 -4
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +1 -5
- package/Divider/DashDivider.tsx +0 -1
- package/Divider/index.tsx +0 -4
- package/FoundationList/index.tsx +0 -4
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +12 -4
- package/Image/index.tsx +7 -9
- package/Input/Input.tsx +9 -8
- package/Input/InputDropDown.tsx +12 -10
- package/Input/InputMoney.tsx +8 -12
- package/Input/InputMoneyView.tsx +7 -14
- package/Input/InputOTP.tsx +33 -35
- package/Input/InputPhoneNumber.tsx +9 -8
- package/Input/InputSearch.tsx +6 -4
- package/Input/InputTextArea.tsx +0 -4
- package/Input/SearchTextTyping.tsx +0 -4
- package/Input/SystemTextInput.tsx +2 -7
- package/Input/TextTyping.tsx +0 -4
- package/Input/common.tsx +12 -26
- package/Input/index.tsx +20 -17
- package/Input/moneyUtils.ts +4 -10
- package/Input/styles.ts +5 -0
- package/Input/utils.ts +12 -14
- package/Layout/Card.tsx +4 -7
- package/Layout/FloatingButton.tsx +0 -4
- package/Layout/Item.tsx +2 -6
- package/Layout/ItemList.tsx +7 -9
- package/Layout/ItemSectionList.tsx +1 -2
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +58 -56
- package/Layout/Section.tsx +5 -8
- package/Layout/TrackingScope.tsx +0 -1
- package/Layout/screenContent.tsx +2 -4
- package/Layout/screenFooter.tsx +18 -28
- package/Layout/screenHeader.tsx +31 -34
- package/Layout/screenQuickReturn.tsx +20 -17
- package/Layout/screenScroll.ts +7 -4
- package/Layout/screenTypes.ts +2 -3
- package/Layout/types.ts +4 -2
- package/Layout/utils.ts +37 -31
- package/Loader/DotLoader.tsx +0 -4
- package/Loader/Loader.tsx +0 -4
- package/Loader/ProgressBar.tsx +0 -1
- package/Loader/Spinner.tsx +0 -4
- package/Loader/utils.ts +7 -11
- package/Pagination/Dot.tsx +0 -4
- package/Pagination/PaginationDot.tsx +0 -1
- package/Pagination/PaginationNumber.tsx +3 -5
- package/Pagination/PaginationScroll.tsx +0 -5
- package/Pagination/PaginationWhiteDot.tsx +0 -1
- package/Pagination/index.tsx +0 -4
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +49 -45
- package/Popup/PopupPromotion.tsx +7 -4
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +4 -11
- package/Radio/index.tsx +0 -4
- package/Skeleton/index.tsx +0 -8
- package/Switch/index.tsx +0 -1
- package/Tag/index.tsx +1 -5
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +3 -14
- package/Text/styles.ts +1 -1
- package/Text/types.ts +6 -1
- package/Text/utils.ts +4 -20
- package/Title/index.tsx +32 -41
- package/ViewBoundary/index.tsx +2 -4
- package/index.ts +6 -0
- package/package.json +1 -1
|
@@ -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;
|
|
32
31
|
// Back, then the right toolkit: help + home capsule
|
|
33
|
-
const DEFAULT_TOOLBAR_HEIGHT =
|
|
32
|
+
const DEFAULT_TOOLBAR_HEIGHT =
|
|
33
|
+
DuoMetrics.controlSize + Spacing.M + 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,12 +39,16 @@ const NO_COMPRESSION: DuoBarCompression = {
|
|
|
39
39
|
tabBarCompressed: false,
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
const DuoBarCompressionContext =
|
|
42
|
+
const DuoBarCompressionContext =
|
|
43
|
+
createContext<DuoBarCompression>(NO_COMPRESSION);
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* keeps the side bar's toolbar and tab bar clear of each other (HIG, Designing for
|
|
47
|
+
* iPhone Duo: Vertical controls):
|
|
48
|
+
* - both fit: both stay whole
|
|
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)
|
|
48
52
|
*/
|
|
49
53
|
const DuoBarCompressionProvider: React.FC<{
|
|
50
54
|
behavior?: DuoBarCompressionBehavior;
|
|
@@ -67,16 +71,18 @@ const DuoBarCompressionProvider: React.FC<{
|
|
|
67
71
|
}
|
|
68
72
|
const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
|
|
69
73
|
const tabBar = getRailHeight(tabCount, hasQr);
|
|
70
|
-
const
|
|
71
|
-
|
|
74
|
+
const compressedTabBar =
|
|
75
|
+
DuoMetrics.controlSize +
|
|
76
|
+
(hasQr ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
|
|
72
77
|
const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
|
|
73
78
|
|
|
74
79
|
if (fits(toolbarHeight, tabBar)) {
|
|
75
80
|
return { ...NO_COMPRESSION, setToolbarHeight };
|
|
76
81
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
const canSwap =
|
|
83
|
+
fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar) &&
|
|
84
|
+
fits(toolbarHeight, compressedTabBar);
|
|
85
|
+
if (canSwap) {
|
|
80
86
|
return {
|
|
81
87
|
toolbarCompressed: expanded === 'tabBar',
|
|
82
88
|
tabBarCompressed: expanded === 'toolbar',
|
|
@@ -91,7 +97,8 @@ const DuoBarCompressionProvider: React.FC<{
|
|
|
91
97
|
expandTabBar: () => setOpened('tabBar'),
|
|
92
98
|
setToolbarHeight,
|
|
93
99
|
};
|
|
94
|
-
//
|
|
100
|
+
// an opened rail always shows, even when only the gap between the groups is short:
|
|
101
|
+
// it closes again once a tab is picked
|
|
95
102
|
if (opened === 'tabBar') {
|
|
96
103
|
return {
|
|
97
104
|
...open,
|
|
@@ -115,7 +122,6 @@ const DuoBarCompressionProvider: React.FC<{
|
|
|
115
122
|
);
|
|
116
123
|
};
|
|
117
124
|
|
|
118
|
-
/** Compression state of the Duo side bars; no compression outside the provider. */
|
|
119
125
|
const useDuoBarCompression = () => useContext(DuoBarCompressionContext);
|
|
120
126
|
|
|
121
127
|
export { DuoBarCompressionProvider, useDuoBarCompression };
|
|
@@ -13,6 +13,7 @@ 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';
|
|
16
17
|
import { usePressLift } from './usePressLift';
|
|
17
18
|
import { ActiveIndicator } from './ActiveIndicator';
|
|
18
19
|
import {
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
import type { DuoTabBarProps } from './DuoTabBar';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
* 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
|
|
27
28
|
*/
|
|
28
29
|
const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
|
|
29
30
|
if (typeof icon === 'string') {
|
|
@@ -39,7 +40,7 @@ const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
|
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
+
* vertical container (.Nav/Duo/Item column) with the sliding active indicator
|
|
43
44
|
*/
|
|
44
45
|
const Rail: React.FC<{
|
|
45
46
|
items: TabItem[];
|
|
@@ -75,6 +76,7 @@ const Rail: React.FC<{
|
|
|
75
76
|
>
|
|
76
77
|
<TabItemContent
|
|
77
78
|
vertical
|
|
79
|
+
fixedFontScale
|
|
78
80
|
label={item.label}
|
|
79
81
|
focused={item.focused}
|
|
80
82
|
icon={item.icon}
|
|
@@ -88,7 +90,7 @@ const Rail: React.FC<{
|
|
|
88
90
|
);
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
|
-
*
|
|
93
|
+
* center action of the rail: round button with its label, under the container
|
|
92
94
|
*/
|
|
93
95
|
const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
94
96
|
floatingButton,
|
|
@@ -114,13 +116,15 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
114
116
|
<CtaIcon icon={floatingButton.icon} />
|
|
115
117
|
</Animated.View>
|
|
116
118
|
<View style={styles.railCtaLabel}>
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
<FixedScaleText>
|
|
120
|
+
<Text
|
|
121
|
+
numberOfLines={1}
|
|
122
|
+
typography={'label_xs_medium'}
|
|
123
|
+
color={theme.colors.text.secondary}
|
|
124
|
+
>
|
|
125
|
+
{floatingButton.label}
|
|
126
|
+
</Text>
|
|
127
|
+
</FixedScaleText>
|
|
124
128
|
{floatingButton.labelContent && (
|
|
125
129
|
<View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
|
|
126
130
|
{floatingButton.labelContent}
|
|
@@ -132,14 +136,14 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
132
136
|
};
|
|
133
137
|
|
|
134
138
|
/**
|
|
135
|
-
*
|
|
136
|
-
* side bar is short it can shrink to one button showing the current tab
|
|
137
|
-
* compressed"); tapping it expands the rail and compresses the toolbar
|
|
139
|
+
* vertical bar anchored to the bottom of the Duo side bar, center action below it.
|
|
140
|
+
* When the side bar is short, it can shrink to one button showing the current tab
|
|
141
|
+
* (HIG "Tab bar compressed"); tapping it expands the rail and compresses the toolbar
|
|
138
142
|
*/
|
|
139
143
|
const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
140
144
|
const { sideBar } = useScreenLayout();
|
|
141
|
-
const
|
|
142
|
-
|
|
145
|
+
const { tabBarCompressed, expandTabBar, onTabSelected } =
|
|
146
|
+
useDuoBarCompression();
|
|
143
147
|
const compactLift = usePressLift();
|
|
144
148
|
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
145
149
|
const items = getTabItems(props);
|
|
@@ -149,7 +153,8 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
149
153
|
return null;
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
//
|
|
156
|
+
// the rail lines up with the header buttons in the side bar: centered on a 48 lane kept 12
|
|
157
|
+
// from the strip's inner edge
|
|
153
158
|
const columnStyle = [
|
|
154
159
|
styles.railColumn,
|
|
155
160
|
{
|
|
@@ -159,10 +164,9 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
159
164
|
},
|
|
160
165
|
sideBar.edge === 'right' ? styles.railColumnRight : styles.railColumnLeft,
|
|
161
166
|
];
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
167
|
+
const cta = floatingButton && (
|
|
168
|
+
<RailCenterAction floatingButton={floatingButton} />
|
|
169
|
+
);
|
|
166
170
|
|
|
167
171
|
if (!tabBarCompressed) {
|
|
168
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();
|
|
@@ -44,6 +44,7 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
44
44
|
]}
|
|
45
45
|
>
|
|
46
46
|
<HorizontalTabBar
|
|
47
|
+
fixedFontScale
|
|
47
48
|
items={getTabItems(props)}
|
|
48
49
|
width={getHorizontalBarWidth(width, height, insets.left, insets.right)}
|
|
49
50
|
floatingButton={floatingButton}
|
|
@@ -55,14 +56,15 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
* iPhone Duo tab bar:
|
|
59
|
+
* iPhone Duo tab bar: rail in the side bar, or the horizontal bar when unfolded portrait
|
|
59
60
|
*/
|
|
60
61
|
const DuoTabBar: React.FC<DuoTabBarProps> = props => {
|
|
61
62
|
const { sideBar } = useScreenLayout();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
return sideBar ? (
|
|
64
|
+
<DuoRailTabBar {...props} />
|
|
65
|
+
) : (
|
|
66
|
+
<DuoFloatingTabBar {...props} />
|
|
67
|
+
);
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
export type { DuoTabBarProps };
|
|
@@ -7,10 +7,9 @@ import Animated, {
|
|
|
7
7
|
} from 'react-native-reanimated';
|
|
8
8
|
|
|
9
9
|
import { Colors, Spacing } from '../../Consts';
|
|
10
|
-
import { ApplicationContext } from '../../Context';
|
|
10
|
+
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
11
11
|
import { BadgeDot } from '../../Badge';
|
|
12
12
|
import { Text } from '../../Text';
|
|
13
|
-
import ScaleSizeProvider from '../ScaleSizeProvider';
|
|
14
13
|
import { TabBarMetrics, TabBarMotion } from './metrics';
|
|
15
14
|
|
|
16
15
|
type Props = {
|
|
@@ -29,8 +28,7 @@ type Props = {
|
|
|
29
28
|
};
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
|
-
*
|
|
33
|
-
* cap at 99+.
|
|
31
|
+
* badge value shown on a tab: undefined hides it, '' is a dot, numbers cap at 99+
|
|
34
32
|
*/
|
|
35
33
|
export const getTabBadge = (
|
|
36
34
|
badgeLabel?: string | number,
|
|
@@ -54,8 +52,27 @@ export const getTabBadge = (
|
|
|
54
52
|
};
|
|
55
53
|
|
|
56
54
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* text at its design size: like the system tab bar it doesn't grow with the OS font scale or
|
|
56
|
+
* the wide Duo displays, which Text otherwise scales up to 5pt
|
|
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)
|
|
59
76
|
*/
|
|
60
77
|
const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
61
78
|
label,
|
|
@@ -63,21 +80,19 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
63
80
|
}) => {
|
|
64
81
|
const { theme } = useContext(ApplicationContext);
|
|
65
82
|
const isNumber = /^\d+\+?$/.test(label);
|
|
66
|
-
let backgroundColor = theme.colors.warning.primary;
|
|
67
|
-
if (isNumber) {
|
|
68
|
-
backgroundColor = theme.colors.error.primary;
|
|
69
|
-
}
|
|
70
83
|
const badge = (
|
|
71
84
|
<View
|
|
72
85
|
style={[
|
|
73
86
|
styles.badge,
|
|
74
87
|
{
|
|
75
|
-
backgroundColor
|
|
88
|
+
backgroundColor: isNumber
|
|
89
|
+
? theme.colors.error.primary
|
|
90
|
+
: theme.colors.warning.primary,
|
|
76
91
|
borderColor: theme.colors.background.surface,
|
|
77
92
|
},
|
|
78
93
|
]}
|
|
79
94
|
>
|
|
80
|
-
<
|
|
95
|
+
<FixedScaleText>
|
|
81
96
|
<Text
|
|
82
97
|
numberOfLines={1}
|
|
83
98
|
typography={'action_xxs_bold'}
|
|
@@ -85,7 +100,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
85
100
|
>
|
|
86
101
|
{label}
|
|
87
102
|
</Text>
|
|
88
|
-
</
|
|
103
|
+
</FixedScaleText>
|
|
89
104
|
</View>
|
|
90
105
|
);
|
|
91
106
|
if (vertical) {
|
|
@@ -95,8 +110,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
95
110
|
};
|
|
96
111
|
|
|
97
112
|
/**
|
|
98
|
-
*
|
|
99
|
-
* focus.
|
|
113
|
+
* 24 icon; active and inactive icons are stacked and cross-faded on focus
|
|
100
114
|
*/
|
|
101
115
|
export function TabBarIcon({
|
|
102
116
|
focused,
|
|
@@ -29,7 +29,8 @@ 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
|
-
//
|
|
32
|
+
// the floating tab bar covers the bottom of the tab: pad the content unless a Screen in it
|
|
33
|
+
// keeps clear of the bar itself (scroll content running under the bar)
|
|
33
34
|
const { inset: tabBarInset } = useContext(BottomTabInsetContext);
|
|
34
35
|
const [insetClaimed, setInsetClaimed] = useState(false);
|
|
35
36
|
const tabInsetValue = useMemo(
|
|
@@ -53,6 +54,9 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
53
54
|
...data,
|
|
54
55
|
});
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* debug toast
|
|
59
|
+
*/
|
|
56
60
|
navigator?.maxApi?.showToastDebug?.({
|
|
57
61
|
appId: context.appId,
|
|
58
62
|
message: `${screenName} screen_navigated`,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
BottomTabBarProps,
|
|
3
2
|
BottomTabNavigationOptions,
|
|
4
3
|
createBottomTabNavigator,
|
|
5
4
|
} from '@react-navigation/bottom-tabs';
|
|
@@ -14,7 +13,6 @@ import {
|
|
|
14
13
|
} from 'react-native';
|
|
15
14
|
import { Icon } from '../../Icon';
|
|
16
15
|
import { BottomTabProps } from '../types';
|
|
17
|
-
import ScaleSizeProvider from '../ScaleSizeProvider';
|
|
18
16
|
import BottomTabBar, { hasCenterAction } from './BottomTabBar';
|
|
19
17
|
import { TabBarMetrics } from './metrics';
|
|
20
18
|
import { getTabBadge } from './TabBarIcon';
|
|
@@ -25,8 +23,6 @@ import TabScreen from './TabScreen';
|
|
|
25
23
|
import { ApplicationContext } from '../../Context';
|
|
26
24
|
import { useScreenLayout } from '../../Layout';
|
|
27
25
|
|
|
28
|
-
type SceneInterpolator = BottomTabNavigationOptions['sceneStyleInterpolator'];
|
|
29
|
-
|
|
30
26
|
const Tab = createBottomTabNavigator();
|
|
31
27
|
|
|
32
28
|
// the tab screens slide in index order over 280ms, CSS `ease`
|
|
@@ -36,11 +32,12 @@ const TAB_SWITCH_SPEC = {
|
|
|
36
32
|
};
|
|
37
33
|
|
|
38
34
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
35
|
+
* scene progress is -1 for tabs before the active one, 1 for tabs after it: they wait a full
|
|
36
|
+
* width off screen on their side
|
|
41
37
|
*/
|
|
42
|
-
const getTabSlide =
|
|
43
|
-
|
|
38
|
+
const getTabSlide =
|
|
39
|
+
(width: number): BottomTabNavigationOptions['sceneStyleInterpolator'] =>
|
|
40
|
+
({ current }) => ({
|
|
44
41
|
sceneStyle: {
|
|
45
42
|
transform: [
|
|
46
43
|
{
|
|
@@ -52,7 +49,6 @@ const getTabSlide = (width: number): SceneInterpolator => {
|
|
|
52
49
|
],
|
|
53
50
|
},
|
|
54
51
|
});
|
|
55
|
-
};
|
|
56
52
|
|
|
57
53
|
/**
|
|
58
54
|
* App tab navigator: the floating bar on phones, the rail or floating bar on
|
|
@@ -71,11 +67,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
71
67
|
const { isIPhoneDuo } = useScreenLayout();
|
|
72
68
|
const { width: windowWidth } = useWindowDimensions();
|
|
73
69
|
const barInset = useDuoTabBarInset();
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
// the center action's button rises above the bar, tab content leaves room for it too
|
|
71
|
+
const tabBarInset =
|
|
72
|
+
barInset && hasCenterAction(floatingButton, tabs.length)
|
|
73
|
+
? barInset + TabBarMetrics.ctaLift
|
|
74
|
+
: barInset;
|
|
79
75
|
const initialIndex = tabs.findIndex(i => i.name === initialRouteName);
|
|
80
76
|
const activeIndex = React.useRef(initialIndex > 0 ? initialIndex : 0);
|
|
81
77
|
|
|
@@ -87,15 +83,15 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
87
83
|
});
|
|
88
84
|
}, [navigation]);
|
|
89
85
|
|
|
90
|
-
// Focus, not mount
|
|
86
|
+
// Focus, not mount: pushing a screen from a tab leaves BottomTab mounted but blurred, and
|
|
87
|
+
// onFocus only fires on tab changes.
|
|
91
88
|
useFocusEffect(
|
|
92
89
|
React.useCallback(() => {
|
|
93
90
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(
|
|
94
91
|
activeIndex.current !== 0,
|
|
95
92
|
);
|
|
96
|
-
return () =>
|
|
93
|
+
return () =>
|
|
97
94
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(false);
|
|
98
|
-
};
|
|
99
95
|
}, []),
|
|
100
96
|
);
|
|
101
97
|
|
|
@@ -132,21 +128,6 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
132
128
|
},
|
|
133
129
|
};
|
|
134
130
|
|
|
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
|
-
|
|
150
131
|
return (
|
|
151
132
|
<DuoBarCompressionProvider
|
|
152
133
|
behavior={compressionBehavior}
|
|
@@ -164,7 +145,23 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
164
145
|
transitionSpec: TAB_SWITCH_SPEC,
|
|
165
146
|
sceneStyleInterpolator: getTabSlide(windowWidth),
|
|
166
147
|
}}
|
|
167
|
-
tabBar={
|
|
148
|
+
tabBar={props =>
|
|
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
|
+
}
|
|
168
165
|
initialRouteName={initialRouteName}
|
|
169
166
|
>
|
|
170
167
|
{tabs.map((item, index) => (
|
|
@@ -3,9 +3,10 @@ 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), in sync with
|
|
7
|
-
* `TabBarMetrics
|
|
8
|
-
*
|
|
6
|
+
* Navigation bar measurements (Figma "Specs Navigation", DS-984), kept in sync with
|
|
7
|
+
* momo-native-kits `TabBarMetrics`
|
|
8
|
+
* - horizontal: floating container with items split evenly, center action in a notch
|
|
9
|
+
* - vertical: iPhone Duo rail, center action under the container
|
|
9
10
|
*/
|
|
10
11
|
const TabBarMetrics = {
|
|
11
12
|
height: 68,
|
|
@@ -19,15 +20,17 @@ const TabBarMetrics = {
|
|
|
19
20
|
indicatorPaddingHorizontal: Spacing.M,
|
|
20
21
|
indicatorRadius: Radius.XL,
|
|
21
22
|
indicatorHeight: 32,
|
|
22
|
-
|
|
23
|
-
labelHeight: 18,
|
|
24
|
-
railLabelHeight: 14,
|
|
23
|
+
// home indicator / gesture bar area under the container
|
|
25
24
|
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
|
|
26
27
|
foldableMinShortSide: 600,
|
|
27
28
|
foldableWidthRatio: 0.65,
|
|
28
29
|
ctaSlotWidth: 92,
|
|
29
30
|
ctaSize: 48,
|
|
31
|
+
// the icon sits 10 inside the 48 button (prototype .cta: 28 icon), 12 inside the 52 rail one
|
|
30
32
|
ctaIconSize: 28,
|
|
33
|
+
// the button rises 22 above the container top edge
|
|
31
34
|
ctaLift: 22,
|
|
32
35
|
ctaLabelGap: Spacing.M,
|
|
33
36
|
ctaLabelHeight: 18,
|
|
@@ -48,9 +51,10 @@ const TabBarMetrics = {
|
|
|
48
51
|
};
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
+
* motion of the navigation bar (MoMo Navigation Prototype):
|
|
55
|
+
* - one active indicator slides to the selected item, overshooting a little
|
|
56
|
+
* - icons cross-fade and the label color follows
|
|
57
|
+
* - the center action shrinks while pressed
|
|
54
58
|
*/
|
|
55
59
|
const TabBarMotion = {
|
|
56
60
|
indicator: { duration: 550, easing: Easing.bezier(0.34, 1.4, 0.5, 1) },
|
|
@@ -81,7 +85,7 @@ const CTA_SHADOW: BoxShadowValue[] = [
|
|
|
81
85
|
];
|
|
82
86
|
|
|
83
87
|
/**
|
|
84
|
-
*
|
|
88
|
+
* inner light and tint of the active indicator: white 12% from below, primary 12% from above
|
|
85
89
|
*/
|
|
86
90
|
const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
|
|
87
91
|
{
|
|
@@ -95,9 +99,9 @@ const getIndicatorShadow = (primary: string): BoxShadowValue[] => [
|
|
|
95
99
|
];
|
|
96
100
|
|
|
97
101
|
/**
|
|
98
|
-
*
|
|
102
|
+
* notch dipped into the container top edge under the center action (Figma "Backgroud" path):
|
|
99
103
|
* starts NOTCH_HALF_WIDTH left of the center, then cubic segments as [control1, control2, end],
|
|
100
|
-
* each point [dx from the center, dy below the top edge]
|
|
104
|
+
* each point [dx from the center, dy below the top edge]
|
|
101
105
|
*/
|
|
102
106
|
const NOTCH_HALF_WIDTH = 50.758;
|
|
103
107
|
const NOTCH_CURVES: [number, number][][] = [
|
|
@@ -124,8 +128,8 @@ const NOTCH_CURVES: [number, number][][] = [
|
|
|
124
128
|
];
|
|
125
129
|
|
|
126
130
|
/**
|
|
127
|
-
*
|
|
128
|
-
* window minus the side safe areas and margins
|
|
131
|
+
* width of the horizontal bar: 65% of the short side on an unfolded foldable, otherwise the
|
|
132
|
+
* window minus the side safe areas and margins
|
|
129
133
|
*/
|
|
130
134
|
const getHorizontalBarWidth = (
|
|
131
135
|
width: number,
|
|
@@ -141,16 +145,12 @@ const getHorizontalBarWidth = (
|
|
|
141
145
|
};
|
|
142
146
|
|
|
143
147
|
/**
|
|
144
|
-
*
|
|
148
|
+
* height of the vertical rail container, plus the center action under it
|
|
145
149
|
*/
|
|
146
|
-
const getRailHeight = (tabCount: number, hasCta: boolean) =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
ctaHeight = TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight;
|
|
151
|
-
}
|
|
152
|
-
return itemsHeight + TabBarMetrics.railPaddingBottom + ctaHeight;
|
|
153
|
-
};
|
|
150
|
+
const getRailHeight = (tabCount: number, hasCta: boolean) =>
|
|
151
|
+
tabCount * TabBarMetrics.railItemHeight +
|
|
152
|
+
TabBarMetrics.railPaddingBottom +
|
|
153
|
+
(hasCta ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
|
|
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
|
|
9
10
|
const PRESSED_SCALE = 1.1;
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
* unless the OS asks for reduced motion.
|
|
13
|
+
* lift for a single button, e.g. the compressed rail
|
|
14
14
|
*/
|
|
15
15
|
const usePressLift = () => {
|
|
16
16
|
const reduceMotion = useReducedMotion();
|
|
@@ -17,10 +17,6 @@ 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
|
-
*/
|
|
24
20
|
const BackgroundImageView: FC<BackgroundImageViewProps> = ({
|
|
25
21
|
useShadowHeader = true,
|
|
26
22
|
headerBackground = null,
|
|
@@ -30,7 +30,6 @@ 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;
|
|
34
33
|
|
|
35
34
|
const fadeStyle = useAnimatedStyle(() => ({
|
|
36
35
|
opacity: interpolate(
|
|
@@ -48,7 +47,8 @@ const DuoGlassBackground: React.FC<{
|
|
|
48
47
|
StyleSheet.absoluteFill,
|
|
49
48
|
styles.translucentBar,
|
|
50
49
|
{
|
|
51
|
-
backgroundColor:
|
|
50
|
+
backgroundColor:
|
|
51
|
+
theme.colors.background.surface + TRANSLUCENT_ALPHA,
|
|
52
52
|
borderBottomColor: theme.colors.border.default,
|
|
53
53
|
},
|
|
54
54
|
fadeStyle,
|
|
@@ -59,9 +59,8 @@ const DuoGlassBackground: React.FC<{
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
* header-high,
|
|
64
|
-
* scrolls it away with the content instead of fading it.
|
|
62
|
+
* pink header gradient drawn under the content, fading out on scroll as main's extended
|
|
63
|
+
* header does: header-high for default headers, 52 taller (collapsing) for extended
|
|
65
64
|
*/
|
|
66
65
|
const DuoScrollGradient: React.FC<{
|
|
67
66
|
animatedValue?: AnimatedCompatValue;
|
|
@@ -19,10 +19,6 @@ 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
|
-
*/
|
|
26
22
|
const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
27
23
|
animatedValue,
|
|
28
24
|
image,
|
|
@@ -9,10 +9,6 @@ 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
|
-
*/
|
|
16
12
|
const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
17
13
|
animatedValue,
|
|
18
14
|
useGradient = true,
|