diff --git a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx index ad0dd304f3..baa8506b1a 100644 --- a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx +++ b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx @@ -2,12 +2,7 @@ import React, { memo, useCallback, useMemo, type ComponentPropsWithRef } from 'react'; -import { - NavigationStatusContext, - useHash, - useIsMounted, - useListOverflow, -} from '@/components/hooks'; +import { useHash, useIsMounted, useListOverflow } from '@/components/hooks'; import { DropdownMenu, DropdownMenuItem } from '@/components/primitives'; import { useLanguage } from '@/intl/client'; import { tString } from '@/intl/translate'; @@ -74,7 +69,6 @@ export function DynamicTabs(props: { }) { const { id, tabs, className } = props; const router = useRouter(); - const { onNavigationClick } = React.useContext(NavigationStatusContext); const hash = useHash(); const [tabsState, setTabsState] = useTabsState(); @@ -106,8 +100,7 @@ export function DynamicTabs(props: { const href = `#${tab.id}`; if (window.location.hash !== href) { - router.replace(href); - onNavigationClick(href); + router.replace(href, { scroll: false }); } setTabsState((prev) => { @@ -128,7 +121,7 @@ export function DynamicTabs(props: { }; }); }, - [onNavigationClick, router, setTabsState, tabs, id] + [router, setTabsState, tabs, id] ); // When the hash changes, we try to select the tab containing the targetted element. @@ -184,7 +177,10 @@ const TabPanel = memo(function TabPanel(props: { role="tabpanel" id={tab.id} aria-labelledby={getTabButtonId(tab.id)} - className={tcls('p-4', isActive ? null : 'hidden')} + className={tcls( + 'scroll-mt-[calc(var(--content-scroll-margin)+var(--spacing)*12)] p-4', + isActive ? null : 'hidden' + )} > {tab.body} diff --git a/packages/gitbook/src/components/hooks/usePrevious.ts b/packages/gitbook/src/components/hooks/usePrevious.ts new file mode 100644 index 0000000000..5a3adfa6f6 --- /dev/null +++ b/packages/gitbook/src/components/hooks/usePrevious.ts @@ -0,0 +1,12 @@ +import * as React from 'react'; + +/** + * Returns the value of the previous render. + */ +export function usePrevious(value: T): T | undefined { + const ref = React.useRef(undefined); + React.useLayoutEffect(() => { + ref.current = value; + }); + return ref.current; +} diff --git a/packages/gitbook/src/components/hooks/useScrollPage.ts b/packages/gitbook/src/components/hooks/useScrollPage.ts index 1b1219a291..100174f627 100644 --- a/packages/gitbook/src/components/hooks/useScrollPage.ts +++ b/packages/gitbook/src/components/hooks/useScrollPage.ts @@ -4,6 +4,7 @@ import { usePathname } from 'next/navigation'; import React from 'react'; import { useHash } from './useHash'; +import { usePrevious } from './usePrevious'; /** * Scroll the page to an anchor point or @@ -12,8 +13,17 @@ import { useHash } from './useHash'; */ export function useScrollPage() { const hash = useHash(); + const previousHash = usePrevious(hash); const pathname = usePathname(); + const previousPathname = usePrevious(pathname); React.useLayoutEffect(() => { + // If there is no change in pathname or hash, do nothing + if (previousHash === hash && previousPathname === pathname) { + return; + } + + // If there is a hash + // - Triggered by a change of hash or pathname if (hash) { const element = document.getElementById(hash); if (element) { @@ -22,16 +32,12 @@ export function useScrollPage() { behavior: 'smooth', }); } - } else { + return; + } + + // If there was a hash but not anymore, scroll to top + if (previousHash && !hash) { window.scrollTo(0, 0); } - return () => { - if (hash) { - const element = document.getElementById(hash); - if (element) { - element.style.scrollMarginTop = ''; - } - } - }; - }, [hash, pathname]); + }, [hash, previousHash, pathname, previousPathname]); }