@onekeyfe/react-native-bottom-tabs 6.7.0-18 → 6.7.0-20
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/android/src/main/java/com/onekey/bottomtabs/BottomTabsDrawerManager.kt +55 -0
- package/android/src/main/java/com/onekey/bottomtabs/BottomTabsDrawerView.kt +496 -0
- package/android/src/main/java/com/onekey/bottomtabs/BottomTabsPackage.kt +1 -1
- package/android/src/main/java/com/onekey/bottomtabs/BottomTabsViewManager.kt +3 -0
- package/docs/SPEC.md +163 -2
- package/ios/RCTBottomTabsComponentView.mm +37 -0
- package/ios/RCTBottomTabsContainerView.swift +192 -7
- package/ios/RCTBottomTabsDrawerComponentView.h +8 -0
- package/ios/RCTBottomTabsDrawerComponentView.mm +79 -0
- package/ios/RCTBottomTabsDrawerContainerView.swift +327 -0
- package/lib/module/BottomTabs.js +144 -87
- package/lib/module/BottomTabsDrawerNativeComponent.ts +19 -0
- package/lib/module/BottomTabsDrawerNativeComponent.web.js +13 -0
- package/lib/module/BottomTabsNativeComponent.ts +13 -0
- package/lib/typescript/src/BottomTabs.d.ts +36 -10
- package/lib/typescript/src/BottomTabsDrawerNativeComponent.d.ts +15 -0
- package/lib/typescript/src/BottomTabsDrawerNativeComponent.web.d.ts +4 -0
- package/lib/typescript/src/BottomTabsNativeComponent.d.ts +8 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/types.d.ts +1 -0
- package/package.json +3 -2
- package/react-native.config.js +4 -1
- package/src/BottomTabs.tsx +235 -115
- package/src/BottomTabsDrawerNativeComponent.ts +19 -0
- package/src/BottomTabsDrawerNativeComponent.web.tsx +9 -0
- package/src/BottomTabsNativeComponent.ts +13 -0
- package/src/index.tsx +1 -1
- package/src/types.ts +3 -0
package/src/BottomTabs.tsx
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
import React, { useLayoutEffect, useRef } from
|
|
2
|
-
import type { NativeSyntheticEvent } from
|
|
1
|
+
import React, { useLayoutEffect, useRef } from "react";
|
|
2
|
+
import type { NativeSyntheticEvent } from "react-native";
|
|
3
3
|
import {
|
|
4
4
|
type ColorValue,
|
|
5
5
|
type DimensionValue,
|
|
6
6
|
Image,
|
|
7
|
+
type ImageSourcePropType,
|
|
7
8
|
Platform,
|
|
8
9
|
type StyleProp,
|
|
9
10
|
StyleSheet,
|
|
10
11
|
View,
|
|
11
12
|
type ViewStyle,
|
|
12
13
|
processColor,
|
|
13
|
-
} from
|
|
14
|
-
import { BottomTabBarHeightContext } from
|
|
14
|
+
} from "react-native";
|
|
15
|
+
import { BottomTabBarHeightContext } from "./utils/BottomTabBarHeightContext";
|
|
15
16
|
|
|
16
17
|
// eslint-disable-next-line @react-native/no-deep-imports
|
|
17
|
-
import type { ImageSource } from
|
|
18
|
+
import type { ImageSource } from "react-native/Libraries/Image/ImageSource";
|
|
18
19
|
import NativeBottomTabs, {
|
|
19
20
|
type BottomTabsItems,
|
|
20
|
-
} from
|
|
21
|
-
import useLatestCallback from
|
|
22
|
-
import type {
|
|
23
|
-
|
|
21
|
+
} from "./BottomTabsNativeComponent";
|
|
22
|
+
import useLatestCallback from "use-latest-callback";
|
|
23
|
+
import type {
|
|
24
|
+
AppleIcon,
|
|
25
|
+
BaseRoute,
|
|
26
|
+
NavigationState,
|
|
27
|
+
TabBarPlacement,
|
|
28
|
+
TabRole,
|
|
29
|
+
} from "./types";
|
|
30
|
+
import DelayedFreeze from "./DelayedFreeze";
|
|
31
|
+
import NativeBottomTabsDrawer from "./BottomTabsDrawerNativeComponent";
|
|
24
32
|
import {
|
|
25
33
|
BottomAccessoryView,
|
|
26
34
|
type RNBottomTabsAccessoryViewProps,
|
|
27
|
-
} from
|
|
35
|
+
} from "./BottomAccessoryView";
|
|
28
36
|
|
|
29
37
|
const isAppleSymbol = (icon: any): icon is { sfSymbol: string } =>
|
|
30
38
|
icon?.sfSymbol;
|
|
@@ -34,6 +42,14 @@ interface Props<Route extends BaseRoute> {
|
|
|
34
42
|
* Whether to show labels in tabs. When false, only icons will be displayed.
|
|
35
43
|
*/
|
|
36
44
|
labeled?: boolean;
|
|
45
|
+
/** Optional physical-left menu; all motion and input are owned by native. */
|
|
46
|
+
renderDrawerContent?: () => React.ReactNode;
|
|
47
|
+
/** External drawer target. Mirror onDrawerOpenChange for user transitions. */
|
|
48
|
+
drawerOpen?: boolean;
|
|
49
|
+
/** Enables root-screen content swipes after inner horizontal views reach their boundary. */
|
|
50
|
+
drawerSwipeEnabled?: boolean;
|
|
51
|
+
/** Reports a completed user target so the external open state can follow it. */
|
|
52
|
+
onDrawerOpenChange?: (open: boolean) => void;
|
|
37
53
|
/**
|
|
38
54
|
* A tab bar style that adapts to each platform.
|
|
39
55
|
*
|
|
@@ -56,12 +72,12 @@ interface Props<Route extends BaseRoute> {
|
|
|
56
72
|
/**
|
|
57
73
|
* Describes the appearance attributes for the tabBar to use when an observable scroll view is scrolled to the bottom. (iOS only)
|
|
58
74
|
*/
|
|
59
|
-
scrollEdgeAppearance?:
|
|
75
|
+
scrollEdgeAppearance?: "default" | "opaque" | "transparent";
|
|
60
76
|
|
|
61
77
|
/**
|
|
62
78
|
* Behavior for minimizing the tab bar. (iOS 26+)
|
|
63
79
|
*/
|
|
64
|
-
minimizeBehavior?:
|
|
80
|
+
minimizeBehavior?: "automatic" | "onScrollDown" | "onScrollUp" | "never";
|
|
65
81
|
/**
|
|
66
82
|
* Active tab color.
|
|
67
83
|
*/
|
|
@@ -190,7 +206,7 @@ interface Props<Route extends BaseRoute> {
|
|
|
190
206
|
* A function that returns a React element to display as bottom accessory view.
|
|
191
207
|
* iOS 26+ only.
|
|
192
208
|
*/
|
|
193
|
-
renderBottomAccessoryView?: RNBottomTabsAccessoryViewProps[
|
|
209
|
+
renderBottomAccessoryView?: RNBottomTabsAccessoryViewProps["renderBottomAccessoryView"];
|
|
194
210
|
/**
|
|
195
211
|
* Whether the tab bar is hidden.
|
|
196
212
|
*/
|
|
@@ -202,7 +218,7 @@ interface Props<Route extends BaseRoute> {
|
|
|
202
218
|
/**
|
|
203
219
|
* Visual style of the Android tab bar. Android only.
|
|
204
220
|
*/
|
|
205
|
-
androidTabBarStyle?:
|
|
221
|
+
androidTabBarStyle?: "default" | "floating";
|
|
206
222
|
/**
|
|
207
223
|
* Whether the tab bar ignores the soft keyboard. Defaults to true, so the
|
|
208
224
|
* keyboard covers the tab bar as on iOS. Set to false to pad the tab bar by
|
|
@@ -215,6 +231,24 @@ interface Props<Route extends BaseRoute> {
|
|
|
215
231
|
* original delayed behavior.
|
|
216
232
|
*/
|
|
217
233
|
delayedFreeze?: boolean;
|
|
234
|
+
/**
|
|
235
|
+
* Called when the system moves the tab bar between the bottom edge and a
|
|
236
|
+
* vertical bar on the leading or trailing edge (iPhone Duo). iOS only.
|
|
237
|
+
*/
|
|
238
|
+
onTabBarPlacementChange?: (placement: TabBarPlacement) => void;
|
|
239
|
+
/**
|
|
240
|
+
* Icon of a round button shown above a vertical tab bar. The button only
|
|
241
|
+
* appears while the tab bar is vertical and visible. iOS only.
|
|
242
|
+
*/
|
|
243
|
+
sideAccessoryIcon?: ImageSourcePropType;
|
|
244
|
+
/**
|
|
245
|
+
* Accessibility label of the side accessory button.
|
|
246
|
+
*/
|
|
247
|
+
sideAccessoryLabel?: string;
|
|
248
|
+
/**
|
|
249
|
+
* Called when the side accessory button is pressed.
|
|
250
|
+
*/
|
|
251
|
+
onSideAccessoryPress?: () => void;
|
|
218
252
|
}
|
|
219
253
|
|
|
220
254
|
const ANDROID_MAX_TABS = 100;
|
|
@@ -225,6 +259,10 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
225
259
|
renderLazyPlaceholder,
|
|
226
260
|
onIndexChange,
|
|
227
261
|
onTabDoublePress,
|
|
262
|
+
renderDrawerContent,
|
|
263
|
+
drawerOpen = false,
|
|
264
|
+
drawerSwipeEnabled = false,
|
|
265
|
+
onDrawerOpenChange,
|
|
228
266
|
rippleColor,
|
|
229
267
|
tabBarActiveTintColor: activeTintColor,
|
|
230
268
|
tabBarInactiveTintColor: inactiveTintColor,
|
|
@@ -247,30 +285,41 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
247
285
|
getSceneStyle = ({ route }: { route: Route }) => route.style,
|
|
248
286
|
getPreventsDefault = ({ route }: { route: Route }) => route.preventsDefault,
|
|
249
287
|
hapticFeedbackEnabled = false,
|
|
250
|
-
labeled = Platform.OS !==
|
|
288
|
+
labeled = Platform.OS !== "android" ? true : undefined,
|
|
251
289
|
getFreezeOnBlur = ({ route }: { route: Route }) => route.freezeOnBlur,
|
|
252
290
|
tabBar: renderCustomTabBar,
|
|
253
291
|
tabBarStyle,
|
|
254
292
|
tabLabelStyle,
|
|
255
293
|
renderBottomAccessoryView,
|
|
256
294
|
activeIndicatorColor,
|
|
257
|
-
androidBottomBlurEnabled = Platform.OS ===
|
|
295
|
+
androidBottomBlurEnabled = Platform.OS === "android" &&
|
|
258
296
|
Number(Platform.Version) >= 36,
|
|
259
297
|
delayedFreeze = false,
|
|
298
|
+
onTabBarPlacementChange,
|
|
299
|
+
sideAccessoryIcon,
|
|
300
|
+
sideAccessoryLabel,
|
|
301
|
+
onSideAccessoryPress,
|
|
260
302
|
...props
|
|
261
303
|
}: Props<Route>) => {
|
|
262
304
|
// @ts-ignore
|
|
263
305
|
const focusedKey = navigationState.routes[navigationState.index].key;
|
|
264
306
|
const customTabBarWrapperRef = useRef<View>(null) as React.RefObject<any>;
|
|
265
|
-
const nativeBottomTabsRef =
|
|
266
|
-
|
|
307
|
+
const nativeBottomTabsRef =
|
|
308
|
+
useRef<React.ElementRef<typeof NativeBottomTabs>>(null);
|
|
309
|
+
const [nativeTabBarHeight, setNativeTabBarHeight] = React.useState(0);
|
|
310
|
+
const [customTabBarHeight, setCustomTabBarHeight] = React.useState(0);
|
|
311
|
+
const tabBarHeight = renderCustomTabBar
|
|
312
|
+
? customTabBarHeight
|
|
313
|
+
: props.tabBarHidden
|
|
314
|
+
? 0
|
|
315
|
+
: nativeTabBarHeight;
|
|
267
316
|
const [measuredDimensions, setMeasuredDimensions] = React.useState<
|
|
268
317
|
{ width: DimensionValue; height: DimensionValue } | undefined
|
|
269
|
-
>({ width:
|
|
318
|
+
>({ width: "100%", height: "100%" });
|
|
270
319
|
|
|
271
320
|
const trimmedRoutes = React.useMemo(() => {
|
|
272
321
|
if (
|
|
273
|
-
Platform.OS ===
|
|
322
|
+
Platform.OS === "android" &&
|
|
274
323
|
navigationState.routes.length > ANDROID_MAX_TABS
|
|
275
324
|
) {
|
|
276
325
|
console.warn(
|
|
@@ -287,7 +336,7 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
287
336
|
setLoaded((loaded) => [...loaded, focusedKey]);
|
|
288
337
|
}
|
|
289
338
|
|
|
290
|
-
const isIOS = Platform.OS ===
|
|
339
|
+
const isIOS = Platform.OS === "ios";
|
|
291
340
|
|
|
292
341
|
// Unfocused (outline) icons — independent of focusedKey so the array stays
|
|
293
342
|
// stable across tab switches and the native side never reloads on switch.
|
|
@@ -324,9 +373,9 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
324
373
|
const icon = icons[index];
|
|
325
374
|
const isSfSymbol = isAppleSymbol(icon);
|
|
326
375
|
|
|
327
|
-
if (Platform.OS ===
|
|
376
|
+
if (Platform.OS === "android" && isSfSymbol) {
|
|
328
377
|
console.warn(
|
|
329
|
-
|
|
378
|
+
"SF Symbols are not supported on Android. Use require() or pass uri to load an image instead."
|
|
330
379
|
);
|
|
331
380
|
}
|
|
332
381
|
|
|
@@ -340,10 +389,10 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
340
389
|
sfSymbol: isSfSymbol ? icon.sfSymbol : undefined,
|
|
341
390
|
badge: getBadge?.({ route }),
|
|
342
391
|
badgeBackgroundColor:
|
|
343
|
-
typeof badgeBgColor ===
|
|
392
|
+
typeof badgeBgColor === "number" ? badgeBgColor : undefined,
|
|
344
393
|
badgeTextColor:
|
|
345
|
-
typeof badgeTxtColor ===
|
|
346
|
-
activeTintColor: typeof color ===
|
|
394
|
+
typeof badgeTxtColor === "number" ? badgeTxtColor : undefined,
|
|
395
|
+
activeTintColor: typeof color === "number" ? color : undefined,
|
|
347
396
|
hidden: getHidden?.({ route }),
|
|
348
397
|
testID: getTestID?.({ route }),
|
|
349
398
|
role: getRole?.({ route }),
|
|
@@ -372,13 +421,13 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
372
421
|
// @ts-ignore - resolveAssetSource accepts ImageSourcePropType
|
|
373
422
|
const resolved = Image.resolveAssetSource(icon);
|
|
374
423
|
return {
|
|
375
|
-
uri: resolved?.uri ??
|
|
424
|
+
uri: resolved?.uri ?? "",
|
|
376
425
|
width: resolved?.width ?? 0,
|
|
377
426
|
height: resolved?.height ?? 0,
|
|
378
427
|
scale: resolved?.scale ?? 1,
|
|
379
428
|
};
|
|
380
429
|
}
|
|
381
|
-
return { uri:
|
|
430
|
+
return { uri: "", width: 0, height: 0, scale: 1 };
|
|
382
431
|
}),
|
|
383
432
|
[icons]
|
|
384
433
|
);
|
|
@@ -392,13 +441,13 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
392
441
|
// @ts-ignore - resolveAssetSource accepts ImageSourcePropType
|
|
393
442
|
const resolved = Image.resolveAssetSource(icon);
|
|
394
443
|
return {
|
|
395
|
-
uri: resolved?.uri ??
|
|
444
|
+
uri: resolved?.uri ?? "",
|
|
396
445
|
width: resolved?.width ?? 0,
|
|
397
446
|
height: resolved?.height ?? 0,
|
|
398
447
|
scale: resolved?.scale ?? 1,
|
|
399
448
|
};
|
|
400
449
|
}
|
|
401
|
-
return { uri:
|
|
450
|
+
return { uri: "", width: 0, height: 0, scale: 1 };
|
|
402
451
|
}),
|
|
403
452
|
[focusedIcons]
|
|
404
453
|
);
|
|
@@ -427,17 +476,19 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
427
476
|
const { key } = event.nativeEvent;
|
|
428
477
|
const accepted = jumpTo(key);
|
|
429
478
|
if (accepted === false) {
|
|
430
|
-
nativeBottomTabsRef.current?.setNativeProps({
|
|
479
|
+
nativeBottomTabsRef.current?.setNativeProps({
|
|
480
|
+
selectedPage: focusedKey,
|
|
481
|
+
});
|
|
431
482
|
}
|
|
432
483
|
},
|
|
433
|
-
[focusedKey, jumpTo]
|
|
484
|
+
[focusedKey, jumpTo]
|
|
434
485
|
);
|
|
435
486
|
|
|
436
487
|
const handleTabBarMeasured = React.useCallback(
|
|
437
488
|
(event: NativeSyntheticEvent<{ height: number }>) => {
|
|
438
|
-
|
|
489
|
+
setNativeTabBarHeight(event.nativeEvent.height);
|
|
439
490
|
},
|
|
440
|
-
[
|
|
491
|
+
[setNativeTabBarHeight]
|
|
441
492
|
);
|
|
442
493
|
|
|
443
494
|
const handleNativeLayout = React.useCallback(
|
|
@@ -448,111 +499,180 @@ const BottomTabs = <Route extends BaseRoute>({
|
|
|
448
499
|
[setMeasuredDimensions]
|
|
449
500
|
);
|
|
450
501
|
|
|
502
|
+
const handleTabBarPlacementChange = React.useCallback(
|
|
503
|
+
(event: NativeSyntheticEvent<{ placement: string }>) => {
|
|
504
|
+
onTabBarPlacementChange?.(event.nativeEvent.placement as TabBarPlacement);
|
|
505
|
+
},
|
|
506
|
+
[onTabBarPlacementChange]
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const handleSideAccessoryPress = React.useCallback(() => {
|
|
510
|
+
onSideAccessoryPress?.();
|
|
511
|
+
}, [onSideAccessoryPress]);
|
|
512
|
+
|
|
513
|
+
const resolvedSideAccessoryIcon = React.useMemo(() => {
|
|
514
|
+
if (!isIOS || !sideAccessoryIcon) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
// @ts-ignore - resolveAssetSource accepts ImageSourcePropType
|
|
518
|
+
const resolved = Image.resolveAssetSource(sideAccessoryIcon);
|
|
519
|
+
return resolved?.uri
|
|
520
|
+
? {
|
|
521
|
+
uri: resolved.uri,
|
|
522
|
+
width: resolved.width ?? 0,
|
|
523
|
+
height: resolved.height ?? 0,
|
|
524
|
+
scale: resolved.scale ?? 1,
|
|
525
|
+
}
|
|
526
|
+
: undefined;
|
|
527
|
+
}, [sideAccessoryIcon]);
|
|
528
|
+
|
|
451
529
|
useLayoutEffect(() => {
|
|
452
530
|
if (renderCustomTabBar && customTabBarWrapperRef.current) {
|
|
453
|
-
customTabBarWrapperRef.current.measure(
|
|
454
|
-
|
|
455
|
-
|
|
531
|
+
customTabBarWrapperRef.current.measure(
|
|
532
|
+
(_x: number, _y: number, _width: number, height: number) => {
|
|
533
|
+
setCustomTabBarHeight(height);
|
|
534
|
+
}
|
|
535
|
+
);
|
|
456
536
|
}
|
|
457
537
|
}, [renderCustomTabBar]);
|
|
458
538
|
|
|
459
539
|
return (
|
|
460
540
|
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
|
461
|
-
<
|
|
462
|
-
ref={nativeBottomTabsRef}
|
|
463
|
-
{...props}
|
|
464
|
-
{...tabLabelStyle}
|
|
541
|
+
<NativeBottomTabsDrawer
|
|
465
542
|
style={styles.fullWidth}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
isIOS && !renderCustomTabBar ? resolvedSelectedIconAssets : undefined
|
|
470
|
-
}
|
|
471
|
-
selectedPage={focusedKey}
|
|
472
|
-
tabBarHidden={props.tabBarHidden ?? !!renderCustomTabBar}
|
|
473
|
-
onTabDoublePress={handleTabDoublePress}
|
|
474
|
-
onPageSelected={handlePageSelected}
|
|
475
|
-
onTabBarMeasured={handleTabBarMeasured}
|
|
476
|
-
onNativeLayout={handleNativeLayout}
|
|
543
|
+
enabled={!!renderDrawerContent}
|
|
544
|
+
open={drawerOpen}
|
|
545
|
+
swipeEnabled={drawerSwipeEnabled}
|
|
477
546
|
hapticFeedbackEnabled={hapticFeedbackEnabled}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
rippleColor={rippleColor}
|
|
482
|
-
activeIndicatorColor={activeIndicatorColor}
|
|
483
|
-
androidBottomBlurEnabled={
|
|
484
|
-
Platform.OS === 'android' &&
|
|
485
|
-
!renderCustomTabBar &&
|
|
486
|
-
androidBottomBlurEnabled
|
|
547
|
+
bottomBarHeight={tabBarHeight ?? 0}
|
|
548
|
+
onDrawerOpenChange={(event) =>
|
|
549
|
+
onDrawerOpenChange?.(event.nativeEvent.open)
|
|
487
550
|
}
|
|
488
|
-
labeled={labeled}
|
|
489
551
|
>
|
|
490
|
-
{
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
552
|
+
<View collapsable={false} style={styles.drawerMenu}>
|
|
553
|
+
{Platform.OS !== "web" ? renderDrawerContent?.() : null}
|
|
554
|
+
</View>
|
|
555
|
+
<View collapsable={false} style={styles.fullWidth}>
|
|
556
|
+
<NativeBottomTabs
|
|
557
|
+
ref={nativeBottomTabsRef}
|
|
558
|
+
{...props}
|
|
559
|
+
{...tabLabelStyle}
|
|
560
|
+
style={styles.fullWidth}
|
|
561
|
+
items={items}
|
|
562
|
+
icons={renderCustomTabBar ? undefined : resolvedIconAssets}
|
|
563
|
+
selectedIcons={
|
|
564
|
+
isIOS && !renderCustomTabBar
|
|
565
|
+
? resolvedSelectedIconAssets
|
|
566
|
+
: undefined
|
|
567
|
+
}
|
|
568
|
+
selectedPage={focusedKey}
|
|
569
|
+
tabBarHidden={props.tabBarHidden ?? !!renderCustomTabBar}
|
|
570
|
+
onTabDoublePress={handleTabDoublePress}
|
|
571
|
+
onPageSelected={handlePageSelected}
|
|
572
|
+
onTabBarMeasured={handleTabBarMeasured}
|
|
573
|
+
onNativeLayout={handleNativeLayout}
|
|
574
|
+
onTabBarPlacementChange={handleTabBarPlacementChange}
|
|
575
|
+
sideAccessoryIcon={
|
|
576
|
+
renderCustomTabBar ? undefined : resolvedSideAccessoryIcon
|
|
577
|
+
}
|
|
578
|
+
sideAccessoryLabel={sideAccessoryLabel}
|
|
579
|
+
onSideAccessoryPress={handleSideAccessoryPress}
|
|
580
|
+
hapticFeedbackEnabled={hapticFeedbackEnabled}
|
|
581
|
+
activeTintColor={activeTintColor}
|
|
582
|
+
inactiveTintColor={inactiveTintColor}
|
|
583
|
+
barTintColor={tabBarStyle?.backgroundColor}
|
|
584
|
+
rippleColor={rippleColor}
|
|
585
|
+
activeIndicatorColor={activeIndicatorColor}
|
|
586
|
+
androidBottomBlurEnabled={
|
|
587
|
+
Platform.OS === "android" &&
|
|
588
|
+
!renderCustomTabBar &&
|
|
589
|
+
androidBottomBlurEnabled
|
|
590
|
+
}
|
|
591
|
+
labeled={labeled}
|
|
592
|
+
>
|
|
593
|
+
{trimmedRoutes.map((route) => {
|
|
594
|
+
if (getLazy({ route }) !== false && !loaded.includes(route.key)) {
|
|
595
|
+
return (
|
|
596
|
+
<View
|
|
597
|
+
key={route.key}
|
|
598
|
+
collapsable={false}
|
|
599
|
+
style={styles.fullWidth}
|
|
600
|
+
>
|
|
601
|
+
{renderLazyPlaceholder?.({ route })}
|
|
602
|
+
</View>
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const focused = route.key === focusedKey;
|
|
607
|
+
const freeze = !focused ? getFreezeOnBlur({ route }) : false;
|
|
608
|
+
|
|
609
|
+
const customStyle = getSceneStyle({ route });
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
<View
|
|
613
|
+
key={route.key}
|
|
614
|
+
style={[
|
|
615
|
+
styles.screen,
|
|
616
|
+
renderCustomTabBar ? styles.fullWidth : measuredDimensions,
|
|
617
|
+
customStyle,
|
|
618
|
+
]}
|
|
619
|
+
collapsable={false}
|
|
620
|
+
pointerEvents={focused ? "auto" : "none"}
|
|
621
|
+
accessibilityElementsHidden={!focused}
|
|
622
|
+
importantForAccessibility={
|
|
623
|
+
focused ? "auto" : "no-hide-descendants"
|
|
624
|
+
}
|
|
625
|
+
>
|
|
626
|
+
<DelayedFreeze
|
|
627
|
+
freeze={!!freeze}
|
|
628
|
+
delayedFreeze={delayedFreeze}
|
|
629
|
+
>
|
|
630
|
+
{renderScene({
|
|
631
|
+
route,
|
|
632
|
+
jumpTo,
|
|
633
|
+
})}
|
|
634
|
+
</DelayedFreeze>
|
|
635
|
+
</View>
|
|
636
|
+
);
|
|
637
|
+
})}
|
|
638
|
+
{Platform.OS === "ios" &&
|
|
639
|
+
parseFloat(Platform.Version) >= 26 &&
|
|
640
|
+
renderBottomAccessoryView &&
|
|
641
|
+
!renderCustomTabBar ? (
|
|
642
|
+
<BottomAccessoryView
|
|
643
|
+
renderBottomAccessoryView={renderBottomAccessoryView}
|
|
644
|
+
/>
|
|
645
|
+
) : null}
|
|
646
|
+
</NativeBottomTabs>
|
|
647
|
+
{renderCustomTabBar ? (
|
|
509
648
|
<View
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
renderCustomTabBar ? styles.fullWidth : measuredDimensions,
|
|
514
|
-
customStyle,
|
|
515
|
-
]}
|
|
516
|
-
collapsable={false}
|
|
517
|
-
pointerEvents={focused ? 'auto' : 'none'}
|
|
518
|
-
accessibilityElementsHidden={!focused}
|
|
519
|
-
importantForAccessibility={
|
|
520
|
-
focused ? 'auto' : 'no-hide-descendants'
|
|
649
|
+
ref={customTabBarWrapperRef}
|
|
650
|
+
onLayout={(event) =>
|
|
651
|
+
setCustomTabBarHeight(event.nativeEvent.layout.height)
|
|
521
652
|
}
|
|
522
653
|
>
|
|
523
|
-
|
|
524
|
-
{renderScene({
|
|
525
|
-
route,
|
|
526
|
-
jumpTo,
|
|
527
|
-
})}
|
|
528
|
-
</DelayedFreeze>
|
|
654
|
+
{renderCustomTabBar()}
|
|
529
655
|
</View>
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
parseFloat(Platform.Version) >= 26 &&
|
|
534
|
-
renderBottomAccessoryView &&
|
|
535
|
-
!renderCustomTabBar ? (
|
|
536
|
-
<BottomAccessoryView
|
|
537
|
-
renderBottomAccessoryView={renderBottomAccessoryView}
|
|
538
|
-
/>
|
|
539
|
-
) : null}
|
|
540
|
-
</NativeBottomTabs>
|
|
541
|
-
{renderCustomTabBar ? (
|
|
542
|
-
<View ref={customTabBarWrapperRef}>{renderCustomTabBar()}</View>
|
|
543
|
-
) : null}
|
|
656
|
+
) : null}
|
|
657
|
+
</View>
|
|
658
|
+
</NativeBottomTabsDrawer>
|
|
544
659
|
</BottomTabBarHeightContext.Provider>
|
|
545
660
|
);
|
|
546
661
|
};
|
|
547
662
|
|
|
548
663
|
const styles = StyleSheet.create({
|
|
549
664
|
fullWidth: {
|
|
550
|
-
width:
|
|
551
|
-
height:
|
|
665
|
+
width: "100%",
|
|
666
|
+
height: "100%",
|
|
552
667
|
flex: 1,
|
|
553
668
|
},
|
|
669
|
+
drawerMenu: {
|
|
670
|
+
position: "absolute",
|
|
671
|
+
width: "78%",
|
|
672
|
+
height: "100%",
|
|
673
|
+
},
|
|
554
674
|
screen: {
|
|
555
|
-
position:
|
|
675
|
+
position: "absolute",
|
|
556
676
|
},
|
|
557
677
|
});
|
|
558
678
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { codegenNativeComponent, type ViewProps } from "react-native";
|
|
2
|
+
import type {
|
|
3
|
+
DirectEventHandler,
|
|
4
|
+
Double,
|
|
5
|
+
WithDefault,
|
|
6
|
+
} from "react-native/Libraries/Types/CodegenTypes";
|
|
7
|
+
|
|
8
|
+
export interface BottomTabsDrawerNativeProps extends ViewProps {
|
|
9
|
+
enabled?: WithDefault<boolean, false>;
|
|
10
|
+
open?: WithDefault<boolean, false>;
|
|
11
|
+
swipeEnabled?: WithDefault<boolean, false>;
|
|
12
|
+
hapticFeedbackEnabled?: WithDefault<boolean, false>;
|
|
13
|
+
bottomBarHeight?: Double;
|
|
14
|
+
onDrawerOpenChange?: DirectEventHandler<Readonly<{ open: boolean }>>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default codegenNativeComponent<BottomTabsDrawerNativeProps>(
|
|
18
|
+
"RNBottomTabsDrawer"
|
|
19
|
+
);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { BottomTabsDrawerNativeProps } from "./BottomTabsDrawerNativeComponent";
|
|
3
|
+
|
|
4
|
+
// The fixed first slot is the menu; Web only renders the foreground slot.
|
|
5
|
+
export default function BottomTabsDrawerWeb({
|
|
6
|
+
children,
|
|
7
|
+
}: React.PropsWithChildren<BottomTabsDrawerNativeProps>) {
|
|
8
|
+
return <>{React.Children.toArray(children)[1]}</>;
|
|
9
|
+
}
|
|
@@ -22,6 +22,14 @@ export type OnNativeLayout = Readonly<{
|
|
|
22
22
|
height: Double;
|
|
23
23
|
}>;
|
|
24
24
|
|
|
25
|
+
// Where the system draws the tab bar: 'bottom', or 'leading' / 'trailing' for
|
|
26
|
+
// a vertical bar (the iPhone Duo side strip, iOS 27.1+).
|
|
27
|
+
export type OnTabBarPlacementChange = Readonly<{
|
|
28
|
+
placement: string;
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
export type OnSideAccessoryPress = Readonly<{}>;
|
|
32
|
+
|
|
25
33
|
export type BottomTabsItems = ReadonlyArray<{
|
|
26
34
|
key: string;
|
|
27
35
|
title: string;
|
|
@@ -65,6 +73,11 @@ export interface BottomTabsNativeProps extends ViewProps {
|
|
|
65
73
|
ignoreBottomInsets?: boolean;
|
|
66
74
|
androidTabBarStyle?: WithDefault<'default' | 'floating', 'default'>;
|
|
67
75
|
ignoreKeyboardInsets?: WithDefault<boolean, true>;
|
|
76
|
+
onTabBarPlacementChange?: DirectEventHandler<OnTabBarPlacementChange>;
|
|
77
|
+
// A round button drawn above a vertical tab bar; iOS only.
|
|
78
|
+
sideAccessoryIcon?: ImageSource;
|
|
79
|
+
sideAccessoryLabel?: string;
|
|
80
|
+
onSideAccessoryPress?: DirectEventHandler<OnSideAccessoryPress>;
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
export default codegenNativeComponent<BottomTabsNativeProps>('RNBottomTabsView');
|
package/src/index.tsx
CHANGED
package/src/types.ts
CHANGED
|
@@ -7,6 +7,9 @@ export type AppleIcon = { sfSymbol: SFSymbol };
|
|
|
7
7
|
|
|
8
8
|
export type TabRole = 'search';
|
|
9
9
|
|
|
10
|
+
// 'leading' and 'trailing' mean a vertical tab bar on that edge.
|
|
11
|
+
export type TabBarPlacement = 'bottom' | 'leading' | 'trailing';
|
|
12
|
+
|
|
10
13
|
export type BaseRoute = {
|
|
11
14
|
key: string;
|
|
12
15
|
title?: string;
|