diff --git a/.changeset/slow-lizards-obey.md b/.changeset/slow-lizards-obey.md new file mode 100644 index 0000000000..ce1ef0e04c --- /dev/null +++ b/.changeset/slow-lizards-obey.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Make TOC height dynamic based on visible header and footer elements diff --git a/packages/gitbook/src/components/Announcement/AnnouncementBanner.tsx b/packages/gitbook/src/components/Announcement/AnnouncementBanner.tsx index c89bb24804..522bde0ca3 100644 --- a/packages/gitbook/src/components/Announcement/AnnouncementBanner.tsx +++ b/packages/gitbook/src/components/Announcement/AnnouncementBanner.tsx @@ -25,7 +25,7 @@ export function AnnouncementBanner(props: { const style = BANNER_STYLES[announcement.style]; return ( -
+
+
- + {customization.trademark.enabled ? ( + + ) : null} + +
+ + + ); } diff --git a/packages/gitbook/src/components/TableOfContents/TableOfContentsScript.tsx b/packages/gitbook/src/components/TableOfContents/TableOfContentsScript.tsx new file mode 100644 index 0000000000..1de9d20c3e --- /dev/null +++ b/packages/gitbook/src/components/TableOfContents/TableOfContentsScript.tsx @@ -0,0 +1,73 @@ +'use client'; + +import { useEffect } from 'react'; + +/** + * Adjusts TableOfContents height based on visible elements + */ +export function TableOfContentsScript() { + useEffect(() => { + const root = document.documentElement; + + // Calculate and set TOC dimensions + const updateTocLayout = () => { + // Get key elements + const header = document.getElementById('site-header'); + const banner = document.getElementById('announcement-banner'); + const footer = document.getElementById('site-footer'); + + // Set sticky top position based on header + const headerHeight = header?.offsetHeight ?? 0; + root.style.setProperty('--toc-top-offset', `${headerHeight}px`); + + // Start with full viewport height minus header + let height = window.innerHeight - headerHeight; + + // Subtract visible banner (if any) + if (banner && banner?.computedStyleMap().get('display') !== 'none') { + const bannerRect = banner.getBoundingClientRect(); + if (bannerRect.height > 0 && bannerRect.bottom > 0) { + height -= Math.min(bannerRect.height, bannerRect.bottom); + } + } + + // Subtract visible footer (if any) + if (footer) { + const footerRect = footer.getBoundingClientRect(); + if (footerRect.top < window.innerHeight) { + height -= Math.min(footerRect.height, window.innerHeight - footerRect.top); + } + } + + // Update height + root.style.setProperty('--toc-height', `${height}px`); + }; + + // Initial update + updateTocLayout(); + + // Let the browser handle scroll throttling naturally + window.addEventListener('scroll', updateTocLayout, { passive: true }); + window.addEventListener('resize', updateTocLayout, { passive: true }); + + // Use MutationObserver for DOM changes + const observer = new MutationObserver(() => { + requestAnimationFrame(updateTocLayout); + }); + + // Only observe what matters + observer.observe(document.documentElement, { + subtree: true, + attributes: true, + attributeFilter: ['style', 'class'], + }); + + return () => { + observer.disconnect(); + window.removeEventListener('scroll', updateTocLayout); + window.removeEventListener('resize', updateTocLayout); + }; + }, []); + + return null; +} diff --git a/packages/gitbook/src/components/TableOfContents/index.ts b/packages/gitbook/src/components/TableOfContents/index.ts index 715a3a239e..6eeff92697 100644 --- a/packages/gitbook/src/components/TableOfContents/index.ts +++ b/packages/gitbook/src/components/TableOfContents/index.ts @@ -1 +1,4 @@ -export * from './TableOfContents'; +export { TableOfContents } from './TableOfContents'; +export { PagesList } from './PagesList'; +export { TOCScrollContainer } from './TOCScroller'; +export { Trademark } from './Trademark'; diff --git a/packages/gitbook/tailwind.config.ts b/packages/gitbook/tailwind.config.ts index 2e5b7c359f..9d248c9a40 100644 --- a/packages/gitbook/tailwind.config.ts +++ b/packages/gitbook/tailwind.config.ts @@ -458,12 +458,16 @@ const config: Config = { /** * Variant when a header is displayed. */ - addVariant('site-header-none', 'html.site-header-none &'); + addVariant('site-header-none', 'body:not(:has(#site-header:not(.mobile-only))) &'); addVariant('site-header', 'body:has(#site-header:not(.mobile-only)) &'); addVariant('site-header-sections', [ 'body:has(#site-header:not(.mobile-only) #sections) &', 'body:has(.page-no-toc):has(#site-header:not(.mobile-only) #variants) &', ]); + addVariant( + 'announcement', + 'html:not(.announcement-hidden):has(#announcement-banner) &' + ); const customisationVariants = { // Sidebar styles