-
+ {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