@momo-kits/foundation 0.169.5 → 0.169.6-beta.1
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 +26 -35
- package/Application/BottomTab/ActiveIndicator.tsx +14 -15
- package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
- package/Application/BottomTab/BottomTabBar.tsx +40 -63
- package/Application/BottomTab/BottomTabInset.tsx +4 -8
- package/Application/BottomTab/CurvedContainer.tsx +20 -16
- package/Application/BottomTab/CustomBottomTabItem.tsx +13 -16
- package/Application/BottomTab/DuoBarCompression.tsx +19 -13
- package/Application/BottomTab/DuoRailTabBar.tsx +13 -17
- package/Application/BottomTab/DuoTabBar.tsx +24 -12
- package/Application/BottomTab/TabBarIcon.tsx +8 -13
- package/Application/BottomTab/TabScreen.tsx +5 -1
- package/Application/BottomTab/index.tsx +30 -38
- package/Application/BottomTab/metrics.ts +46 -24
- 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/Localize.ts +0 -7
- package/Application/ModalScreen.tsx +3 -9
- package/Application/Navigation.ts +16 -14
- package/Application/NavigationContainer.tsx +16 -19
- package/Application/Navigator.ts +48 -20
- package/Application/StackScreen.tracking.ts +26 -11
- package/Application/StackScreen.tsx +35 -8
- package/Application/TooltipPortal.tsx +5 -12
- package/Application/WidgetContainer.tsx +4 -10
- package/Application/__tests__/modalLayout.test.ts +128 -0
- package/Application/modalLayout.ts +14 -13
- package/Application/navigationOptions.tsx +17 -21
- package/Application/types.tabs.ts +0 -1
- 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 +0 -1
- 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 +48 -44
- package/Popup/PopupPromotion.tsx +7 -4
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +0 -4
- 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 +0 -9
- package/Title/index.tsx +32 -41
- package/ViewBoundary/index.tsx +2 -4
- package/index.ts +6 -0
- package/package.json +1 -1
|
@@ -10,10 +10,10 @@ import Animated, {
|
|
|
10
10
|
useSharedValue,
|
|
11
11
|
withTiming,
|
|
12
12
|
} from 'react-native-reanimated';
|
|
13
|
-
import {
|
|
13
|
+
import { TabBarIcon } from './TabBarIcon';
|
|
14
14
|
import { Text } from '../../Text';
|
|
15
15
|
import { MiniAppContext } from '../../Context';
|
|
16
|
-
import { Colors
|
|
16
|
+
import { Colors } from '../../Consts';
|
|
17
17
|
import { TabBarMetrics, TabBarMotion } from './metrics';
|
|
18
18
|
|
|
19
19
|
type IconRenderer = (props: {
|
|
@@ -31,14 +31,12 @@ type TabItemContentProps = {
|
|
|
31
31
|
badge?: string | number;
|
|
32
32
|
// rail item (.Nav/Duo/Item) instead of the horizontal one (.Nav/itemTab)
|
|
33
33
|
vertical?: boolean;
|
|
34
|
-
// keep the label at its design size, the Duo displays otherwise scale it up
|
|
35
|
-
fixedFontScale?: boolean;
|
|
36
34
|
hideLabel?: boolean;
|
|
37
35
|
};
|
|
38
36
|
|
|
39
37
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* icon in its indicator slot, label under it. The bar slides one shared active indicator
|
|
39
|
+
* behind the slots; the icon and label tint cross-fade on focus
|
|
42
40
|
*/
|
|
43
41
|
const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
44
42
|
label,
|
|
@@ -48,7 +46,6 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
48
46
|
icon,
|
|
49
47
|
badge,
|
|
50
48
|
vertical,
|
|
51
|
-
fixedFontScale,
|
|
52
49
|
hideLabel,
|
|
53
50
|
}) => {
|
|
54
51
|
const progress = useSharedValue(focused ? 1 : 0);
|
|
@@ -72,7 +69,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
72
69
|
</Text>
|
|
73
70
|
);
|
|
74
71
|
|
|
75
|
-
//
|
|
72
|
+
// the active label fades in over the inactive one
|
|
76
73
|
const text = (
|
|
77
74
|
<View style={!vertical && styles.labelBox}>
|
|
78
75
|
{renderText(inactiveTintColor)}
|
|
@@ -85,11 +82,6 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
85
82
|
</View>
|
|
86
83
|
);
|
|
87
84
|
|
|
88
|
-
let labelNode = text;
|
|
89
|
-
if (fixedFontScale) {
|
|
90
|
-
labelNode = <FixedScaleText>{text}</FixedScaleText>;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
85
|
return (
|
|
94
86
|
<>
|
|
95
87
|
<View style={vertical ? styles.railSlot : styles.slot}>
|
|
@@ -102,7 +94,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
102
94
|
renderIcon={icon}
|
|
103
95
|
/>
|
|
104
96
|
</View>
|
|
105
|
-
{!hideLabel &&
|
|
97
|
+
{!hideLabel && text}
|
|
106
98
|
</>
|
|
107
99
|
);
|
|
108
100
|
};
|
|
@@ -116,7 +108,7 @@ type CustomBottomTabItemProps = TabItemContentProps & {
|
|
|
116
108
|
};
|
|
117
109
|
|
|
118
110
|
/**
|
|
119
|
-
*
|
|
111
|
+
* horizontal navigation item (.Nav/itemTab): fills an even share of the container
|
|
120
112
|
*/
|
|
121
113
|
const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
|
|
122
114
|
onPress,
|
|
@@ -151,7 +143,7 @@ const styles = StyleSheet.create({
|
|
|
151
143
|
minWidth: 1,
|
|
152
144
|
alignItems: 'center',
|
|
153
145
|
justifyContent: 'center',
|
|
154
|
-
gap:
|
|
146
|
+
gap: TabBarMetrics.itemIconLabelGap,
|
|
155
147
|
},
|
|
156
148
|
slot: {
|
|
157
149
|
height: TabBarMetrics.indicatorHeight,
|
|
@@ -170,6 +162,11 @@ const styles = StyleSheet.create({
|
|
|
170
162
|
},
|
|
171
163
|
labelBox: {
|
|
172
164
|
alignSelf: 'stretch',
|
|
165
|
+
// fixed to the design line height so a capped-scale label overflows downward and the
|
|
166
|
+
// icon above it stays put
|
|
167
|
+
height: TabBarMetrics.labelHeight,
|
|
168
|
+
justifyContent: 'flex-start',
|
|
169
|
+
overflow: 'visible',
|
|
173
170
|
},
|
|
174
171
|
label: {
|
|
175
172
|
textAlign: 'center',
|
|
@@ -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,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: round button with its label, under the container
|
|
94
92
|
*/
|
|
95
93
|
const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
96
94
|
floatingButton,
|
|
@@ -116,7 +114,6 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
116
114
|
<CtaIcon icon={floatingButton.icon} />
|
|
117
115
|
</Animated.View>
|
|
118
116
|
<View style={styles.railCtaLabel}>
|
|
119
|
-
<FixedScaleText>
|
|
120
117
|
<Text
|
|
121
118
|
numberOfLines={1}
|
|
122
119
|
typography={'label_xs_medium'}
|
|
@@ -124,7 +121,6 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
124
121
|
>
|
|
125
122
|
{floatingButton.label}
|
|
126
123
|
</Text>
|
|
127
|
-
</FixedScaleText>
|
|
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
|
-
* side bar is short it can shrink to one button showing the current tab
|
|
141
|
-
* compressed"); tapping it expands the rail and compresses the toolbar
|
|
135
|
+
* vertical bar anchored to the bottom of the Duo side bar, center action below it.
|
|
136
|
+
* When the side bar is short, it can shrink to one button showing the current tab
|
|
137
|
+
* (HIG "Tab bar 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 { tabBarCompressed, expandTabBar, onTabSelected } =
|
|
142
|
+
useDuoBarCompression();
|
|
147
143
|
const compactLift = usePressLift();
|
|
148
144
|
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
149
145
|
const items = getTabItems(props);
|
|
@@ -153,7 +149,8 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
153
149
|
return null;
|
|
154
150
|
}
|
|
155
151
|
|
|
156
|
-
//
|
|
152
|
+
// the rail lines up with the header buttons in the side bar: centered on a 48 lane kept 12
|
|
153
|
+
// from the strip's inner edge
|
|
157
154
|
const columnStyle = [
|
|
158
155
|
styles.railColumn,
|
|
159
156
|
{
|
|
@@ -163,10 +160,9 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
163
160
|
},
|
|
164
161
|
sideBar.edge === 'right' ? styles.railColumnRight : styles.railColumnLeft,
|
|
165
162
|
];
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
163
|
+
const cta = floatingButton && (
|
|
164
|
+
<RailCenterAction floatingButton={floatingButton} />
|
|
165
|
+
);
|
|
170
166
|
|
|
171
167
|
if (!tabBarCompressed) {
|
|
172
168
|
return (
|
|
@@ -3,7 +3,11 @@ import React from 'react';
|
|
|
3
3
|
import { useWindowDimensions, View } from 'react-native';
|
|
4
4
|
import { useScreenLayout } from '../../Layout/utils';
|
|
5
5
|
import { FloatingButtonProps } from '../types';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getSystemIndicatorHeight,
|
|
8
|
+
hasCenterAction,
|
|
9
|
+
HorizontalTabBar,
|
|
10
|
+
} from './BottomTabBar';
|
|
7
11
|
import { getHorizontalBarWidth, TabBarMetrics } from './metrics';
|
|
8
12
|
import { getTabItems } from './tabItems';
|
|
9
13
|
import { DuoRailTabBar } from './DuoRailTabBar';
|
|
@@ -16,8 +20,8 @@ type DuoTabBarProps = BottomTabBarProps & {
|
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
|
-
*
|
|
20
|
-
* 0 when the Duo rail sits in the side bar
|
|
23
|
+
* space tab content leaves under the floating horizontal tab bar (phone and unfolded Duo);
|
|
24
|
+
* 0 when the Duo rail sits in the side bar
|
|
21
25
|
*/
|
|
22
26
|
const useDuoTabBarInset = () => {
|
|
23
27
|
const { isIPhoneDuo, sideBar, insets } = useScreenLayout();
|
|
@@ -28,12 +32,13 @@ const useDuoTabBarInset = () => {
|
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
|
-
*
|
|
35
|
+
* horizontal navigation bar of the unfolded display: 65% of the short side, centered
|
|
32
36
|
*/
|
|
33
37
|
const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
34
38
|
const { insets } = useScreenLayout();
|
|
35
39
|
const { width, height } = useWindowDimensions();
|
|
36
40
|
const { floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
41
|
+
const tabItems = getTabItems(props);
|
|
37
42
|
|
|
38
43
|
return (
|
|
39
44
|
<View
|
|
@@ -44,9 +49,15 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
44
49
|
]}
|
|
45
50
|
>
|
|
46
51
|
<HorizontalTabBar
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
items={tabItems}
|
|
53
|
+
width={getHorizontalBarWidth(
|
|
54
|
+
width,
|
|
55
|
+
height,
|
|
56
|
+
insets.left,
|
|
57
|
+
insets.right,
|
|
58
|
+
tabItems.length,
|
|
59
|
+
hasCenterAction(floatingButton, tabItems.length),
|
|
60
|
+
)}
|
|
50
61
|
floatingButton={floatingButton}
|
|
51
62
|
activeTintColor={activeTintColor}
|
|
52
63
|
inactiveTintColor={inactiveTintColor}
|
|
@@ -56,14 +67,15 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
56
67
|
};
|
|
57
68
|
|
|
58
69
|
/**
|
|
59
|
-
* iPhone Duo tab bar:
|
|
70
|
+
* iPhone Duo tab bar: rail in the side bar, or the horizontal bar when unfolded portrait
|
|
60
71
|
*/
|
|
61
72
|
const DuoTabBar: React.FC<DuoTabBarProps> = props => {
|
|
62
73
|
const { sideBar } = useScreenLayout();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
return sideBar ? (
|
|
75
|
+
<DuoRailTabBar {...props} />
|
|
76
|
+
) : (
|
|
77
|
+
<DuoFloatingTabBar {...props} />
|
|
78
|
+
);
|
|
67
79
|
};
|
|
68
80
|
|
|
69
81
|
export type { DuoTabBarProps };
|
|
@@ -28,8 +28,7 @@ type Props = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
* cap at 99+.
|
|
31
|
+
* badge value shown on a tab: undefined hides it, '' is a dot, numbers cap at 99+
|
|
33
32
|
*/
|
|
34
33
|
export const getTabBadge = (
|
|
35
34
|
badgeLabel?: string | number,
|
|
@@ -53,8 +52,8 @@ export const getTabBadge = (
|
|
|
53
52
|
};
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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
|
|
58
57
|
*/
|
|
59
58
|
export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
60
59
|
children,
|
|
@@ -73,8 +72,7 @@ export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
|
73
72
|
};
|
|
74
73
|
|
|
75
74
|
/**
|
|
76
|
-
*
|
|
77
|
-
* round numeric / text).
|
|
75
|
+
* numbers use the error color, text the warning color (Figma Badge round numeric / text)
|
|
78
76
|
*/
|
|
79
77
|
const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
80
78
|
label,
|
|
@@ -82,16 +80,14 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
82
80
|
}) => {
|
|
83
81
|
const { theme } = useContext(ApplicationContext);
|
|
84
82
|
const isNumber = /^\d+\+?$/.test(label);
|
|
85
|
-
let backgroundColor = theme.colors.warning.primary;
|
|
86
|
-
if (isNumber) {
|
|
87
|
-
backgroundColor = theme.colors.error.primary;
|
|
88
|
-
}
|
|
89
83
|
const badge = (
|
|
90
84
|
<View
|
|
91
85
|
style={[
|
|
92
86
|
styles.badge,
|
|
93
87
|
{
|
|
94
|
-
backgroundColor
|
|
88
|
+
backgroundColor: isNumber
|
|
89
|
+
? theme.colors.error.primary
|
|
90
|
+
: theme.colors.warning.primary,
|
|
95
91
|
borderColor: theme.colors.background.surface,
|
|
96
92
|
},
|
|
97
93
|
]}
|
|
@@ -114,8 +110,7 @@ const TabBadge: React.FC<{ label: string; vertical?: boolean }> = ({
|
|
|
114
110
|
};
|
|
115
111
|
|
|
116
112
|
/**
|
|
117
|
-
*
|
|
118
|
-
* focus.
|
|
113
|
+
* 24 icon; active and inactive icons are stacked and cross-faded on focus
|
|
119
114
|
*/
|
|
120
115
|
export function TabBarIcon({
|
|
121
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';
|
|
@@ -24,8 +23,6 @@ import TabScreen from './TabScreen';
|
|
|
24
23
|
import { ApplicationContext } from '../../Context';
|
|
25
24
|
import { useScreenLayout } from '../../Layout';
|
|
26
25
|
|
|
27
|
-
type SceneInterpolator = BottomTabNavigationOptions['sceneStyleInterpolator'];
|
|
28
|
-
|
|
29
26
|
const Tab = createBottomTabNavigator();
|
|
30
27
|
|
|
31
28
|
// the tab screens slide in index order over 280ms, CSS `ease`
|
|
@@ -35,11 +32,12 @@ const TAB_SWITCH_SPEC = {
|
|
|
35
32
|
};
|
|
36
33
|
|
|
37
34
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
|
40
37
|
*/
|
|
41
|
-
const getTabSlide =
|
|
42
|
-
|
|
38
|
+
const getTabSlide =
|
|
39
|
+
(width: number): BottomTabNavigationOptions['sceneStyleInterpolator'] =>
|
|
40
|
+
({ current }) => ({
|
|
43
41
|
sceneStyle: {
|
|
44
42
|
transform: [
|
|
45
43
|
{
|
|
@@ -51,7 +49,6 @@ const getTabSlide = (width: number): SceneInterpolator => {
|
|
|
51
49
|
],
|
|
52
50
|
},
|
|
53
51
|
});
|
|
54
|
-
};
|
|
55
52
|
|
|
56
53
|
/**
|
|
57
54
|
* App tab navigator: the floating bar on phones, the rail or floating bar on
|
|
@@ -70,11 +67,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
70
67
|
const { isIPhoneDuo } = useScreenLayout();
|
|
71
68
|
const { width: windowWidth } = useWindowDimensions();
|
|
72
69
|
const barInset = useDuoTabBarInset();
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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;
|
|
78
75
|
const initialIndex = tabs.findIndex(i => i.name === initialRouteName);
|
|
79
76
|
const activeIndex = React.useRef(initialIndex > 0 ? initialIndex : 0);
|
|
80
77
|
|
|
@@ -86,15 +83,15 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
86
83
|
});
|
|
87
84
|
}, [navigation]);
|
|
88
85
|
|
|
89
|
-
// 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.
|
|
90
88
|
useFocusEffect(
|
|
91
89
|
React.useCallback(() => {
|
|
92
90
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(
|
|
93
91
|
activeIndex.current !== 0,
|
|
94
92
|
);
|
|
95
|
-
return () =>
|
|
93
|
+
return () =>
|
|
96
94
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(false);
|
|
97
|
-
};
|
|
98
95
|
}, []),
|
|
99
96
|
);
|
|
100
97
|
|
|
@@ -131,27 +128,6 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
131
128
|
},
|
|
132
129
|
};
|
|
133
130
|
|
|
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
|
-
|
|
155
131
|
return (
|
|
156
132
|
<DuoBarCompressionProvider
|
|
157
133
|
behavior={compressionBehavior}
|
|
@@ -169,7 +145,23 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
169
145
|
transitionSpec: TAB_SWITCH_SPEC,
|
|
170
146
|
sceneStyleInterpolator: getTabSlide(windowWidth),
|
|
171
147
|
}}
|
|
172
|
-
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
|
+
}
|
|
173
165
|
initialRouteName={initialRouteName}
|
|
174
166
|
>
|
|
175
167
|
{tabs.map((item, index) => (
|