From aebc64a4919e99f427cdd0ebbcaa94f33cbfe565 Mon Sep 17 00:00:00 2001 From: Shannon Anahata Date: Mon, 27 Oct 2025 15:03:45 -0700 Subject: [PATCH 1/4] exploring a top of page ToC --- src/components/docPage/index.tsx | 2 + .../inlineTableOfContents/index.tsx | 106 ++++++++++++++++++ .../inlineTableOfContents/style.module.scss | 49 ++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/components/inlineTableOfContents/index.tsx create mode 100644 src/components/inlineTableOfContents/style.module.scss diff --git a/src/components/docPage/index.tsx b/src/components/docPage/index.tsx index 51e1c7fb995dab..d68f6feb45b810 100644 --- a/src/components/docPage/index.tsx +++ b/src/components/docPage/index.tsx @@ -16,6 +16,7 @@ import {CopyMarkdownButton} from '../copyMarkdownButton'; import {DocFeedback} from '../docFeedback'; import {GitHubCTA} from '../githubCTA'; import {Header} from '../header'; +import {InlineTableOfContents} from '../inlineTableOfContents'; import Mermaid from '../mermaid'; import {PaginationNav} from '../paginationNav'; import {PlatformSdkDetail} from '../platformSdkDetail'; @@ -96,6 +97,7 @@ export function DocPage({

{frontMatter.title}

{frontMatter.description}

+ {/* This exact id is important for Algolia indexing */}
{children} diff --git a/src/components/inlineTableOfContents/index.tsx b/src/components/inlineTableOfContents/index.tsx new file mode 100644 index 00000000000000..1bd8aabe1e68d9 --- /dev/null +++ b/src/components/inlineTableOfContents/index.tsx @@ -0,0 +1,106 @@ +'use client'; + +import {useEffect, useState} from 'react'; + +import {isNotNil} from 'sentry-docs/utils'; + +import styles from './style.module.scss'; + +interface TocItem { + title: string; + url: string; +} + +function getMainElement() { + if (typeof document === 'undefined') { + return null; + } + return document.getElementById('main'); +} + +function getTocItems(main: HTMLElement): TocItem[] { + return Array.from(main.querySelectorAll('h2')) + .map(el => { + const title = el.textContent?.trim(); + if (!el.id || !title) { + return null; + } + // This is a relatively new API, that checks if the element is visible in the document + // With this, we filter out e.g. sections hidden via CSS + if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) { + return null; + } + return { + url: `#${el.id}`, + title, + }; + }) + .filter(isNotNil); +} + +// Inline table of contents that appears at the top of the page content +// Shows only H2 headings, and only appears if there are 3 or more sections +export function InlineTableOfContents() { + const [tocItems, setTocItems] = useState([]); + + // gather the toc items on mount + useEffect(() => { + const main = getMainElement(); + if (!main) { + return; + } + + const items = getTocItems(main); + setTocItems(items); + }, []); + + // ensure toc items are kept up-to-date if the DOM changes + useEffect(() => { + const main = getMainElement(); + if (!main) { + return () => {}; + } + + const observer = new MutationObserver(() => { + const newTocItems = getTocItems(main); + + // Avoid flashing if nothing changes + if ( + newTocItems.length === tocItems.length && + newTocItems.every((item, index) => item.url === tocItems[index].url) + ) { + return; + } + setTocItems(newTocItems); + }); + + // Start observing the target node for any changes in its subtree + observer.observe(main, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['class', 'id', 'style'], + }); + + return () => observer.disconnect(); + }, [tocItems]); + + // Only show if there are 3 or more sections + if (tocItems.length < 3) { + return null; + } + + return ( +
+
On this page
+ +
+ ); +} + diff --git a/src/components/inlineTableOfContents/style.module.scss b/src/components/inlineTableOfContents/style.module.scss new file mode 100644 index 00000000000000..cfa8336837aa02 --- /dev/null +++ b/src/components/inlineTableOfContents/style.module.scss @@ -0,0 +1,49 @@ +.inline-toc { + --title-color: var(--gray-11); + --link-color: var(--gray-11); + --link-hover-color: var(--accent-purple); + + border-left: 2px solid var(--gray-6); + padding-left: 1rem; + margin: 1.5rem 0; +} + +:global(.dark) { + .inline-toc { + --title-color: var(--gray-11); + --link-color: var(--gray-11); + } +} + +.inline-toc-title { + font-weight: 600; + font-size: 0.925rem; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--title-color); + margin-bottom: 0.5rem; +} + +.inline-toc-list { + list-style-type: none; + padding-left: 0; + margin: 0; + + li { + margin: 0; + } + + a { + color: var(--link-color); + text-decoration: none; + font-size: 0.875rem; + line-height: 1.8; + display: block; + transition: color 0.2s ease; + + &:hover { + color: var(--link-hover-color); + } + } +} + From 277086dd17351d4b0552659611f19db79088b427 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:05:52 +0000 Subject: [PATCH 2/4] [getsentry/action-github-commit] Auto commit --- src/components/inlineTableOfContents/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/inlineTableOfContents/index.tsx b/src/components/inlineTableOfContents/index.tsx index 1bd8aabe1e68d9..4c6be2d1765c98 100644 --- a/src/components/inlineTableOfContents/index.tsx +++ b/src/components/inlineTableOfContents/index.tsx @@ -103,4 +103,3 @@ export function InlineTableOfContents() {
); } - From d975e4a1d041ec78710e90a491f21335de56fe1c Mon Sep 17 00:00:00 2001 From: Shannon Anahata Date: Mon, 27 Oct 2025 15:34:56 -0700 Subject: [PATCH 3/4] removing side bar ToC entirely --- src/components/docPage/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/docPage/index.tsx b/src/components/docPage/index.tsx index d68f6feb45b810..3c94ac73a783a1 100644 --- a/src/components/docPage/index.tsx +++ b/src/components/docPage/index.tsx @@ -21,7 +21,6 @@ import Mermaid from '../mermaid'; import {PaginationNav} from '../paginationNav'; import {PlatformSdkDetail} from '../platformSdkDetail'; import {Sidebar} from '../sidebar'; -import {SidebarTableOfContents} from '../sidebarTableOfContents'; import {ReaderDepthTracker} from '../track-reader-depth'; type Props = { @@ -124,7 +123,6 @@ export function DocPage({ className="sticky h-[calc(100vh-var(--header-height))] top-[var(--header-height)] overflow-y-auto hidden toc:block flex-none w-[250px] min-w-[250px]" >
-
From 79760a71b2790121d3c20965188253b4de0f7c48 Mon Sep 17 00:00:00 2001 From: Shannon Anahata Date: Tue, 28 Oct 2025 09:54:55 -0700 Subject: [PATCH 4/4] making sure text wraps or flows, but never cuts off --- .../inlineTableOfContents/style.module.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/inlineTableOfContents/style.module.scss b/src/components/inlineTableOfContents/style.module.scss index cfa8336837aa02..57a83d0d6eccbd 100644 --- a/src/components/inlineTableOfContents/style.module.scss +++ b/src/components/inlineTableOfContents/style.module.scss @@ -5,7 +5,10 @@ border-left: 2px solid var(--gray-6); padding-left: 1rem; + padding-right: 1rem; margin: 1.5rem 0; + max-width: 100%; + width: 100%; } :global(.dark) { @@ -22,15 +25,21 @@ letter-spacing: 0.5px; color: var(--title-color); margin-bottom: 0.5rem; + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; } .inline-toc-list { list-style-type: none; padding-left: 0; margin: 0; + max-width: 100%; + width: 100%; li { margin: 0; + max-width: 100%; } a { @@ -40,6 +49,10 @@ line-height: 1.8; display: block; transition: color 0.2s ease; + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; + max-width: 100%; &:hover { color: var(--link-hover-color);