@momo-kits/foundation 0.169.7-beta.1 → 0.169.7-beta.10
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.styles.ts +1 -18
- package/Application/BottomTab/BottomTabBar.tsx +72 -57
- package/Application/BottomTab/BottomTabInset.tsx +4 -8
- package/Application/BottomTab/CurvedContainer.tsx +14 -22
- package/Application/BottomTab/CustomBottomTabItem.tsx +15 -15
- package/Application/BottomTab/DuoBarCompression.tsx +19 -13
- package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
- package/Application/BottomTab/DuoTabBar.tsx +13 -23
- 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 +26 -46
- 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 -13
- package/Application/navigationOptions.tsx +17 -21
- package/Application/types.tabs.ts +1 -0
- 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 +13 -16
- 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 +13 -17
- package/Layout/TrackingScope.tsx +0 -1
- package/Layout/constants.ts +19 -0
- 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 +41 -3
- package/Layout/useGridSystem.ts +49 -0
- package/Layout/utils.ts +37 -38
- 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 +4 -20
- package/Title/index.tsx +32 -41
- package/ViewBoundary/index.tsx +2 -4
- package/index.ts +6 -0
- package/package.json +1 -1
|
@@ -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
|
|
6
|
+
import { NOTCH_CURVES, NOTCH_HALF_WIDTH } from './metrics';
|
|
7
7
|
|
|
8
8
|
type Props = {
|
|
9
9
|
width: number;
|
|
@@ -15,18 +15,16 @@ 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
|
-
|
|
20
|
-
* half the CSS blur radius; the spread shrinks the shape the key shadow is cast from.
|
|
21
|
-
*/
|
|
18
|
+
// Figma Shadow/floating: 12% blur 24, plus 4% blur 32 spread -16 offset 12; svg blur takes
|
|
19
|
+
// half the CSS blur radius, the spread shrinks the shape the key shadow is cast from
|
|
22
20
|
const AMBIENT_BLUR = 12;
|
|
23
21
|
const KEY_BLUR = 16;
|
|
24
22
|
const KEY_SPREAD = 16;
|
|
25
23
|
const KEY_OFFSET_Y = 12;
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* rounded container path placed at (`x`, `y`) on the canvas, with the notch dipped into the
|
|
27
|
+
* top edge around `notchCenterX`
|
|
30
28
|
*/
|
|
31
29
|
const getContainerPath = (
|
|
32
30
|
width: number,
|
|
@@ -45,10 +43,11 @@ const getContainerPath = (
|
|
|
45
43
|
if (notchCenterX !== undefined) {
|
|
46
44
|
const cx = x + notchCenterX;
|
|
47
45
|
const point = ([dx, dy]: [number, number]) => `${cx + dx} ${top + dy}`;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
notch =
|
|
47
|
+
` L ${cx - NOTCH_HALF_WIDTH} ${top}` +
|
|
48
|
+
NOTCH_CURVES.map(
|
|
49
|
+
([c1, c2, end]) => ` C ${point(c1)} ${point(c2)} ${point(end)}`,
|
|
50
|
+
).join('');
|
|
52
51
|
}
|
|
53
52
|
return (
|
|
54
53
|
`M ${left + r} ${top}` +
|
|
@@ -65,8 +64,8 @@ const getContainerPath = (
|
|
|
65
64
|
};
|
|
66
65
|
|
|
67
66
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* navigation bar container: surface fill with the floating shadow, drawn as a shape so
|
|
68
|
+
* the shadow follows the notch
|
|
70
69
|
*/
|
|
71
70
|
const CurvedContainer: React.FC<Props> = ({
|
|
72
71
|
width,
|
|
@@ -88,7 +87,6 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
88
87
|
SHADOW_PADDING,
|
|
89
88
|
notchCenterX,
|
|
90
89
|
);
|
|
91
|
-
const hasKeyShadow = width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD;
|
|
92
90
|
|
|
93
91
|
return (
|
|
94
92
|
<Svg
|
|
@@ -125,7 +123,7 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
125
123
|
fillOpacity={0.12}
|
|
126
124
|
filter={'url(#ambientShadow)'}
|
|
127
125
|
/>
|
|
128
|
-
{
|
|
126
|
+
{width > 2 * KEY_SPREAD && height > 2 * KEY_SPREAD && (
|
|
129
127
|
<Path
|
|
130
128
|
d={getContainerPath(
|
|
131
129
|
width - 2 * KEY_SPREAD,
|
|
@@ -139,13 +137,7 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
139
137
|
filter={'url(#keyShadow)'}
|
|
140
138
|
/>
|
|
141
139
|
)}
|
|
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
|
-
/>
|
|
140
|
+
<Path d={path} fill={theme.colors.background.surface} />
|
|
149
141
|
</Svg>
|
|
150
142
|
);
|
|
151
143
|
};
|
|
@@ -10,10 +10,10 @@ import Animated, {
|
|
|
10
10
|
useSharedValue,
|
|
11
11
|
withTiming,
|
|
12
12
|
} from 'react-native-reanimated';
|
|
13
|
-
import { TabBarIcon } from './TabBarIcon';
|
|
13
|
+
import { FixedScaleText, TabBarIcon } from './TabBarIcon';
|
|
14
14
|
import { Text } from '../../Text';
|
|
15
15
|
import { MiniAppContext } from '../../Context';
|
|
16
|
-
import { Colors } from '../../Consts';
|
|
16
|
+
import { Colors, Spacing } from '../../Consts';
|
|
17
17
|
import { TabBarMetrics, TabBarMotion } from './metrics';
|
|
18
18
|
|
|
19
19
|
type IconRenderer = (props: {
|
|
@@ -31,12 +31,14 @@ 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;
|
|
34
36
|
hideLabel?: boolean;
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* icon in its indicator slot, label under it. The bar slides one shared active indicator
|
|
41
|
+
* behind the slots; the icon and label tint cross-fade on focus
|
|
40
42
|
*/
|
|
41
43
|
const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
42
44
|
label,
|
|
@@ -46,6 +48,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
46
48
|
icon,
|
|
47
49
|
badge,
|
|
48
50
|
vertical,
|
|
51
|
+
fixedFontScale,
|
|
49
52
|
hideLabel,
|
|
50
53
|
}) => {
|
|
51
54
|
const progress = useSharedValue(focused ? 1 : 0);
|
|
@@ -69,9 +72,9 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
69
72
|
</Text>
|
|
70
73
|
);
|
|
71
74
|
|
|
72
|
-
//
|
|
73
|
-
const
|
|
74
|
-
<View style={vertical
|
|
75
|
+
// the active label fades in over the inactive one
|
|
76
|
+
const text = (
|
|
77
|
+
<View style={!vertical && styles.labelBox}>
|
|
75
78
|
{renderText(inactiveTintColor)}
|
|
76
79
|
<Animated.View
|
|
77
80
|
pointerEvents={'none'}
|
|
@@ -94,7 +97,8 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
94
97
|
renderIcon={icon}
|
|
95
98
|
/>
|
|
96
99
|
</View>
|
|
97
|
-
{!hideLabel &&
|
|
100
|
+
{!hideLabel &&
|
|
101
|
+
(fixedFontScale ? <FixedScaleText>{text}</FixedScaleText> : text)}
|
|
98
102
|
</>
|
|
99
103
|
);
|
|
100
104
|
};
|
|
@@ -108,7 +112,7 @@ type CustomBottomTabItemProps = TabItemContentProps & {
|
|
|
108
112
|
};
|
|
109
113
|
|
|
110
114
|
/**
|
|
111
|
-
*
|
|
115
|
+
* horizontal navigation item (.Nav/itemTab): fills an even share of the container
|
|
112
116
|
*/
|
|
113
117
|
const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
|
|
114
118
|
onPress,
|
|
@@ -143,7 +147,7 @@ const styles = StyleSheet.create({
|
|
|
143
147
|
minWidth: 1,
|
|
144
148
|
alignItems: 'center',
|
|
145
149
|
justifyContent: 'center',
|
|
146
|
-
gap:
|
|
150
|
+
gap: Spacing.XXS,
|
|
147
151
|
},
|
|
148
152
|
slot: {
|
|
149
153
|
height: TabBarMetrics.indicatorHeight,
|
|
@@ -160,13 +164,9 @@ const styles = StyleSheet.create({
|
|
|
160
164
|
paddingVertical: TabBarMetrics.railIndicatorPaddingVertical,
|
|
161
165
|
paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
|
|
162
166
|
},
|
|
163
|
-
|
|
164
|
-
height: TabBarMetrics.labelHeight,
|
|
167
|
+
labelBox: {
|
|
165
168
|
alignSelf: 'stretch',
|
|
166
169
|
},
|
|
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;
|
|
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 (
|
|
@@ -3,11 +3,7 @@ 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 {
|
|
7
|
-
getSystemIndicatorHeight,
|
|
8
|
-
hasCenterAction,
|
|
9
|
-
HorizontalTabBar,
|
|
10
|
-
} from './BottomTabBar';
|
|
6
|
+
import { getSystemIndicatorHeight, HorizontalTabBar } from './BottomTabBar';
|
|
11
7
|
import { getHorizontalBarWidth, TabBarMetrics } from './metrics';
|
|
12
8
|
import { getTabItems } from './tabItems';
|
|
13
9
|
import { DuoRailTabBar } from './DuoRailTabBar';
|
|
@@ -20,8 +16,8 @@ type DuoTabBarProps = BottomTabBarProps & {
|
|
|
20
16
|
};
|
|
21
17
|
|
|
22
18
|
/**
|
|
23
|
-
*
|
|
24
|
-
* 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
|
|
25
21
|
*/
|
|
26
22
|
const useDuoTabBarInset = () => {
|
|
27
23
|
const { isIPhoneDuo, sideBar, insets } = useScreenLayout();
|
|
@@ -32,13 +28,12 @@ const useDuoTabBarInset = () => {
|
|
|
32
28
|
};
|
|
33
29
|
|
|
34
30
|
/**
|
|
35
|
-
*
|
|
31
|
+
* horizontal navigation bar of the unfolded display: 65% of the short side, centered
|
|
36
32
|
*/
|
|
37
33
|
const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
38
34
|
const { insets } = useScreenLayout();
|
|
39
35
|
const { width, height } = useWindowDimensions();
|
|
40
36
|
const { floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
41
|
-
const tabItems = getTabItems(props);
|
|
42
37
|
|
|
43
38
|
return (
|
|
44
39
|
<View
|
|
@@ -49,15 +44,9 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
49
44
|
]}
|
|
50
45
|
>
|
|
51
46
|
<HorizontalTabBar
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
height,
|
|
56
|
-
insets.left,
|
|
57
|
-
insets.right,
|
|
58
|
-
tabItems.length,
|
|
59
|
-
hasCenterAction(floatingButton, tabItems.length),
|
|
60
|
-
)}
|
|
47
|
+
fixedFontScale
|
|
48
|
+
items={getTabItems(props)}
|
|
49
|
+
width={getHorizontalBarWidth(width, height, insets.left, insets.right)}
|
|
61
50
|
floatingButton={floatingButton}
|
|
62
51
|
activeTintColor={activeTintColor}
|
|
63
52
|
inactiveTintColor={inactiveTintColor}
|
|
@@ -67,14 +56,15 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
67
56
|
};
|
|
68
57
|
|
|
69
58
|
/**
|
|
70
|
-
* iPhone Duo tab bar:
|
|
59
|
+
* iPhone Duo tab bar: rail in the side bar, or the horizontal bar when unfolded portrait
|
|
71
60
|
*/
|
|
72
61
|
const DuoTabBar: React.FC<DuoTabBarProps> = props => {
|
|
73
62
|
const { sideBar } = useScreenLayout();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
return sideBar ? (
|
|
64
|
+
<DuoRailTabBar {...props} />
|
|
65
|
+
) : (
|
|
66
|
+
<DuoFloatingTabBar {...props} />
|
|
67
|
+
);
|
|
78
68
|
};
|
|
79
69
|
|
|
80
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`,
|