@momo-kits/foundation 0.169.7-beta.4 → 0.169.7-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Application/BottomSheet.hooks.ts +8 -8
- package/Application/BottomSheet.parts.tsx +44 -34
- package/Application/BottomSheet.tsx +40 -37
- package/Application/BottomTab/ActiveIndicator.tsx +20 -15
- package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
- package/Application/BottomTab/BottomTabBar.tsx +57 -72
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +22 -14
- package/Application/BottomTab/CustomBottomTabItem.tsx +15 -15
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
- package/Application/BottomTab/DuoTabBar.tsx +23 -13
- package/Application/BottomTab/TabBarIcon.tsx +15 -29
- package/Application/BottomTab/TabScreen.tsx +1 -5
- package/Application/BottomTab/index.tsx +33 -30
- package/Application/BottomTab/metrics.ts +46 -26
- package/Application/BottomTab/tabItems.ts +1 -1
- package/Application/BottomTab/usePressLift.ts +2 -2
- package/Application/Components/BackgroundImageView.tsx +4 -0
- package/Application/Components/DuoGlassBackground.tsx +5 -4
- package/Application/Components/HeaderAnimated.tsx +4 -0
- package/Application/Components/HeaderBackground.tsx +4 -0
- package/Application/Components/HeaderExtendHeader.tsx +11 -10
- package/Application/Components/HeaderLeft.tsx +2 -1
- package/Application/Components/HeaderRight.tsx +2 -1
- package/Application/Components/HeaderTitle.shared.ts +11 -9
- package/Application/Components/HeaderTitle.tsx +4 -3
- package/Application/Components/HeaderToolkitAction.tsx +17 -20
- package/Application/Components/NavigationButton.tsx +2 -1
- package/Application/Components/SearchHeader.tsx +17 -10
- package/Application/Components/TitleJourney.tsx +2 -2
- package/Application/Components/TitleLocation.tsx +2 -2
- package/Application/Components/TitleUser.tsx +2 -2
- package/Application/Components/useToolkitActions.tsx +17 -32
- package/Application/Localize.ts +7 -0
- package/Application/ModalScreen.tsx +10 -10
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -18
- package/Application/Navigator.ts +20 -48
- package/Application/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -33
- package/Application/StackScreen.tsx +10 -54
- package/Application/TooltipPortal.tsx +12 -5
- package/Application/WidgetContainer.tsx +10 -4
- package/Application/modalLayout.ts +13 -14
- package/Application/navigationOptions.tsx +21 -17
- package/Application/types.tabs.ts +0 -1
- package/Badge/Badge.tsx +5 -0
- package/Badge/BadgeDot.tsx +1 -0
- package/Badge/BadgeDotAnimation.tsx +15 -7
- package/Badge/BadgeRibbon.tsx +46 -38
- package/Badge/Shape.tsx +2 -0
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +5 -16
- package/Button/utils.ts +9 -15
- package/CheckBox/index.tsx +4 -0
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +5 -1
- package/Divider/DashDivider.tsx +1 -0
- package/Divider/index.tsx +4 -0
- package/FoundationList/index.tsx +4 -0
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +4 -12
- package/Image/index.tsx +9 -7
- package/Input/Input.tsx +8 -9
- package/Input/InputDropDown.tsx +10 -12
- package/Input/InputMoney.tsx +12 -8
- package/Input/InputMoneyView.tsx +14 -7
- package/Input/InputOTP.tsx +35 -33
- package/Input/InputPhoneNumber.tsx +8 -9
- package/Input/InputSearch.tsx +4 -6
- package/Input/InputTextArea.tsx +4 -0
- package/Input/SearchTextTyping.tsx +4 -0
- package/Input/SystemTextInput.tsx +7 -2
- package/Input/TextTyping.tsx +4 -0
- package/Input/common.tsx +26 -12
- package/Input/index.tsx +17 -20
- package/Input/moneyUtils.ts +10 -4
- package/Input/styles.ts +0 -5
- package/Input/utils.ts +14 -12
- package/Layout/Card.tsx +7 -4
- package/Layout/FloatingButton.tsx +4 -0
- package/Layout/Item.tsx +6 -2
- package/Layout/ItemList.tsx +9 -7
- package/Layout/ItemSectionList.tsx +2 -1
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +56 -58
- package/Layout/Section.tsx +8 -5
- package/Layout/TrackingScope.tsx +1 -0
- package/Layout/screenContent.tsx +4 -2
- package/Layout/screenFooter.tsx +28 -18
- package/Layout/screenHeader.tsx +34 -31
- package/Layout/screenQuickReturn.tsx +17 -20
- package/Layout/screenScroll.ts +4 -7
- package/Layout/screenTypes.ts +3 -2
- package/Layout/types.ts +2 -4
- package/Layout/utils.ts +31 -37
- package/Loader/DotLoader.tsx +4 -0
- package/Loader/Loader.tsx +4 -0
- package/Loader/ProgressBar.tsx +1 -0
- package/Loader/Spinner.tsx +4 -0
- package/Loader/utils.ts +11 -7
- package/Pagination/Dot.tsx +4 -0
- package/Pagination/PaginationDot.tsx +1 -0
- package/Pagination/PaginationNumber.tsx +5 -3
- package/Pagination/PaginationScroll.tsx +5 -0
- package/Pagination/PaginationWhiteDot.tsx +1 -0
- package/Pagination/index.tsx +4 -0
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +44 -48
- package/Popup/PopupPromotion.tsx +4 -7
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +4 -0
- package/Radio/index.tsx +4 -0
- package/Skeleton/index.tsx +8 -0
- package/Switch/index.tsx +1 -0
- package/Tag/index.tsx +5 -1
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +14 -3
- package/Text/styles.ts +1 -1
- package/Text/types.ts +1 -6
- package/Text/utils.ts +20 -4
- package/Title/index.tsx +41 -32
- package/ViewBoundary/index.tsx +4 -2
- package/index.ts +0 -6
- package/package.json +1 -1
- package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
- package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/Authorship/utils.ts +0 -137
- package/Application/KitAdoption/Slots/utils.ts +0 -68
- package/Application/KitAdoption/constants.ts +0 -120
- package/Application/KitAdoption/index.ts +0 -12
- package/Application/KitAdoption/styles.ts +0 -55
- package/Application/KitAdoption/types.ts +0 -61
- package/Application/KitAdoption/utils.ts +0 -376
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
- package/Application/__tests__/KitAdoption.test.ts +0 -283
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
- package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
- package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
- package/Application/__tests__/modalLayout.test.ts +0 -128
|
@@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native';
|
|
|
3
3
|
import Svg, { Defs, FeGaussianBlur, Filter, Path } from 'react-native-svg';
|
|
4
4
|
import { Colors } from '../../Consts';
|
|
5
5
|
import { ApplicationContext } from '../../Context';
|
|
6
|
-
import { NOTCH_CURVES, NOTCH_HALF_WIDTH } from './metrics';
|
|
6
|
+
import { NOTCH_CURVES, NOTCH_HALF_WIDTH, TabBarMetrics } from './metrics';
|
|
7
7
|
|
|
8
8
|
type Props = {
|
|
9
9
|
width: number;
|
|
@@ -15,16 +15,18 @@ type Props = {
|
|
|
15
15
|
|
|
16
16
|
// room for the floating shadow around the shape: the key shadow reaches 12 + 3 × 16 below it
|
|
17
17
|
const SHADOW_PADDING = 64;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Figma Shadow/floating: 12% blur 24, plus 4% blur 32 spread -16 offset 12. SVG blur takes
|
|
20
|
+
* half the CSS blur radius; the spread shrinks the shape the key shadow is cast from.
|
|
21
|
+
*/
|
|
20
22
|
const AMBIENT_BLUR = 12;
|
|
21
23
|
const KEY_BLUR = 16;
|
|
22
24
|
const KEY_SPREAD = 16;
|
|
23
25
|
const KEY_OFFSET_Y = 12;
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
|
-
* rounded container
|
|
27
|
-
* top edge around `notchCenterX
|
|
28
|
+
* SVG path of the rounded container placed at (`x`, `y`) on the canvas, with the notch
|
|
29
|
+
* dipped into the top edge around `notchCenterX`.
|
|
28
30
|
*/
|
|
29
31
|
const getContainerPath = (
|
|
30
32
|
width: number,
|
|
@@ -43,11 +45,10 @@ const getContainerPath = (
|
|
|
43
45
|
if (notchCenterX !== undefined) {
|
|
44
46
|
const cx = x + notchCenterX;
|
|
45
47
|
const point = ([dx, dy]: [number, number]) => `${cx + dx} ${top + dy}`;
|
|
46
|
-
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
).join('');
|
|
48
|
+
const curves = NOTCH_CURVES.map(([c1, c2, end]) => {
|
|
49
|
+
return ` C ${point(c1)} ${point(c2)} ${point(end)}`;
|
|
50
|
+
});
|
|
51
|
+
notch = ` L ${cx - NOTCH_HALF_WIDTH} ${top}${curves.join('')}`;
|
|
51
52
|
}
|
|
52
53
|
return (
|
|
53
54
|
`M ${left + r} ${top}` +
|
|
@@ -64,8 +65,8 @@ const getContainerPath = (
|
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
-
* the shadow follows the notch
|
|
68
|
+
* Navigation bar container: surface fill with the floating shadow, drawn as an SVG shape
|
|
69
|
+
* so the shadow follows the notch.
|
|
69
70
|
*/
|
|
70
71
|
const CurvedContainer: React.FC<Props> = ({
|
|
71
72
|
width,
|
|
@@ -87,6 +88,7 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
87
88
|
SHADOW_PADDING,
|
|
88
89
|
notchCenterX,
|
|
89
90
|
);
|
|
91
|
+
const hasKeyShadow = width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD;
|
|
90
92
|
|
|
91
93
|
return (
|
|
92
94
|
<Svg
|
|
@@ -123,7 +125,7 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
123
125
|
fillOpacity={0.12}
|
|
124
126
|
filter={'url(#ambientShadow)'}
|
|
125
127
|
/>
|
|
126
|
-
{
|
|
128
|
+
{hasKeyShadow && (
|
|
127
129
|
<Path
|
|
128
130
|
d={getContainerPath(
|
|
129
131
|
width - 2 * KEY_SPREAD,
|
|
@@ -137,7 +139,13 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
137
139
|
filter={'url(#keyShadow)'}
|
|
138
140
|
/>
|
|
139
141
|
)}
|
|
140
|
-
<Path
|
|
142
|
+
<Path
|
|
143
|
+
d={path}
|
|
144
|
+
fill={theme.colors.background.surface}
|
|
145
|
+
fillOpacity={TabBarMetrics.backgroundOpacity}
|
|
146
|
+
stroke={theme.colors.border.default}
|
|
147
|
+
strokeWidth={1}
|
|
148
|
+
/>
|
|
141
149
|
</Svg>
|
|
142
150
|
);
|
|
143
151
|
};
|
|
@@ -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
|
-
* icon in its indicator slot, label
|
|
41
|
-
*
|
|
38
|
+
* Tab item body: the icon in its indicator slot, the label in a fixed slot under it so a
|
|
39
|
+
* scaled label grows down. The bar slides one shared active indicator behind the icon slots.
|
|
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,9 +69,9 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
72
69
|
</Text>
|
|
73
70
|
);
|
|
74
71
|
|
|
75
|
-
//
|
|
76
|
-
const
|
|
77
|
-
<View style={
|
|
72
|
+
// Active label fades in over the inactive one; a scaled label overflows down.
|
|
73
|
+
const labelNode = (
|
|
74
|
+
<View style={vertical ? styles.railLabelSlot : styles.labelSlot}>
|
|
78
75
|
{renderText(inactiveTintColor)}
|
|
79
76
|
<Animated.View
|
|
80
77
|
pointerEvents={'none'}
|
|
@@ -97,8 +94,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
97
94
|
renderIcon={icon}
|
|
98
95
|
/>
|
|
99
96
|
</View>
|
|
100
|
-
{!hideLabel &&
|
|
101
|
-
(fixedFontScale ? <FixedScaleText>{text}</FixedScaleText> : text)}
|
|
97
|
+
{!hideLabel && labelNode}
|
|
102
98
|
</>
|
|
103
99
|
);
|
|
104
100
|
};
|
|
@@ -112,7 +108,7 @@ type CustomBottomTabItemProps = TabItemContentProps & {
|
|
|
112
108
|
};
|
|
113
109
|
|
|
114
110
|
/**
|
|
115
|
-
*
|
|
111
|
+
* Horizontal navigation item (.Nav/itemTab), filling an even share of the container.
|
|
116
112
|
*/
|
|
117
113
|
const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
|
|
118
114
|
onPress,
|
|
@@ -147,7 +143,7 @@ const styles = StyleSheet.create({
|
|
|
147
143
|
minWidth: 1,
|
|
148
144
|
alignItems: 'center',
|
|
149
145
|
justifyContent: 'center',
|
|
150
|
-
gap:
|
|
146
|
+
gap: TabBarMetrics.itemIconLabelGap,
|
|
151
147
|
},
|
|
152
148
|
slot: {
|
|
153
149
|
height: TabBarMetrics.indicatorHeight,
|
|
@@ -164,9 +160,13 @@ const styles = StyleSheet.create({
|
|
|
164
160
|
paddingVertical: TabBarMetrics.railIndicatorPaddingVertical,
|
|
165
161
|
paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
|
|
166
162
|
},
|
|
167
|
-
|
|
163
|
+
labelSlot: {
|
|
164
|
+
height: TabBarMetrics.labelHeight,
|
|
168
165
|
alignSelf: 'stretch',
|
|
169
166
|
},
|
|
167
|
+
railLabelSlot: {
|
|
168
|
+
height: TabBarMetrics.railLabelHeight,
|
|
169
|
+
},
|
|
170
170
|
label: {
|
|
171
171
|
textAlign: 'center',
|
|
172
172
|
},
|
|
@@ -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 (
|
|
@@ -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,15 +67,14 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
56
67
|
};
|
|
57
68
|
|
|
58
69
|
/**
|
|
59
|
-
* iPhone Duo tab bar: rail in the side bar, or the horizontal bar when unfolded portrait
|
|
70
|
+
* iPhone Duo tab bar: the 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
|
-
<DuoRailTabBar {...props}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
74
|
+
if (sideBar) {
|
|
75
|
+
return <DuoRailTabBar {...props} />;
|
|
76
|
+
}
|
|
77
|
+
return <DuoFloatingTabBar {...props} />;
|
|
68
78
|
};
|
|
69
79
|
|
|
70
80
|
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`,
|