aboutsummaryrefslogtreecommitdiff
path: root/examples/docs/src/components
diff options
context:
space:
mode:
authorGravatar Okiki <okikio.dev@gmail.com> 2021-08-14 00:58:00 +0000
committerGravatar Fred K. Schott <fkschott@gmail.com> 2021-08-26 12:15:33 -0700
commitb7ada11ddbabe4dd2f0798e140e5b280de4d6952 (patch)
treec01ff3eee4f770fc108c19d126dce31d9cbe5e51 /examples/docs/src/components
parent59cc62f7bd53dbcb6ad8051fa501b7b797614178 (diff)
downloadastro-b7ada11ddbabe4dd2f0798e140e5b280de4d6952.tar.gz
astro-b7ada11ddbabe4dd2f0798e140e5b280de4d6952.tar.zst
astro-b7ada11ddbabe4dd2f0798e140e5b280de4d6952.zip
WIP update examples/docs/
Diffstat (limited to 'examples/docs/src/components')
-rw-r--r--examples/docs/src/components/AvatarList.astro74
-rw-r--r--examples/docs/src/components/DocSidebar.tsx61
-rw-r--r--examples/docs/src/components/EditOnGithub.tsx26
-rw-r--r--examples/docs/src/components/Footer/AvatarList.astro151
-rw-r--r--examples/docs/src/components/Footer/Footer.astro (renamed from examples/docs/src/components/ArticleFooter.astro)3
-rw-r--r--examples/docs/src/components/HeadCommon.astro40
-rw-r--r--examples/docs/src/components/HeadSEO.astro40
-rw-r--r--examples/docs/src/components/Header/AstroLogo.astro20
-rw-r--r--examples/docs/src/components/Header/Header.astro158
-rw-r--r--examples/docs/src/components/Header/LanguageSelect.css47
-rw-r--r--examples/docs/src/components/Header/LanguageSelect.tsx38
-rw-r--r--examples/docs/src/components/Header/Search.css76
-rw-r--r--examples/docs/src/components/Header/Search.tsx76
-rw-r--r--examples/docs/src/components/Header/SidebarToggle.tsx27
-rw-r--r--examples/docs/src/components/Header/SkipToContent.astro21
-rw-r--r--examples/docs/src/components/LeftSidebar/LeftSidebar.astro109
-rw-r--r--examples/docs/src/components/Note.astro52
-rw-r--r--examples/docs/src/components/PageContent/PageContent.astro41
-rw-r--r--examples/docs/src/components/RightSidebar/MoreMenu.astro68
-rw-r--r--examples/docs/src/components/RightSidebar/RightSidebar.astro25
-rw-r--r--examples/docs/src/components/RightSidebar/TableOfContents.tsx45
-rw-r--r--examples/docs/src/components/RightSidebar/ThemeToggleButton.css37
-rw-r--r--examples/docs/src/components/RightSidebar/ThemeToggleButton.tsx (renamed from examples/docs/src/components/ThemeToggle.tsx)62
-rw-r--r--examples/docs/src/components/SiteSidebar.astro20
-rw-r--r--examples/docs/src/components/util.ts4
25 files changed, 1056 insertions, 265 deletions
diff --git a/examples/docs/src/components/AvatarList.astro b/examples/docs/src/components/AvatarList.astro
deleted file mode 100644
index aafcb371b..000000000
--- a/examples/docs/src/components/AvatarList.astro
+++ /dev/null
@@ -1,74 +0,0 @@
-<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
-
-<ul class="avatar-list">
- <li><a href="https://smolcss.dev/#smol-avatar-list"><img alt="Avatar 1" width="64" height="64" src='https://avataaars.io/?avatarStyle=Transparent&topType=LongHairBun&accessoriesType=Blank&hairColor=Auburn&facialHairType=BeardMedium&facialHairColor=Auburn&clotheType=ShirtCrewNeck&clotheColor=Blue01&eyeType=Side&eyebrowType=RaisedExcitedNatural&mouthType=Serious&skinColor=Tanned' /></a></li>
- <li><a href="https://smolcss.dev/#smol-avatar-list"><img alt="Avatar 2" width="64" height="64" src='https://avataaars.io/?avatarStyle=Transparent&topType=LongHairDreads&accessoriesType=Blank&hairColor=Brown&facialHairType=Blank&clotheType=ShirtScoopNeck&clotheColor=PastelGreen&eyeType=Default&eyebrowType=DefaultNatural&mouthType=Smile&skinColor=Tanned' /></a></li>
- <li><a href="https://smolcss.dev/#smol-avatar-list"><img alt="Avatar 3" width="64" height="64" src='https://avataaars.io/?avatarStyle=Transparent&topType=LongHairCurly&hairColor=BrownDark&facialHairType=Blank&clotheType=GraphicShirt&clotheColor=Pink&graphicType=Diamond&eyeType=Side&eyebrowType=Default&mouthType=Default&skinColor=Brown'/></a></li>
-</ul>
-
-<style>
-.avatar-list {
- --avatar-size: 2.5rem;
- --avatar-count: 3;
-
- display: grid;
- list-style: none;
- /* Default to displaying most of the avatar to
- enable easier access on touch devices, ensuring
- the WCAG touch target size is met or exceeded */
- grid-template-columns: repeat(
- var(--avatar-count),
- max(44px, calc(var(--avatar-size) / 1.15))
- );
- /* `padding` matches added visual dimensions of
- the `box-shadow` to help create a more accurate
- computed component size */
- padding: 0.08em;
- font-size: var(--avatar-size);
-}
-
-@media (any-hover: hover) and (any-pointer: fine) {
- .avatar-list {
- /* We create 1 extra cell to enable the computed
- width to match the final visual width */
- grid-template-columns: repeat(
- calc(var(--avatar-count) + 1),
- calc(var(--avatar-size) / 1.75)
- );
- }
-}
-
-.avatar-list li {
- width: var(--avatar-size);
- height: var(--avatar-size);
-}
-
-.avatar-list li:hover ~ li a,
-.avatar-list li:focus-within ~ li a {
- transform: translateX(33%);
-}
-
-.avatar-list img,
-.avatar-list a {
- display: block;
- border-radius: 50%;
-}
-
-.avatar-list a {
- transition: transform 180ms ease-in-out;
-}
-
-.avatar-list img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- background-color: #fff;
- box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
-}
-
-.avatar-list a:focus {
- outline: 2px solid transparent;
- /* Double-layer trick to work for dark and light backgrounds */
- box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
-}
-</style>
diff --git a/examples/docs/src/components/DocSidebar.tsx b/examples/docs/src/components/DocSidebar.tsx
deleted file mode 100644
index 076d460cc..000000000
--- a/examples/docs/src/components/DocSidebar.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { FunctionalComponent } from 'preact';
-import { h } from 'preact';
-import { useState, useEffect, useRef } from 'preact/hooks';
-import EditOnGithub from './EditOnGithub';
-
-const DocSidebar: FunctionalComponent<{ headers: any[]; editHref: string }> = ({ headers = [], editHref }) => {
- const itemOffsets = useRef([]);
- const [activeId, setActiveId] = useState<string>(undefined);
-
- useEffect(() => {
- const getItemOffsets = () => {
- const titles = document.querySelectorAll('article :is(h2, h3, h4)');
- itemOffsets.current = Array.from(titles).map((title) => ({
- id: title.id,
- topOffset: title.getBoundingClientRect().top + window.scrollY,
- }));
- };
-
- const onScroll = () => {
- const itemIndex = itemOffsets.current.findIndex((item) => item.topOffset > window.scrollY + window.innerHeight / 3);
- if (itemIndex === 0) {
- setActiveId(undefined);
- } else if (itemIndex === -1) {
- setActiveId(itemOffsets.current[itemOffsets.current.length - 1].id);
- } else {
- setActiveId(itemOffsets.current[itemIndex - 1].id);
- }
- };
-
- getItemOffsets();
- window.addEventListener('resize', getItemOffsets);
- window.addEventListener('scroll', onScroll);
-
- return () => {
- window.removeEventListener('resize', getItemOffsets);
- window.removeEventListener('scroll', onScroll);
- };
- }, []);
-
- return (
- <nav>
- <div>
- <h4>Contents</h4>
- <ul>
- {headers
- .filter(({ depth }) => depth > 1 && depth < 5)
- .map((header) => (
- <li class={`header-link depth-${header.depth} ${activeId === header.slug ? 'active' : ''}`.trim()}>
- <a href={`#${header.slug}`}>{header.text}</a>
- </li>
- ))}
- </ul>
- </div>
- <div>
- <EditOnGithub href={editHref} />
- </div>
- </nav>
- );
-};
-
-export default DocSidebar;
diff --git a/examples/docs/src/components/EditOnGithub.tsx b/examples/docs/src/components/EditOnGithub.tsx
deleted file mode 100644
index f7478934f..000000000
--- a/examples/docs/src/components/EditOnGithub.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import type { FunctionalComponent } from 'preact';
-import { h } from 'preact';
-
-const EditOnGithub: FunctionalComponent<{ href: string }> = ({ href }) => {
- return (
- <a class="edit-on-github" href={href}>
- <svg
- preserveAspectRatio="xMidYMid meet"
- height="1em"
- width="1em"
- fill="currentColor"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 438.549 438.549"
- stroke="none"
- class="icon-7f6730be--text-3f89f380"
- >
- <g>
- <path d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 0 1-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"></path>
- </g>
- </svg>
- <span>Edit on GitHub</span>
- </a>
- );
-};
-
-export default EditOnGithub;
diff --git a/examples/docs/src/components/Footer/AvatarList.astro b/examples/docs/src/components/Footer/AvatarList.astro
new file mode 100644
index 000000000..589e296b9
--- /dev/null
+++ b/examples/docs/src/components/Footer/AvatarList.astro
@@ -0,0 +1,151 @@
+---
+// fetch all commits for just this page's path
+const path = "docs/" + Astro.props.path;
+const url = `https://api.github.com/repos/snowpackjs/astro/commits?path=${path}`;
+const commitsURL = `https://github.com/snowpackjs/astro/commits/main/${path}`;
+
+async function getCommits(url) {
+ try {
+ const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN;
+ if (!token) {
+ throw new Error(
+ 'Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.'
+ );
+ }
+
+ const auth = `Basic ${Buffer.from(token, "binary").toString("base64")}`;
+
+ const res = await fetch(url, {
+ method: "GET",
+ headers: {
+ Authorization: auth,
+ "User-Agent": "astro-docs/1.0",
+ },
+ });
+
+ const data = await res.json();
+
+ if (!res.ok) {
+ throw new Error(
+ `Request to fetch commits failed. Reason: ${res.statusText}
+ Message: ${data.message}`
+ );
+ }
+
+ return data;
+ } catch (e) {
+ console.warn(`[error] /src/components/AvatarList.astro
+ ${e?.message ?? e}`);
+ return new Array();
+ }
+}
+
+function removeDups(arr) {
+ if (!arr) {
+ return new Array();
+ }
+ let map = new Map();
+
+ for (let item of arr) {
+ let author = item.author;
+ // Deduplicate based on author.id
+ map.set(author.id, { login: author.login, id: author.id });
+ }
+
+ return Array.from(map.values());
+}
+
+const data = await getCommits(url);
+const unique = removeDups(data);
+const recentContributors = unique.slice(0, 3); // only show avatars for the 3 most recent contributors
+const additionalContributors = unique.length - recentContributors.length; // list the rest of them as # of extra contributors
+
+---
+<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
+<div class="contributors">
+<ul class="avatar-list" style={`--avatar-count: ${recentContributors.length}`}>
+
+{recentContributors.map((item) => (
+ <li><a href={`https://github.com/${item.login}`}><img alt={`Contributor ${item.login}`} title={`Contributor ${item.login}`} width="64" height="64" src={`https://avatars.githubusercontent.com/u/${item.id}`}/></a></li>
+
+))}
+ </ul>
+ {additionalContributors > 0 && <span><a href={commitsURL}>{`and ${additionalContributors} additional contributor${additionalContributors > 1 ? 's' : ''}.`}</a></span>}
+ {unique.length === 0 && <a href={commitsURL}>Contributors</a>}
+</div>
+
+<style>
+.avatar-list {
+ --avatar-size: 2.5rem;
+ --avatar-count: 3;
+
+ display: grid;
+ list-style: none;
+ /* Default to displaying most of the avatar to
+ enable easier access on touch devices, ensuring
+ the WCAG touch target size is met or exceeded */
+ grid-template-columns: repeat(
+ var(--avatar-count),
+ max(44px, calc(var(--avatar-size) / 1.15))
+ );
+ /* `padding` matches added visual dimensions of
+ the `box-shadow` to help create a more accurate
+ computed component size */
+ padding: 0.08em;
+ font-size: var(--avatar-size);
+}
+
+@media (any-hover: hover) and (any-pointer: fine) {
+ .avatar-list {
+ /* We create 1 extra cell to enable the computed
+ width to match the final visual width */
+ grid-template-columns: repeat(
+ calc(var(--avatar-count) + 1),
+ calc(var(--avatar-size) / 1.75)
+ );
+ }
+}
+
+.avatar-list li {
+ width: var(--avatar-size);
+ height: var(--avatar-size);
+}
+
+.avatar-list li:hover ~ li a,
+.avatar-list li:focus-within ~ li a {
+ transform: translateX(33%);
+}
+
+.avatar-list img,
+.avatar-list a {
+ display: block;
+ border-radius: 50%;
+}
+
+.avatar-list a {
+ transition: transform 180ms ease-in-out;
+}
+
+.avatar-list img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ background-color: #fff;
+ box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
+}
+
+.avatar-list a:focus {
+ outline: 2px solid transparent;
+ /* Double-layer trick to work for dark and light backgrounds */
+ box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
+}
+
+.contributors {
+ display: flex;
+ align-items: center;
+}
+
+.contributors > * + * {
+ margin-left: .75rem;
+}
+</style>
diff --git a/examples/docs/src/components/ArticleFooter.astro b/examples/docs/src/components/Footer/Footer.astro
index 8078e2cc3..48de51054 100644
--- a/examples/docs/src/components/ArticleFooter.astro
+++ b/examples/docs/src/components/Footer/Footer.astro
@@ -1,9 +1,10 @@
---
import AvatarList from './AvatarList.astro';
+const { path } = Astro.props;
---
<footer>
- <AvatarList />
+ <AvatarList path={path} />
</footer>
<style>
diff --git a/examples/docs/src/components/HeadCommon.astro b/examples/docs/src/components/HeadCommon.astro
new file mode 100644
index 000000000..83045c0d1
--- /dev/null
+++ b/examples/docs/src/components/HeadCommon.astro
@@ -0,0 +1,40 @@
+<!-- Global Metadata -->
+<meta name="viewport" content="width=device-width">
+
+<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+<link rel="alternate icon" type="image/x-icon" href="/favicon.ico" />
+
+<link rel="sitemap" href="/sitemap.xml"/>
+
+<!-- Global CSS -->
+<link rel="stylesheet" href="/theme.css" />
+<link rel="stylesheet" href="/code.css" />
+<link rel="stylesheet" href="/index.css" />
+
+<!-- Preload Fonts -->
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital@0;1&display=swap" rel="stylesheet">
+
+<!-- Scrollable a11y code helper -->
+<script type="module" src="/make-scrollable-code-focusable.js" />
+
+<!-- This is intentionally inlined to avoid FOUC -->
+<script>
+ const root = document.documentElement;
+ const theme = localStorage.getItem('theme');
+ if (theme === 'dark' || (!theme) && window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ root.classList.add('theme-dark');
+ } else {
+ root.classList.remove('theme-dark');
+ }
+</script>
+
+<!-- Global site tag (gtag.js) - Google Analytics -->
+<!-- <script async src="https://www.googletagmanager.com/gtag/js?id=G-TEL60V1WM9"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-TEL60V1WM9');
+</script> --> \ No newline at end of file
diff --git a/examples/docs/src/components/HeadSEO.astro b/examples/docs/src/components/HeadSEO.astro
new file mode 100644
index 000000000..5553eb2d0
--- /dev/null
+++ b/examples/docs/src/components/HeadSEO.astro
@@ -0,0 +1,40 @@
+---
+import {SITE, OPEN_GRAPH} from '../config.ts';
+export interface Props {
+ content: any,
+ site: any,
+ canonicalURL: URL | string,
+};
+const { content = {}, canonicalURL } = Astro.props;
+const formattedContentTitle = content.title ? `${content.title} 🚀 ${SITE.title}` : SITE.title;
+const imageSrc = content?.image?.src ?? OPEN_GRAPH.image.src;
+const canonicalImageSrc = new URL(imageSrc, Astro.site);
+const imageAlt = content?.image?.alt ?? OPEN_GRAPH.image.alt;
+---
+<!-- Page Metadata -->
+<link rel="canonical" href={canonicalURL}/>
+
+<!-- OpenGraph Tags -->
+<meta property="og:title" content={formattedContentTitle}/>
+<meta property="og:type" content="article"/>
+<meta property="og:url" content={canonicalURL}/>
+<meta property="og:locale" content={content.ogLocale ?? OPEN_GRAPH.locale}/>
+<meta property="og:image" content={canonicalImageSrc}/>
+<meta property="og:image:alt" content={imageAlt}/>
+<meta property="og:description" content={content.description ? content.description : SITE.description}/>
+<meta property="og:site_name" content={SITE.title}/>
+
+<!-- Twitter Tags -->
+<meta name="twitter:card" content="summary_large_image"/>
+<meta name="twitter:site" content={OPEN_GRAPH.twitter}/>
+<meta name="twitter:title" content={formattedContentTitle}/>
+<meta name="twitter:description" content={content.description ? content.description : SITE.description}/>
+<meta name="twitter:image" content={canonicalImageSrc}/>
+<meta name="twitter:image:alt" content={imageAlt}/>
+
+<!--
+ TODO: Add json+ld data, maybe https://schema.org/APIReference makes sense?
+ Docs: https://developers.google.com/search/docs/advanced/structured-data/intro-structured-data
+ https://www.npmjs.com/package/schema-dts seems like a great resource for implementing this.
+ Even better, there's a React component that integrates with `schema-dts`: https://github.com/google/react-schemaorg
+-->
diff --git a/examples/docs/src/components/Header/AstroLogo.astro b/examples/docs/src/components/Header/AstroLogo.astro
new file mode 100644
index 000000000..ff1939ad9
--- /dev/null
+++ b/examples/docs/src/components/Header/AstroLogo.astro
@@ -0,0 +1,20 @@
+---
+const {size} = Astro.props;
+---
+<svg class="logo" width={size} height={size} viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <style>
+ #flame {
+ /* fill: #ff5d01; */
+ fill: #3894ff;
+ }
+ #a {
+ /* fill: #000014; */
+ fill: #3894ff;
+ }
+ </style>
+ <title>Logo</title>
+ <path id="a" fill-rule="evenodd" clip-rule="evenodd"
+ d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z" />
+ <path id="flame" fill-rule="evenodd" clip-rule="evenodd"
+ d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z" />
+</svg> \ No newline at end of file
diff --git a/examples/docs/src/components/Header/Header.astro b/examples/docs/src/components/Header/Header.astro
new file mode 100644
index 000000000..cc54585b5
--- /dev/null
+++ b/examples/docs/src/components/Header/Header.astro
@@ -0,0 +1,158 @@
+---
+import SkipToContent from './SkipToContent.astro';
+import SidebarToggle from './SidebarToggle.tsx';
+import LanguageSelect from './LanguageSelect.jsx';
+import Search from "./Search.jsx";
+import { getLanguageFromURL } from '../util.ts';
+
+const {currentPage} = Astro.props;
+const lang = currentPage && getLanguageFromURL(currentPage);
+---
+<style>
+ header {
+ z-index: 11;
+ height: var(--theme-navbar-height);
+ width: 100%;
+ background-color: var(--theme-navbar-bg);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ position: sticky;
+ top: 0;
+ }
+
+
+ .logo {
+ display: flex;
+ overflow: hidden;
+ width: 30px;
+ font-size: 1rem;
+ flex-shrink: 0;
+ font-weight: 600;
+ line-height: 1;
+ color: hsla(var(--color-base-white), 100%, 1);
+ text-decoration: none;
+ gap: 0.5em;
+ z-index: -1;
+ }
+
+ .logo a {
+ padding: 0.5em 0.25em;
+ margin: -0.5em -0.25em;
+ }
+
+ .logo svg {
+ height: 40px;
+ width: auto;
+ display: block;
+ color: var(--theme-accent);
+ }
+
+ .logo .hover {
+ opacity: 0.0;
+ }
+ .logo a {
+ transition: transform 180ms ease-out;
+ }
+
+ .logo a:hover,
+ .logo a:focus {
+ outline: none;
+ opacity: 1.0;
+ transform: translateY(-2px);
+ }
+
+ .logo h1 {
+ font: inherit;
+ color: inherit;
+ margin: 0;
+ }
+
+ .nav-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 1em;
+ width: 100%;
+ max-width: 82em;
+ padding: 0 1rem;
+ }
+
+ @media (min-width: 50em) {
+ header {
+ position: static;
+ padding: 2rem 0rem 0 2rem;
+ }
+ .logo {
+ width: auto;
+ margin: 0;
+ z-index: 0;
+ }
+ .menu-toggle {
+ display: none;
+ }
+ .logo {
+ width: auto;
+ }
+ }
+
+ /** Style Algolia */
+ :root {
+ --docsearch-primary-color: var(--theme-accent);
+ --docsearch-logo-color: var(--theme-text);
+ }
+
+ .search-item {
+ display: none;
+ position: relative;
+ z-index: 10;
+ flex-grow: 1;
+ padding-right: 0.7rem;
+ display: flex;
+ max-width: 200px;
+ }
+ :global(.search-item > *) {
+ flex-grow: 1;
+ }
+ @media (min-width: 50em) {
+ .search-item {
+ max-width: 400px;
+ }
+ }
+</style>
+<header>
+ <SkipToContent />
+ <nav class="nav-wrapper" title="Top Navigation">
+ <div class="menu-toggle">
+ <SidebarToggle client:idle/>
+ </div>
+ <div class="logo flex">
+ <a href="/">
+ <h1 class="sr-only">Astro</h1>
+ <svg xmlns="http://www.w3.org/2000/svg" width="363" height="102" viewBox="0 0 363 102" fill="none">
+ <style>
+ .text {
+ fill: var(--theme-text);
+ }
+ .hover {
+ fill: var(--theme-accent);
+ }
+ </style>
+ <path class="text" fill-rule="evenodd" d="M55.07 14.216l16.81 54.865a72.6 72.6 0 00-20.765-6.984L39.808 24.135a1.475 1.475 0 00-2.827.005L25.81 62.078a72.598 72.598 0 00-20.859 6.995L21.847 14.2c.998-3.243 1.497-4.865 2.47-6.066a8 8 0 013.239-2.392c1.434-.576 3.13-.576 6.524-.576h8.751c3.398 0 5.097 0 6.532.577a8 8 0 013.241 2.397c.972 1.203 1.47 2.827 2.465 6.076z" clip-rule="evenodd"/>
+ <path fill="#FF5D01" fill-rule="evenodd" d="M54.618 71.779c-2.863 2.432-8.578 4.091-15.161 4.091-8.08 0-14.852-2.499-16.649-5.86-.642 1.926-.786 4.13-.786 5.539 0 0-.423 6.915 4.418 11.725 0-2.498 2.037-4.522 4.551-4.522 4.309 0 4.304 3.734 4.3 6.764v.27c0 4.6 2.829 8.541 6.852 10.203a9.22 9.22 0 01-.938-4.064c0-4.386 2.592-6.02 5.604-7.917 2.396-1.51 5.06-3.188 6.894-6.554a12.297 12.297 0 001.502-5.905c0-1.314-.206-2.581-.587-3.77z" clip-rule="evenodd"/>
+ <path class="text" d="M126.554 69c13.115 0 21.047-3.14 25.68-9.654 0 2.904.157 5.651.55 8.163h7.774c-.706-4.082-.863-6.75-.863-14.128V43.334c0-10.831-8.403-16.56-24.424-16.56-15.47 0-25.522 5.964-26.779 14.598h8.246c1.256-5.808 7.774-8.87 18.533-8.87 10.602 0 16.885 3.69 16.885 9.969v.785l-24.502 1.413c-9.974.549-13.665 1.962-16.492 4.003-2.67 1.962-4.162 5.023-4.162 8.555C107 64.683 114.696 69 126.554 69zm2.513-5.573c-9.109 0-14.135-2.119-14.135-6.357 0-4.553 3.141-6.593 14.214-7.3l23.01-1.412v1.805c0 8.241-9.66 13.264-23.089 13.264zM196.086 69c16.256 0 22.775-5.337 22.775-13.108 0-6.436-4.006-9.732-14.215-10.596l-19.083-1.49c-5.183-.393-8.088-1.884-8.088-5.102 0-4.082 4.476-6.201 14.135-6.201 10.995 0 16.727 2.198 20.497 7.064l6.361-3.061c-3.927-6.122-12.644-9.733-26.151-9.733-13.9 0-22.224 4.631-22.224 12.244 0 6.829 4.947 10.125 14.292 10.91l18.926 1.492c6.204.47 8.089 1.726 8.089 4.944 0 4.631-4.79 6.829-14.293 6.829-11.544 0-18.847-3.14-22.381-8.87l-6.204 3.376C173.312 64.918 181.715 69 196.086 69zM234.929 34.151v18.916c0 7.77 2.67 15.54 17.198 15.54 3.691 0 8.167-.706 10.131-1.57V60.68c-2.749.628-6.047 1.1-9.267 1.1-6.832 0-10.523-2.67-10.523-9.42V34.151h19.633v-5.887h-19.633V15l-7.539 3.061v10.204h-12.33v5.886h12.33zM280.823 28.265h-6.911v39.244h7.461V52.83c0-5.65 1.099-10.439 4.24-13.735 2.749-3.061 6.283-4.788 12.487-4.788 2.12 0 3.455.157 5.262.471v-7.22c-1.65-.393-3.063-.472-5.184-.472-8.402 0-15.078 4.945-17.355 12.558v-11.38zM334.807 69C351.534 69 363 60.523 363 47.887c0-12.637-11.466-21.114-28.193-21.114-16.727 0-28.193 8.477-28.193 21.114C306.614 60.523 318.08 69 334.807 69zm0-6.2c-12.329 0-20.261-5.809-20.261-14.913 0-9.105 7.932-14.913 20.261-14.913 12.251 0 20.261 5.808 20.261 14.913 0 9.104-8.01 14.912-20.261 14.912z"/>
+ </svg>
+ </a>
+ <a href="/">
+ <h1 class="sr-only">Docs</h1>
+ <svg xmlns="http://www.w3.org/2000/svg" width="226" height="102" viewBox="0 0 226 102" fill="none">
+ <path fill="currentColor" d="M25.805 68c14.688 0 24.883-8.41 24.883-21.14 0-12.786-9.62-19.756-24.653-19.756H0V68h25.805zm-14.17-33.005H24.25c8.352 0 14.17 4.09 14.17 12.039 0 8.236-5.3 13.075-14.113 13.075H11.635V34.995zM82.673 69.382c16.704 0 27.418-8.582 27.418-21.83 0-13.248-10.771-21.83-27.418-21.83-16.589 0-27.418 8.582-27.418 21.83 0 13.19 10.83 21.83 27.418 21.83zm0-8.64c-9.1 0-15.149-5.299-15.149-13.19 0-7.891 6.048-13.19 15.15-13.19 9.1 0 15.205 5.299 15.205 13.19 0 7.891-6.105 13.19-15.206 13.19zM141.497 69.382c13.306 0 22.637-5.299 25.978-14.572l-11.866-2.535c-1.67 5.415-6.393 8.295-13.709 8.295-9.216 0-15.033-5.127-15.033-13.018 0-8.006 5.702-13.018 14.918-13.018 7.43 0 12.154 3.053 13.709 8.64l12.038-2.13c-2.707-9.562-12.268-15.322-25.574-15.322-16.128 0-27.302 9.043-27.302 22.003 0 13.133 10.425 21.657 26.841 21.657zM194.94 69.382c14.745 0 23.212-5.01 23.212-14.054 0-7.603-4.665-10.944-15.955-12.096l-11.289-1.094c-5.242-.576-6.97-1.556-6.97-4.09 0-2.765 3.456-4.262 9.792-4.262 7.834 0 13.709 2.476 16.762 6.508l7.315-6.163c-5.069-5.702-13.133-8.41-23.501-8.41-13.997 0-21.888 4.781-21.888 12.903 0 7.546 4.781 11.232 14.803 12.326l12.557 1.383c4.896.518 6.624 1.555 6.624 4.09 0 3.225-3.456 4.723-10.886 4.723-8.352 0-14.688-3.226-18.087-8.007l-8.294 5.818c4.205 6.451 13.709 10.425 25.805 10.425z"/>
+ </svg>
+ </a>
+ </div>
+ <div style="flex-grow: 1;"></div>
+ {lang && <LanguageSelect lang={lang} client:idle />}
+ <div class="search-item"><Search client:idle /></div>
+ </nav>
+</header> \ No newline at end of file
diff --git a/examples/docs/src/components/Header/LanguageSelect.css b/examples/docs/src/components/Header/LanguageSelect.css
new file mode 100644
index 000000000..4e878714b
--- /dev/null
+++ b/examples/docs/src/components/Header/LanguageSelect.css
@@ -0,0 +1,47 @@
+.language-select {
+ flex-grow: 1;
+ width: 48px;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0.33em 0.5em;
+ overflow: visible;
+ font-weight: 500;
+ font-size: 1rem;
+ font-family: inherit;
+ line-height: inherit;
+ background-color: var(--theme-bg);
+ border-color: var(--theme-text-lighter);
+ color: var(--theme-text-light);
+ border-style: solid;
+ border-width: 1px;
+ border-radius: 0.25rem;
+ outline: 0;
+ cursor: pointer;
+ transition-timing-function: ease-out;
+ transition-duration: 0.2s;
+ transition-property: border-color, color;
+ -webkit-font-smoothing: antialiased;
+ padding-left: 30px;
+ padding-right: 1rem;
+}
+.language-select-wrapper .language-select:hover,
+.language-select-wrapper .language-select:focus {
+ color: var(--theme-text);
+ border-color: var(--theme-text-light);
+}
+.language-select-wrapper {
+ color: var(--theme-text-light);
+ position: relative;
+}
+.language-select-wrapper > svg {
+ position: absolute;
+ top: 7px;
+ left: 10px;
+ pointer-events: none;
+}
+
+@media (min-width: 50em) {
+ .language-select {
+ width: 100%;
+ }
+}
diff --git a/examples/docs/src/components/Header/LanguageSelect.tsx b/examples/docs/src/components/Header/LanguageSelect.tsx
new file mode 100644
index 000000000..cf325eedc
--- /dev/null
+++ b/examples/docs/src/components/Header/LanguageSelect.tsx
@@ -0,0 +1,38 @@
+import type { FunctionalComponent } from 'preact';
+import { h } from 'preact';
+import './LanguageSelect.css';
+import { LANGUAGE_NAMES, langPathRegex } from '../../languages';
+
+const LanguageSelect: FunctionalComponent<{ lang: string }> = ({ lang }) => {
+ return (
+ <div class="language-select-wrapper">
+ <svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88.6 77.3" height="1.2em" width="1.2em">
+ <path fill="currentColor" d="M61,24.6h7.9l18.7,51.6h-7.7l-5.4-15.5H54.3l-5.6,15.5h-7.2L61,24.6z M72.6,55l-8-22.8L56.3,55H72.6z" />
+ <path
+ fill="currentColor"
+ d="M53.6,60.6c-10-4-16-9-22-14c0,0,1.3,1.3,0,0c-6,5-20,13-20,13l-4-6c8-5,10-6,19-13c-2.1-1.9-12-13-13-19h8 c4,9,10,14,10,14c10-8,10-19,10-19h8c0,0-1,13-12,24l0,0c5,5,10,9,19,13L53.6,60.6z M1.6,16.6h56v-8h-23v-7h-9v7h-24V16.6z"
+ />
+ </svg>
+ <select
+ class="language-select"
+ value={lang}
+ onChange={(e) => {
+ const newLang = e.target.value;
+ let actualDest = window.location.pathname.replace(langPathRegex, '/');
+ if (actualDest == '/') actualDest = `/introduction`;
+ window.location.pathname = '/' + newLang + actualDest;
+ }}
+ >
+ {Object.keys(LANGUAGE_NAMES).map((key) => {
+ return (
+ <option value={LANGUAGE_NAMES[key]}>
+ <span>{key}</span>
+ </option>
+ );
+ })}
+ </select>
+ </div>
+ );
+};
+
+export default LanguageSelect;
diff --git a/examples/docs/src/components/Header/Search.css b/examples/docs/src/components/Header/Search.css
new file mode 100644
index 000000000..2056c2c8f
--- /dev/null
+++ b/examples/docs/src/components/Header/Search.css
@@ -0,0 +1,76 @@
+/** Style Algolia */
+:root {
+ --docsearch-primary-color: var(--theme-accent);
+ --docsearch-logo-color: var(--theme-text);
+}
+.search-input {
+ flex-grow: 1;
+ box-sizing: border-box;
+ width: 100%;
+ margin: 0;
+ padding: 0.33em 0.5em;
+ overflow: visible;
+ font-weight: 500;
+ font-size: 1rem;
+ font-family: inherit;
+ line-height: inherit;
+ background-color: var(--theme-divider);
+ border-color: var(--theme-divider);
+ color: var(--theme-text-light);
+ border-style: solid;
+ border-width: 1px;
+ border-radius: 0.25rem;
+ outline: 0;
+ cursor: pointer;
+ transition-timing-function: ease-out;
+ transition-duration: 0.2s;
+ transition-property: border-color, color;
+ -webkit-font-smoothing: antialiased;
+}
+.search-input:hover,
+.search-input:focus {
+ color: var(--theme-text);
+ border-color: var(--theme-text-light);
+}
+.search-input:hover::placeholder,
+.search-input:focus::placeholder {
+ color: var(--theme-text-light);
+}
+.search-input::placeholder {
+ color: var(--theme-text-light);
+}
+.search-hint {
+ position: absolute;
+ top: 7px;
+ right: 19px;
+ padding: 3px 5px;
+ display: none;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ letter-spacing: 0.125em;
+ font-size: 13px;
+ font-family: var(--font-mono);
+ pointer-events: none;
+ border-color: var(--theme-text-lighter);
+ color: var(--theme-text-light);
+ border-style: solid;
+ border-width: 1px;
+ border-radius: 0.25rem;
+ line-height: 14px;
+}
+
+@media (min-width: 50em) {
+ .search-hint {
+ display: flex;
+ }
+}
+
+/* ------------------------------------------------------------ *\
+ DocSearch (Algolia)
+\* ------------------------------------------------------------ */
+
+.DocSearch-Modal .DocSearch-Hit a {
+ box-shadow: none;
+ border: 1px solid var(--theme-accent);
+}
diff --git a/examples/docs/src/components/Header/Search.tsx b/examples/docs/src/components/Header/Search.tsx
new file mode 100644
index 000000000..d842e007f
--- /dev/null
+++ b/examples/docs/src/components/Header/Search.tsx
@@ -0,0 +1,76 @@
+/* jsxImportSource: react */
+import { useState, useCallback, useRef } from 'react';
+import { createPortal } from 'react-dom';
+import { DocSearchModal, useDocSearchKeyboardEvents } from '@docsearch/react';
+import '@docsearch/css//dist/style.css';
+import './Search.css';
+
+export default function Search() {
+ const [isOpen, setIsOpen] = useState(false);
+ const searchButtonRef = useRef();
+ const [initialQuery, setInitialQuery] = useState(null);
+
+ const onOpen = useCallback(() => {
+ setIsOpen(true);
+ }, [setIsOpen]);
+
+ const onClose = useCallback(() => {
+ setIsOpen(false);
+ }, [setIsOpen]);
+
+ const onInput = useCallback(
+ (e) => {
+ setIsOpen(true);
+ setInitialQuery(e.key);
+ },
+ [setIsOpen, setInitialQuery]
+ );
+
+ useDocSearchKeyboardEvents({
+ isOpen,
+ onOpen,
+ onClose,
+ onInput,
+ searchButtonRef,
+ });
+
+ return (
+ <>
+ <button type="button" ref={searchButtonRef} onClick={onOpen} className="search-input">
+ <svg width="24" height="24" fill="none">
+ <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
+ </svg>
+ <span>Search</span>
+ <span className="search-hint">
+ <span className="sr-only">Press </span>
+ <kbd>/</kbd>
+ <span className="sr-only"> to search</span>
+ </span>
+ </button>
+ {isOpen &&
+ createPortal(
+ <DocSearchModal
+ initialQuery={initialQuery}
+ initialScrollY={window.scrollY}
+ onClose={onClose}
+ indexName="astro"
+ apiKey="0f387260ad74f9cbf4353facd29c919c"
+ transformItems={(items) => {
+ return items.map((item) => {
+ // We transform the absolute URL into a relative URL to
+ // work better on localhost, preview URLS.
+ const a = document.createElement('a');
+ a.href = item.url;
+ const hash = a.hash === '#overview' ? '' : a.hash;
+ return {
+ ...item,
+ url: `${a.pathname}${hash}`,
+ };
+ });
+ }}
+ />,
+ document.body
+ )}
+ </>
+ );
+}
diff --git a/examples/docs/src/components/Header/SidebarToggle.tsx b/examples/docs/src/components/Header/SidebarToggle.tsx
new file mode 100644
index 000000000..97fece6b2
--- /dev/null
+++ b/examples/docs/src/components/Header/SidebarToggle.tsx
@@ -0,0 +1,27 @@
+import type { FunctionalComponent } from 'preact';
+import { h, Fragment } from 'preact';
+import { useState, useEffect } from 'preact/hooks';
+
+const MenuToggle: FunctionalComponent = () => {
+ const [sidebarShown, setSidebarShown] = useState(false);
+
+ useEffect(() => {
+ const body = document.getElementsByTagName('body')[0];
+ if (sidebarShown) {
+ body.classList.add('mobile-sidebar-toggle');
+ } else {
+ body.classList.remove('mobile-sidebar-toggle');
+ }
+ }, [sidebarShown]);
+
+ return (
+ <button type="button" aria-pressed={sidebarShown ? 'true' : 'false'} id="menu-toggle" onClick={() => setSidebarShown(!sidebarShown)}>
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
+ </svg>
+ <span className="sr-only">Toggle sidebar</span>
+ </button>
+ );
+};
+
+export default MenuToggle;
diff --git a/examples/docs/src/components/Header/SkipToContent.astro b/examples/docs/src/components/Header/SkipToContent.astro
new file mode 100644
index 000000000..6df3a72ed
--- /dev/null
+++ b/examples/docs/src/components/Header/SkipToContent.astro
@@ -0,0 +1,21 @@
+<style>
+.skiplink,
+.skiplink:focus,
+.skiplink:focus-visible {
+ position: absolute;
+ padding: 0.25em;
+ font-size: larger;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 9;
+ display: block;
+ text-align: center;
+ background-color: var(--theme-text-accent);
+ color: var(--theme-bg);
+ border-radius: 0.25em;
+ outline: var(--theme-bg) solid 1px;
+ outline-offset: 0;
+}
+</style>
+<a href="#article" class="sr-only skiplink"><span>Skip to Content</span></a>
diff --git a/examples/docs/src/components/LeftSidebar/LeftSidebar.astro b/examples/docs/src/components/LeftSidebar/LeftSidebar.astro
new file mode 100644
index 000000000..96bd36fba
--- /dev/null
+++ b/examples/docs/src/components/LeftSidebar/LeftSidebar.astro
@@ -0,0 +1,109 @@
+---
+import { SIDEBAR } from '../../config.ts';
+import { getLanguageFromURL } from '../util.ts';
+const {currentPage} = Astro.props;
+const currentPageMatch = currentPage.slice(1);
+const langCode = getLanguageFromURL(currentPage);
+// SIDEBAR is a flat array. Group it by sections to properly render.
+const sidebarSections = SIDEBAR[langCode].reduce((col, item) => {
+ if (item.header) {
+ col.push({...item, children: []});
+ } else {
+ col[col.length-1].children.push(item);
+ }
+ return col;
+}, []);
+
+---
+
+<nav aria-labelledby="grid-left">
+ <ul class="nav-groups">
+ {sidebarSections.map(section => (
+ <li>
+ <div class="nav-group">
+ <h2 class="nav-group-title">{section.text}</h2>
+ <ul>
+ {section.children.map(child => (
+ <li class="nav-link"><a href={`${Astro.site.pathname}${child.link}`} aria-current={`${currentPageMatch === child.link ? 'page' : 'false'}`}>{child.text}</a></li>
+ ))}
+ </ul>
+ </div>
+ </li>
+ ))}
+ </ul>
+</nav>
+
+<script>
+ window.addEventListener('DOMContentLoaded', (event) => {
+ var target = document.querySelector('[aria-current="page"]');
+ if (target && (target.offsetTop > (window.innerHeight - 100))) {
+ document.querySelector('.nav-groups').scrollTop = target.offsetTop;
+ }
+ });
+</script>
+
+<style>
+ nav {
+ width: 100%;
+ margin-right: 1rem;
+ }
+ .nav-groups {
+ height: 100%;
+ padding: 2rem 0;
+ overflow-x: visible;
+ overflow-y: auto;
+ max-height: 100vh;
+ }
+
+ .nav-groups > li + li {
+ margin-top: 2rem;
+ }
+
+ .nav-groups > :first-child {
+ padding-top: var(--doc-padding);
+ }
+
+ .nav-groups > :last-child {
+ padding-bottom: 2rem;
+ margin-bottom: var(--theme-navbar-height);
+ }
+
+ .nav-group-title {
+ font-size: 1.0rem;
+ font-weight: 700;
+ padding: 0.1rem 1rem;
+ text-transform: uppercase;
+ margin-bottom: 0.5rem;
+ }
+
+ .nav-link a {
+ font-size: 1.0rem;
+ margin: 1px;
+ padding: 0.3rem 1rem;
+ font: inherit;
+ color: inherit;
+ text-decoration: none;
+ display: block;
+ }
+ .nav-link a:hover,
+ .nav-link a:focus {
+ background-color: var(--theme-bg-hover);
+ }
+
+ .nav-link a[aria-current="page"] {
+ color: var(--theme-text-accent);
+ background-color: var(--theme-bg-accent);
+ font-weight: 600;
+ }
+
+ :global(:root.theme-dark) .nav-link a[aria-current="page"] {
+ color: hsla(var(--color-base-white), 100%, 1);
+ }
+
+ @media (min-width: 50em) {
+ .nav-groups {
+ padding: 0;
+ }
+ }
+
+</style>
diff --git a/examples/docs/src/components/Note.astro b/examples/docs/src/components/Note.astro
deleted file mode 100644
index c57ede3a0..000000000
--- a/examples/docs/src/components/Note.astro
+++ /dev/null
@@ -1,52 +0,0 @@
----
-export interface Props {
- title: string;
- type?: 'tip' | 'warning' | 'error'
-}
-const { type = 'tip', title } = Astro.props;
----
-
-<aside class={`note type-${type}`}>
- {title && <label>{title}</label>}
- <slot />
-</aside>
-
-<style>
- .note {
- --padding-block: 1rem;
- --padding-inline: 1.25rem;
-
- display: flex;
- flex-direction: column;
-
- padding: var(--padding-block) var(--padding-inline);
- margin-left: calc(var(--padding-inline) * -1);
- margin-right: calc(var(--padding-inline) * -1);
-
- background: var(--theme-bg-offset);
- border-left: calc(var(--padding-inline) / 2) solid var(--color);
- border-radius: 0;
- }
-
- .note label {
- font-weight: 500;
- color: var(--color);
- }
-
- /* .note :global(a) {
- color: var(--color);
- } */
-
- .note.type-tip {
- --color: var(--color-green);
- --color-rgb: var(--color-green-rgb);
- }
- .note.type-warning {
- --color: var(--color-yellow);
- --color-rgb: var(--color-yellow-rgb);
- }
- .note.type-error {
- --color: var(--color-red);
- --color-rgb: var(--color-red-rgb);
- }
-</style>
diff --git a/examples/docs/src/components/PageContent/PageContent.astro b/examples/docs/src/components/PageContent/PageContent.astro
new file mode 100644
index 000000000..fd1e9d242
--- /dev/null
+++ b/examples/docs/src/components/PageContent/PageContent.astro
@@ -0,0 +1,41 @@
+---
+const {content, githubEditUrl} = Astro.props;
+const title = content.title;
+const headers = content.astro.headers;
+import MoreMenu from '../RightSidebar/MoreMenu.astro';
+import TableOfContents from '../RightSidebar/TableOfContents.tsx';
+---
+<style>
+ .content {
+ padding: 0;
+ max-width: 75ch;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ }
+ .content > section {
+ margin-bottom: 4rem;
+ }
+ .block {
+ display: block;
+ }
+
+ @media (min-width: 50em) {
+ .sm\:hidden {
+ display: none;
+ }
+ }
+</style>
+<article id="article" class="content">
+ <section class="main-section">
+ <h1 class="content-title" id="overview">{title}</h1>
+ <nav class="block sm:hidden">
+ <TableOfContents client:media="(max-width: 50em)" headers={headers}/>
+ </nav>
+ <slot />
+ </section>
+ <nav class="block sm:hidden">
+ <MoreMenu editHref={githubEditUrl}/>
+ </nav>
+</article> \ No newline at end of file
diff --git a/examples/docs/src/components/RightSidebar/MoreMenu.astro b/examples/docs/src/components/RightSidebar/MoreMenu.astro
new file mode 100644
index 000000000..6be2d86ee
--- /dev/null
+++ b/examples/docs/src/components/RightSidebar/MoreMenu.astro
@@ -0,0 +1,68 @@
+---
+import ThemeToggleButton from './ThemeToggleButton.jsx';
+const {editHref} = Astro.props;
+---
+<style>
+ .edit-on-github {
+ text-decoration: none;
+ font: inherit;
+ color: inherit;
+ font-size: 1rem;
+ }
+</style>
+<h2 class="heading">More</h2>
+<ul>
+ <li class={`header-link depth-2`}>
+ <a class="edit-on-github" href={editHref} target="_blank">
+ <svg
+ aria-hidden="true"
+ focusable="false"
+ data-prefix="fas"
+ data-icon="pen"
+ class="svg-inline--fa fa-pen fa-w-16"
+ role="img"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 512 512"
+ height="1em"
+ width="1em"
+ >
+ <path
+ fill="currentColor"
+ d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"
+ ></path>
+ </svg>
+ <span>Edit this page</span>
+ </a>
+ </li>
+ <li class={`header-link depth-2`}>
+ <a href="https://github.com/snowpackjs/astro/blob/main/CONTRIBUTING.md#translations" target="_blank">
+ <svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88.6 77.3" height="1.24em" width="1.24em" style="margin: -2px;"> <path fill="currentColor" d="M61,24.6h7.9l18.7,51.6h-7.7l-5.4-15.5H54.3l-5.6,15.5h-7.2L61,24.6z M72.6,55l-8-22.8L56.3,55H72.6z" /> <path fill="currentColor" d="M53.6,60.6c-10-4-16-9-22-14c0,0,1.3,1.3,0,0c-6,5-20,13-20,13l-4-6c8-5,10-6,19-13c-2.1-1.9-12-13-13-19h8 c4,9,10,14,10,14c10-8,10-19,10-19h8c0,0-1,13-12,24l0,0c5,5,10,9,19,13L53.6,60.6z M1.6,16.6h56v-8h-23v-7h-9v7h-24V16.6z" /> </svg>
+ <span>Translate this page</span>
+ </a>
+ </li>
+ <li class={`header-link depth-2`}>
+ <a href="https://astro.build/chat" target="_blank">
+ <svg
+ aria-hidden="true"
+ focusable="false"
+ data-prefix="fas"
+ data-icon="comment-alt"
+ class="svg-inline--fa fa-comment-alt fa-w-16"
+ role="img"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 512 512"
+ height="1em"
+ width="1em"
+ >
+ <path
+ fill="currentColor"
+ d="M448 0H64C28.7 0 0 28.7 0 64v288c0 35.3 28.7 64 64 64h96v84c0 9.8 11.2 15.5 19.1 9.7L304 416h144c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64z"
+ ></path>
+ </svg>
+ <span>Join our community</span>
+ </a>
+ </li>
+</ul>
+<div style="margin: 2rem 0; text-align: center;">
+ <ThemeToggleButton client:visible />
+</div>
diff --git a/examples/docs/src/components/RightSidebar/RightSidebar.astro b/examples/docs/src/components/RightSidebar/RightSidebar.astro
new file mode 100644
index 000000000..ed1dd37cc
--- /dev/null
+++ b/examples/docs/src/components/RightSidebar/RightSidebar.astro
@@ -0,0 +1,25 @@
+---
+import TableOfContents from './TableOfContents.jsx';
+import MoreMenu from './MoreMenu.astro';
+const {content, githubEditUrl} = Astro.props;
+const headers = content.astro.headers;
+---
+<style>
+ .sidebar-nav {
+ width: 100%;
+ position: sticky;
+ top: 0;
+ }
+ .sidebar-nav-inner {
+ height: 100%;
+ padding: 0;
+ padding-top: var(--doc-padding);
+ overflow: auto;
+ }
+</style>
+<nav class="sidebar-nav" aria-labelledby="grid-right">
+ <div class="sidebar-nav-inner">
+ <TableOfContents client:media="(min-width: 50em)" headers={headers} />
+ <MoreMenu editHref={githubEditUrl} />
+ </div>
+</nav> \ No newline at end of file
diff --git a/examples/docs/src/components/RightSidebar/TableOfContents.tsx b/examples/docs/src/components/RightSidebar/TableOfContents.tsx
new file mode 100644
index 000000000..d8ea998d4
--- /dev/null
+++ b/examples/docs/src/components/RightSidebar/TableOfContents.tsx
@@ -0,0 +1,45 @@
+import type { FunctionalComponent } from 'preact';
+import { h, Fragment } from 'preact';
+import { useState, useEffect, useRef } from 'preact/hooks';
+
+const TableOfContents: FunctionalComponent<{ headers: any[] }> = ({ headers = [] }) => {
+ const itemOffsets = useRef([]);
+ const [activeId, setActiveId] = useState<string>(undefined);
+
+ useEffect(() => {
+ const getItemOffsets = () => {
+ const titles = document.querySelectorAll('article :is(h1, h2, h3, h4)');
+ itemOffsets.current = Array.from(titles).map((title) => ({
+ id: title.id,
+ topOffset: title.getBoundingClientRect().top + window.scrollY,
+ }));
+ };
+
+ getItemOffsets();
+ window.addEventListener('resize', getItemOffsets);
+
+ return () => {
+ window.removeEventListener('resize', getItemOffsets);
+ };
+ }, []);
+
+ return (
+ <>
+ <h2 class="heading">On this page</h2>
+ <ul>
+ <li class={`header-link depth-2 ${activeId === 'overview' ? 'active' : ''}`.trim()}>
+ <a href="#overview">Overview</a>
+ </li>
+ {headers
+ .filter(({ depth }) => depth > 1 && depth < 4)
+ .map((header) => (
+ <li class={`header-link depth-${header.depth} ${activeId === header.slug ? 'active' : ''}`.trim()}>
+ <a href={`#${header.slug}`}>{header.text}</a>
+ </li>
+ ))}
+ </ul>
+ </>
+ );
+};
+
+export default TableOfContents;
diff --git a/examples/docs/src/components/RightSidebar/ThemeToggleButton.css b/examples/docs/src/components/RightSidebar/ThemeToggleButton.css
new file mode 100644
index 000000000..7de231d1b
--- /dev/null
+++ b/examples/docs/src/components/RightSidebar/ThemeToggleButton.css
@@ -0,0 +1,37 @@
+.theme-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25em;
+ padding: 0.33em 0.67em;
+ border-radius: 99em;
+ background-color: var(--theme-code-inline-bg);
+}
+
+.theme-toggle > label:focus-within {
+ outline: 2px solid transparent;
+ box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
+}
+
+.theme-toggle > label {
+ color: var(--theme-code-inline-text);
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0.5;
+}
+
+.theme-toggle .checked {
+ color: var(--theme-accent);
+ opacity: 1;
+}
+
+input[name='theme-toggle'] {
+ position: absolute;
+ opacity: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: -1;
+}
diff --git a/examples/docs/src/components/ThemeToggle.tsx b/examples/docs/src/components/RightSidebar/ThemeToggleButton.tsx
index 5a5061c15..75ea775f4 100644
--- a/examples/docs/src/components/ThemeToggle.tsx
+++ b/examples/docs/src/components/RightSidebar/ThemeToggleButton.tsx
@@ -1,19 +1,13 @@
import type { FunctionalComponent } from 'preact';
import { h, Fragment } from 'preact';
import { useState, useEffect } from 'preact/hooks';
+import './ThemeToggleButton.css';
-const themes = ['system', 'light', 'dark'];
+const themes = ['light', 'dark'];
const icons = [
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path
- fill-rule="evenodd"
- d="M3 5a2 2 0 012-2h10a2 2 0 012 2v8a2 2 0 01-2 2h-2.22l.123.489.804.804A1 1 0 0113 18H7a1 1 0 01-.707-1.707l.804-.804L7.22 15H5a2 2 0 01-2-2V5zm5.771 7H5V5h10v7H8.771z"
- clip-rule="evenodd"
- />
- </svg>,
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
- <path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
@@ -25,42 +19,48 @@ const icons = [
];
const ThemeToggle: FunctionalComponent = () => {
- const [theme, setTheme] = useState(themes[0]);
-
- useEffect(() => {
- const user = localStorage.getItem('theme');
- if (!user) return;
- setTheme(user);
- }, []);
+ const [theme, setTheme] = useState(() => {
+ if (import.meta.env.SSR) {
+ return undefined;
+ }
+ if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
+ return localStorage.getItem('theme');
+ }
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ return 'dark';
+ }
+ return 'light';
+ });
useEffect(() => {
const root = document.documentElement;
- if (theme === 'system') {
- localStorage.removeItem('theme');
- if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
- root.classList.add('theme-dark');
- } else {
- root.classList.remove('theme-dark');
- }
+ if (theme === 'light') {
+ root.classList.remove('theme-dark');
} else {
- localStorage.setItem('theme', theme);
- if (theme === 'light') {
- root.classList.remove('theme-dark');
- } else {
- root.classList.add('theme-dark');
- }
+ root.classList.add('theme-dark');
}
}, [theme]);
return (
- <div id="theme-toggle">
+ <div class="theme-toggle">
{themes.map((t, i) => {
const icon = icons[i];
const checked = t === theme;
return (
- <label className={checked ? 'checked' : ''}>
+ <label className={checked ? ' checked' : ''}>
{icon}
- <input type="radio" name="theme-toggle" checked={checked} value={t} title={`Use ${t} theme`} aria-label={`Use ${t} theme`} onChange={() => setTheme(t)} />
+ <input
+ type="radio"
+ name="theme-toggle"
+ checked={checked}
+ value={t}
+ title={`Use ${t} theme`}
+ aria-label={`Use ${t} theme`}
+ onChange={() => {
+ localStorage.setItem('theme', t);
+ setTheme(t);
+ }}
+ />
</label>
);
})}
diff --git a/examples/docs/src/components/SiteSidebar.astro b/examples/docs/src/components/SiteSidebar.astro
deleted file mode 100644
index 0fbad0c83..000000000
--- a/examples/docs/src/components/SiteSidebar.astro
+++ /dev/null
@@ -1,20 +0,0 @@
----
-import { sidebar } from '../config.ts';
----
-
-<nav>
- <ul class="nav-groups">
- {sidebar.map(category => (
- <li>
- <div class="nav-group">
- <h4 class="nav-group-title"><a href={`${Astro.site}${category.link}`}>{category.text}</a></h4>
- <ul>
- {category.children.map(child => (
- <li class="nav-link"><a href={`${Astro.site}${child.link}`}>{child.text}</a></li>
- ))}
- </ul>
- </div>
- </li>
- ))}
- </ul>
-</nav>
diff --git a/examples/docs/src/components/util.ts b/examples/docs/src/components/util.ts
new file mode 100644
index 000000000..0ec91bce0
--- /dev/null
+++ b/examples/docs/src/components/util.ts
@@ -0,0 +1,4 @@
+export function getLanguageFromURL(pathname: string) {
+ const langCodeMatch = pathname.match(/\/([a-z]{2}-?[A-Z]{0,2})\//);
+ return langCodeMatch ? langCodeMatch[1] : 'en';
+}