import { getHeaderTitle, Header, SafeAreaProviderCompat, Screen, useFrameSize, } from '@react-navigation/elements'; import { DrawerActions, type DrawerNavigationState, type DrawerStatus, type ParamListBase, StackActions, useLocale, useTheme, } from '@react-navigation/native'; import * as React from 'react'; import { Platform, StyleSheet } from 'react-native'; import { Drawer } from 'react-native-drawer-layout'; import useLatestCallback from 'use-latest-callback'; import type { DrawerContentComponentProps, DrawerDescriptorMap, DrawerHeaderProps, DrawerNavigationConfig, DrawerNavigationHelpers, } from '../types'; import { addCancelListener } from '../utils/addCancelListener'; import { DrawerPositionContext } from '../utils/DrawerPositionContext'; import { DrawerStatusContext } from '../utils/DrawerStatusContext'; import { getDrawerStatusFromState } from '../utils/getDrawerStatusFromState'; import { DrawerContent } from './DrawerContent'; import { DrawerToggleButton } from './DrawerToggleButton'; import { MaybeScreen, MaybeScreenContainer } from './ScreenFallback'; type Props = DrawerNavigationConfig & { defaultStatus: DrawerStatus; state: DrawerNavigationState; navigation: DrawerNavigationHelpers; descriptors: DrawerDescriptorMap; }; const DRAWER_BORDER_RADIUS = 16; const renderDrawerContentDefault = (props: DrawerContentComponentProps) => ( ); function DrawerViewBase({ state, navigation, descriptors, defaultStatus, drawerContent = renderDrawerContentDefault, detachInactiveScreens = Platform.OS === 'web' || Platform.OS === 'android' || Platform.OS === 'ios', }: Props) { const { direction } = useLocale(); const focusedRouteKey = state.routes[state.index].key; const { drawerHideStatusBarOnOpen, drawerPosition = direction === 'rtl' ? 'right' : 'left', drawerStatusBarAnimation, drawerStyle, drawerType = Platform.select({ ios: 'slide', default: 'front' }), configureGestureHandler, keyboardDismissMode, overlayColor = 'rgba(0, 0, 0, 0.5)', overlayStyle, swipeEdgeWidth, swipeEnabled = Platform.OS !== 'web' && Platform.OS !== 'windows' && Platform.OS !== 'macos', swipeMinDistance, overlayAccessibilityLabel, } = descriptors[focusedRouteKey].options; const [loaded, setLoaded] = React.useState([focusedRouteKey]); if (!loaded.includes(focusedRouteKey)) { setLoaded([...loaded, focusedRouteKey]); } const previousRouteKeyRef = React.useRef(focusedRouteKey); React.useEffect(() => { const previousRouteKey = previousRouteKeyRef.current; if ( previousRouteKey !== focusedRouteKey && descriptors[previousRouteKey]?.options.popToTopOnBlur ) { const currentState = navigation.getState(); const prevRoute = currentState.routes.find( (route) => route.key === previousRouteKey ); if ( prevRoute?.state?.type === 'stack' && prevRoute.state.key && (prevRoute.state.index ?? prevRoute.state.routes.length - 1) > 0 ) { navigation.dispatch({ ...StackActions.popToTop(), target: prevRoute.state.key, }); } } previousRouteKeyRef.current = focusedRouteKey; }, [descriptors, focusedRouteKey, navigation]); const dimensions = useFrameSize((size) => size, true); const { colors } = useTheme(); const drawerStatus = getDrawerStatusFromState(state); const handleDrawerOpen = useLatestCallback(() => { navigation.dispatch({ ...DrawerActions.openDrawer(), target: state.key, }); }); const handleDrawerClose = useLatestCallback(() => { navigation.dispatch({ ...DrawerActions.closeDrawer(), target: state.key, }); }); const handleGestureStart = useLatestCallback(() => { navigation.emit({ type: 'gestureStart', target: state.key, }); }); const handleGestureEnd = useLatestCallback(() => { navigation.emit({ type: 'gestureEnd', target: state.key, }); }); const handleGestureCancel = useLatestCallback(() => { navigation.emit({ type: 'gestureCancel', target: state.key, }); }); const handleTransitionStart = useLatestCallback((closing: boolean) => { navigation.emit({ type: 'transitionStart', data: { closing }, target: state.key, }); }); const handleTransitionEnd = useLatestCallback((closing: boolean) => { navigation.emit({ type: 'transitionEnd', data: { closing }, target: state.key, }); }); React.useEffect(() => { if (drawerStatus === defaultStatus || drawerType === 'permanent') { return; } const handleHardwareBack = () => { // We shouldn't handle the back button if the parent screen isn't focused // This will avoid the drawer overriding event listeners from a focused screen if (!navigation.isFocused()) { return false; } if (defaultStatus === 'open') { handleDrawerOpen(); } else { handleDrawerClose(); } return true; }; // We only add the listeners when drawer opens // This way we can make sure that the listener is added as late as possible // This will make sure that our handler will run first when back button is pressed return addCancelListener(handleHardwareBack); }, [ defaultStatus, drawerStatus, drawerType, handleDrawerClose, handleDrawerOpen, navigation, ]); const renderDrawerContent = () => { return ( {drawerContent({ state: state, navigation: navigation, descriptors: descriptors, })} ); }; const renderSceneContent = () => { return ( {state.routes.map((route, index) => { const descriptor = descriptors[route.key]; const { lazy = true } = descriptor.options; const isFocused = state.index === index; const isPreloaded = state.preloadedRouteKeys.includes(route.key); if ( lazy && !loaded.includes(route.key) && !isFocused && !isPreloaded ) { // Don't render a lazy screen if we've never navigated to it or it wasn't preloaded return null; } const { freezeOnBlur, header = ({ layout, options }: DrawerHeaderProps) => (
: options.headerLeft } headerRight={ drawerPosition === 'right' && options.headerRight == null ? (props) => : options.headerRight } /> ), headerShown, headerStatusBarHeight, headerTransparent, sceneStyle, } = descriptor.options; return ( {descriptor.render()} ); })} ); }; return ( {renderSceneContent()} ); } export function DrawerView({ navigation, ...rest }: Props) { return ( ); } const styles = StyleSheet.create({ content: { flex: 1, }, });