@momo-kits/foundation 0.169.1 → 0.169.2
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 +17 -37
- package/Application/BottomSheet.parts.tsx +81 -165
- package/Application/BottomSheet.styles.ts +7 -23
- package/Application/BottomSheet.tsx +15 -67
- package/Application/BottomTab/ActiveIndicator.tsx +90 -0
- package/Application/BottomTab/BottomTabBar.styles.ts +48 -43
- package/Application/BottomTab/BottomTabBar.tsx +229 -223
- package/Application/BottomTab/BottomTabInset.tsx +14 -0
- package/Application/BottomTab/CurvedContainer.tsx +153 -0
- package/Application/BottomTab/CustomBottomTabItem.tsx +132 -108
- package/Application/BottomTab/DuoBarCompression.tsx +4 -12
- package/Application/BottomTab/DuoRailTabBar.tsx +174 -96
- package/Application/BottomTab/DuoTabBar.styles.ts +42 -84
- package/Application/BottomTab/DuoTabBar.tsx +35 -115
- package/Application/BottomTab/TabBarIcon.tsx +160 -72
- package/Application/BottomTab/TabScreen.tsx +48 -61
- package/Application/BottomTab/index.tsx +85 -125
- package/Application/BottomTab/metrics.ts +164 -0
- package/Application/BottomTab/tabItems.ts +46 -0
- package/Application/BottomTab/usePressLift.ts +32 -0
- package/Application/ModalIconButton.tsx +66 -0
- package/Application/ModalScreen.tsx +32 -15
- package/Application/__tests__/modalLayout.test.ts +128 -0
- package/Application/index.ts +10 -0
- package/Application/modalLayout.ts +177 -0
- package/Application/types.tabs.ts +5 -0
- package/Application/utils.ts +0 -36
- package/Layout/ReservedRegions.tsx +21 -4
- package/Layout/Screen.tsx +11 -2
- package/Layout/index.ts +2 -0
- package/Popup/PopupNotify.tsx +38 -68
- package/Popup/PopupPromotion.tsx +5 -4
- package/Popup/utils.ts +1 -26
- package/package.json +2 -1
- package/Application/BottomTab/DuoTabBar.parts.tsx +0 -202
- package/Application/BottomTab/badge.ts +0 -41
- package/Application/BottomTab/hooks.ts +0 -358
- package/Application/BottomTab/index.styles.ts +0 -30
|
@@ -1,136 +1,142 @@
|
|
|
1
|
-
import React, { useContext } from 'react';
|
|
2
|
-
import { TouchableOpacity, StyleSheet } from 'react-native';
|
|
1
|
+
import React, { useContext, useEffect } from 'react';
|
|
3
2
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
LayoutChangeEvent,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
View,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import Animated, {
|
|
9
|
+
useAnimatedStyle,
|
|
10
|
+
useSharedValue,
|
|
11
|
+
withTiming,
|
|
12
|
+
} from 'react-native-reanimated';
|
|
13
|
+
import { FixedScaleText, TabBarIcon } from './TabBarIcon';
|
|
9
14
|
import { Text } from '../../Text';
|
|
10
15
|
import { MiniAppContext } from '../../Context';
|
|
11
|
-
import { Colors } from '../../Consts';
|
|
16
|
+
import { Colors, Spacing } from '../../Consts';
|
|
17
|
+
import { TabBarMetrics, TabBarMotion } from './metrics';
|
|
12
18
|
|
|
13
|
-
type
|
|
19
|
+
type IconRenderer = (props: {
|
|
20
|
+
focused: boolean;
|
|
21
|
+
color: string;
|
|
22
|
+
size: number;
|
|
23
|
+
}) => React.ReactNode;
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
type TabItemContentProps = {
|
|
26
|
+
label: string;
|
|
17
27
|
focused: boolean;
|
|
18
|
-
label: LabelType;
|
|
19
|
-
onPress: () => void;
|
|
20
|
-
onLongPress: () => void;
|
|
21
28
|
activeTintColor: string;
|
|
22
29
|
inactiveTintColor: string;
|
|
23
|
-
|
|
24
|
-
inactiveBackgroundColor?: string;
|
|
25
|
-
icon: any;
|
|
30
|
+
icon?: IconRenderer;
|
|
26
31
|
badge?: string | number;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
button?: (props: BottomTabBarButtonProps) => React.ReactNode;
|
|
34
|
-
/**
|
|
35
|
-
* The accessibility label for the tab.
|
|
36
|
-
*/
|
|
37
|
-
accessibilityLabel?: string;
|
|
38
|
-
/**
|
|
39
|
-
* An unique ID for testing for the tab.
|
|
40
|
-
*/
|
|
41
|
-
testID?: string;
|
|
42
|
-
}
|
|
32
|
+
// rail item (.Nav/Duo/Item) instead of the horizontal one (.Nav/itemTab)
|
|
33
|
+
vertical?: boolean;
|
|
34
|
+
// keep the label at its design size, the Duo displays otherwise scale it up
|
|
35
|
+
fixedFontScale?: boolean;
|
|
36
|
+
hideLabel?: boolean;
|
|
37
|
+
};
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
42
|
+
*/
|
|
43
|
+
const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
47
44
|
label,
|
|
48
|
-
|
|
49
|
-
onLongPress,
|
|
45
|
+
focused,
|
|
50
46
|
activeTintColor,
|
|
51
47
|
inactiveTintColor,
|
|
52
|
-
activeBackgroundColor,
|
|
53
|
-
inactiveBackgroundColor,
|
|
54
48
|
icon,
|
|
55
49
|
badge,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
style,
|
|
60
|
-
badgeStyle,
|
|
61
|
-
allowFontScaling,
|
|
50
|
+
vertical,
|
|
51
|
+
fixedFontScale,
|
|
52
|
+
hideLabel,
|
|
62
53
|
}) => {
|
|
63
|
-
const
|
|
64
|
-
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
54
|
+
const progress = useSharedValue(focused ? 1 : 0);
|
|
65
55
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
progress.value = withTiming(focused ? 1 : 0, TabBarMotion.tint);
|
|
58
|
+
}, [focused, progress]);
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
const activeLabelStyle = useAnimatedStyle(() => ({
|
|
61
|
+
opacity: progress.value,
|
|
62
|
+
}));
|
|
73
63
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
renderIcon={icon}
|
|
85
|
-
style={iconStyle}
|
|
86
|
-
/>
|
|
87
|
-
);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const renderLabel = ({ focused: isFocused }: { focused: boolean }) => {
|
|
91
|
-
if (showLabel === false) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
64
|
+
const renderText = (color: string) => (
|
|
65
|
+
<Text
|
|
66
|
+
numberOfLines={1}
|
|
67
|
+
typography={vertical ? 'label_xs_medium' : 'label_s_medium'}
|
|
68
|
+
color={color}
|
|
69
|
+
style={!vertical && styles.label}
|
|
70
|
+
>
|
|
71
|
+
{label}
|
|
72
|
+
</Text>
|
|
73
|
+
);
|
|
94
74
|
|
|
95
|
-
|
|
75
|
+
// the active label fades in over the inactive one
|
|
76
|
+
const text = (
|
|
77
|
+
<View style={!vertical && styles.labelBox}>
|
|
78
|
+
{renderText(inactiveTintColor)}
|
|
79
|
+
<Animated.View
|
|
80
|
+
pointerEvents={'none'}
|
|
81
|
+
style={[StyleSheet.absoluteFill, activeLabelStyle]}
|
|
82
|
+
>
|
|
83
|
+
{renderText(activeTintColor)}
|
|
84
|
+
</Animated.View>
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
96
87
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
88
|
+
return (
|
|
89
|
+
<>
|
|
90
|
+
<View style={vertical ? styles.railSlot : styles.slot}>
|
|
91
|
+
<TabBarIcon
|
|
92
|
+
focused={focused}
|
|
93
|
+
badge={badge}
|
|
94
|
+
vertical={vertical}
|
|
95
|
+
activeTintColor={activeTintColor}
|
|
96
|
+
inactiveTintColor={inactiveTintColor}
|
|
97
|
+
renderIcon={icon}
|
|
98
|
+
/>
|
|
99
|
+
</View>
|
|
100
|
+
{!hideLabel &&
|
|
101
|
+
(fixedFontScale ? <FixedScaleText>{text}</FixedScaleText> : text)}
|
|
102
|
+
</>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
110
105
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
type CustomBottomTabItemProps = TabItemContentProps & {
|
|
107
|
+
onPress: () => void;
|
|
108
|
+
onLongPress: () => void;
|
|
109
|
+
onLayout?: (e: LayoutChangeEvent) => void;
|
|
110
|
+
accessibilityLabel?: string;
|
|
111
|
+
testID?: string;
|
|
112
|
+
};
|
|
118
113
|
|
|
119
|
-
|
|
114
|
+
/**
|
|
115
|
+
* horizontal navigation item (.Nav/itemTab): fills an even share of the container
|
|
116
|
+
*/
|
|
117
|
+
const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
|
|
118
|
+
onPress,
|
|
119
|
+
onLongPress,
|
|
120
|
+
onLayout,
|
|
121
|
+
accessibilityLabel,
|
|
122
|
+
testID,
|
|
123
|
+
...content
|
|
124
|
+
}) => {
|
|
125
|
+
const context = useContext<any>(MiniAppContext);
|
|
126
|
+
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
120
127
|
|
|
121
128
|
return (
|
|
122
129
|
<TouchableOpacity
|
|
130
|
+
accessibilityRole={'tab'}
|
|
131
|
+
accessibilityLabel={accessibilityLabel ?? content.label}
|
|
132
|
+
accessibilityState={{ selected: content.focused }}
|
|
133
|
+
testID={testID}
|
|
123
134
|
onPress={onPress}
|
|
124
135
|
onLongPress={onLongPress}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
{ backgroundColor: background },
|
|
128
|
-
style,
|
|
129
|
-
showBaseLineDebug && styles.debugBaseLine,
|
|
130
|
-
]}
|
|
136
|
+
onLayout={onLayout}
|
|
137
|
+
style={[styles.container, showBaseLineDebug && styles.debugBaseLine]}
|
|
131
138
|
>
|
|
132
|
-
{
|
|
133
|
-
{renderLabel({ focused })}
|
|
139
|
+
<TabItemContent {...content} />
|
|
134
140
|
</TouchableOpacity>
|
|
135
141
|
);
|
|
136
142
|
};
|
|
@@ -138,16 +144,34 @@ const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
|
|
|
138
144
|
const styles = StyleSheet.create({
|
|
139
145
|
container: {
|
|
140
146
|
flex: 1,
|
|
147
|
+
minWidth: 1,
|
|
148
|
+
alignItems: 'center',
|
|
149
|
+
justifyContent: 'center',
|
|
150
|
+
gap: Spacing.XXS,
|
|
151
|
+
},
|
|
152
|
+
slot: {
|
|
153
|
+
height: TabBarMetrics.indicatorHeight,
|
|
154
|
+
alignSelf: 'stretch',
|
|
141
155
|
alignItems: 'center',
|
|
142
156
|
justifyContent: 'center',
|
|
143
|
-
paddingVertical:
|
|
144
|
-
|
|
157
|
+
paddingVertical: TabBarMetrics.indicatorPaddingVertical,
|
|
158
|
+
paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
|
|
159
|
+
},
|
|
160
|
+
railSlot: {
|
|
161
|
+
height: TabBarMetrics.railIndicatorHeight,
|
|
162
|
+
alignItems: 'center',
|
|
163
|
+
justifyContent: 'center',
|
|
164
|
+
paddingVertical: TabBarMetrics.railIndicatorPaddingVertical,
|
|
165
|
+
paddingHorizontal: TabBarMetrics.indicatorPaddingHorizontal,
|
|
166
|
+
},
|
|
167
|
+
labelBox: {
|
|
168
|
+
alignSelf: 'stretch',
|
|
145
169
|
},
|
|
146
170
|
label: {
|
|
147
|
-
|
|
148
|
-
fontWeight: '600',
|
|
171
|
+
textAlign: 'center',
|
|
149
172
|
},
|
|
150
173
|
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
151
174
|
});
|
|
152
175
|
|
|
176
|
+
export { TabItemContent };
|
|
153
177
|
export default CustomBottomTabItem;
|
|
@@ -8,6 +8,7 @@ import React, {
|
|
|
8
8
|
import { Spacing } from '../../Consts';
|
|
9
9
|
import { useScreenLayout } from '../../Layout';
|
|
10
10
|
import { DuoMetrics } from '../../Layout/utils';
|
|
11
|
+
import { getRailHeight, TabBarMetrics } from './metrics';
|
|
11
12
|
|
|
12
13
|
export type DuoBarCompressionBehavior = 'toolbar' | 'tabBar';
|
|
13
14
|
|
|
@@ -25,8 +26,6 @@ type DuoBarCompression = {
|
|
|
25
26
|
setToolbarHeight?: (height: number) => void;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
|
-
const RAIL_PADDING = 6;
|
|
29
|
-
const RAIL_GAP = Spacing.XXS;
|
|
30
29
|
// space kept between the top and the bottom groups (HIG Vertical controls)
|
|
31
30
|
const GROUP_GAP = Spacing.M;
|
|
32
31
|
// Back, then the right toolkit: help + home capsule
|
|
@@ -43,15 +42,6 @@ const NO_COMPRESSION: DuoBarCompression = {
|
|
|
43
42
|
const DuoBarCompressionContext =
|
|
44
43
|
createContext<DuoBarCompression>(NO_COMPRESSION);
|
|
45
44
|
|
|
46
|
-
/**
|
|
47
|
-
* height of the vertical tab rail, plus the QR button under it
|
|
48
|
-
*/
|
|
49
|
-
const getRailHeight = (tabCount: number, hasQr: boolean) =>
|
|
50
|
-
2 * RAIL_PADDING +
|
|
51
|
-
tabCount * DuoMetrics.controlSize +
|
|
52
|
-
Math.max(tabCount - 1, 0) * RAIL_GAP +
|
|
53
|
-
(hasQr ? Spacing.M + DuoMetrics.controlSize : 0);
|
|
54
|
-
|
|
55
45
|
/**
|
|
56
46
|
* keeps the side bar's toolbar and tab bar clear of each other (HIG, Designing for
|
|
57
47
|
* iPhone Duo: Vertical controls):
|
|
@@ -81,7 +71,9 @@ const DuoBarCompressionProvider: React.FC<{
|
|
|
81
71
|
}
|
|
82
72
|
const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
|
|
83
73
|
const tabBar = getRailHeight(tabCount, hasQr);
|
|
84
|
-
const compressedTabBar =
|
|
74
|
+
const compressedTabBar =
|
|
75
|
+
DuoMetrics.controlSize +
|
|
76
|
+
(hasQr ? TabBarMetrics.railCtaGap + TabBarMetrics.railCtaHeight : 0);
|
|
85
77
|
const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
|
|
86
78
|
|
|
87
79
|
if (fits(toolbarHeight, tabBar)) {
|
|
@@ -1,98 +1,161 @@
|
|
|
1
|
-
import React, { useContext
|
|
2
|
-
import { TouchableOpacity, View } from 'react-native';
|
|
3
|
-
import { GestureDetector } from 'react-native-gesture-handler';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import { Pressable, TouchableOpacity, View } from 'react-native';
|
|
4
3
|
import Animated from 'react-native-reanimated';
|
|
5
|
-
import { Radius } from '../../Consts';
|
|
4
|
+
import { Colors, Radius } from '../../Consts';
|
|
6
5
|
import { ApplicationContext } from '../../Context';
|
|
6
|
+
import { Icon } from '../../Icon';
|
|
7
7
|
import { useScreenLayout } from '../../Layout/utils';
|
|
8
|
+
import { Text } from '../../Text';
|
|
9
|
+
import { FloatingButtonProps } from '../types';
|
|
8
10
|
import { useDuoBarCompression } from './DuoBarCompression';
|
|
9
|
-
import {
|
|
11
|
+
import { useCenterActionPress } from './BottomTabBar';
|
|
12
|
+
import { TabItemContent } from './CustomBottomTabItem';
|
|
13
|
+
import { CurvedContainer } from './CurvedContainer';
|
|
14
|
+
import { TabBarMetrics } from './metrics';
|
|
15
|
+
import { getTabItems, TabItem } from './tabItems';
|
|
16
|
+
import { FixedScaleText } from './TabBarIcon';
|
|
17
|
+
import { usePressLift } from './usePressLift';
|
|
18
|
+
import { ActiveIndicator } from './ActiveIndicator';
|
|
10
19
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
LiquidTabItem,
|
|
14
|
-
Props,
|
|
15
|
-
QrIcon,
|
|
16
|
-
SelectionMaterial,
|
|
17
|
-
TabItem,
|
|
18
|
-
} from './DuoTabBar.parts';
|
|
19
|
-
import {
|
|
20
|
-
ITEM_SIZE,
|
|
21
|
-
QR_ICON_SIZE,
|
|
22
|
-
RAIL_BUBBLE_WIDTH,
|
|
23
|
-
RAIL_PADDING,
|
|
24
|
-
RAIL_PITCH,
|
|
20
|
+
RAIL_COMPACT_SIZE,
|
|
21
|
+
RAIL_ITEM_HEIGHT,
|
|
25
22
|
styles,
|
|
26
23
|
} from './DuoTabBar.styles';
|
|
24
|
+
import type { DuoTabBarProps } from './DuoTabBar';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Center action icon: an icon name drawn at the CTA size, or a custom node
|
|
28
|
+
*/
|
|
29
|
+
const CtaIcon: React.FC<{ icon: FloatingButtonProps['icon'] }> = ({ icon }) => {
|
|
30
|
+
if (typeof icon === 'string') {
|
|
31
|
+
return (
|
|
32
|
+
<Icon
|
|
33
|
+
source={icon}
|
|
34
|
+
size={TabBarMetrics.ctaIconSize}
|
|
35
|
+
color={Colors.black_01}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return <>{icon}</>;
|
|
40
|
+
};
|
|
27
41
|
|
|
28
42
|
/**
|
|
29
|
-
*
|
|
43
|
+
* vertical container (.Nav/Duo/Item column) with the sliding active indicator
|
|
30
44
|
*/
|
|
31
45
|
const Rail: React.FC<{
|
|
32
46
|
items: TabItem[];
|
|
33
47
|
index: number;
|
|
48
|
+
activeTintColor: string;
|
|
49
|
+
inactiveTintColor: string;
|
|
34
50
|
onItemPress?: () => void;
|
|
35
|
-
}> = ({ items, index, onItemPress }) =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
}> = ({ items, index, activeTintColor, inactiveTintColor, onItemPress }) => (
|
|
52
|
+
<View style={styles.rail} accessibilityLabel={'bottom_tab_bar'}>
|
|
53
|
+
<CurvedContainer
|
|
54
|
+
width={TabBarMetrics.railContainerWidth}
|
|
55
|
+
height={items.length * RAIL_ITEM_HEIGHT + TabBarMetrics.railPaddingBottom}
|
|
56
|
+
radius={Radius.XL}
|
|
57
|
+
/>
|
|
58
|
+
<ActiveIndicator
|
|
59
|
+
axis={'y'}
|
|
60
|
+
offset={index * RAIL_ITEM_HEIGHT}
|
|
61
|
+
radius={TabBarMetrics.railIndicatorRadius}
|
|
62
|
+
style={styles.railIndicator}
|
|
63
|
+
/>
|
|
64
|
+
{items.map(item => (
|
|
65
|
+
<TouchableOpacity
|
|
66
|
+
key={item.route.key}
|
|
67
|
+
accessibilityRole={'tab'}
|
|
68
|
+
accessibilityLabel={item.label}
|
|
69
|
+
accessibilityState={{ selected: item.focused }}
|
|
70
|
+
onPress={() => {
|
|
71
|
+
item.onPress();
|
|
72
|
+
onItemPress?.();
|
|
73
|
+
}}
|
|
74
|
+
onLongPress={item.onLongPress}
|
|
75
|
+
style={styles.railItem}
|
|
76
|
+
>
|
|
77
|
+
<TabItemContent
|
|
78
|
+
vertical
|
|
79
|
+
fixedFontScale
|
|
80
|
+
label={item.label}
|
|
81
|
+
focused={item.focused}
|
|
82
|
+
icon={item.icon}
|
|
83
|
+
badge={item.badge}
|
|
84
|
+
activeTintColor={activeTintColor}
|
|
85
|
+
inactiveTintColor={inactiveTintColor}
|
|
86
|
+
/>
|
|
87
|
+
</TouchableOpacity>
|
|
88
|
+
))}
|
|
89
|
+
</View>
|
|
90
|
+
);
|
|
56
91
|
|
|
92
|
+
/**
|
|
93
|
+
* center action of the rail: round button with its label, under the container
|
|
94
|
+
*/
|
|
95
|
+
const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
96
|
+
floatingButton,
|
|
97
|
+
}) => {
|
|
98
|
+
const { theme } = useContext(ApplicationContext);
|
|
99
|
+
const { buttonStyle, onPressIn, onPressOut } = useCenterActionPress();
|
|
57
100
|
return (
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
))}
|
|
101
|
+
<Pressable
|
|
102
|
+
accessibilityRole={'button'}
|
|
103
|
+
accessibilityLabel={floatingButton.label}
|
|
104
|
+
onPress={floatingButton.onPress}
|
|
105
|
+
onPressIn={onPressIn}
|
|
106
|
+
onPressOut={onPressOut}
|
|
107
|
+
style={styles.railCta}
|
|
108
|
+
>
|
|
109
|
+
<Animated.View
|
|
110
|
+
style={[
|
|
111
|
+
styles.railCtaButton,
|
|
112
|
+
{ backgroundColor: theme.colors.primary },
|
|
113
|
+
buttonStyle,
|
|
114
|
+
]}
|
|
115
|
+
>
|
|
116
|
+
<CtaIcon icon={floatingButton.icon} />
|
|
75
117
|
</Animated.View>
|
|
76
|
-
|
|
118
|
+
<View style={styles.railCtaLabel}>
|
|
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>
|
|
128
|
+
{floatingButton.labelContent && (
|
|
129
|
+
<View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
|
|
130
|
+
{floatingButton.labelContent}
|
|
131
|
+
</View>
|
|
132
|
+
)}
|
|
133
|
+
</View>
|
|
134
|
+
</Pressable>
|
|
77
135
|
);
|
|
78
136
|
};
|
|
79
137
|
|
|
80
138
|
/**
|
|
81
|
-
* vertical bar anchored to the bottom of the Duo side bar,
|
|
139
|
+
* vertical bar anchored to the bottom of the Duo side bar, center action below it.
|
|
82
140
|
* When the side bar is short, it can shrink to one button showing the current tab
|
|
83
141
|
* (HIG "Tab bar compressed"); tapping it expands the rail and compresses the toolbar
|
|
84
142
|
*/
|
|
85
|
-
|
|
86
|
-
const { theme } = useContext(ApplicationContext);
|
|
143
|
+
const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
87
144
|
const { sideBar } = useScreenLayout();
|
|
88
145
|
const { tabBarCompressed, expandTabBar, onTabSelected } =
|
|
89
146
|
useDuoBarCompression();
|
|
90
147
|
const compactLift = usePressLift();
|
|
91
|
-
const { state, floatingButton } = props;
|
|
148
|
+
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
92
149
|
const items = getTabItems(props);
|
|
93
150
|
const current = items[state.index];
|
|
94
151
|
|
|
95
|
-
|
|
152
|
+
if (!sideBar) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
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
|
|
158
|
+
const columnStyle = [
|
|
96
159
|
styles.railColumn,
|
|
97
160
|
{
|
|
98
161
|
[sideBar.edge]: 0,
|
|
@@ -101,49 +164,64 @@ export const DuoRailTabBar: React.FC<Props> = props => {
|
|
|
101
164
|
},
|
|
102
165
|
sideBar.edge === 'right' ? styles.railColumnRight : styles.railColumnLeft,
|
|
103
166
|
];
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<TouchableOpacity
|
|
107
|
-
accessibilityRole={'button'}
|
|
108
|
-
accessibilityLabel={floatingButton.label}
|
|
109
|
-
onPress={floatingButton.onPress}
|
|
110
|
-
style={[styles.railQr, { backgroundColor: theme.colors.primary }]}
|
|
111
|
-
>
|
|
112
|
-
<QrIcon icon={floatingButton.icon} size={QR_ICON_SIZE} />
|
|
113
|
-
</TouchableOpacity>
|
|
167
|
+
const cta = floatingButton && (
|
|
168
|
+
<RailCenterAction floatingButton={floatingButton} />
|
|
114
169
|
);
|
|
115
170
|
|
|
116
|
-
if (!sideBar) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
171
|
if (!tabBarCompressed) {
|
|
121
172
|
return (
|
|
122
173
|
<View pointerEvents={'box-none'} style={columnStyle}>
|
|
123
|
-
<
|
|
124
|
-
|
|
174
|
+
<View pointerEvents={'box-none'} style={styles.railLane}>
|
|
175
|
+
<Rail
|
|
176
|
+
items={items}
|
|
177
|
+
index={state.index}
|
|
178
|
+
activeTintColor={activeTintColor}
|
|
179
|
+
inactiveTintColor={inactiveTintColor}
|
|
180
|
+
onItemPress={onTabSelected}
|
|
181
|
+
/>
|
|
182
|
+
{cta}
|
|
183
|
+
</View>
|
|
125
184
|
</View>
|
|
126
185
|
);
|
|
127
186
|
}
|
|
128
187
|
|
|
129
188
|
return (
|
|
130
189
|
<View pointerEvents={'box-none'} style={columnStyle}>
|
|
131
|
-
<
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
190
|
+
<View pointerEvents={'box-none'} style={styles.railLane}>
|
|
191
|
+
<Animated.View style={compactLift.style}>
|
|
192
|
+
<TouchableOpacity
|
|
193
|
+
accessibilityRole={'button'}
|
|
194
|
+
accessibilityLabel={current?.label}
|
|
195
|
+
accessibilityHint={'bottom_tab_bar'}
|
|
196
|
+
activeOpacity={1}
|
|
197
|
+
onPressIn={compactLift.onPressIn}
|
|
198
|
+
onPressOut={compactLift.onPressOut}
|
|
199
|
+
onPress={expandTabBar}
|
|
200
|
+
style={styles.railCompact}
|
|
201
|
+
>
|
|
202
|
+
<CurvedContainer
|
|
203
|
+
width={RAIL_COMPACT_SIZE}
|
|
204
|
+
height={RAIL_COMPACT_SIZE}
|
|
205
|
+
radius={RAIL_COMPACT_SIZE / 2}
|
|
206
|
+
/>
|
|
207
|
+
{current && (
|
|
208
|
+
<TabItemContent
|
|
209
|
+
vertical
|
|
210
|
+
hideLabel
|
|
211
|
+
focused
|
|
212
|
+
label={current.label}
|
|
213
|
+
icon={current.icon}
|
|
214
|
+
badge={current.badge}
|
|
215
|
+
activeTintColor={activeTintColor}
|
|
216
|
+
inactiveTintColor={inactiveTintColor}
|
|
217
|
+
/>
|
|
218
|
+
)}
|
|
219
|
+
</TouchableOpacity>
|
|
220
|
+
</Animated.View>
|
|
221
|
+
{cta}
|
|
222
|
+
</View>
|
|
147
223
|
</View>
|
|
148
224
|
);
|
|
149
225
|
};
|
|
226
|
+
|
|
227
|
+
export { DuoRailTabBar };
|