@momo-kits/foundation 0.167.1-beta.5 → 0.167.1-duo-test.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Application/BottomSheet.tsx +115 -14
- package/Application/BottomTab/BottomTabBar.tsx +6 -3
- package/Application/BottomTab/DuoBarCompression.tsx +135 -0
- package/Application/BottomTab/DuoTabBar.tsx +462 -0
- package/Application/BottomTab/index.tsx +150 -134
- package/Application/Components/DuoGlassBackground.tsx +179 -0
- package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
- package/Application/Components/HeaderExtendHeader.tsx +75 -28
- package/Application/Components/HeaderLeft.tsx +38 -0
- package/Application/Components/HeaderRight.tsx +142 -9
- package/Application/Components/HeaderTitle.tsx +67 -17
- package/Application/Components/OptionalGlassView.ts +11 -0
- package/Application/Components/OptionalScrollObserver.ts +12 -0
- package/Application/Components/SearchHeader.tsx +22 -15
- package/Application/Components/SideBarButton.tsx +139 -0
- package/Application/ModalScreen.tsx +16 -2
- package/Application/NavigationContainer.tsx +36 -45
- package/Application/Navigator.ts +1 -3
- package/Application/StackScreen.tsx +63 -9
- package/Application/WidgetContainer.tsx +9 -7
- package/Application/device.ts +29 -0
- package/Application/types.ts +27 -1
- package/Application/utils.tsx +255 -7
- package/Consts/index.ts +0 -1
- package/Consts/theme.ts +5 -6
- package/Input/Input.tsx +3 -4
- package/Input/InputMoney.tsx +31 -51
- package/Input/InputPhoneNumber.tsx +3 -4
- package/Input/InputSearch.tsx +3 -4
- package/Input/InputTextArea.tsx +3 -4
- package/Layout/Card.tsx +8 -26
- package/Layout/FloatingButton.tsx +3 -3
- package/Layout/Item.tsx +1 -10
- package/Layout/ReservedRegions.tsx +47 -0
- package/Layout/Screen.tsx +199 -52
- package/Layout/Section.tsx +5 -18
- package/Layout/index.ts +4 -4
- package/Layout/styles.ts +5 -0
- package/Layout/utils.ts +465 -5
- package/Popup/PopupNotify.tsx +33 -22
- package/Popup/PopupPromotion.tsx +20 -8
- package/Popup/utils.ts +32 -0
- package/Text/index.tsx +2 -11
- package/Text/utils.ts +1 -2
- package/package.json +3 -2
- package/Consts/designSystemFont.ts +0 -38
- package/Layout/GridSystem.tsx +0 -137
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
|
|
2
|
+
import { CommonActions, Route } from '@react-navigation/native';
|
|
3
|
+
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
LayoutChangeEvent,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
View,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import Animated, {
|
|
11
|
+
useAnimatedStyle,
|
|
12
|
+
useSharedValue,
|
|
13
|
+
withSpring,
|
|
14
|
+
} from 'react-native-reanimated';
|
|
15
|
+
import { Colors, Radius, Spacing } from '../../Consts';
|
|
16
|
+
import { ApplicationContext } from '../../Context';
|
|
17
|
+
import { Icon } from '../../Icon';
|
|
18
|
+
import { DuoMetrics, useScreenLayout } from '../../Layout/utils';
|
|
19
|
+
import { Text } from '../../Text';
|
|
20
|
+
import { GlassView } from '../Components/OptionalGlassView';
|
|
21
|
+
import { useDuoBarCompression } from './DuoBarCompression';
|
|
22
|
+
import { FloatingButtonProps } from '../types';
|
|
23
|
+
import { TabBarIcon } from './TabBarIcon';
|
|
24
|
+
|
|
25
|
+
type Props = BottomTabBarProps & {
|
|
26
|
+
activeTintColor: string;
|
|
27
|
+
inactiveTintColor: string;
|
|
28
|
+
floatingButton?: FloatingButtonProps;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ITEM_SIZE = DuoMetrics.controlSize;
|
|
32
|
+
const ICON_SIZE = 28;
|
|
33
|
+
const QR_ICON_SIZE = 24;
|
|
34
|
+
const RAIL_PADDING = 6;
|
|
35
|
+
const RAIL_GAP = Spacing.XXS;
|
|
36
|
+
// selection bubble overflows the 48 rail cell by 5 above and below (Figma MT2)
|
|
37
|
+
const RAIL_BUBBLE_WIDTH = 44;
|
|
38
|
+
const RAIL_BUBBLE_HEIGHT = 58;
|
|
39
|
+
const FLOATING_HEIGHT = 62;
|
|
40
|
+
const FLOATING_PADDING = Spacing.XS;
|
|
41
|
+
const FLOATING_QR_SIZE = 44;
|
|
42
|
+
// floating bar spans 6 of the 8 portrait grid columns, like the content
|
|
43
|
+
const GRID_COLUMNS = 8;
|
|
44
|
+
const GRID_SPAN = 6;
|
|
45
|
+
const SPRING = { damping: 18, stiffness: 220, mass: 0.8 };
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* space tab content leaves under the Duo floating tab bar, 0 elsewhere
|
|
49
|
+
*/
|
|
50
|
+
const useDuoTabBarInset = () => {
|
|
51
|
+
const { isIPhoneDuo, sideBar, insets } = useScreenLayout();
|
|
52
|
+
return isIPhoneDuo && !sideBar
|
|
53
|
+
? FLOATING_HEIGHT + Spacing.M + insets.bottom
|
|
54
|
+
: 0;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const gridSpanWidth = (width: number) => {
|
|
58
|
+
const columnWidth =
|
|
59
|
+
(width - 2 * Spacing.M - (GRID_COLUMNS - 1) * Spacing.M) / GRID_COLUMNS;
|
|
60
|
+
return columnWidth * GRID_SPAN + (GRID_SPAN - 1) * Spacing.M;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const BarMaterial: React.FC<{ radius: number }> = ({ radius }) => {
|
|
64
|
+
const { theme } = useContext(ApplicationContext);
|
|
65
|
+
if (GlassView) {
|
|
66
|
+
return (
|
|
67
|
+
<GlassView
|
|
68
|
+
pointerEvents={'none'}
|
|
69
|
+
effect={'glass'}
|
|
70
|
+
cornerRadius={radius}
|
|
71
|
+
useShadow
|
|
72
|
+
style={StyleSheet.absoluteFill}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return (
|
|
77
|
+
<View
|
|
78
|
+
pointerEvents={'none'}
|
|
79
|
+
style={[
|
|
80
|
+
StyleSheet.absoluteFill,
|
|
81
|
+
styles.fallbackMaterial,
|
|
82
|
+
{
|
|
83
|
+
borderRadius: radius,
|
|
84
|
+
backgroundColor: theme.colors.background.surface,
|
|
85
|
+
borderColor: theme.colors.border.default,
|
|
86
|
+
},
|
|
87
|
+
]}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const getTabItems = ({
|
|
93
|
+
state,
|
|
94
|
+
descriptors,
|
|
95
|
+
navigation,
|
|
96
|
+
activeTintColor,
|
|
97
|
+
inactiveTintColor,
|
|
98
|
+
}: Props) => {
|
|
99
|
+
return state.routes.map((route: Route<string>, index: number) => {
|
|
100
|
+
const { options } = descriptors[route.key];
|
|
101
|
+
const focused = index === state.index;
|
|
102
|
+
const label = options.title ?? route.name;
|
|
103
|
+
|
|
104
|
+
const onPress = () => {
|
|
105
|
+
const event = navigation.emit({
|
|
106
|
+
type: 'tabPress',
|
|
107
|
+
target: route.key,
|
|
108
|
+
canPreventDefault: true,
|
|
109
|
+
});
|
|
110
|
+
if (!focused && !event.defaultPrevented) {
|
|
111
|
+
navigation.dispatch({
|
|
112
|
+
...CommonActions.navigate(route.name),
|
|
113
|
+
target: state.key,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const onLongPress = () => {
|
|
119
|
+
navigation.emit({ type: 'tabLongPress', target: route.key });
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const icon = options.tabBarIcon && (
|
|
123
|
+
<TabBarIcon
|
|
124
|
+
route={route}
|
|
125
|
+
badge={options.tabBarBadge}
|
|
126
|
+
badgeStyle={options.tabBarBadgeStyle}
|
|
127
|
+
activeOpacity={focused ? 1 : 0}
|
|
128
|
+
inactiveOpacity={focused ? 0 : 1}
|
|
129
|
+
activeTintColor={activeTintColor}
|
|
130
|
+
inactiveTintColor={inactiveTintColor}
|
|
131
|
+
renderIcon={options.tabBarIcon}
|
|
132
|
+
style={styles.icon}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return { route, focused, label, onPress, onLongPress, icon };
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const QrIcon: React.FC<{ icon: FloatingButtonProps['icon']; size: number }> = ({
|
|
141
|
+
icon,
|
|
142
|
+
size,
|
|
143
|
+
}) => {
|
|
144
|
+
if (typeof icon === 'string') {
|
|
145
|
+
return <Icon source={icon} size={size} color={Colors.black_01} />;
|
|
146
|
+
}
|
|
147
|
+
return <>{icon}</>;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
type TabItem = ReturnType<typeof getTabItems>[number];
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* glass column of tab items with the sliding selection bubble
|
|
154
|
+
*/
|
|
155
|
+
const Rail: React.FC<{
|
|
156
|
+
items: TabItem[];
|
|
157
|
+
index: number;
|
|
158
|
+
onItemPress?: () => void;
|
|
159
|
+
}> = ({ items, index, onItemPress }) => {
|
|
160
|
+
const { theme } = useContext(ApplicationContext);
|
|
161
|
+
const bubbleOffset = useSharedValue(index * (ITEM_SIZE + RAIL_GAP));
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
bubbleOffset.value = withSpring(index * (ITEM_SIZE + RAIL_GAP), SPRING);
|
|
165
|
+
}, [index, bubbleOffset]);
|
|
166
|
+
|
|
167
|
+
const bubbleStyle = useAnimatedStyle(() => ({
|
|
168
|
+
transform: [{ translateY: bubbleOffset.value }],
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<View style={styles.rail} accessibilityLabel={'bottom_tab_bar'}>
|
|
173
|
+
<BarMaterial radius={Radius.XL} />
|
|
174
|
+
<Animated.View
|
|
175
|
+
pointerEvents={'none'}
|
|
176
|
+
style={[
|
|
177
|
+
styles.railBubble,
|
|
178
|
+
{ backgroundColor: theme.colors.background.selected },
|
|
179
|
+
bubbleStyle,
|
|
180
|
+
]}
|
|
181
|
+
/>
|
|
182
|
+
{items.map(item => (
|
|
183
|
+
<TouchableOpacity
|
|
184
|
+
key={item.route.key}
|
|
185
|
+
accessibilityRole={'tab'}
|
|
186
|
+
accessibilityLabel={item.label}
|
|
187
|
+
accessibilityState={{ selected: item.focused }}
|
|
188
|
+
onPress={() => {
|
|
189
|
+
item.onPress();
|
|
190
|
+
onItemPress?.();
|
|
191
|
+
}}
|
|
192
|
+
onLongPress={item.onLongPress}
|
|
193
|
+
style={styles.railItem}
|
|
194
|
+
>
|
|
195
|
+
{item.icon}
|
|
196
|
+
</TouchableOpacity>
|
|
197
|
+
))}
|
|
198
|
+
</View>
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* vertical bar anchored to the bottom of the Duo side bar, QR button below it (Figma MT1-C/E).
|
|
204
|
+
* When the side bar is short, it can shrink to one button showing the current tab
|
|
205
|
+
* (HIG "Tab bar compressed"); tapping it expands the rail and compresses the toolbar
|
|
206
|
+
*/
|
|
207
|
+
const DuoRailTabBar: React.FC<Props> = props => {
|
|
208
|
+
const { theme } = useContext(ApplicationContext);
|
|
209
|
+
const { sideBar } = useScreenLayout();
|
|
210
|
+
const { tabBarCompressed, expandTabBar, onTabSelected } =
|
|
211
|
+
useDuoBarCompression();
|
|
212
|
+
const { state, floatingButton } = props;
|
|
213
|
+
const items = getTabItems(props);
|
|
214
|
+
const current = items[state.index];
|
|
215
|
+
|
|
216
|
+
const columnStyle = sideBar && [
|
|
217
|
+
styles.railColumn,
|
|
218
|
+
{
|
|
219
|
+
[sideBar.edge]: 0,
|
|
220
|
+
width: sideBar.width,
|
|
221
|
+
bottom: sideBar.bottom,
|
|
222
|
+
},
|
|
223
|
+
sideBar.edge === 'right'
|
|
224
|
+
? { alignItems: 'flex-start' as const, paddingLeft: Spacing.M }
|
|
225
|
+
: { alignItems: 'flex-end' as const, paddingRight: Spacing.M },
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const qr = floatingButton && (
|
|
229
|
+
<TouchableOpacity
|
|
230
|
+
accessibilityRole={'button'}
|
|
231
|
+
accessibilityLabel={floatingButton.label}
|
|
232
|
+
onPress={floatingButton.onPress}
|
|
233
|
+
style={[styles.railQr, { backgroundColor: theme.colors.primary }]}
|
|
234
|
+
>
|
|
235
|
+
<QrIcon icon={floatingButton.icon} size={QR_ICON_SIZE} />
|
|
236
|
+
</TouchableOpacity>
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (!sideBar) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!tabBarCompressed) {
|
|
244
|
+
return (
|
|
245
|
+
<View pointerEvents={'box-none'} style={columnStyle}>
|
|
246
|
+
<Rail items={items} index={state.index} onItemPress={onTabSelected} />
|
|
247
|
+
{qr}
|
|
248
|
+
</View>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<View pointerEvents={'box-none'} style={columnStyle}>
|
|
254
|
+
<TouchableOpacity
|
|
255
|
+
accessibilityRole={'button'}
|
|
256
|
+
accessibilityLabel={current?.label}
|
|
257
|
+
accessibilityHint={'bottom_tab_bar'}
|
|
258
|
+
onPress={expandTabBar}
|
|
259
|
+
style={styles.railCompact}
|
|
260
|
+
>
|
|
261
|
+
<BarMaterial radius={Radius.XL} />
|
|
262
|
+
{current?.icon}
|
|
263
|
+
</TouchableOpacity>
|
|
264
|
+
{qr}
|
|
265
|
+
</View>
|
|
266
|
+
);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* floating labelled bar for the unfolded portrait display, QR in the middle cell (Figma MT1-M)
|
|
271
|
+
*/
|
|
272
|
+
const DuoFloatingTabBar: React.FC<Props> = props => {
|
|
273
|
+
const { theme } = useContext(ApplicationContext);
|
|
274
|
+
const { width, insets } = useScreenLayout();
|
|
275
|
+
const { state, floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
276
|
+
const items = getTabItems(props);
|
|
277
|
+
const [rowWidth, setRowWidth] = useState(0);
|
|
278
|
+
const barWidth = gridSpanWidth(width);
|
|
279
|
+
|
|
280
|
+
const useQr = !!floatingButton && state.routes.length % 2 === 0;
|
|
281
|
+
const middle = state.routes.length / 2;
|
|
282
|
+
const cellWidth = rowWidth / (state.routes.length + (useQr ? 1 : 0));
|
|
283
|
+
const visualIndex =
|
|
284
|
+
useQr && state.index >= middle ? state.index + 1 : state.index;
|
|
285
|
+
|
|
286
|
+
const bubbleOffset = useSharedValue(0);
|
|
287
|
+
const measured = useRef(false);
|
|
288
|
+
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
const target = visualIndex * cellWidth;
|
|
291
|
+
// snap on first layout so the bubble doesn't slide in from the left edge
|
|
292
|
+
bubbleOffset.value = measured.current ? withSpring(target, SPRING) : target;
|
|
293
|
+
measured.current = cellWidth > 0;
|
|
294
|
+
}, [visualIndex, cellWidth, bubbleOffset]);
|
|
295
|
+
|
|
296
|
+
const bubbleStyle = useAnimatedStyle(() => ({
|
|
297
|
+
transform: [{ translateX: bubbleOffset.value }],
|
|
298
|
+
}));
|
|
299
|
+
|
|
300
|
+
const onRowLayout = (e: LayoutChangeEvent) =>
|
|
301
|
+
setRowWidth(e.nativeEvent.layout.width);
|
|
302
|
+
|
|
303
|
+
const cells = items.map(item => (
|
|
304
|
+
<TouchableOpacity
|
|
305
|
+
key={item.route.key}
|
|
306
|
+
accessibilityRole={'tab'}
|
|
307
|
+
accessibilityLabel={item.label}
|
|
308
|
+
accessibilityState={{ selected: item.focused }}
|
|
309
|
+
onPress={item.onPress}
|
|
310
|
+
onLongPress={item.onLongPress}
|
|
311
|
+
style={styles.floatingItem}
|
|
312
|
+
>
|
|
313
|
+
{item.icon}
|
|
314
|
+
<Text
|
|
315
|
+
numberOfLines={1}
|
|
316
|
+
typography={'label_xs_medium'}
|
|
317
|
+
color={item.focused ? activeTintColor : inactiveTintColor}
|
|
318
|
+
>
|
|
319
|
+
{item.label}
|
|
320
|
+
</Text>
|
|
321
|
+
</TouchableOpacity>
|
|
322
|
+
));
|
|
323
|
+
|
|
324
|
+
if (useQr && floatingButton) {
|
|
325
|
+
cells.splice(
|
|
326
|
+
middle,
|
|
327
|
+
0,
|
|
328
|
+
<View key={'duo_tab_qr'} style={styles.floatingItem}>
|
|
329
|
+
<TouchableOpacity
|
|
330
|
+
accessibilityRole={'button'}
|
|
331
|
+
accessibilityLabel={floatingButton.label}
|
|
332
|
+
onPress={floatingButton.onPress}
|
|
333
|
+
style={[styles.floatingQr, { backgroundColor: theme.colors.primary }]}
|
|
334
|
+
>
|
|
335
|
+
<QrIcon icon={floatingButton.icon} size={QR_ICON_SIZE} />
|
|
336
|
+
</TouchableOpacity>
|
|
337
|
+
</View>,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<View
|
|
343
|
+
style={[
|
|
344
|
+
styles.floating,
|
|
345
|
+
{
|
|
346
|
+
width: barWidth,
|
|
347
|
+
left: (width - barWidth) / 2,
|
|
348
|
+
bottom: insets.bottom + Spacing.M,
|
|
349
|
+
},
|
|
350
|
+
]}
|
|
351
|
+
accessibilityLabel={'bottom_tab_bar'}
|
|
352
|
+
>
|
|
353
|
+
<BarMaterial radius={FLOATING_HEIGHT / 2} />
|
|
354
|
+
<View style={styles.floatingRow} onLayout={onRowLayout}>
|
|
355
|
+
{rowWidth > 0 && (
|
|
356
|
+
<Animated.View
|
|
357
|
+
pointerEvents={'none'}
|
|
358
|
+
style={[
|
|
359
|
+
styles.floatingBubble,
|
|
360
|
+
{
|
|
361
|
+
width: cellWidth,
|
|
362
|
+
backgroundColor: theme.colors.background.selected,
|
|
363
|
+
},
|
|
364
|
+
bubbleStyle,
|
|
365
|
+
]}
|
|
366
|
+
/>
|
|
367
|
+
)}
|
|
368
|
+
{cells}
|
|
369
|
+
</View>
|
|
370
|
+
</View>
|
|
371
|
+
);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* iPhone Duo tab bar: glass rail in the side bar, or a floating glass bar when unfolded portrait
|
|
376
|
+
*/
|
|
377
|
+
const DuoTabBar: React.FC<Props> = props => {
|
|
378
|
+
const { sideBar } = useScreenLayout();
|
|
379
|
+
return sideBar ? (
|
|
380
|
+
<DuoRailTabBar {...props} />
|
|
381
|
+
) : (
|
|
382
|
+
<DuoFloatingTabBar {...props} />
|
|
383
|
+
);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const styles = StyleSheet.create({
|
|
387
|
+
fallbackMaterial: {
|
|
388
|
+
borderWidth: 1,
|
|
389
|
+
},
|
|
390
|
+
icon: {
|
|
391
|
+
width: ICON_SIZE,
|
|
392
|
+
height: ICON_SIZE,
|
|
393
|
+
},
|
|
394
|
+
railColumn: {
|
|
395
|
+
position: 'absolute',
|
|
396
|
+
gap: Spacing.M,
|
|
397
|
+
},
|
|
398
|
+
railCompact: {
|
|
399
|
+
width: ITEM_SIZE,
|
|
400
|
+
height: ITEM_SIZE,
|
|
401
|
+
borderRadius: ITEM_SIZE / 2,
|
|
402
|
+
alignItems: 'center',
|
|
403
|
+
justifyContent: 'center',
|
|
404
|
+
},
|
|
405
|
+
rail: {
|
|
406
|
+
width: ITEM_SIZE,
|
|
407
|
+
paddingVertical: RAIL_PADDING,
|
|
408
|
+
gap: RAIL_GAP,
|
|
409
|
+
},
|
|
410
|
+
railBubble: {
|
|
411
|
+
position: 'absolute',
|
|
412
|
+
top: RAIL_PADDING - (RAIL_BUBBLE_HEIGHT - ITEM_SIZE) / 2,
|
|
413
|
+
left: (ITEM_SIZE - RAIL_BUBBLE_WIDTH) / 2,
|
|
414
|
+
width: RAIL_BUBBLE_WIDTH,
|
|
415
|
+
height: RAIL_BUBBLE_HEIGHT,
|
|
416
|
+
borderRadius: RAIL_BUBBLE_WIDTH / 2,
|
|
417
|
+
},
|
|
418
|
+
railItem: {
|
|
419
|
+
width: ITEM_SIZE,
|
|
420
|
+
height: ITEM_SIZE,
|
|
421
|
+
alignItems: 'center',
|
|
422
|
+
justifyContent: 'center',
|
|
423
|
+
},
|
|
424
|
+
railQr: {
|
|
425
|
+
width: ITEM_SIZE,
|
|
426
|
+
height: ITEM_SIZE,
|
|
427
|
+
borderRadius: ITEM_SIZE / 2,
|
|
428
|
+
alignItems: 'center',
|
|
429
|
+
justifyContent: 'center',
|
|
430
|
+
},
|
|
431
|
+
floating: {
|
|
432
|
+
position: 'absolute',
|
|
433
|
+
height: FLOATING_HEIGHT,
|
|
434
|
+
padding: FLOATING_PADDING,
|
|
435
|
+
},
|
|
436
|
+
floatingRow: {
|
|
437
|
+
flex: 1,
|
|
438
|
+
flexDirection: 'row',
|
|
439
|
+
},
|
|
440
|
+
floatingBubble: {
|
|
441
|
+
position: 'absolute',
|
|
442
|
+
top: 0,
|
|
443
|
+
bottom: 0,
|
|
444
|
+
left: 0,
|
|
445
|
+
borderRadius: (FLOATING_HEIGHT - 2 * FLOATING_PADDING) / 2,
|
|
446
|
+
},
|
|
447
|
+
floatingItem: {
|
|
448
|
+
flex: 1,
|
|
449
|
+
alignItems: 'center',
|
|
450
|
+
justifyContent: 'center',
|
|
451
|
+
gap: Spacing.XXS,
|
|
452
|
+
},
|
|
453
|
+
floatingQr: {
|
|
454
|
+
width: FLOATING_QR_SIZE,
|
|
455
|
+
height: FLOATING_QR_SIZE,
|
|
456
|
+
borderRadius: FLOATING_QR_SIZE / 2,
|
|
457
|
+
alignItems: 'center',
|
|
458
|
+
justifyContent: 'center',
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
export { DuoTabBar, useDuoTabBarInset };
|