summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Swithinbank2023-05-11 16:09:22 +0200
committerGitHub2023-05-11 16:09:22 +0200
commit62265e4a653d161483e3844b568ab150334e9238 (patch)
treee213ed805314191c614d19931238884b5c676308
parent294f851c23aeb454fc6dcec81bf2548112422540 (diff)
downloadIT.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.md5
-rw-r--r--packages/starlight/components/Header.astro2
-rw-r--r--packages/starlight/components/RightSidebar.astro29
-rw-r--r--packages/starlight/components/TableOfContents/MobileTableOfContents.astro125
-rw-r--r--packages/starlight/components/TableOfContents/TableOfContentsList.astro37
-rw-r--r--packages/starlight/index.astro23
-rw-r--r--packages/starlight/layout/PageFrame.astro3
-rw-r--r--packages/starlight/layout/TwoColumnContent.astro2
-rw-r--r--packages/starlight/style/props.css8
-rw-r--r--packages/starlight/style/util.css11
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;
+ }
+}