@onekeyfe/react-native-bottom-tabs 6.7.0-alpha.383 → 6.7.0-alpha.384

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.
@@ -9,6 +9,7 @@ import { BottomTabBarHeightContext } from "./utils/BottomTabBarHeightContext.js"
9
9
  import NativeBottomTabs from './BottomTabsNativeComponent';
10
10
  import useLatestCallback from 'use-latest-callback';
11
11
  import DelayedFreeze from "./DelayedFreeze.js";
12
+ import NativeBottomTabsDrawer from './BottomTabsDrawerNativeComponent';
12
13
  import { BottomAccessoryView } from "./BottomAccessoryView.js";
13
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
15
  const isAppleSymbol = icon => icon?.sfSymbol;
@@ -19,6 +20,10 @@ const BottomTabs = ({
19
20
  renderLazyPlaceholder,
20
21
  onIndexChange,
21
22
  onTabDoublePress,
23
+ renderDrawerContent,
24
+ drawerOpen = false,
25
+ drawerSwipeEnabled = false,
26
+ onDrawerOpenChange,
22
27
  rippleColor,
23
28
  tabBarActiveTintColor: activeTintColor,
24
29
  tabBarInactiveTintColor: inactiveTintColor,
@@ -77,7 +82,9 @@ const BottomTabs = ({
77
82
  const focusedKey = navigationState.routes[navigationState.index].key;
78
83
  const customTabBarWrapperRef = useRef(null);
79
84
  const nativeBottomTabsRef = useRef(null);
80
- const [tabBarHeight, setTabBarHeight] = React.useState(0);
85
+ const [nativeTabBarHeight, setNativeTabBarHeight] = React.useState(0);
86
+ const [customTabBarHeight, setCustomTabBarHeight] = React.useState(0);
87
+ const tabBarHeight = renderCustomTabBar ? customTabBarHeight : props.tabBarHidden ? 0 : nativeTabBarHeight;
81
88
  const [measuredDimensions, setMeasuredDimensions] = React.useState({
82
89
  width: '100%',
83
90
  height: '100%'
@@ -225,8 +232,8 @@ const BottomTabs = ({
225
232
  }
226
233
  }, [focusedKey, jumpTo]);
227
234
  const handleTabBarMeasured = React.useCallback(event => {
228
- setTabBarHeight(event.nativeEvent.height);
229
- }, [setTabBarHeight]);
235
+ setNativeTabBarHeight(event.nativeEvent.height);
236
+ }, [setNativeTabBarHeight]);
230
237
  const handleNativeLayout = React.useCallback(event => {
231
238
  const {
232
239
  width,
@@ -240,75 +247,93 @@ const BottomTabs = ({
240
247
  useLayoutEffect(() => {
241
248
  if (renderCustomTabBar && customTabBarWrapperRef.current) {
242
249
  customTabBarWrapperRef.current.measure((_x, _y, _width, height) => {
243
- setTabBarHeight(height);
250
+ setCustomTabBarHeight(height);
244
251
  });
245
252
  }
246
253
  }, [renderCustomTabBar]);
247
- return /*#__PURE__*/_jsxs(BottomTabBarHeightContext.Provider, {
254
+ return /*#__PURE__*/_jsx(BottomTabBarHeightContext.Provider, {
248
255
  value: tabBarHeight,
249
- children: [/*#__PURE__*/_jsxs(NativeBottomTabs, {
250
- ref: nativeBottomTabsRef,
251
- ...props,
252
- ...tabLabelStyle,
256
+ children: /*#__PURE__*/_jsxs(NativeBottomTabsDrawer, {
253
257
  style: styles.fullWidth,
254
- items: items,
255
- icons: renderCustomTabBar ? undefined : resolvedIconAssets,
256
- selectedIcons: isIOS && !renderCustomTabBar ? resolvedSelectedIconAssets : undefined,
257
- selectedPage: focusedKey,
258
- tabBarHidden: props.tabBarHidden ?? !!renderCustomTabBar,
259
- onTabDoublePress: handleTabDoublePress,
260
- onPageSelected: handlePageSelected,
261
- onTabBarMeasured: handleTabBarMeasured,
262
- onNativeLayout: handleNativeLayout,
258
+ enabled: !!renderDrawerContent,
259
+ open: drawerOpen,
260
+ swipeEnabled: drawerSwipeEnabled,
263
261
  hapticFeedbackEnabled: hapticFeedbackEnabled,
264
- activeTintColor: activeTintColor,
265
- inactiveTintColor: inactiveTintColor,
266
- barTintColor: tabBarStyle?.backgroundColor,
267
- rippleColor: rippleColor,
268
- activeIndicatorColor: activeIndicatorColor,
269
- androidBottomBlurEnabled: Platform.OS === 'android' && !renderCustomTabBar && androidBottomBlurEnabled,
270
- labeled: labeled,
271
- children: [trimmedRoutes.map(route => {
272
- if (getLazy({
273
- route
274
- }) !== false && !loaded.includes(route.key)) {
275
- return /*#__PURE__*/_jsx(View, {
276
- collapsable: false,
277
- style: styles.fullWidth,
278
- children: renderLazyPlaceholder?.({
262
+ bottomBarHeight: tabBarHeight ?? 0,
263
+ onDrawerOpenChange: event => onDrawerOpenChange?.(event.nativeEvent.open),
264
+ children: [/*#__PURE__*/_jsx(View, {
265
+ collapsable: false,
266
+ style: styles.drawerMenu,
267
+ children: Platform.OS !== 'web' ? renderDrawerContent?.() : null
268
+ }), /*#__PURE__*/_jsxs(View, {
269
+ collapsable: false,
270
+ style: styles.fullWidth,
271
+ children: [/*#__PURE__*/_jsxs(NativeBottomTabs, {
272
+ ref: nativeBottomTabsRef,
273
+ ...props,
274
+ ...tabLabelStyle,
275
+ style: styles.fullWidth,
276
+ items: items,
277
+ icons: renderCustomTabBar ? undefined : resolvedIconAssets,
278
+ selectedIcons: isIOS && !renderCustomTabBar ? resolvedSelectedIconAssets : undefined,
279
+ selectedPage: focusedKey,
280
+ tabBarHidden: props.tabBarHidden ?? !!renderCustomTabBar,
281
+ onTabDoublePress: handleTabDoublePress,
282
+ onPageSelected: handlePageSelected,
283
+ onTabBarMeasured: handleTabBarMeasured,
284
+ onNativeLayout: handleNativeLayout,
285
+ hapticFeedbackEnabled: hapticFeedbackEnabled,
286
+ activeTintColor: activeTintColor,
287
+ inactiveTintColor: inactiveTintColor,
288
+ barTintColor: tabBarStyle?.backgroundColor,
289
+ rippleColor: rippleColor,
290
+ activeIndicatorColor: activeIndicatorColor,
291
+ androidBottomBlurEnabled: Platform.OS === 'android' && !renderCustomTabBar && androidBottomBlurEnabled,
292
+ labeled: labeled,
293
+ children: [trimmedRoutes.map(route => {
294
+ if (getLazy({
279
295
  route
280
- })
281
- }, route.key);
282
- }
283
- const focused = route.key === focusedKey;
284
- const freeze = !focused ? getFreezeOnBlur({
285
- route
286
- }) : false;
287
- const customStyle = getSceneStyle({
288
- route
289
- });
290
- return /*#__PURE__*/_jsx(View, {
291
- style: [styles.screen, renderCustomTabBar ? styles.fullWidth : measuredDimensions, customStyle],
292
- collapsable: false,
293
- pointerEvents: focused ? 'auto' : 'none',
294
- accessibilityElementsHidden: !focused,
295
- importantForAccessibility: focused ? 'auto' : 'no-hide-descendants',
296
- children: /*#__PURE__*/_jsx(DelayedFreeze, {
297
- freeze: !!freeze,
298
- delayedFreeze: delayedFreeze,
299
- children: renderScene({
300
- route,
301
- jumpTo
302
- })
303
- })
304
- }, route.key);
305
- }), Platform.OS === 'ios' && parseFloat(Platform.Version) >= 26 && renderBottomAccessoryView && !renderCustomTabBar ? /*#__PURE__*/_jsx(BottomAccessoryView, {
306
- renderBottomAccessoryView: renderBottomAccessoryView
307
- }) : null]
308
- }), renderCustomTabBar ? /*#__PURE__*/_jsx(View, {
309
- ref: customTabBarWrapperRef,
310
- children: renderCustomTabBar()
311
- }) : null]
296
+ }) !== false && !loaded.includes(route.key)) {
297
+ return /*#__PURE__*/_jsx(View, {
298
+ collapsable: false,
299
+ style: styles.fullWidth,
300
+ children: renderLazyPlaceholder?.({
301
+ route
302
+ })
303
+ }, route.key);
304
+ }
305
+ const focused = route.key === focusedKey;
306
+ const freeze = !focused ? getFreezeOnBlur({
307
+ route
308
+ }) : false;
309
+ const customStyle = getSceneStyle({
310
+ route
311
+ });
312
+ return /*#__PURE__*/_jsx(View, {
313
+ style: [styles.screen, renderCustomTabBar ? styles.fullWidth : measuredDimensions, customStyle],
314
+ collapsable: false,
315
+ pointerEvents: focused ? 'auto' : 'none',
316
+ accessibilityElementsHidden: !focused,
317
+ importantForAccessibility: focused ? 'auto' : 'no-hide-descendants',
318
+ children: /*#__PURE__*/_jsx(DelayedFreeze, {
319
+ freeze: !!freeze,
320
+ delayedFreeze: delayedFreeze,
321
+ children: renderScene({
322
+ route,
323
+ jumpTo
324
+ })
325
+ })
326
+ }, route.key);
327
+ }), Platform.OS === 'ios' && parseFloat(Platform.Version) >= 26 && renderBottomAccessoryView && !renderCustomTabBar ? /*#__PURE__*/_jsx(BottomAccessoryView, {
328
+ renderBottomAccessoryView: renderBottomAccessoryView
329
+ }) : null]
330
+ }), renderCustomTabBar ? /*#__PURE__*/_jsx(View, {
331
+ ref: customTabBarWrapperRef,
332
+ onLayout: event => setCustomTabBarHeight(event.nativeEvent.layout.height),
333
+ children: renderCustomTabBar()
334
+ }) : null]
335
+ })]
336
+ })
312
337
  });
313
338
  };
314
339
  const styles = StyleSheet.create({
@@ -317,6 +342,11 @@ const styles = StyleSheet.create({
317
342
  height: '100%',
318
343
  flex: 1
319
344
  },
345
+ drawerMenu: {
346
+ position: 'absolute',
347
+ width: '78%',
348
+ height: '100%'
349
+ },
320
350
  screen: {
321
351
  position: 'absolute'
322
352
  }
@@ -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,13 @@
1
+ "use strict";
2
+
3
+ import React from "react";
4
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
5
+ // The fixed first slot is the menu; Web only renders the foreground slot.
6
+ export default function BottomTabsDrawerWeb({
7
+ children
8
+ }) {
9
+ return /*#__PURE__*/_jsx(_Fragment, {
10
+ children: React.Children.toArray(children)[1]
11
+ });
12
+ }
13
+ //# sourceMappingURL=BottomTabsDrawerNativeComponent.web.js.map
@@ -5,6 +5,14 @@ import type { AppleIcon, BaseRoute, NavigationState, TabRole } from './types';
5
5
  import { type RNBottomTabsAccessoryViewProps } from './BottomAccessoryView';
6
6
  interface Props<Route extends BaseRoute> {
7
7
  labeled?: boolean;
8
+ /** Optional physical-left menu; all motion and input are owned by native. */
9
+ renderDrawerContent?: () => React.ReactNode;
10
+ /** External drawer target. Mirror onDrawerOpenChange for user transitions. */
11
+ drawerOpen?: boolean;
12
+ /** Enables root-screen content swipes after inner horizontal views reach their boundary. */
13
+ drawerSwipeEnabled?: boolean;
14
+ /** Reports a completed user target so the external open state can follow it. */
15
+ onDrawerOpenChange?: (open: boolean) => void;
8
16
  /**
9
17
  * A tab bar style that adapts to each platform.
10
18
  *
@@ -204,6 +212,6 @@ interface Props<Route extends BaseRoute> {
204
212
  */
205
213
  delayedFreeze?: boolean;
206
214
  }
207
- declare const BottomTabs: <Route extends BaseRoute>({ navigationState, renderScene, renderLazyPlaceholder, onIndexChange, onTabDoublePress, rippleColor, tabBarActiveTintColor: activeTintColor, tabBarInactiveTintColor: inactiveTintColor, getBadge, getBadgeBackgroundColor, getBadgeTextColor, getLazy, getLabelText, getIcon, getHidden, getActiveTintColor, getTestID, getRole, getSceneStyle, getPreventsDefault, hapticFeedbackEnabled, labeled, getFreezeOnBlur, tabBar: renderCustomTabBar, tabBarStyle, tabLabelStyle, renderBottomAccessoryView, activeIndicatorColor, androidBottomBlurEnabled, delayedFreeze, ...props }: Props<Route>) => import("react/jsx-runtime").JSX.Element;
215
+ declare const BottomTabs: <Route extends BaseRoute>({ navigationState, renderScene, renderLazyPlaceholder, onIndexChange, onTabDoublePress, renderDrawerContent, drawerOpen, drawerSwipeEnabled, onDrawerOpenChange, rippleColor, tabBarActiveTintColor: activeTintColor, tabBarInactiveTintColor: inactiveTintColor, getBadge, getBadgeBackgroundColor, getBadgeTextColor, getLazy, getLabelText, getIcon, getHidden, getActiveTintColor, getTestID, getRole, getSceneStyle, getPreventsDefault, hapticFeedbackEnabled, labeled, getFreezeOnBlur, tabBar: renderCustomTabBar, tabBarStyle, tabLabelStyle, renderBottomAccessoryView, activeIndicatorColor, androidBottomBlurEnabled, delayedFreeze, ...props }: Props<Route>) => import("react/jsx-runtime").JSX.Element;
208
216
  export default BottomTabs;
209
217
  //# sourceMappingURL=BottomTabs.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { type ViewProps } from "react-native";
2
+ import type { DirectEventHandler, Double, WithDefault } from "react-native/Libraries/Types/CodegenTypes";
3
+ export interface BottomTabsDrawerNativeProps extends ViewProps {
4
+ enabled?: WithDefault<boolean, false>;
5
+ open?: WithDefault<boolean, false>;
6
+ swipeEnabled?: WithDefault<boolean, false>;
7
+ hapticFeedbackEnabled?: WithDefault<boolean, false>;
8
+ bottomBarHeight?: Double;
9
+ onDrawerOpenChange?: DirectEventHandler<Readonly<{
10
+ open: boolean;
11
+ }>>;
12
+ }
13
+ declare const _default: import("react-native/types_generated/Libraries/Utilities/codegenNativeComponent").NativeComponentType<BottomTabsDrawerNativeProps>;
14
+ export default _default;
15
+ //# sourceMappingURL=BottomTabsDrawerNativeComponent.d.ts.map
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import type { BottomTabsDrawerNativeProps } from "./BottomTabsDrawerNativeComponent";
3
+ export default function BottomTabsDrawerWeb({ children, }: React.PropsWithChildren<BottomTabsDrawerNativeProps>): import("react/jsx-runtime").JSX.Element;
4
+ //# sourceMappingURL=BottomTabsDrawerNativeComponent.web.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-bottom-tabs",
3
- "version": "6.7.0-alpha.383",
3
+ "version": "6.7.0-alpha.384",
4
4
  "description": "Native bottom tabs (tab bar + scene container) for React Native on iOS and Android",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
@@ -107,7 +107,8 @@
107
107
  "ios": {
108
108
  "componentProvider": {
109
109
  "RNBottomTabsView": "RCTBottomTabsComponentView",
110
- "RNBottomTabsAccessoryView": "RCTBottomAccessoryComponentView"
110
+ "RNBottomTabsAccessoryView": "RCTBottomAccessoryComponentView",
111
+ "RNBottomTabsDrawer": "RCTBottomTabsDrawerComponentView"
111
112
  },
112
113
  "modulesConformingToProtocol": {
113
114
  "RCTImageDataDecoder": [
@@ -2,7 +2,10 @@ module.exports = {
2
2
  dependency: {
3
3
  platforms: {
4
4
  android: {
5
- componentDescriptors: ['RNBottomTabsViewComponentDescriptor'],
5
+ componentDescriptors: [
6
+ 'RNBottomTabsViewComponentDescriptor',
7
+ 'RNBottomTabsDrawerComponentDescriptor',
8
+ ],
6
9
  cmakeListsPath: undefined,
7
10
  packageImportPath:
8
11
  'import com.onekey.bottomtabs.BottomTabsPackage;',
@@ -21,6 +21,7 @@ import NativeBottomTabs, {
21
21
  import useLatestCallback from 'use-latest-callback';
22
22
  import type { AppleIcon, BaseRoute, NavigationState, TabRole } from './types';
23
23
  import DelayedFreeze from './DelayedFreeze';
24
+ import NativeBottomTabsDrawer from './BottomTabsDrawerNativeComponent';
24
25
  import {
25
26
  BottomAccessoryView,
26
27
  type RNBottomTabsAccessoryViewProps,
@@ -34,6 +35,14 @@ interface Props<Route extends BaseRoute> {
34
35
  * Whether to show labels in tabs. When false, only icons will be displayed.
35
36
  */
36
37
  labeled?: boolean;
38
+ /** Optional physical-left menu; all motion and input are owned by native. */
39
+ renderDrawerContent?: () => React.ReactNode;
40
+ /** External drawer target. Mirror onDrawerOpenChange for user transitions. */
41
+ drawerOpen?: boolean;
42
+ /** Enables root-screen content swipes after inner horizontal views reach their boundary. */
43
+ drawerSwipeEnabled?: boolean;
44
+ /** Reports a completed user target so the external open state can follow it. */
45
+ onDrawerOpenChange?: (open: boolean) => void;
37
46
  /**
38
47
  * A tab bar style that adapts to each platform.
39
48
  *
@@ -225,6 +234,10 @@ const BottomTabs = <Route extends BaseRoute>({
225
234
  renderLazyPlaceholder,
226
235
  onIndexChange,
227
236
  onTabDoublePress,
237
+ renderDrawerContent,
238
+ drawerOpen = false,
239
+ drawerSwipeEnabled = false,
240
+ onDrawerOpenChange,
228
241
  rippleColor,
229
242
  tabBarActiveTintColor: activeTintColor,
230
243
  tabBarInactiveTintColor: inactiveTintColor,
@@ -263,7 +276,11 @@ const BottomTabs = <Route extends BaseRoute>({
263
276
  const focusedKey = navigationState.routes[navigationState.index].key;
264
277
  const customTabBarWrapperRef = useRef<View>(null) as React.RefObject<any>;
265
278
  const nativeBottomTabsRef = useRef<React.ElementRef<typeof NativeBottomTabs>>(null);
266
- const [tabBarHeight, setTabBarHeight] = React.useState<number | undefined>(0);
279
+ const [nativeTabBarHeight, setNativeTabBarHeight] = React.useState(0);
280
+ const [customTabBarHeight, setCustomTabBarHeight] = React.useState(0);
281
+ const tabBarHeight = renderCustomTabBar
282
+ ? customTabBarHeight
283
+ : props.tabBarHidden ? 0 : nativeTabBarHeight;
267
284
  const [measuredDimensions, setMeasuredDimensions] = React.useState<
268
285
  { width: DimensionValue; height: DimensionValue } | undefined
269
286
  >({ width: '100%', height: '100%' });
@@ -435,9 +452,9 @@ const BottomTabs = <Route extends BaseRoute>({
435
452
 
436
453
  const handleTabBarMeasured = React.useCallback(
437
454
  (event: NativeSyntheticEvent<{ height: number }>) => {
438
- setTabBarHeight(event.nativeEvent.height);
455
+ setNativeTabBarHeight(event.nativeEvent.height);
439
456
  },
440
- [setTabBarHeight]
457
+ [setNativeTabBarHeight]
441
458
  );
442
459
 
443
460
  const handleNativeLayout = React.useCallback(
@@ -451,96 +468,118 @@ const BottomTabs = <Route extends BaseRoute>({
451
468
  useLayoutEffect(() => {
452
469
  if (renderCustomTabBar && customTabBarWrapperRef.current) {
453
470
  customTabBarWrapperRef.current.measure((_x: number, _y: number, _width: number, height: number) => {
454
- setTabBarHeight(height);
471
+ setCustomTabBarHeight(height);
455
472
  });
456
473
  }
457
474
  }, [renderCustomTabBar]);
458
475
 
459
476
  return (
460
477
  <BottomTabBarHeightContext.Provider value={tabBarHeight}>
461
- <NativeBottomTabs
462
- ref={nativeBottomTabsRef}
463
- {...props}
464
- {...tabLabelStyle}
478
+ <NativeBottomTabsDrawer
465
479
  style={styles.fullWidth}
466
- items={items}
467
- icons={renderCustomTabBar ? undefined : resolvedIconAssets}
468
- selectedIcons={
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}
480
+ enabled={!!renderDrawerContent}
481
+ open={drawerOpen}
482
+ swipeEnabled={drawerSwipeEnabled}
477
483
  hapticFeedbackEnabled={hapticFeedbackEnabled}
478
- activeTintColor={activeTintColor}
479
- inactiveTintColor={inactiveTintColor}
480
- barTintColor={tabBarStyle?.backgroundColor}
481
- rippleColor={rippleColor}
482
- activeIndicatorColor={activeIndicatorColor}
483
- androidBottomBlurEnabled={
484
- Platform.OS === 'android' &&
485
- !renderCustomTabBar &&
486
- androidBottomBlurEnabled
484
+ bottomBarHeight={tabBarHeight ?? 0}
485
+ onDrawerOpenChange={(event) =>
486
+ onDrawerOpenChange?.(event.nativeEvent.open)
487
487
  }
488
- labeled={labeled}
489
488
  >
490
- {trimmedRoutes.map((route) => {
491
- if (getLazy({ route }) !== false && !loaded.includes(route.key)) {
492
- return (
493
- <View
494
- key={route.key}
495
- collapsable={false}
496
- style={styles.fullWidth}
497
- >
498
- {renderLazyPlaceholder?.({ route })}
499
- </View>
500
- );
501
- }
502
-
503
- const focused = route.key === focusedKey;
504
- const freeze = !focused ? getFreezeOnBlur({ route }) : false;
505
-
506
- const customStyle = getSceneStyle({ route });
507
-
508
- return (
509
- <View
510
- key={route.key}
511
- style={[
512
- styles.screen,
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'
489
+ <View collapsable={false} style={styles.drawerMenu}>
490
+ {Platform.OS !== 'web' ? renderDrawerContent?.() : null}
491
+ </View>
492
+ <View collapsable={false} style={styles.fullWidth}>
493
+ <NativeBottomTabs
494
+ ref={nativeBottomTabsRef}
495
+ {...props}
496
+ {...tabLabelStyle}
497
+ style={styles.fullWidth}
498
+ items={items}
499
+ icons={renderCustomTabBar ? undefined : resolvedIconAssets}
500
+ selectedIcons={
501
+ isIOS && !renderCustomTabBar ? resolvedSelectedIconAssets : undefined
502
+ }
503
+ selectedPage={focusedKey}
504
+ tabBarHidden={props.tabBarHidden ?? !!renderCustomTabBar}
505
+ onTabDoublePress={handleTabDoublePress}
506
+ onPageSelected={handlePageSelected}
507
+ onTabBarMeasured={handleTabBarMeasured}
508
+ onNativeLayout={handleNativeLayout}
509
+ hapticFeedbackEnabled={hapticFeedbackEnabled}
510
+ activeTintColor={activeTintColor}
511
+ inactiveTintColor={inactiveTintColor}
512
+ barTintColor={tabBarStyle?.backgroundColor}
513
+ rippleColor={rippleColor}
514
+ activeIndicatorColor={activeIndicatorColor}
515
+ androidBottomBlurEnabled={
516
+ Platform.OS === 'android' &&
517
+ !renderCustomTabBar &&
518
+ androidBottomBlurEnabled
519
+ }
520
+ labeled={labeled}
521
+ >
522
+ {trimmedRoutes.map((route) => {
523
+ if (getLazy({ route }) !== false && !loaded.includes(route.key)) {
524
+ return (
525
+ <View
526
+ key={route.key}
527
+ collapsable={false}
528
+ style={styles.fullWidth}
529
+ >
530
+ {renderLazyPlaceholder?.({ route })}
531
+ </View>
532
+ );
521
533
  }
534
+
535
+ const focused = route.key === focusedKey;
536
+ const freeze = !focused ? getFreezeOnBlur({ route }) : false;
537
+
538
+ const customStyle = getSceneStyle({ route });
539
+
540
+ return (
541
+ <View
542
+ key={route.key}
543
+ style={[
544
+ styles.screen,
545
+ renderCustomTabBar ? styles.fullWidth : measuredDimensions,
546
+ customStyle,
547
+ ]}
548
+ collapsable={false}
549
+ pointerEvents={focused ? 'auto' : 'none'}
550
+ accessibilityElementsHidden={!focused}
551
+ importantForAccessibility={
552
+ focused ? 'auto' : 'no-hide-descendants'
553
+ }
554
+ >
555
+ <DelayedFreeze freeze={!!freeze} delayedFreeze={delayedFreeze}>
556
+ {renderScene({
557
+ route,
558
+ jumpTo,
559
+ })}
560
+ </DelayedFreeze>
561
+ </View>
562
+ );
563
+ })}
564
+ {Platform.OS === 'ios' &&
565
+ parseFloat(Platform.Version) >= 26 &&
566
+ renderBottomAccessoryView &&
567
+ !renderCustomTabBar ? (
568
+ <BottomAccessoryView
569
+ renderBottomAccessoryView={renderBottomAccessoryView}
570
+ />
571
+ ) : null}
572
+ </NativeBottomTabs>
573
+ {renderCustomTabBar ? (
574
+ <View
575
+ ref={customTabBarWrapperRef}
576
+ onLayout={(event) => setCustomTabBarHeight(event.nativeEvent.layout.height)}
522
577
  >
523
- <DelayedFreeze freeze={!!freeze} delayedFreeze={delayedFreeze}>
524
- {renderScene({
525
- route,
526
- jumpTo,
527
- })}
528
- </DelayedFreeze>
578
+ {renderCustomTabBar()}
529
579
  </View>
530
- );
531
- })}
532
- {Platform.OS === 'ios' &&
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}
580
+ ) : null}
581
+ </View>
582
+ </NativeBottomTabsDrawer>
544
583
  </BottomTabBarHeightContext.Provider>
545
584
  );
546
585
  };
@@ -551,6 +590,11 @@ const styles = StyleSheet.create({
551
590
  height: '100%',
552
591
  flex: 1,
553
592
  },
593
+ drawerMenu: {
594
+ position: 'absolute',
595
+ width: '78%',
596
+ height: '100%',
597
+ },
554
598
  screen: {
555
599
  position: 'absolute',
556
600
  },
@@ -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
+ }