diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx index 23513b8b61..15bce69621 100644 --- a/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx @@ -15,6 +15,7 @@ import { useThemeConfig, useMobileSecondaryMenuRenderer, usePrevious, + useHistoryPopHandler, } from '@docusaurus/theme-common'; import useHideableNavbar from '@theme/hooks/useHideableNavbar'; import useLockBodyScroll from '@theme/hooks/useLockBodyScroll'; @@ -58,6 +59,18 @@ function useMobileSidebar() { const [shown, setShown] = useState(false); + // Close mobile sidebar on navigation pop + // Most likely firing when using the Android back button (but not only) + useHistoryPopHandler(() => { + if (shown) { + setShown(false); + // Should we prevent the navigation here? + // See https://github.com/facebook/docusaurus/pull/5462#issuecomment-911699846 + // return false; // prevent pop navigation + } + return undefined; + }); + const toggle = useCallback(() => { setShown((s) => !s); }, []); diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index 08b097871e..d2ca0db42d 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -71,3 +71,5 @@ export {useLocalPathname} from './utils/useLocalPathname'; export {translateTagsPageTitle, listTagsByLetters} from './utils/tagsUtils'; export type {TagLetterEntry} from './utils/tagsUtils'; + +export {useHistoryPopHandler} from './utils/historyUtils'; diff --git a/packages/docusaurus-theme-common/src/utils/historyUtils.ts b/packages/docusaurus-theme-common/src/utils/historyUtils.ts new file mode 100644 index 0000000000..b120c7981f --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/historyUtils.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {useEffect, useRef} from 'react'; +import {useHistory} from '@docusaurus/router'; +import type {Location, Action} from '@docusaurus/history'; + +type HistoryBlockHandler = (location: Location, action: Action) => void | false; + +/* +Permits to register a handler that will be called on history actions (pop,push,replace) +If the handler returns false, the navigation transition will be blocked/cancelled + */ +export function useHistoryActionHandler(handler: HistoryBlockHandler): void { + const {block} = useHistory(); + + // Avoid stale closure issues without triggering useless re-renders + const lastHandlerRef = useRef(handler); + useEffect(() => { + lastHandlerRef.current = handler; + }, [handler]); + + useEffect(() => { + // See https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md + return block((location, action) => { + return lastHandlerRef.current(location, action); + }); + }, [block, lastHandlerRef]); +} + +/* +Permits to register a handler that will be called on history pop navigation (backward/forward) +If the handler returns false, the backward/forward transition will be blocked + +Unfortunately there's no good way to detect the "direction" (backward/forward) of the POP event. + */ +export function useHistoryPopHandler(handler: HistoryBlockHandler): void { + useHistoryActionHandler((location, action) => { + if (action === 'POP') { + // Eventually block navigation if handler returns false + return handler(location, action); + } + // Don't block other navigation actions + return undefined; + }); +}