diff options
author | 2021-08-14 00:58:00 +0000 | |
---|---|---|
committer | 2021-08-26 12:15:33 -0700 | |
commit | b7ada11ddbabe4dd2f0798e140e5b280de4d6952 (patch) | |
tree | c01ff3eee4f770fc108c19d126dce31d9cbe5e51 /examples/docs/src/components | |
parent | 59cc62f7bd53dbcb6ad8051fa501b7b797614178 (diff) | |
download | astro-b7ada11ddbabe4dd2f0798e140e5b280de4d6952.tar.gz astro-b7ada11ddbabe4dd2f0798e140e5b280de4d6952.tar.zst astro-b7ada11ddbabe4dd2f0798e140e5b280de4d6952.zip |
WIP update examples/docs/
Diffstat (limited to 'examples/docs/src/components')
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'; +} |