@momo-kits/foundation 0.167.1-duotest.2 → 0.167.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.
Files changed (43) hide show
  1. package/Application/BottomSheet.tsx +14 -115
  2. package/Application/BottomTab/BottomTabBar.tsx +3 -6
  3. package/Application/BottomTab/index.tsx +135 -162
  4. package/Application/Components/HeaderExtendHeader.tsx +28 -75
  5. package/Application/Components/HeaderLeft.tsx +0 -38
  6. package/Application/Components/HeaderRight.tsx +9 -142
  7. package/Application/Components/HeaderTitle.tsx +7 -43
  8. package/Application/Components/SearchHeader.tsx +15 -22
  9. package/Application/ModalScreen.tsx +2 -16
  10. package/Application/NavigationContainer.tsx +38 -22
  11. package/Application/Navigator.ts +3 -1
  12. package/Application/StackScreen.tsx +9 -63
  13. package/Application/types.ts +1 -27
  14. package/Application/utils.tsx +6 -242
  15. package/Consts/theme.ts +4 -3
  16. package/Input/Input.tsx +4 -3
  17. package/Input/InputMoney.tsx +8 -5
  18. package/Input/InputPhoneNumber.tsx +4 -3
  19. package/Input/InputSearch.tsx +4 -3
  20. package/Input/InputTextArea.tsx +4 -3
  21. package/Layout/Card.tsx +26 -8
  22. package/Layout/FloatingButton.tsx +3 -3
  23. package/Layout/GridSystem.tsx +137 -0
  24. package/Layout/Item.tsx +10 -1
  25. package/Layout/Screen.tsx +52 -199
  26. package/Layout/Section.tsx +18 -5
  27. package/Layout/index.ts +4 -4
  28. package/Layout/styles.ts +0 -5
  29. package/Layout/utils.ts +5 -465
  30. package/Popup/PopupNotify.tsx +22 -33
  31. package/Popup/PopupPromotion.tsx +8 -20
  32. package/Text/utils.ts +2 -1
  33. package/package.json +2 -3
  34. package/Application/BottomTab/DuoBarCompression.tsx +0 -135
  35. package/Application/BottomTab/DuoTabBar.tsx +0 -462
  36. package/Application/Components/DuoGlassBackground.tsx +0 -179
  37. package/Application/Components/DuoQuickReturnHeader.tsx +0 -42
  38. package/Application/Components/OptionalGlassView.ts +0 -11
  39. package/Application/Components/OptionalScrollObserver.ts +0 -12
  40. package/Application/Components/SideBarButton.tsx +0 -139
  41. package/Application/device.ts +0 -29
  42. package/Layout/ReservedRegions.tsx +0 -47
  43. package/Popup/utils.ts +0 -32
@@ -1,135 +0,0 @@
1
- import React, {
2
- createContext,
3
- ReactNode,
4
- useContext,
5
- useMemo,
6
- useState,
7
- } from 'react';
8
- import { Spacing } from '../../Consts';
9
- import { useScreenLayout } from '../../Layout';
10
- import { DuoMetrics } from '../../Layout/utils';
11
-
12
- export type DuoBarCompressionBehavior = 'toolbar' | 'tabBar';
13
-
14
- type DuoBarCompression = {
15
- toolbarCompressed: boolean;
16
- tabBarCompressed: boolean;
17
- // the open rail needs the toolbar button slot too: only Back stays
18
- toolbarHidden?: boolean;
19
- // tapping the compressed bar expands it and compresses the other one
20
- expandToolbar?: () => void;
21
- expandTabBar?: () => void;
22
- // a rail opened from the compressed state closes once a tab is picked
23
- onTabSelected?: () => void;
24
- // header buttons report their full height so the tab bar knows what is left
25
- setToolbarHeight?: (height: number) => void;
26
- };
27
-
28
- const RAIL_PADDING = 6;
29
- const RAIL_GAP = Spacing.XXS;
30
- // space kept between the top and the bottom groups (HIG Vertical controls)
31
- const GROUP_GAP = Spacing.M;
32
- // Back, then the right toolkit: help + home capsule
33
- const DEFAULT_TOOLBAR_HEIGHT =
34
- DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch;
35
- // Back, then the single button the toolbar folds into
36
- const COMPRESSED_TOOLBAR_HEIGHT = 2 * DuoMetrics.controlSize + Spacing.M;
37
- // Back alone, while a rail opened from the compressed state takes the toolbar slot
38
- const BACK_ONLY_HEIGHT = DuoMetrics.controlSize;
39
-
40
- const NO_COMPRESSION: DuoBarCompression = {
41
- toolbarCompressed: false,
42
- tabBarCompressed: false,
43
- };
44
-
45
- const DuoBarCompressionContext =
46
- createContext<DuoBarCompression>(NO_COMPRESSION);
47
-
48
- /**
49
- * height of the vertical tab rail, plus the QR button under it
50
- */
51
- const getRailHeight = (tabCount: number, hasQr: boolean) =>
52
- 2 * RAIL_PADDING +
53
- tabCount * DuoMetrics.controlSize +
54
- Math.max(tabCount - 1, 0) * RAIL_GAP +
55
- (hasQr ? Spacing.M + DuoMetrics.controlSize : 0);
56
-
57
- /**
58
- * keeps the side bar's toolbar and tab bar clear of each other (HIG, Designing for
59
- * iPhone Duo: Vertical controls):
60
- * - both fit: both stay whole
61
- * - one fits whole beside the other compressed: one is compressed, tapping it swaps them
62
- * - otherwise both rest compressed; tapping one opens it for a moment (the rail closes
63
- * again once a tab is picked)
64
- */
65
- const DuoBarCompressionProvider: React.FC<{
66
- behavior?: DuoBarCompressionBehavior;
67
- tabCount: number;
68
- hasQr: boolean;
69
- children: ReactNode;
70
- }> = ({ behavior = 'toolbar', tabCount, hasQr, children }) => {
71
- const { height, sideBar } = useScreenLayout();
72
- const [toolbarHeight, setToolbarHeight] = useState(DEFAULT_TOOLBAR_HEIGHT);
73
- // 'toolbar' compression keeps the tab bar whole until the toolbar is tapped
74
- const [expanded, setExpanded] = useState<'toolbar' | 'tabBar'>(
75
- behavior === 'toolbar' ? 'tabBar' : 'toolbar',
76
- );
77
- // bar opened for a moment while both rest compressed
78
- const [opened, setOpened] = useState<'toolbar' | 'tabBar'>();
79
-
80
- const value = useMemo(() => {
81
- if (!sideBar) {
82
- return NO_COMPRESSION;
83
- }
84
- const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
85
- const tabBar = getRailHeight(tabCount, hasQr);
86
- const compressedTabBar = getRailHeight(1, hasQr) - 2 * RAIL_PADDING;
87
- const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
88
-
89
- if (fits(toolbarHeight, tabBar)) {
90
- return { ...NO_COMPRESSION, setToolbarHeight };
91
- }
92
- const canSwap =
93
- fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar) &&
94
- fits(toolbarHeight, compressedTabBar);
95
- if (canSwap) {
96
- return {
97
- toolbarCompressed: expanded === 'tabBar',
98
- tabBarCompressed: expanded === 'toolbar',
99
- expandToolbar: () => setExpanded('toolbar'),
100
- expandTabBar: () => setExpanded('tabBar'),
101
- setToolbarHeight,
102
- };
103
- }
104
-
105
- const open = {
106
- expandToolbar: () => setOpened('toolbar'),
107
- expandTabBar: () => setOpened('tabBar'),
108
- setToolbarHeight,
109
- };
110
- if (opened === 'tabBar' && fits(BACK_ONLY_HEIGHT, tabBar)) {
111
- return {
112
- ...open,
113
- toolbarCompressed: true,
114
- toolbarHidden: true,
115
- tabBarCompressed: false,
116
- onTabSelected: () => setOpened(undefined),
117
- };
118
- }
119
- return {
120
- ...open,
121
- toolbarCompressed: opened !== 'toolbar',
122
- tabBarCompressed: true,
123
- };
124
- }, [expanded, opened, hasQr, height, sideBar, tabCount, toolbarHeight]);
125
-
126
- return (
127
- <DuoBarCompressionContext.Provider value={value}>
128
- {children}
129
- </DuoBarCompressionContext.Provider>
130
- );
131
- };
132
-
133
- const useDuoBarCompression = () => useContext(DuoBarCompressionContext);
134
-
135
- export { DuoBarCompressionProvider, useDuoBarCompression };
@@ -1,462 +0,0 @@
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 };