summaryrefslogtreecommitdiff
path: root/examples/docs/src
diff options
context:
space:
mode:
authorGravatar Rishi Raj Jain <rjain@llnw.com> 2022-11-17 21:18:57 +0530
committerGravatar GitHub <noreply@github.com> 2022-11-17 10:48:57 -0500
commitfcfd166f2d52dcf800fbaff6d618ad57eaf3cc9b (patch)
treeb9f79e2c4ab451e3e3f8706806bc92d9be64931c /examples/docs/src
parent12236dbc06e1e43618b61d180020a67cb31499f8 (diff)
downloadastro-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.astro4
-rw-r--r--examples/docs/src/components/RightSidebar/TableOfContents.tsx59
-rw-r--r--examples/docs/src/styles/index.css83
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);
+}