From 6a4f1224e77b11b0803f54dc1f1cca6a8720e5d7 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Fri, 28 Feb 2025 11:00:07 +0100 Subject: [PATCH 01/11] fix: sidebar is scrolled on client side navigation --- .changeset/silver-numbers-call.md | 5 +++++ .../src/components/sidebar.tsx | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 .changeset/silver-numbers-call.md diff --git a/.changeset/silver-numbers-call.md b/.changeset/silver-numbers-call.md new file mode 100644 index 0000000000..56327f681b --- /dev/null +++ b/.changeset/silver-numbers-call.md @@ -0,0 +1,5 @@ +--- +"nextra-theme-docs": patch +--- + +fix: sidebar is scrolled on client side navigation diff --git a/packages/nextra-theme-docs/src/components/sidebar.tsx b/packages/nextra-theme-docs/src/components/sidebar.tsx index 15fbbccff5..1cb39f9c2c 100644 --- a/packages/nextra-theme-docs/src/components/sidebar.tsx +++ b/packages/nextra-theme-docs/src/components/sidebar.tsx @@ -355,26 +355,33 @@ export const MobileNav: FC = () => { ) } +let isInitialLoaded = false + export const Sidebar: FC<{ toc: Heading[] }> = ({ toc }) => { const { normalizePagesResult, hideSidebar } = useConfig() const themeConfig = useThemeConfig() const [isExpanded, setIsExpanded] = useState(themeConfig.sidebar.defaultOpen) const [showToggleAnimation, setToggleAnimation] = useState(false) - const sidebarRef = useRef(null) + const sidebarRef = useRef(null!) const sidebarControlsId = useId() const { docsDirectories, activeThemeContext } = normalizePagesResult const includePlaceholder = activeThemeContext.layout === 'default' useEffect(() => { - const activeElement = sidebarRef.current?.querySelector('li.active') - - if (activeElement && window.innerWidth > 767) { + if (isInitialLoaded || window.innerWidth < 768) { + return + } + // Since `` is placed in `useMDXComponents.wrapper` this prevents + // scroll on client side navigation + isInitialLoaded = true + const activeElement = sidebarRef.current.querySelector('li.active') + if (activeElement) { scrollIntoView(activeElement, { block: 'center', inline: 'center', scrollMode: 'always', - boundary: sidebarRef.current!.parentNode as HTMLDivElement + boundary: sidebarRef.current.parentNode as HTMLDivElement }) } }, []) From 30235b03059de14491b8deb52e45c2dc83167ec8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Fri, 28 Feb 2025 11:02:35 +0100 Subject: [PATCH 02/11] improve comment --- packages/nextra-theme-docs/src/components/sidebar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nextra-theme-docs/src/components/sidebar.tsx b/packages/nextra-theme-docs/src/components/sidebar.tsx index 1cb39f9c2c..a60e5beee7 100644 --- a/packages/nextra-theme-docs/src/components/sidebar.tsx +++ b/packages/nextra-theme-docs/src/components/sidebar.tsx @@ -372,8 +372,8 @@ export const Sidebar: FC<{ toc: Heading[] }> = ({ toc }) => { if (isInitialLoaded || window.innerWidth < 768) { return } - // Since `` is placed in `useMDXComponents.wrapper` this prevents - // scroll on client side navigation + // Since `` is placed in `useMDXComponents.wrapper` on client side navigation he will + // be remounted and re-centered to active link, isInitialLoaded prevents this isInitialLoaded = true const activeElement = sidebarRef.current.querySelector('li.active') if (activeElement) { From 46f2a1cccce4b2726fcd38abf80cc5191dd9b5d8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Fri, 28 Feb 2025 14:40:39 +0100 Subject: [PATCH 03/11] more --- .../docs/src/app/docs/[[...mdxPath]]/page.jsx | 6 ++-- .../app/[lang]/[[...mdxPath]]/page.tsx | 6 ++-- .../remote/graphql-yoga/[[...slug]]/page.tsx | 5 ++-- .../nextra-theme-docs/src/components/toc.tsx | 8 ++--- .../src/mdx-components/index.tsx | 30 +++++++++---------- .../src/mdx-components/wrapper.client.tsx | 20 ++++--------- .../nextra-theme-docs/src/stores/index.ts | 2 +- packages/nextra-theme-docs/src/stores/toc.ts | 19 +++++------- 8 files changed, 37 insertions(+), 59 deletions(-) diff --git a/examples/docs/src/app/docs/[[...mdxPath]]/page.jsx b/examples/docs/src/app/docs/[[...mdxPath]]/page.jsx index 499dd4f546..7a19336854 100644 --- a/examples/docs/src/app/docs/[[...mdxPath]]/page.jsx +++ b/examples/docs/src/app/docs/[[...mdxPath]]/page.jsx @@ -1,7 +1,5 @@ -/* eslint-disable react-hooks/rules-of-hooks -- false positive, useMDXComponents isn't react hooks */ - import { generateStaticParamsFor, importPage } from 'nextra/pages' -import { useMDXComponents } from '../../../../mdx-components' +import { useMDXComponents as getMDXComponents } from '../../../../mdx-components' export const generateStaticParams = generateStaticParamsFor('mdxPath') @@ -11,7 +9,7 @@ export async function generateMetadata(props) { return metadata } -const Wrapper = useMDXComponents().wrapper +const Wrapper = getMDXComponents().wrapper export default async function Page(props) { const params = await props.params diff --git a/examples/swr-site/app/[lang]/[[...mdxPath]]/page.tsx b/examples/swr-site/app/[lang]/[[...mdxPath]]/page.tsx index 5c530036c5..ed6569b52a 100644 --- a/examples/swr-site/app/[lang]/[[...mdxPath]]/page.tsx +++ b/examples/swr-site/app/[lang]/[[...mdxPath]]/page.tsx @@ -1,7 +1,5 @@ -/* eslint-disable react-hooks/rules-of-hooks -- false positive, useMDXComponents isn't react hooks */ - import { generateStaticParamsFor, importPage } from 'nextra/pages' -import { useMDXComponents } from '../../../mdx-components' +import { useMDXComponents as getMDXComponents } from '../../../mdx-components' export const generateStaticParams = generateStaticParamsFor('mdxPath') @@ -17,7 +15,7 @@ type PageProps = Readonly<{ lang: string }> }> -const Wrapper = useMDXComponents().wrapper +const Wrapper = getMDXComponents().wrapper export default async function Page(props: PageProps) { const params = await props.params diff --git a/examples/swr-site/app/[lang]/remote/graphql-yoga/[[...slug]]/page.tsx b/examples/swr-site/app/[lang]/remote/graphql-yoga/[[...slug]]/page.tsx index 7f84990814..bffd83cd9b 100644 --- a/examples/swr-site/app/[lang]/remote/graphql-yoga/[[...slug]]/page.tsx +++ b/examples/swr-site/app/[lang]/remote/graphql-yoga/[[...slug]]/page.tsx @@ -1,6 +1,5 @@ -/* eslint-disable react-hooks/rules-of-hooks -- false positive, useMDXComponents isn't react hooks */ import { notFound } from 'next/navigation' -import { useMDXComponents } from 'nextra-theme-docs' +import { useMDXComponents as getMDXComponents } from 'nextra-theme-docs' import { compileMdx } from 'nextra/compile' import { Callout, Tabs } from 'nextra/components' import { evaluate } from 'nextra/evaluate' @@ -61,7 +60,7 @@ const yogaPageMap = mergeMetaWithPageMap(yogaPage, { export const pageMap = normalizePageMap(yogaPageMap) -const { wrapper: Wrapper, ...components } = useMDXComponents({ +const { wrapper: Wrapper, ...components } = getMDXComponents({ Callout, Tabs, Tab: Tabs.Tab, diff --git a/packages/nextra-theme-docs/src/components/toc.tsx b/packages/nextra-theme-docs/src/components/toc.tsx index 29040b81a2..ea3d6af437 100644 --- a/packages/nextra-theme-docs/src/components/toc.tsx +++ b/packages/nextra-theme-docs/src/components/toc.tsx @@ -1,17 +1,15 @@ 'use client' import cn from 'clsx' -import type { Heading } from 'nextra' import { Anchor } from 'nextra/components' import type { FC } from 'react' import { useEffect, useRef } from 'react' import scrollIntoView from 'scroll-into-view-if-needed' -import { useActiveAnchor, useConfig, useThemeConfig } from '../stores' +import { useActiveAnchor, useConfig, useThemeConfig, useTOC } from '../stores' import { getGitIssueUrl, gitUrlParse } from '../utils' import { BackToTop } from './back-to-top' type TOCProps = { - toc: Heading[] filePath: string pageTitle: string } @@ -23,11 +21,11 @@ const linkClassName = cn( 'x:contrast-more:text-gray-700 x:contrast-more:dark:text-gray-100' ) -export const TOC: FC = ({ toc, filePath, pageTitle }) => { +export const TOC: FC = ({ filePath, pageTitle }) => { const activeSlug = useActiveAnchor() const tocRef = useRef(null) const themeConfig = useThemeConfig() - + const toc = useTOC() const hasMetaInfo = themeConfig.feedback.content || themeConfig.editLink || diff --git a/packages/nextra-theme-docs/src/mdx-components/index.tsx b/packages/nextra-theme-docs/src/mdx-components/index.tsx index 679c87ead5..90552df7e5 100644 --- a/packages/nextra-theme-docs/src/mdx-components/index.tsx +++ b/packages/nextra-theme-docs/src/mdx-components/index.tsx @@ -19,6 +19,7 @@ import type { MDXComponents } from 'nextra/mdx-components' import { removeLinks } from 'nextra/remove-links' import type { ComponentProps, FC } from 'react' import { Sidebar } from '../components' +import { TOCProvider } from '../stores' import { H1, H2, H3, H4, H5, H6 } from './heading' import { Link } from './link' import { ClientWrapper } from './wrapper.client' @@ -94,22 +95,19 @@ const DEFAULT_COMPONENTS = getNextraMDXComponents({ // Attach user-defined props to wrapper container, e.g. `data-pagefind-filter` {...props} > - - - - -
- {children} -
-
+ + + + +
+ {children} +
+
+
) } diff --git a/packages/nextra-theme-docs/src/mdx-components/wrapper.client.tsx b/packages/nextra-theme-docs/src/mdx-components/wrapper.client.tsx index 737ce06fc6..bb7d1ca157 100644 --- a/packages/nextra-theme-docs/src/mdx-components/wrapper.client.tsx +++ b/packages/nextra-theme-docs/src/mdx-components/wrapper.client.tsx @@ -2,12 +2,12 @@ import cn from 'clsx' import type { MDXWrapper } from 'nextra' -import { cloneElement, useEffect } from 'react' +import type { ComponentProps, FC } from 'react' +import { cloneElement } from 'react' import { Breadcrumb, Pagination, TOC } from '../components' -import { setToc, useConfig, useThemeConfig } from '../stores' +import { useConfig, useThemeConfig } from '../stores' -export const ClientWrapper: MDXWrapper = ({ - toc, +export const ClientWrapper: FC, 'toc'>> = ({ children, metadata, bottomContent @@ -18,14 +18,8 @@ export const ClientWrapper: MDXWrapper = ({ activePath } = useConfig().normalizePagesResult const themeConfig = useThemeConfig() - const date = themeContext.timestamp && metadata.timestamp - // We can't update store in server component so doing it in client component - useEffect(() => { - setToc(toc) - }, [toc]) - return ( <> {(themeContext.layout === 'default' || themeContext.toc) && ( @@ -34,11 +28,7 @@ export const ClientWrapper: MDXWrapper = ({ aria-label="table of contents" > {themeContext.toc && ( - + )} )} diff --git a/packages/nextra-theme-docs/src/stores/index.ts b/packages/nextra-theme-docs/src/stores/index.ts index 5711a140f2..5c0f55f832 100644 --- a/packages/nextra-theme-docs/src/stores/index.ts +++ b/packages/nextra-theme-docs/src/stores/index.ts @@ -3,4 +3,4 @@ export { useConfig, ConfigProvider } from './config' export { useFocusedRoute, setFocusedRoute } from './focused-route' export { useMenu, setMenu } from './menu' export { ThemeConfigProvider, useThemeConfig } from './theme-config' -export { useToc, setToc } from './toc' +export { useTOC } from './toc' diff --git a/packages/nextra-theme-docs/src/stores/toc.ts b/packages/nextra-theme-docs/src/stores/toc.ts index a07db37599..36924d939d 100644 --- a/packages/nextra-theme-docs/src/stores/toc.ts +++ b/packages/nextra-theme-docs/src/stores/toc.ts @@ -1,17 +1,14 @@ 'use no memo' +'use client' import type { Heading } from 'nextra' -import type { Dispatch } from 'react' -import { create } from 'zustand' +import type { ComponentProps } from 'react' +import { createContext, createElement, useContext } from 'react' -const useTocStore = create<{ - toc: Heading[] -}>(() => ({ - toc: [] -})) +const TOCContext = createContext([]) -export const useToc = () => useTocStore(state => state.toc) +export const useTOC = () => useContext(TOCContext) -export const setToc: Dispatch = toc => { - useTocStore.setState({ toc }) -} +export const TOCProvider = ( + props: ComponentProps +) => createElement(TOCContext.Provider, props) From ce7af066755e0dfce9398691d8bac3b67040a04f Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 3 Mar 2025 09:58:55 +0100 Subject: [PATCH 04/11] upd --- packages/nextra-theme-docs/src/stores/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextra-theme-docs/src/stores/index.ts b/packages/nextra-theme-docs/src/stores/index.ts index 5c0f55f832..8de48b5f56 100644 --- a/packages/nextra-theme-docs/src/stores/index.ts +++ b/packages/nextra-theme-docs/src/stores/index.ts @@ -3,4 +3,4 @@ export { useConfig, ConfigProvider } from './config' export { useFocusedRoute, setFocusedRoute } from './focused-route' export { useMenu, setMenu } from './menu' export { ThemeConfigProvider, useThemeConfig } from './theme-config' -export { useTOC } from './toc' +export { useTOC, TOCProvider } from './toc' From cbca0534bb8d985400face1a9df49069b49d9637 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 3 Mar 2025 10:05:04 +0100 Subject: [PATCH 05/11] upd --- .../src/components/sidebar.tsx | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/nextra-theme-docs/src/components/sidebar.tsx b/packages/nextra-theme-docs/src/components/sidebar.tsx index a60e5beee7..cd5d344cca 100644 --- a/packages/nextra-theme-docs/src/components/sidebar.tsx +++ b/packages/nextra-theme-docs/src/components/sidebar.tsx @@ -18,7 +18,7 @@ import { useFocusedRoute, useMenu, useThemeConfig, - useToc + useTOC } from '../stores' import { LocaleSwitch } from './locale-switch' import { ThemeSwitch } from './theme-switch' @@ -286,7 +286,7 @@ Menu.displayName = 'Menu' export const MobileNav: FC = () => { const { directories } = useConfig().normalizePagesResult - const toc = useToc() + const toc = useTOC() const menu = useMenu() const pathname = usePathname() @@ -301,14 +301,15 @@ export const MobileNav: FC = () => { const sidebarRef = useRef(null!) useEffect(() => { - const activeElement = sidebarRef.current.querySelector('li.active') + const sidebar = sidebarRef.current + const activeLink = sidebar.querySelector('li.active') - if (activeElement && menu) { - scrollIntoView(activeElement, { + if (activeLink && menu) { + scrollIntoView(activeLink, { block: 'center', inline: 'center', scrollMode: 'always', - boundary: sidebarRef.current.parentNode as HTMLElement + boundary: sidebar.parentNode as HTMLElement }) } }, [menu]) @@ -355,9 +356,10 @@ export const MobileNav: FC = () => { ) } -let isInitialLoaded = false +let lastScrollPosition = 0 -export const Sidebar: FC<{ toc: Heading[] }> = ({ toc }) => { +export const Sidebar: FC = () => { + const toc = useTOC() const { normalizePagesResult, hideSidebar } = useConfig() const themeConfig = useThemeConfig() const [isExpanded, setIsExpanded] = useState(themeConfig.sidebar.defaultOpen) @@ -369,26 +371,31 @@ export const Sidebar: FC<{ toc: Heading[] }> = ({ toc }) => { const includePlaceholder = activeThemeContext.layout === 'default' useEffect(() => { - if (isInitialLoaded || window.innerWidth < 768) { + if (window.innerWidth < 768) { return } + const sidebar = sidebarRef.current + // Since `` is placed in `useMDXComponents.wrapper` on client side navigation he will - // be remounted and re-centered to active link, isInitialLoaded prevents this - isInitialLoaded = true - const activeElement = sidebarRef.current.querySelector('li.active') - if (activeElement) { - scrollIntoView(activeElement, { + // be remounted, this is workaround to restore scroll position, this will be fixed in Nextra 5 + if (lastScrollPosition) { + sidebar.scrollTop = lastScrollPosition + return + } + + const activeLink = sidebar.querySelector('li.active') + if (activeLink) { + scrollIntoView(activeLink, { block: 'center', inline: 'center', scrollMode: 'always', - boundary: sidebarRef.current.parentNode as HTMLDivElement + boundary: sidebar.parentNode as HTMLDivElement }) } }, []) const anchors = - // When the viewport size is larger than `md`, hide the anchors in - // the sidebar when `floatTOC` is enabled. + // hide the anchors in the sidebar when `floatTOC` is enabled. themeConfig.toc.float ? [] : toc.filter(v => v.depth === 2) const hasI18n = themeConfig.i18n.length > 0 @@ -419,6 +426,10 @@ export const Sidebar: FC<{ toc: Heading[] }> = ({ toc }) => { !isExpanded && 'no-scrollbar' )} ref={sidebarRef} + // @ts-expect-error -- false positive https://github.com/DefinitelyTyped/DefinitelyTyped/pull/72078 + onScrollEnd={(event) => { // eslint-disable-line react/no-unknown-property + lastScrollPosition = event.currentTarget.scrollTop + }} > {/* without !hideSidebar check 's inner.clientWidth on `layout: "raw"` will be 0 and element will not have width on initial loading */} {(!hideSidebar || !isExpanded) && ( From 3dcf68e1832a9926adf3380289c0b0b4853c6069 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 3 Mar 2025 10:14:11 +0100 Subject: [PATCH 06/11] upd --- .../nextra-theme-docs/src/components/sidebar.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/nextra-theme-docs/src/components/sidebar.tsx b/packages/nextra-theme-docs/src/components/sidebar.tsx index cd5d344cca..af530876e2 100644 --- a/packages/nextra-theme-docs/src/components/sidebar.tsx +++ b/packages/nextra-theme-docs/src/components/sidebar.tsx @@ -7,7 +7,12 @@ import { Anchor, Button, Collapse } from 'nextra/components' import { useFSRoute, useHash } from 'nextra/hooks' import { ArrowRightIcon, ExpandIcon } from 'nextra/icons' import type { Item, MenuItem, PageItem } from 'nextra/normalize-pages' -import type { FC, FocusEventHandler, MouseEventHandler } from 'react' +import type { + ComponentProps, + FC, + FocusEventHandler, + MouseEventHandler +} from 'react' import { forwardRef, useEffect, useId, useRef, useState } from 'react' import scrollIntoView from 'scroll-into-view-if-needed' import { @@ -358,6 +363,10 @@ export const MobileNav: FC = () => { let lastScrollPosition = 0 +const handleScrollEnd: ComponentProps<'div'>['onScroll'] = event => { + lastScrollPosition = event.currentTarget.scrollTop +} + export const Sidebar: FC = () => { const toc = useTOC() const { normalizePagesResult, hideSidebar } = useConfig() @@ -427,9 +436,7 @@ export const Sidebar: FC = () => { )} ref={sidebarRef} // @ts-expect-error -- false positive https://github.com/DefinitelyTyped/DefinitelyTyped/pull/72078 - onScrollEnd={(event) => { // eslint-disable-line react/no-unknown-property - lastScrollPosition = event.currentTarget.scrollTop - }} + onScrollEnd={handleScrollEnd} // eslint-disable-line react/no-unknown-property > {/* without !hideSidebar check 's inner.clientWidth on `layout: "raw"` will be 0 and element will not have width on initial loading */} {(!hideSidebar || !isExpanded) && ( From 0915d800d4297f0e27e8dc72a5e89f94311d1903 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 3 Mar 2025 11:39:46 +0100 Subject: [PATCH 07/11] fixes --- docs/app/docs/file-conventions/content-directory/page.mdx | 5 +---- packages/nextra-theme-docs/src/stores/theme-config.ts | 7 ++----- packages/nextra-theme-docs/src/stores/toc.ts | 7 ++----- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/docs/app/docs/file-conventions/content-directory/page.mdx b/docs/app/docs/file-conventions/content-directory/page.mdx index 3f6281f104..2ef8d8eb1e 100644 --- a/docs/app/docs/file-conventions/content-directory/page.mdx +++ b/docs/app/docs/file-conventions/content-directory/page.mdx @@ -15,10 +15,7 @@ import { MDXRemote } from 'nextra/mdx-remote' export async function MDXPathPage() { const filename = '[[...mdxPath]]/page.jsx' const rawMdx = `~~~jsx filename="${filename}" showLineNumbers -${(await fs.readFile(`../examples/docs/src/app/docs/${filename}`, 'utf8')) - .split('\n') - .slice(2, -1) - .join('\n')} +${(await fs.readFile(`../examples/docs/src/app/docs/${filename}`, 'utf8')).trimEnd()} ~~~` const rawJs = await compileMdx(rawMdx, { defaultShowCopyCode: true }) return diff --git a/packages/nextra-theme-docs/src/stores/theme-config.ts b/packages/nextra-theme-docs/src/stores/theme-config.ts index 07c1d1ae07..62c0dcc6e8 100644 --- a/packages/nextra-theme-docs/src/stores/theme-config.ts +++ b/packages/nextra-theme-docs/src/stores/theme-config.ts @@ -1,7 +1,6 @@ 'use client' -import type { ComponentProps } from 'react' -import { createContext, createElement, useContext } from 'react' +import { createContext, useContext } from 'react' import type { ThemeConfigProps } from '../layout' const ThemeConfigContext = createContext< @@ -18,6 +17,4 @@ const ThemeConfigContext = createContext< export const useThemeConfig = () => useContext(ThemeConfigContext) -export const ThemeConfigProvider = ( - props: ComponentProps -) => createElement(ThemeConfigContext.Provider, props) +export const ThemeConfigProvider = ThemeConfigContext.Provider diff --git a/packages/nextra-theme-docs/src/stores/toc.ts b/packages/nextra-theme-docs/src/stores/toc.ts index 36924d939d..432ce4b963 100644 --- a/packages/nextra-theme-docs/src/stores/toc.ts +++ b/packages/nextra-theme-docs/src/stores/toc.ts @@ -2,13 +2,10 @@ 'use client' import type { Heading } from 'nextra' -import type { ComponentProps } from 'react' -import { createContext, createElement, useContext } from 'react' +import { createContext, useContext } from 'react' const TOCContext = createContext([]) export const useTOC = () => useContext(TOCContext) -export const TOCProvider = ( - props: ComponentProps -) => createElement(TOCContext.Provider, props) +export const TOCProvider = TOCContext.Provider From 4bafce9ba3f60778bdbe1a2496b44d3347eb1b71 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 3 Mar 2025 11:40:05 +0100 Subject: [PATCH 08/11] Update .changeset/silver-numbers-call.md --- .changeset/silver-numbers-call.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/silver-numbers-call.md b/.changeset/silver-numbers-call.md index 56327f681b..50f2ce4148 100644 --- a/.changeset/silver-numbers-call.md +++ b/.changeset/silver-numbers-call.md @@ -2,4 +2,4 @@ "nextra-theme-docs": patch --- -fix: sidebar is scrolled on client side navigation +fix: make scroll position in sidebar stable between client-side navigation From 93a83744425af68d85b02710266f7d536b112be8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 3 Mar 2025 11:40:11 +0100 Subject: [PATCH 09/11] Update packages/nextra-theme-docs/src/components/sidebar.tsx --- packages/nextra-theme-docs/src/components/sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextra-theme-docs/src/components/sidebar.tsx b/packages/nextra-theme-docs/src/components/sidebar.tsx index af530876e2..bebb4bf90f 100644 --- a/packages/nextra-theme-docs/src/components/sidebar.tsx +++ b/packages/nextra-theme-docs/src/components/sidebar.tsx @@ -386,7 +386,7 @@ export const Sidebar: FC = () => { const sidebar = sidebarRef.current // Since `` is placed in `useMDXComponents.wrapper` on client side navigation he will - // be remounted, this is workaround to restore scroll position, this will be fixed in Nextra 5 + // be remounted, this is a workaround to restore the scroll position, and be fixed in Nextra 5 if (lastScrollPosition) { sidebar.scrollTop = lastScrollPosition return From 8bab1a86f841a681e825c1c9a859725c8613be4c Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 3 Mar 2025 11:49:12 +0100 Subject: [PATCH 10/11] Update packages/nextra-theme-docs/src/components/sidebar.tsx --- packages/nextra-theme-docs/src/components/sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextra-theme-docs/src/components/sidebar.tsx b/packages/nextra-theme-docs/src/components/sidebar.tsx index bebb4bf90f..02eebf58a1 100644 --- a/packages/nextra-theme-docs/src/components/sidebar.tsx +++ b/packages/nextra-theme-docs/src/components/sidebar.tsx @@ -386,7 +386,7 @@ export const Sidebar: FC = () => { const sidebar = sidebarRef.current // Since `` is placed in `useMDXComponents.wrapper` on client side navigation he will - // be remounted, this is a workaround to restore the scroll position, and be fixed in Nextra 5 + // be remounted, this is a workaround to restore the scroll position, and will be fixed in Nextra 5 if (lastScrollPosition) { sidebar.scrollTop = lastScrollPosition return From 82f0c4992e3007b7af536eb115b6022169ce5271 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 3 Mar 2025 11:49:04 +0100 Subject: [PATCH 11/11] oops --- packages/nextra-theme-docs/src/stores/theme-config.ts | 7 +++++-- packages/nextra-theme-docs/src/stores/toc.ts | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/nextra-theme-docs/src/stores/theme-config.ts b/packages/nextra-theme-docs/src/stores/theme-config.ts index 62c0dcc6e8..07c1d1ae07 100644 --- a/packages/nextra-theme-docs/src/stores/theme-config.ts +++ b/packages/nextra-theme-docs/src/stores/theme-config.ts @@ -1,6 +1,7 @@ 'use client' -import { createContext, useContext } from 'react' +import type { ComponentProps } from 'react' +import { createContext, createElement, useContext } from 'react' import type { ThemeConfigProps } from '../layout' const ThemeConfigContext = createContext< @@ -17,4 +18,6 @@ const ThemeConfigContext = createContext< export const useThemeConfig = () => useContext(ThemeConfigContext) -export const ThemeConfigProvider = ThemeConfigContext.Provider +export const ThemeConfigProvider = ( + props: ComponentProps +) => createElement(ThemeConfigContext.Provider, props) diff --git a/packages/nextra-theme-docs/src/stores/toc.ts b/packages/nextra-theme-docs/src/stores/toc.ts index 432ce4b963..36924d939d 100644 --- a/packages/nextra-theme-docs/src/stores/toc.ts +++ b/packages/nextra-theme-docs/src/stores/toc.ts @@ -2,10 +2,13 @@ 'use client' import type { Heading } from 'nextra' -import { createContext, useContext } from 'react' +import type { ComponentProps } from 'react' +import { createContext, createElement, useContext } from 'react' const TOCContext = createContext([]) export const useTOC = () => useContext(TOCContext) -export const TOCProvider = TOCContext.Provider +export const TOCProvider = ( + props: ComponentProps +) => createElement(TOCContext.Provider, props)