diff options
author | 2022-11-17 21:18:57 +0530 | |
---|---|---|
committer | 2022-11-17 10:48:57 -0500 | |
commit | fcfd166f2d52dcf800fbaff6d618ad57eaf3cc9b (patch) | |
tree | b9f79e2c4ab451e3e3f8706806bc92d9be64931c /examples/docs/src | |
parent | 12236dbc06e1e43618b61d180020a67cb31499f8 (diff) | |
download | astro-fcfd166f2d52dcf800fbaff6d618ad57eaf3cc9b.tar.gz astro-fcfd166f2d52dcf800fbaff6d618ad57eaf3cc9b.tar.zst astro-fcfd166f2d52dcf800fbaff6d618ad57eaf3cc9b.zip |
fix: Docs Site - Table of contents highlight not working (#5411)
* fix: Docs Site - Table of contents highlight not working
* Add html-escaper devDep
* add html-escaper via pnpm
Diffstat (limited to 'examples/docs/src')
-rw-r--r-- | examples/docs/src/components/RightSidebar/MoreMenu.astro | 4 | ||||
-rw-r--r-- | examples/docs/src/components/RightSidebar/TableOfContents.tsx | 59 | ||||
-rw-r--r-- | examples/docs/src/styles/index.css | 83 |
3 files changed, 106 insertions, 40 deletions
diff --git a/examples/docs/src/components/RightSidebar/MoreMenu.astro b/examples/docs/src/components/RightSidebar/MoreMenu.astro index 655f8eac1..4adffaff4 100644 --- a/examples/docs/src/components/RightSidebar/MoreMenu.astro +++ b/examples/docs/src/components/RightSidebar/MoreMenu.astro @@ -14,7 +14,7 @@ const showMoreSection = CONFIG.COMMUNITY_INVITE_URL; <ul> { editHref && ( - <li class={`heading-link depth-2`}> + <li class={`header-link depth-2`}> <a class="edit-on-github" href={editHref} target="_blank"> <svg aria-hidden="true" @@ -40,7 +40,7 @@ const showMoreSection = CONFIG.COMMUNITY_INVITE_URL; } { CONFIG.COMMUNITY_INVITE_URL && ( - <li class={`heading-link depth-2`}> + <li class={`header-link depth-2`}> <a href={CONFIG.COMMUNITY_INVITE_URL} target="_blank"> <svg aria-hidden="true" diff --git a/examples/docs/src/components/RightSidebar/TableOfContents.tsx b/examples/docs/src/components/RightSidebar/TableOfContents.tsx index 5c6851462..34b0ab732 100644 --- a/examples/docs/src/components/RightSidebar/TableOfContents.tsx +++ b/examples/docs/src/components/RightSidebar/TableOfContents.tsx @@ -1,6 +1,7 @@ +import { unescape } from 'html-escaper'; +import type { MarkdownHeading } from 'astro'; import type { FunctionalComponent } from 'preact'; import { useState, useEffect, useRef } from 'preact/hooks'; -import type { MarkdownHeading } from 'astro'; type ItemOffsets = { id: string; @@ -10,9 +11,10 @@ type ItemOffsets = { const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({ headings = [], }) => { + const toc = useRef<HTMLUListElement>(); + const onThisPageID = 'on-this-page-heading'; const itemOffsets = useRef<ItemOffsets[]>([]); - // FIXME: Not sure what this state is doing. It was never set to anything truthy. - const [activeId] = useState<string>(''); + const [currentID, setCurrentID] = useState('overview'); useEffect(() => { const getItemOffsets = () => { const titles = document.querySelectorAll('article :is(h1, h2, h3, h4)'); @@ -30,22 +32,57 @@ const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({ }; }, []); + useEffect(() => { + if (!toc.current) return; + + const setCurrent: IntersectionObserverCallback = (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + const { id } = entry.target; + if (id === onThisPageID) continue; + setCurrentID(entry.target.id); + break; + } + } + }; + + const observerOptions: IntersectionObserverInit = { + // Negative top margin accounts for `scroll-margin`. + // Negative bottom margin means heading needs to be towards top of viewport to trigger intersection. + rootMargin: '-100px 0% -66%', + threshold: 1, + }; + + const headingsObserver = new IntersectionObserver(setCurrent, observerOptions); + + // Observe all the headings in the main page content. + document.querySelectorAll('article :is(h1,h2,h3)').forEach((h) => headingsObserver.observe(h)); + + // Stop observing when the component is unmounted. + return () => headingsObserver.disconnect(); + }, [toc.current]); + + const onLinkClick = (e) => { + setCurrentID(e.target.getAttribute('href').replace('#', '')); + }; + return ( <> - <h2 className="heading">On this page</h2> - <ul> - <li className={`heading-link depth-2 ${activeId === 'overview' ? 'active' : ''}`.trim()}> - <a href="#overview">Overview</a> - </li> + <h2 id={onThisPageID} className="heading"> + On this page + </h2> + <ul ref={toc}> {headings .filter(({ depth }) => depth > 1 && depth < 4) .map((heading) => ( <li - className={`heading-link depth-${heading.depth} ${ - activeId === heading.slug ? 'active' : '' + className={`header-link depth-${heading.depth} ${ + currentID === heading.slug ? 'current-header-link' : '' }`.trim()} > - <a href={`#${heading.slug}`}>{heading.text}</a> + <a href={`#${heading.slug}`} onClick={onLinkClick}> + {unescape(heading.text)} + </a> </li> ))} </ul> diff --git a/examples/docs/src/styles/index.css b/examples/docs/src/styles/index.css index 2a735decf..fe3e04fc2 100644 --- a/examples/docs/src/styles/index.css +++ b/examples/docs/src/styles/index.css @@ -311,45 +311,57 @@ h2.heading { margin-bottom: 0.5rem; } -.heading-link { - font-size: 1rem; - padding: 0.1rem 0 0.1rem 1rem; +.header-link { + font-size: 1em; + transition: border-inline-start-color 100ms ease-out, background-color 200ms ease-out; border-left: 4px solid var(--theme-divider); } -.heading-link:hover, -.heading-link:focus { - border-left-color: var(--theme-accent); - color: var(--theme-accent); +.header-link a { + display: inline-flex; + gap: 0.5em; + width: 100%; + font: inherit; + padding: 0.4rem 0; + line-height: 1.3; + color: inherit; + text-decoration: none; + unicode-bidi: plaintext; } -.heading-link:focus-within { - color: var(--theme-text-light); - border-left-color: hsla(var(--color-gray-40), 1); + +@media (min-width: 50em) { + .header-link a { + padding: 0.275rem 0; + } +} + +.header-link:hover, +.header-link:focus, +.header-link:focus-within { + border-inline-start-color: var(--theme-accent-secondary); +} + +.header-link:hover a, +.header-link a:focus { + color: var(--theme-text); + text-decoration: underline; } -.heading-link svg { +.header-link svg { opacity: 0.6; } -.heading-link:hover svg { +.header-link:hover svg { opacity: 0.8; } -.heading-link a { - display: inline-flex; - gap: 0.5em; - width: 100%; - padding: 0.15em 0 0.15em 0; -} -.heading-link.depth-3 { - padding-left: 2rem; +/* Add line and padding on the left side */ +.header-link { + padding-inline-start: 1rem; } -.heading-link.depth-4 { - padding-left: 3rem; +.header-link.depth-3 { + padding-inline-start: 2rem; } - -.heading-link a { - font: inherit; - color: inherit; - text-decoration: none; +.header-link.depth-4 { + padding-inline-start: 3rem; } /* Screenreader Only Text */ @@ -380,3 +392,20 @@ h2.heading { :target { scroll-margin: calc(var(--theme-sidebar-offset, 5rem) + 2rem) 0 2rem; } + +/* Highlight TOC header link matching the current scroll position */ +.current-header-link { + background-color: var(--theme-bg-accent); + /* Indicates the current heading for forced colors users in older browsers */ + outline: 1px solid transparent; +} + +@media (forced-colors: active) { + .current-header-link { + border: 1px solid CanvasText; + } +} + +.current-header-link a { + color: var(--theme-text); +} |