diff options
author | Chris Swithinbank | 2023-05-11 16:09:22 +0200 |
---|---|---|
committer | GitHub | 2023-05-11 16:09:22 +0200 |
commit | 62265e4a653d161483e3844b568ab150334e9238 (patch) | |
tree | e213ed805314191c614d19931238884b5c676308 | |
parent | 294f851c23aeb454fc6dcec81bf2548112422540 (diff) | |
download | IT.starlight-62265e4a653d161483e3844b568ab150334e9238.tar.gz IT.starlight-62265e4a653d161483e3844b568ab150334e9238.tar.bz2 IT.starlight-62265e4a653d161483e3844b568ab150334e9238.zip |
Add mobile table of contents component (#36)
-rw-r--r-- | .changeset/tasty-balloons-hang.md | 5 | ||||
-rw-r--r-- | packages/starlight/components/Header.astro | 2 | ||||
-rw-r--r-- | packages/starlight/components/RightSidebar.astro | 29 | ||||
-rw-r--r-- | packages/starlight/components/TableOfContents/MobileTableOfContents.astro | 125 | ||||
-rw-r--r-- | packages/starlight/components/TableOfContents/TableOfContentsList.astro | 37 | ||||
-rw-r--r-- | packages/starlight/index.astro | 23 | ||||
-rw-r--r-- | packages/starlight/layout/PageFrame.astro | 3 | ||||
-rw-r--r-- | packages/starlight/layout/TwoColumnContent.astro | 2 | ||||
-rw-r--r-- | packages/starlight/style/props.css | 8 | ||||
-rw-r--r-- | packages/starlight/style/util.css | 11 |
10 files changed, 214 insertions, 31 deletions
diff --git a/.changeset/tasty-balloons-hang.md b/.changeset/tasty-balloons-hang.md new file mode 100644 index 00000000..212cc72f --- /dev/null +++ b/.changeset/tasty-balloons-hang.md @@ -0,0 +1,5 @@ +--- +"@astrojs/starlight": patch +--- + +Collapse table of contents in dropdown on narrower viewports diff --git a/packages/starlight/components/Header.astro b/packages/starlight/components/Header.astro index 7547c30e..235ade3a 100644 --- a/packages/starlight/components/Header.astro +++ b/packages/starlight/components/Header.astro @@ -30,7 +30,7 @@ interface Props { } .site-title { - font-size: var(--sl-text-2xl); + font-size: var(--sl-text-h4); font-weight: 600; color: var(--sl-color-text-accent); text-decoration: none; diff --git a/packages/starlight/components/RightSidebar.astro b/packages/starlight/components/RightSidebar.astro new file mode 100644 index 00000000..b7a30a6b --- /dev/null +++ b/packages/starlight/components/RightSidebar.astro @@ -0,0 +1,29 @@ +--- +import type { MarkdownHeading } from 'astro'; +import type { CollectionEntry } from 'astro:content'; +import config from 'virtual:starlight/user-config'; +import EditLink from './EditLink.astro'; +import RightSidebarPanel from './RightSidebarPanel.astro'; +import TableOfContents from './TableOfContents.astro'; + +interface Props { + entry: CollectionEntry<'docs'>; + headings: MarkdownHeading[]; +} + +const { entry, headings } = Astro.props; +--- + +<RightSidebarPanel> + <TableOfContents headings={headings} /> +</RightSidebarPanel> +<RightSidebarPanel> + { + config.editLink.baseUrl && ( + <> + <h2>Contribute</h2> + <EditLink data={entry.data} id={entry.id} /> + </> + ) + } +</RightSidebarPanel> diff --git a/packages/starlight/components/TableOfContents/MobileTableOfContents.astro b/packages/starlight/components/TableOfContents/MobileTableOfContents.astro new file mode 100644 index 00000000..494b1d00 --- /dev/null +++ b/packages/starlight/components/TableOfContents/MobileTableOfContents.astro @@ -0,0 +1,125 @@ +--- +import type { MarkdownHeading } from 'astro'; +import config from 'virtual:starlight/user-config'; +import { generateToC } from './generateToC'; +import TableOfContentsList from './TableOfContentsList.astro'; +import Icon from '../Icon.astro'; + +interface Props { + headings: MarkdownHeading[]; +} + +const toc = generateToC(Astro.props.headings, config.tableOfContents); +--- + +<nav aria-labelledby="starlight__on-this-page--mobile" class="lg:hidden"> + <details id="starlight__mobile-toc"> + <summary id="starlight__on-this-page--mobile" class="flex"> + <div class="toggle flex"> + On this page + <Icon name={'right-caret'} class="caret" size="1rem" /> + </div> + </summary> + <div class="dropdown"> + <TableOfContentsList toc={toc} isMobile /> + </div> + </details> +</nav> + +<style> + nav { + position: fixed; + top: calc(var(--sl-nav-height) - 1px); + inset-inline: 0; + border-top: 1px solid var(--sl-color-gray-5); + background-color: var(--sl-color-bg-nav); + } + @media (min-width: 50rem) { + nav { + inset-inline-start: var(--sl-sidebar-width); + } + } + + summary { + height: var(--sl-mobile-toc-height); + border-bottom: 1px solid var(--sl-color-hairline-shade); + padding: 0.5rem 1rem; + outline-offset: var(--sl-outline-offset-inside); + } + summary::marker, + summary::-webkit-details-marker { + display: none; + } + + .toggle { + gap: 1rem; + align-items: center; + justify-content: space-between; + border: 1px solid var(--sl-color-gray-5); + border-radius: 0.5rem; + padding-block: 0.5rem; + padding-inline-start: 0.75rem; + padding-inline-end: 0.5rem; + font-size: var(--sl-text-xs); + line-height: 1; + background-color: var(--sl-color-black); + user-select: none; + cursor: pointer; + } + details[open] .toggle { + color: var(--sl-color-white); + border-color: var(--sl-color-accent); + } + details .toggle:hover { + color: var(--sl-color-white); + border-color: var(--sl-color-gray-2); + } + + :global([dir='rtl']) .caret { + transform: rotateZ(180deg); + } + details[open] .caret { + transform: rotateZ(90deg); + } + + .dropdown { + --border-top: 1px; + margin-top: calc(-1 * var(--border-top)); + border: var(--border-top) solid var(--sl-color-gray-6); + border-top-color: var(--sl-color-hairline-shade); + max-height: calc(85vh - var(--sl-nav-height) - var(--sl-mobile-toc-height)); + overflow-y: auto; + background-color: var(--sl-color-black); + box-shadow: var(--sl-shadow-md); + } +</style> + +<script> + const details = document.querySelector<HTMLDetailsElement>( + '#starlight__mobile-toc' + ); + if (details) { + const closeToC = () => { + details.open = false; + }; + // Close the table of contents whenever a link is clicked. + details.querySelectorAll('a').forEach((a) => { + a.addEventListener('click', closeToC); + }); + // Close the table of contents when a user clicks outside of it. + window.addEventListener('click', (e) => { + if (!details.contains(e.target as Node)) closeToC(); + }); + // Or when they press the escape key. + window.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && details.open) { + const hasFocus = details.contains(document.activeElement); + closeToC(); + if (hasFocus) { + const summary = details.querySelector('summary'); + if (summary) summary.focus(); + } + } + }); + } +</script> diff --git a/packages/starlight/components/TableOfContents/TableOfContentsList.astro b/packages/starlight/components/TableOfContents/TableOfContentsList.astro index d05b60bd..f19c91bd 100644 --- a/packages/starlight/components/TableOfContents/TableOfContentsList.astro +++ b/packages/starlight/components/TableOfContents/TableOfContentsList.astro @@ -3,33 +3,54 @@ import type { generateToC } from './generateToC'; interface Props { toc: ReturnType<typeof generateToC>; - nested?: boolean; + depth?: number; + isMobile?: boolean; } -const { toc, nested } = Astro.props; +const { toc, isMobile = false, depth = 0 } = Astro.props; --- -<ul class:list={{ nested }}> +<ul class:list={{ isMobile }}> { toc.map((heading) => ( <li> <a href={'#' + heading.slug}>{heading.text}</a> {heading.children.length > 0 && ( - <Astro.self toc={heading.children} nested /> + <Astro.self + toc={heading.children} + depth={depth + 1} + isMobile={isMobile} + /> )} </li> )) } </ul> -<style> +<style define:vars={{ depth }}> ul { padding: 0; } - .nested { - padding-inline-start: 1rem; - } ul :global(::marker) { color: transparent; } + a { + --pad-inline: 0rem; + display: block; + padding-inline: calc(1rem * var(--depth) + var(--pad-inline)) + var(--pad-inline); + } + .isMobile a { + --pad-inline: 1rem; + border-top: 1px solid var(--sl-color-gray-6); + padding-block: 0.5rem; + color: var(--sl-color-text); + font-size: var(--sl-text-sm); + line-height: 1.25; + text-decoration: none; + outline-offset: var(--sl-outline-offset-inside); + } + .isMobile:first-child > li:first-child > a { + border-top: 0; + } </style> diff --git a/packages/starlight/index.astro b/packages/starlight/index.astro index 238ce8e6..d0c79d34 100644 --- a/packages/starlight/index.astro +++ b/packages/starlight/index.astro @@ -1,6 +1,5 @@ --- import type { InferGetStaticPropsType } from 'astro'; -import config from 'virtual:starlight/user-config'; import { getPrevNextLinks, getSidebar } from './utils/navigation'; import { paths } from './utils/routing'; @@ -13,17 +12,16 @@ import './style/util.css'; // Components — can override built-in CSS, but not user CSS. import ContentPanel from './components/ContentPanel.astro'; -import EditLink from './components/EditLink.astro'; import FallbackContentNotice from './components/FallbackContentNotice.astro'; import HeadSEO from './components/HeadSEO.astro'; import Header from './components/Header.astro'; import LastUpdated from './components/LastUpdated.astro'; import MarkdownContent from './components/MarkdownContent.astro'; import PrevNextLinks from './components/PrevNextLinks.astro'; -import RightSidebarPanel from './components/RightSidebarPanel.astro'; +import RightSidebar from './components/RightSidebar.astro'; import Sidebar from './components/Sidebar.astro'; import SkipLink from './components/SkipLink.astro'; -import TableOfContents from './components/TableOfContents.astro'; +import MobileTableOfContents from './components/TableOfContents/MobileTableOfContents.astro'; import ThemeProvider from './components/ThemeProvider.astro'; import PageFrame from './layout/PageFrame.astro'; import TwoColumnContent from './layout/TwoColumnContent.astro'; @@ -58,22 +56,9 @@ const prevNextLinks = getPrevNextLinks(sidebar); <PageFrame> <Header slot="header" locale={locale} /> <Sidebar slot="sidebar" sidebar={sidebar} locale={locale} /> + <MobileTableOfContents headings={headings} /> <TwoColumnContent> - <Fragment slot="right-sidebar"> - <RightSidebarPanel> - <TableOfContents headings={headings} /> - </RightSidebarPanel> - <RightSidebarPanel> - { - config.editLink.baseUrl && ( - <> - <h2>Contribute</h2> - <EditLink data={entry.data} id={entry.id} /> - </> - ) - } - </RightSidebarPanel> - </Fragment> + <RightSidebar slot="right-sidebar" entry={entry} headings={headings} /> <main id="starlight__overview" data-pagefind-body diff --git a/packages/starlight/layout/PageFrame.astro b/packages/starlight/layout/PageFrame.astro index facce586..b250175b 100644 --- a/packages/starlight/layout/PageFrame.astro +++ b/packages/starlight/layout/PageFrame.astro @@ -50,6 +50,7 @@ const hasSidebar = Astro.slots.has('sidebar'); .sidebar-pane { visibility: var(--sl-sidebar-visibility, hidden); position: fixed; + z-index: var(--sl-z-index-menu); inset-block: 0; inset-inline-start: 0; padding-top: var(--sl-nav-height); @@ -67,7 +68,7 @@ const hasSidebar = Astro.slots.has('sidebar'); } .main-frame { - padding-top: var(--sl-nav-height); + padding-top: calc(var(--sl-nav-height) + var(--sl-mobile-toc-height)); } @media (min-width: 50rem) { diff --git a/packages/starlight/layout/TwoColumnContent.astro b/packages/starlight/layout/TwoColumnContent.astro index 14e9db77..c951ca52 100644 --- a/packages/starlight/layout/TwoColumnContent.astro +++ b/packages/starlight/layout/TwoColumnContent.astro @@ -1,5 +1,5 @@ <div class="container"> - <aside class="right-sidebar-container"> + <aside class="right-sidebar-container hidden lg:block"> <div class="right-sidebar"><slot name="right-sidebar" /></div> </aside> <div class="main-pane"><slot /></div> diff --git a/packages/starlight/style/props.css b/packages/starlight/style/props.css index 54b7c4dc..d8062fb8 100644 --- a/packages/starlight/style/props.css +++ b/packages/starlight/style/props.css @@ -113,17 +113,21 @@ --__sb-font-mono: var(--sl-font-mono, ''), var(--sl-font-system-mono); /** Key layout values */ - --sl-nav-height: 4rem; + --sl-nav-height: 3.5rem; --sl-nav-pad-x: 1.5rem; --sl-nav-pad-y: 0.75rem; + --sl-mobile-toc-height: 3rem; --sl-sidebar-width: 18.75rem; --sl-sidebar-pad-x: 1rem; --sl-content-width: 45rem; --sl-content-pad-x: 1rem; --sl-menu-button-size: 2rem; --sl-nav-gap: var(--sl-content-pad-x); + /* Offset required to show outline inside an element instead of round the outside */ + --sl-outline-offset-inside: -0.1875rem; /* Global z-index values */ + --sl-z-index-menu: 5; --sl-z-index-navbar: 10; --sl-z-index-skiplink: 20; } @@ -183,6 +187,7 @@ @media (min-width: 50em) { :root { + --sl-nav-height: 4rem; --sl-text-h1: var(--sl-text-5xl); --sl-text-h2: var(--sl-text-4xl); --sl-text-h3: var(--sl-text-3xl); @@ -193,5 +198,6 @@ @media (min-width: 72rem) { :root { --sl-content-pad-x: 1.5rem; + --sl-mobile-toc-height: 0rem; } } diff --git a/packages/starlight/style/util.css b/packages/starlight/style/util.css index e9354174..1bb88be2 100644 --- a/packages/starlight/style/util.css +++ b/packages/starlight/style/util.css @@ -30,3 +30,14 @@ display: block; } } +@media (min-width: 72rem) { + .lg\:hidden { + display: none; + } + .lg\:flex { + display: flex; + } + .lg\:block { + display: block; + } +} |