diff --git a/.changeset/cyan-taxis-help.md b/.changeset/cyan-taxis-help.md new file mode 100644 index 000000000..1997eb949 --- /dev/null +++ b/.changeset/cyan-taxis-help.md @@ -0,0 +1,7 @@ +--- +'@myst-theme/providers': patch +'@myst-theme/site': patch +'@myst-theme/book': patch +--- + +Grow TOC on narrow screens diff --git a/packages/providers/src/ui.tsx b/packages/providers/src/ui.tsx index 31c50b9e8..2e9e811b9 100644 --- a/packages/providers/src/ui.tsx +++ b/packages/providers/src/ui.tsx @@ -3,11 +3,12 @@ import { useMediaQuery } from './hooks.js'; type UiState = { isNavOpen?: boolean; + isWide?: boolean; }; type UiContextType = [UiState, (state: UiState) => void]; -const UiContext = createContext(undefined); +export const UiContext = createContext(undefined); // Create a provider for components to consume and subscribe to changes export function UiStateProvider({ children }: { children: React.ReactNode }) { @@ -15,7 +16,7 @@ export function UiStateProvider({ children }: { children: React.ReactNode }) { const wide = useMediaQuery('(min-width: 1280px)'); const [state, setState] = useState({ isNavOpen: false }); useEffect(() => { - if (wide) setState({ ...state, isNavOpen: false }); + if (wide) setState({ ...state, isNavOpen: false, isWide: wide }); }, [wide]); return {children}; } @@ -28,3 +29,8 @@ export function useNavOpen(): [boolean, (open: boolean) => void] { }; return [state?.isNavOpen ?? false, setOpen]; } + +export function useIsWide(): boolean { + const [state] = useContext(UiContext) ?? []; + return state?.isWide ?? false; +} diff --git a/packages/site/src/components/Navigation/PrimarySidebar.tsx b/packages/site/src/components/Navigation/PrimarySidebar.tsx index af1287a85..396ca8723 100644 --- a/packages/site/src/components/Navigation/PrimarySidebar.tsx +++ b/packages/site/src/components/Navigation/PrimarySidebar.tsx @@ -6,6 +6,7 @@ import { useSiteManifest, useGridSystemProvider, useThemeTop, + useIsWide, } from '@myst-theme/providers'; import type { Heading } from '@myst-theme/common'; import { Toc } from './TableOfContentsItems.js'; @@ -95,10 +96,15 @@ export function useSidebarHeight(top = 0, i const container = useRef(null); const toc = useRef(null); const transitionState = useNavigation().state; + const wide = useIsWide(); const setHeight = () => { if (!container.current || !toc.current) return; const height = container.current.offsetHeight - window.scrollY; const div = toc.current.firstChild as HTMLDivElement; + if (div) + div.style.height = wide + ? `min(calc(100vh - ${top}px), ${height + inset}px)` + : `calc(100vh - ${top}px)`; if (div) div.style.height = `min(calc(100vh - ${top}px), ${height + inset}px)`; const nav = toc.current.querySelector('nav'); if (nav) nav.style.opacity = height > 150 ? '1' : '0'; @@ -111,7 +117,7 @@ export function useSidebarHeight(top = 0, i return () => { window.removeEventListener('scroll', handleScroll); }; - }, [container, toc, transitionState]); + }, [container, toc, transitionState, wide]); return { container, toc }; } diff --git a/themes/book/app/routes/$.tsx b/themes/book/app/routes/$.tsx index 917615563..adab7dfa8 100644 --- a/themes/book/app/routes/$.tsx +++ b/themes/book/app/routes/$.tsx @@ -73,7 +73,7 @@ export const loader: LoaderFunction = async ({ params, request }) => { return json({ config, page, project }); }; -export function ArticlePageAndNavigation({ +function ArticlePageAndNavigationInternal({ children, hide_toc, projectSlug, @@ -87,7 +87,7 @@ export function ArticlePageAndNavigation({ const top = useThemeTop(); const { container, toc } = useSidebarHeight(top, inset); return ( - + <> + + ); +} + +export function ArticlePageAndNavigation({ + children, + hide_toc, + projectSlug, + inset = 20, // begin text 20px from the top (aligned with menu) +}: { + hide_toc?: boolean; + projectSlug?: string; + children: React.ReactNode; + inset?: number; +}) { + return ( + + ); }