@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.
Files changed (38) hide show
  1. package/Application/BottomSheet.hooks.ts +17 -37
  2. package/Application/BottomSheet.parts.tsx +81 -165
  3. package/Application/BottomSheet.styles.ts +7 -23
  4. package/Application/BottomSheet.tsx +15 -67
  5. package/Application/BottomTab/ActiveIndicator.tsx +90 -0
  6. package/Application/BottomTab/BottomTabBar.styles.ts +48 -43
  7. package/Application/BottomTab/BottomTabBar.tsx +229 -223
  8. package/Application/BottomTab/BottomTabInset.tsx +14 -0
  9. package/Application/BottomTab/CurvedContainer.tsx +153 -0
  10. package/Application/BottomTab/CustomBottomTabItem.tsx +132 -108
  11. package/Application/BottomTab/DuoBarCompression.tsx +4 -12
  12. package/Application/BottomTab/DuoRailTabBar.tsx +174 -96
  13. package/Application/BottomTab/DuoTabBar.styles.ts +42 -84
  14. package/Application/BottomTab/DuoTabBar.tsx +35 -115
  15. package/Application/BottomTab/TabBarIcon.tsx +160 -72
  16. package/Application/BottomTab/TabScreen.tsx +48 -61
  17. package/Application/BottomTab/index.tsx +85 -125
  18. package/Application/BottomTab/metrics.ts +164 -0
  19. package/Application/BottomTab/tabItems.ts +46 -0
  20. package/Application/BottomTab/usePressLift.ts +32 -0
  21. package/Application/ModalIconButton.tsx +66 -0
  22. package/Application/ModalScreen.tsx +32 -15
  23. package/Application/__tests__/modalLayout.test.ts +128 -0
  24. package/Application/index.ts +10 -0
  25. package/Application/modalLayout.ts +177 -0
  26. package/Application/types.tabs.ts +5 -0
  27. package/Application/utils.ts +0 -36
  28. package/Layout/ReservedRegions.tsx +21 -4
  29. package/Layout/Screen.tsx +11 -2
  30. package/Layout/index.ts +2 -0
  31. package/Popup/PopupNotify.tsx +38 -68
  32. package/Popup/PopupPromotion.tsx +5 -4
  33. package/Popup/utils.ts +1 -26
  34. package/package.json +2 -1
  35. package/Application/BottomTab/DuoTabBar.parts.tsx +0 -202
  36. package/Application/BottomTab/badge.ts +0 -41
  37. package/Application/BottomTab/hooks.ts +0 -358
  38. 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
- BottomTabBarButtonProps,
5
- BottomTabNavigationOptions,
6
- } from '@react-navigation/bottom-tabs';
7
- import { Route } from '@react-navigation/native';
8
- import { TabBarIcon } from './TabBarIcon';
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 LabelType = NonNullable<BottomTabNavigationOptions['tabBarLabel']>;
19
+ type IconRenderer = (props: {
20
+ focused: boolean;
21
+ color: string;
22
+ size: number;
23
+ }) => React.ReactNode;
14
24
 
15
- interface CustomBottomTabItemProps {
16
- route: Route<string>;
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
- activeBackgroundColor?: string;
24
- inactiveBackgroundColor?: string;
25
- icon: any;
30
+ icon?: IconRenderer;
26
31
  badge?: string | number;
27
- showLabel?: boolean;
28
- labelStyle?: any;
29
- iconStyle?: any;
30
- style?: any;
31
- badgeStyle?: any;
32
- allowFontScaling?: boolean;
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
- const CustomBottomTabItem: React.FC<CustomBottomTabItemProps> = ({
45
- route,
46
- focused,
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
- onPress,
49
- onLongPress,
45
+ focused,
50
46
  activeTintColor,
51
47
  inactiveTintColor,
52
- activeBackgroundColor,
53
- inactiveBackgroundColor,
54
48
  icon,
55
49
  badge,
56
- showLabel,
57
- labelStyle,
58
- iconStyle,
59
- style,
60
- badgeStyle,
61
- allowFontScaling,
50
+ vertical,
51
+ fixedFontScale,
52
+ hideLabel,
62
53
  }) => {
63
- const context = useContext<any>(MiniAppContext);
64
- const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
54
+ const progress = useSharedValue(focused ? 1 : 0);
65
55
 
66
- const renderIcon = () => {
67
- if (icon === undefined) {
68
- return null;
69
- }
56
+ useEffect(() => {
57
+ progress.value = withTiming(focused ? 1 : 0, TabBarMotion.tint);
58
+ }, [focused, progress]);
70
59
 
71
- const activeOpacity = focused ? 1 : 0;
72
- const inactiveOpacity = focused ? 0 : 1;
60
+ const activeLabelStyle = useAnimatedStyle(() => ({
61
+ opacity: progress.value,
62
+ }));
73
63
 
74
- return (
75
- <TabBarIcon
76
- route={route}
77
- badge={badge}
78
- badgeStyle={badgeStyle}
79
- activeOpacity={activeOpacity}
80
- allowFontScaling={allowFontScaling}
81
- inactiveOpacity={inactiveOpacity}
82
- activeTintColor={activeTintColor}
83
- inactiveTintColor={inactiveTintColor}
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
- const color = isFocused ? activeTintColor : inactiveTintColor;
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
- if (typeof label === 'string') {
98
- return (
99
- <Text
100
- numberOfLines={1}
101
- style={[styles.label, labelStyle]}
102
- color={isFocused ? activeTintColor : inactiveTintColor}
103
- typography={isFocused ? 'label_xs_medium' : 'description_xs_regular'}
104
- allowFontScaling={allowFontScaling}
105
- >
106
- {label}
107
- </Text>
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
- return label({
112
- focused: isFocused,
113
- color,
114
- children: route.name,
115
- position: 'below-icon',
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
- const background = focused ? activeBackgroundColor : inactiveBackgroundColor;
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
- style={[
126
- styles.container,
127
- { backgroundColor: background },
128
- style,
129
- showBaseLineDebug && styles.debugBaseLine,
130
- ]}
136
+ onLayout={onLayout}
137
+ style={[styles.container, showBaseLineDebug && styles.debugBaseLine]}
131
138
  >
132
- {renderIcon()}
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: 8,
144
- bottom: -2,
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
- fontSize: 12,
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 = getRailHeight(1, hasQr) - 2 * RAIL_PADDING;
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, useMemo } from 'react';
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 { useLiquidSelection, usePressLift } from './hooks';
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
- BarMaterial,
12
- getTabItems,
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
- * column of icon-only tab items with the sliding selection bubble
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
- const onSelect = (itemIndex: number) => {
37
- items[itemIndex]?.onPress();
38
- onItemPress?.();
39
- };
40
- const selection = useLiquidSelection({
41
- count: items.length,
42
- pitch: RAIL_PITCH,
43
- firstCenter: RAIL_PADDING + ITEM_SIZE / 2,
44
- axis: 'y',
45
- index,
46
- onSelect,
47
- });
48
- const { barStyle, bubbleStyle } = selection;
49
- // stable style arrays: the tab switch re-renders the rail while the lens is still
50
- // springing back, a new array would re-attach the animated style mid animation
51
- const railStyle = useMemo(() => [styles.rail, barStyle], [barStyle]);
52
- const railBubbleStyle = useMemo(
53
- () => [styles.railBubble, bubbleStyle],
54
- [bubbleStyle],
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
- <GestureDetector gesture={selection.gesture}>
59
- <Animated.View style={railStyle} accessibilityLabel={'bottom_tab_bar'}>
60
- <BarMaterial radius={ITEM_SIZE / 2} />
61
- <Animated.View pointerEvents={'none'} style={railBubbleStyle}>
62
- <SelectionMaterial radius={RAIL_BUBBLE_WIDTH / 2} />
63
- </Animated.View>
64
- {items.map((item, itemIndex) => (
65
- <LiquidTabItem
66
- key={item.route.key}
67
- item={item}
68
- itemIndex={itemIndex}
69
- selection={selection}
70
- onSelect={() => onSelect(itemIndex)}
71
- renderContent={item.renderIcon}
72
- style={styles.railItem}
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
- </GestureDetector>
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, QR button below it (Figma MT1-C/E).
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
- export const DuoRailTabBar: React.FC<Props> = props => {
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
- const columnStyle = sideBar && [
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
- const qr = floatingButton && (
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
- <Rail items={items} index={state.index} onItemPress={onTabSelected} />
124
- {qr}
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
- <Animated.View style={compactLift.style}>
132
- <TouchableOpacity
133
- accessibilityRole={'button'}
134
- accessibilityLabel={current?.label}
135
- accessibilityHint={'bottom_tab_bar'}
136
- activeOpacity={1}
137
- onPressIn={compactLift.onPressIn}
138
- onPressOut={compactLift.onPressOut}
139
- onPress={expandTabBar}
140
- style={styles.railCompact}
141
- >
142
- <BarMaterial radius={Radius.XL} />
143
- {current?.renderIcon(true)}
144
- </TouchableOpacity>
145
- </Animated.View>
146
- {qr}
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 };