Compare commits

...

1 commit

Author SHA1 Message Date
Fred K. Schott
a7a521034e wip 2021-09-06 22:46:59 -07:00
46 changed files with 866 additions and 725 deletions

View file

@ -9,6 +9,7 @@
"preview": "astro preview"
},
"dependencies": {
"@astrojs/docs": "^0.0.1",
"@docsearch/react": "^1.0.0-alpha.27"
},
"devDependencies": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 KiB

View file

@ -1,386 +0,0 @@
* {
box-sizing: border-box;
margin: 0;
}
/* Global focus outline reset */
*:focus:not(:focus-visible) {
outline: none;
}
:root {
--user-font-scale: 1rem - 16px;
--max-width: calc(100% - 1rem);
}
@media (min-width: 50em) {
:root {
--max-width: 46em;
}
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
font-family: var(--font-body);
font-size: 1rem;
font-size: clamp(0.9rem, 0.75rem + 0.375vw + var(--user-font-scale), 1rem);
line-height: 1.5;
max-width: 100vw;
}
nav ul {
list-style: none;
padding: 0;
}
.content > section > * + * {
margin-top: 1.25rem;
}
.content > section > :first-child {
margin-top: 0;
}
/* Typography */
h1,
h2,
h3,
h4,
h5,
h6 {
margin-bottom: 1rem;
font-weight: bold;
line-height: 1;
}
h1,
h2 {
max-width: 40ch;
}
:is(h2, h3):not(:first-child) {
margin-top: 3rem;
}
:is(h4, h5, h6):not(:first-child) {
margin-top: 2rem;
}
h1 {
font-size: 3.25rem;
font-weight: 800;
}
h2 {
font-size: 2.5rem;
}
h3 {
font-size: 1.75rem;
}
h4 {
font-size: 1.3rem;
}
h5 {
font-size: 1rem;
}
p {
line-height: 1.65em;
}
.content ul {
line-height: 1.1em;
}
p,
.content ul {
color: var(--theme-text-light);
}
small,
.text_small {
font-size: 0.833rem;
}
a {
color: var(--theme-text-accent);
font-weight: 400;
text-underline-offset: 0.08em;
align-items: center;
gap: 0.5rem;
}
article > section :is(ul, ol) > * + * {
margin-top: 0.75rem;
}
article > section nav :is(ul, ol) > * + * {
margin-top: inherit;
}
article > section li > :is(p, pre, blockquote):not(:first-child) {
margin-top: 1rem;
}
article > section :is(ul, ol) {
padding-left: 1em;
}
article > section nav :is(ul, ol) {
padding-left: inherit;
}
article > section nav {
margin-top: 1rem;
margin-bottom: 2rem;
}
article > section ::marker {
font-weight: bold;
color: var(--theme-text-light);
}
article > section iframe {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
a > code:not([class*='language']) {
position: relative;
color: var(--theme-text-accent);
background: transparent;
text-underline-offset: var(--padding-block);
}
a > code:not([class*='language'])::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
background: var(--theme-accent);
opacity: var(--theme-accent-opacity);
border-radius: var(--border-radius);
}
a:hover,
a:focus {
text-decoration: underline;
}
a:focus {
outline: 2px solid currentColor;
outline-offset: 0.25em;
}
strong {
font-weight: 600;
color: inherit;
}
/* Supporting Content */
code {
font-family: var(--font-mono);
font-size: 0.85em;
}
code:not([class*='language']) {
--border-radius: 3px;
--padding-block: 0.2rem;
--padding-inline: 0.4rem;
color: var(--theme-code-inline-text);
background-color: var(--theme-code-inline-bg);
padding: var(--padding-block) var(--padding-inline);
margin: calc(var(--padding-block) * -1) -0.125em;
border-radius: var(--border-radius);
box-shadow: 0 2px 1px 0 rgba(0, 0, 0, 0.08);
}
pre > code:not([class*='language']) {
background-color: transparent;
padding: 0;
margin: 0;
border-radius: 0;
color: inherit;
}
pre > code {
font-size: 1em;
}
table,
pre {
position: relative;
--padding-block: 1rem;
--padding-inline: 2rem;
padding: var(--padding-block) var(--padding-inline);
padding-right: calc(var(--padding-inline) * 2);
margin-left: calc(var(--padding-inline) * -1);
margin-right: calc(var(--padding-inline) * -1);
font-family: var(--font-mono);
line-height: 1.5;
font-size: 0.85em;
overflow-y: hidden;
overflow-x: auto;
}
table {
width: 100%;
padding: var(--padding-block) 0;
margin: 0;
border-collapse: collapse;
}
/* Zebra striping */
tr:nth-of-type(odd) {
background: var(--theme-bg-hover);
}
th {
background: var(--color-black);
color: var(--theme-color);
font-weight: bold;
}
td,
th {
padding: 6px;
text-align: left;
}
pre {
background-color: var(--theme-code-bg);
color: var(--theme-code-text);
}
blockquote code:not([class*='language']) {
background-color: var(--theme-bg);
}
@media (min-width: 37.75em) {
pre {
--padding-inline: 1.25rem;
border-radius: 8px;
margin-left: 0;
margin-right: 0;
}
}
blockquote {
margin: 2rem 0;
padding: 1.25em 1.5rem;
border-left: 3px solid var(--theme-text-light);
background-color: var(--theme-bg-offset);
border-radius: 0 0.25rem 0.25rem 0;
line-height: 1.7;
}
img {
max-width: 100%;
}
.flex {
display: flex;
align-items: center;
}
button {
display: flex;
align-items: center;
justify-items: center;
gap: 0.25em;
padding: 0.33em 0.67em;
border: 0;
background: var(--theme-bg);
display: flex;
font-size: 1rem;
align-items: center;
gap: 0.25em;
border-radius: 99em;
color: var(--theme-text);
background-color: var(--theme-bg);
}
h2.heading {
font-size: 1rem;
font-weight: 700;
padding: 0.1rem 1rem;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.header-link {
font-size: 1rem;
padding: 0.1rem 0 0.1rem 1rem;
border-left: 4px solid var(--theme-divider);
}
.header-link:hover,
.header-link:focus {
border-left-color: var(--theme-accent);
color: var(--theme-accent);
}
.header-link:focus-within {
color: var(--theme-text-light);
border-left-color: hsla(var(--color-gray-40), 1);
}
.header-link svg {
opacity: 0.6;
}
.header-link:hover svg {
opacity: 0.8;
}
.header-link a {
display: inline-flex;
gap: 0.5em;
width: 100%;
}
.header-link.depth-3 {
padding-left: 2rem;
}
.header-link.depth-4 {
padding-left: 3rem;
}
.header-link a {
font: inherit;
color: inherit;
text-decoration: none;
}
/* Screenreader Only Text */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.focus\:not-sr-only:focus,
.focus\:not-sr-only:focus-visible {
position: static;
width: auto;
height: auto;
padding: 0;
margin: 0;
overflow: visible;
clip: auto;
white-space: normal;
}
:target {
scroll-margin: calc(var(--theme-sidebar-offset, 5rem) + 2rem) 0 2rem;
}

View file

@ -1,3 +0,0 @@
Array.from(document.getElementsByTagName('pre')).forEach((element) => {
element.setAttribute('tabindex', '0');
});

View file

@ -1,16 +0,0 @@
---
import AvatarList from './AvatarList.astro';
const { path } = Astro.props;
---
<footer>
<AvatarList path={path} />
</footer>
<style>
footer {
margin-top: auto;
padding: 2rem 0;
border-top: 3px solid var(--theme-divider);
}
</style>

View file

@ -0,0 +1,52 @@
---
// The <Head /> Component - Responsible for site-wide `<head>` metadata and SEO tags.
import * as CONFIG from '../config.ts';
export interface Props {
/** A markdown content prop. */
content?: any;
}
const {content = {}} = Astro.props as Props;
const siteTitle: string = CONFIG.SITE.title;
const pageTitle: string = content.title ? `${content.title} | ${siteTitle}` : siteTitle;
const pageDescription: string = content.description || CONFIG.SITE.description;
const lang: string = content.lang || CONFIG.SITE.lang;
const imageURL: string = content.image?.url || CONFIG.SITE.image.url;
const imageAlt: string = content.image?.alt || CONFIG.SITE.image.alt;
const canonicalURL: string = Astro.request.canonicalURL;
const twitter: string | undefined = CONFIG.TWITTER;
---
<!-- Global Metadata -->
<meta name="viewport" content="width=device-width">
<link rel="sitemap" href="/sitemap.xml"/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="alternate icon" type="image/x-icon" href="/favicon.ico" />
<!-- Page Metadata -->
<title>{pageTitle}</title>
<link rel="canonical" href={canonicalURL}/>
<!-- CSS -->
<link rel="stylesheet" href={Astro.resolve('../styles/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">
<!-- OpenGraph Tags -->
<meta property="og:title" content={pageTitle}/>
<meta property="og:description" content={pageDescription}/>
<meta property="og:type" content="article"/>
<meta property="og:url" content={canonicalURL}/>
<meta property="og:locale" content={lang}/>
<meta property="og:image" content={imageURL}/>
<meta property="og:image:alt" content={imageAlt}/>
<meta property="og:site_name" content={siteTitle}/>
<!-- Twitter Tags -->
{twitter && <meta name="twitter:card" content="summary_large_image"/>}
{twitter && <meta name="twitter:site" content={twitter} />}
<!-- Add custom tags here! -->

View file

@ -1,40 +0,0 @@
<!-- 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> -->

View file

@ -1,40 +0,0 @@
---
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 ?? SITE.defaultLanguage}/>
<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
-->

View file

@ -1,41 +0,0 @@
---
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>

View file

@ -1,25 +0,0 @@
---
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>

View file

@ -1,15 +1,12 @@
export const SITE = {
title: 'Your Documentation Website',
description: 'Your website description.',
defaultLanguage: 'en_US',
};
export const OPEN_GRAPH = {
lang: 'en',
dir: 'ltr',
image: {
src: 'https://github.com/snowpackjs/astro/blob/main/assets/social/banner.png?raw=true',
alt: 'astro logo on a starry expanse of space,' + ' with a purple saturn-like planet floating in the right foreground',
alt: 'The Astro logo over an outerspace background image, with stars and planets.',
},
twitter: 'astrodotbuild',
};
export const KNOWN_LANGUAGES = {
@ -17,10 +14,14 @@ export const KNOWN_LANGUAGES = {
};
// Uncomment this to add an "Edit this page" button to every page of documentation.
// export const GITHUB_EDIT_URL = `https://github.com/snowpackjs/astro/blob/main/docs/`;
// The path of the page content is always appended to this URL.
// export const EDIT_URL = `https://github.com/snowpackjs/astro/blob/main/docs/`;
// Uncomment this to add an "Join our Community" button to every page of documentation.
// export const COMMUNITY_INVITE_URL = `https://astro.build/chat`;
// export const COMMUNITY_URL = `https://astro.build/chat`;
// Uncomment this to enable the Twitter SEO meta tag.
// export const TWITTER = 'astrodotbuild';
// Uncomment this to enable site search.
// See "Algolia" section of the README for more information.

View file

@ -1,8 +0,0 @@
import { KNOWN_LANGUAGES } from './config.js';
export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES);
export function getLanguageFromURL(pathname: string) {
const langCodeMatch = pathname.match(/\/([a-z]{2}-?[A-Z]{0,2})\//);
return langCodeMatch ? langCodeMatch[1] : 'en';
}

View file

@ -1,122 +1,38 @@
---
import HeadCommon from "../components/HeadCommon.astro";
import HeadSEO from "../components/HeadSEO.astro";
import Header from '../components/Header/Header.astro';
import Footer from '../components/Footer/Footer.astro';
import PageContent from '../components/PageContent/PageContent.astro';
import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro';
import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
import * as CONFIG from "../config.ts";
// <MainLayout /> - The main layout for your docs site.
// This includes all reusable UI from the '@astrojs/docs' package.
// Markdown content will be injected at the `<slot />` component.
//
// The fastest way to customize this layout is to add new components
// to any existing layout "slot". For example, `<Head />` is a custom
// component that islives in this project "src/components" directory.
// Components will end up in each slot in the order that they appear.
//
// You can also completely replace layout components like `LeftSidebar`
// and `PageContent` with your own UI.
import {PageLayout, Header, PageContent, LeftSidebar, RightSidebar} from '@astrojs/docs';
import Head from '../components/Head.astro';
import * as CONFIG from '../config.ts';
export interface Props {
/** A markdown content prop. Automatically provided by Astro. */
content?: any;
}
const { content = {} } = Astro.props;
const dir = content.dir || CONFIG.SITE.dir;
const lang = content.lang || CONFIG.SITE.lang;
const currentPage = Astro.request.url.pathname;
const currentFile = `src/pages${currentPage.replace(/\/$/, "")}.md`;
const githubEditUrl = CONFIG.GITHUB_EDIT_URL && (CONFIG.GITHUB_EDIT_URL + currentFile);
const editUrlWithFile = CONFIG.EDIT_URL && (CONFIG.EDIT_URL + currentFile);
---
<html dir="{content.dir ?? 'ltr'}" lang="{content.lang ?? 'en-us'}" class="initial">
<head>
<HeadCommon />
<HeadSEO {content} canonicalURL={Astro.request.canonicalURL} />
<title>{content.title ? `${content.title} 🚀 ${CONFIG.SITE.title}` : CONFIG.SITE.title}</title>
<style>
body {
width: 100%;
display: grid;
grid-template-rows: var(--theme-navbar-height) 1fr;
--gutter: 0.5rem;
--doc-padding: 2rem;
}
.layout {
display: grid;
grid-auto-flow: column;
grid-template-columns:
minmax(var(--gutter), 1fr)
minmax(0, var(--max-width))
minmax(var(--gutter), 1fr);
overflow-x: hidden;
}
.layout :global(> *) {
width: 100%;
height: 100%;
}
.grid-sidebar {
height: 100vh;
position: sticky;
top: 0;
padding: 0;
}
#grid-left {
position: fixed;
background-color: var(--theme-bg);
z-index: 10;
display: none;
}
#grid-main {
padding: var(--doc-padding) var(--gutter);
grid-column: 2;
display: flex;
flex-direction: column;
height: 100%;
}
#grid-right {
display: none;
}
:global(.mobile-sidebar-toggle) {
overflow: hidden;
}
:global(.mobile-sidebar-toggle) #grid-left {
display: block;
top: 2rem;
}
@media (min-width: 50em) {
.layout {
overflow: initial;
grid-template-columns:
20rem
minmax(0, var(--max-width));
gap: 1em;
}
#grid-left {
display: flex;
padding-left: 2rem;
position: sticky;
grid-column: 1;
}
}
@media (min-width: 72em) {
.layout {
grid-template-columns:
20rem
minmax(0, var(--max-width))
18rem;
padding-left: 0;
padding-right: 0;
margin: 0 auto;
}
#grid-right {
grid-column: 3;
display: flex;
}
}
</style>
</head>
<body>
<Header currentPage={currentPage} />
<main class="layout">
<aside id="grid-left" class="grid-sidebar" title="Site Navigation">
<LeftSidebar currentPage={currentPage} />
</aside>
<div id="grid-main">
<PageContent content={content} githubEditUrl={githubEditUrl}>
<slot />
</PageContent>
</div>
<aside id="grid-right" class="grid-sidebar" title="Table of Contents">
<RightSidebar content={content} githubEditUrl={githubEditUrl} />
</aside>
</main>
</body>
</html>
<PageLayout lang={lang} dir={dir}>
<Head slot="head" content={content} />
<Header slot="header" currentPage={currentPage} lang={lang} languages={CONFIG.KNOWN_LANGUAGES} search={CONFIG.ALGOLIA} />
<LeftSidebar slot="left" currentPage={currentPage} navigation={CONFIG.SIDEBAR[lang]} />
<PageContent slot="main" content={content}>
<slot />
</PageContent>
<RightSidebar slot="right" content={content} editUrl={editUrlWithFile} inviteUrl={CONFIG.COMMUNITY_URL} />
</PageLayout>

View file

@ -1,5 +1,6 @@
---
title: Introduction
lang: en
layout: ../../layouts/MainLayout.astro
---

View file

@ -1,5 +1,6 @@
---
title: Page 2
lang: en
layout: ../../layouts/MainLayout.astro
---

View file

@ -1,5 +1,6 @@
---
title: Page 3
lang: en
layout: ../../layouts/MainLayout.astro
---

View file

@ -1,5 +1,6 @@
---
title: Page 4
lang: en
layout: ../../layouts/MainLayout.astro
---

View file

@ -0,0 +1,170 @@
* {
box-sizing: border-box;
margin: 0;
}
/* Global focus outline reset */
*:focus:not(:focus-visible) {
outline: none;
}
:root {
--user-font-scale: 1rem - 16px;
--max-width: calc(100% - 1rem);
}
@media (min-width: 50em) {
:root {
--max-width: 46em;
}
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
font-family: var(--font-body);
font-size: 1rem;
font-size: clamp(0.9rem, 0.75rem + 0.375vw + var(--user-font-scale), 1rem);
line-height: 1.5;
max-width: 100vw;
}
/* Supporting Content */
nav ul {
list-style: none;
padding: 0;
}
table,
pre {
position: relative;
--padding-block: 1rem;
--padding-inline: 2rem;
padding: var(--padding-block) var(--padding-inline);
padding-right: calc(var(--padding-inline) * 2);
margin-left: calc(var(--padding-inline) * -1);
margin-right: calc(var(--padding-inline) * -1);
font-family: var(--font-mono);
line-height: 1.5;
font-size: 0.85em;
overflow-y: hidden;
overflow-x: auto;
}
table {
width: 100%;
padding: var(--padding-block) 0;
margin: 0;
border-collapse: collapse;
}
blockquote {
margin: 2rem 0;
padding: 1.25em 1.5rem;
border-left: 3px solid var(--theme-text-light);
background-color: var(--theme-bg-offset);
border-radius: 0 0.25rem 0.25rem 0;
line-height: 1.7;
}
img {
max-width: 100%;
}
.flex {
display: flex;
align-items: center;
}
button {
display: flex;
align-items: center;
justify-items: center;
gap: 0.25em;
padding: 0.33em 0.67em;
border: 0;
background: var(--theme-bg);
display: flex;
font-size: 1rem;
align-items: center;
gap: 0.25em;
border-radius: 99em;
color: var(--theme-text);
background-color: var(--theme-bg);
}
/** Headings */
h2.heading {
font-size: 1rem;
font-weight: 700;
padding: 0.1rem 1rem;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.header-link {
font-size: 1rem;
line-height: 1.5;
padding: 0.1rem 0 0.1rem 0.75rem;
margin-left: 0.25rem;
border-left: 4px solid var(--theme-divider);
}
.header-link:hover,
.header-link:focus {
border-left-color: var(--theme-accent);
color: var(--theme-accent);
}
.header-link:focus-within {
color: var(--theme-text-light);
border-left-color: hsla(var(--color-gray-40), 1);
}
.header-link svg {
opacity: 0.6;
}
.header-link:hover svg {
opacity: 0.8;
}
.header-link a {
display: inline-flex;
gap: 0.5em;
width: 100%;
}
.header-link.depth-3 {
padding-left: 2rem;
}
.header-link.depth-4 {
padding-left: 3rem;
}
.header-link a {
font: inherit;
color: inherit;
text-decoration: none;
}
/* Screenreader Only Text */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.focus\:not-sr-only:focus,
.focus\:not-sr-only:focus-visible {
position: static;
width: auto;
height: auto;
padding: 0;
margin: 0;
overflow: visible;
clip: auto;
white-space: normal;
}
:target {
scroll-margin: calc(var(--theme-sidebar-offset, 5rem) + 2rem) 0 2rem;
}

View file

@ -1,3 +1,52 @@
/* General */
pre {
background-color: var(--theme-code-bg);
color: var(--theme-code-text);
}
code {
font-family: var(--font-mono);
font-size: 0.85em;
}
blockquote code:not([class*='language']) {
background-color: var(--theme-bg);
}
@media (min-width: 37.75em) {
pre {
--padding-inline: 1.25rem;
border-radius: 8px;
margin-left: 0;
margin-right: 0;
}
}
code:not([class*='language']) {
--border-radius: 3px;
--padding-block: 0.2rem;
--padding-inline: 0.4rem;
color: var(--theme-code-inline-text);
background-color: var(--theme-code-inline-bg);
padding: var(--padding-block) var(--padding-inline);
margin: calc(var(--padding-block) * -1) -0.125em;
border-radius: var(--border-radius);
box-shadow: 0 2px 1px 0 rgba(0, 0, 0, 0.08);
}
pre > code:not([class*='language']) {
background-color: transparent;
padding: 0;
margin: 0;
border-radius: 0;
color: inherit;
}
pre > code {
font-size: 1em;
}
/* Syntax Highlighting */
.language-css > code,
.language-sass > code,
.language-scss > code {

View file

@ -0,0 +1,4 @@
@import './theme.css';
@import './base.css';
@import './typography.css';
@import './code.css';

View file

@ -0,0 +1,99 @@
/* Typography */
h1,
h2,
h3,
h4,
h5,
h6 {
margin-bottom: 1rem;
font-weight: bold;
line-height: 1;
}
h1,
h2 {
max-width: 40ch;
}
:is(h2, h3):not(:first-child) {
margin-top: 3rem;
}
:is(h4, h5, h6):not(:first-child) {
margin-top: 2rem;
}
h1 {
font-size: 3.25rem;
font-weight: 800;
}
h2 {
font-size: 2.5rem;
}
h3 {
font-size: 1.75rem;
}
h4 {
font-size: 1.3rem;
}
h5 {
font-size: 1rem;
}
p {
line-height: 1.65em;
color: var(--theme-text-light);
}
small,
.text_small {
font-size: 0.833rem;
}
a {
color: var(--theme-text-accent);
font-weight: 400;
text-underline-offset: 0.08em;
align-items: center;
gap: 0.5rem;
}
a > code:not([class*='language']) {
position: relative;
color: var(--theme-text-accent);
background: transparent;
text-underline-offset: var(--padding-block);
}
a > code:not([class*='language'])::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
background: var(--theme-accent);
opacity: var(--theme-accent-opacity);
border-radius: var(--border-radius);
}
a:hover,
a:focus {
text-decoration: underline;
}
a:focus {
outline: 2px solid currentColor;
outline-offset: 0.25em;
}
strong {
font-weight: 600;
color: inherit;
}

View file

@ -24,6 +24,7 @@
},
"workspaces": [
"packages/renderers/*",
"packages/components/*",
"packages/*",
"examples/*",
"scripts",

View file

@ -0,0 +1,105 @@
# `@astrojs/docs` - Astro Components Kit
```
npm install @astrojs/docs
```
This package is the component library that powers the Astro `docs` starter template.
This is only a collection of components. **If you are looking for an already-themed, batteries-included template for your next project, check out the `docs` starter kit via `npm init astro` instead.**
## What is in this package?
This package contains several common UI components for documentation websites, including an i18n language selector and an Algolia-powered search bar.
This package also includes a CSS Grid layout for your site, using a left sidebar for page navigation and a right sidebar for section navigation within each page. The design is responsive to work across desktop and mobile.
## How to Use This Package
Because `@astrojs/docs` is a lower-level UI library, you will need to write some code on your end to integrate it. The best way to do that is with a [Page Layout](https://docs.astro.build/core-concepts/layouts) component, like the example below:
```astro
---
// Example: src/layouts/MainLayout.astro
//
// <MainLayout /> - The main layout for your docs site.
// This includes all reusable UI from the '@astrojs/docs' package.
// Markdown content will be injected at the `<slot />` component.
//
// To use this layout: set the `layout` in your markdown page's frontmatter.
//
// The fastest way to customize your layout is to add new components
// to any existing layout "slot". For example, `<Head />` is a custom
// component that lives in this project "src/components" directory.
//
// You can also completely replace layout components like `LeftSidebar`
// and `PageContent` with your own UI.
import {PageLayout, Header, PageContent, LeftSidebar, RightSidebar} from '@astrojs/docs';
import Head from '../components/Head.astro';
import * as CONFIG from '../config.ts';
const { content = {} } = Astro.props;
const dir = content.dir || CONFIG.SITE.dir;
const lang = content.lang || CONFIG.SITE.lang;
const currentPage = Astro.request.url.pathname;
const currentFile = `src/pages${currentPage.replace(/\/$/, "")}.md`;
const editUrlWithFile = CONFIG.EDIT_URL && (CONFIG.EDIT_URL + currentFile);
---
<PageLayout lang={lang} dir={dir}>
<Head slot="head" content={content} />
<Header slot="header" currentPage={currentPage} lang={lang} languages={CONFIG.KNOWN_LANGUAGES} search={CONFIG.ALGOLIA} />
<LeftSidebar slot="left" currentPage={currentPage} navigation={CONFIG.SIDEBAR[lang]} />
<PageContent slot="main" content={content}>
<slot />
</PageContent>
<RightSidebar slot="right" content={content} editUrl={editUrlWithFile} inviteUrl={CONFIG.COMMUNITY_URL} />
</PageLayout>
```
You can customize any section of the page layout by replacing these components with components of your own. Replacing `<LeftSidebar slot="left" ... />` with `<MySidebar slot="left" />`, for example, will place your `<MySidebar />` component in the left sidebar section of the page layout. Many components can share the same slot, if you'd like to add new UI without removing existing.
You can control the document `<head>` using the `slot="head"` slot. By default, no elements are added to the page head for you. We recommend placing all `<head>` elements into one (or more) `src/components/Head.astro` component(s) in your project.
See the `<PageLayout />` documentation below for a list of all supported slots.
## Components
### All Layout Components
These components control full pieces of the docs page layout, including sidebars and headers. `<PageLayout />` controls the CSS Grid layout, while the rest control the UI that ends up in each area of the grid. You can use all of them together, or mix-and-match different UI inside of `<PageLayout />`.
```js
import {
// The full page layout. See supported slots in the example above.
PageLayout,
// Individual parts of the default page layout.
Header,
LeftSidebar,
RightSidebar,
PageContent,
} from '@astrojs/docs';
```
### All Static UI Components
These components are static and require zero JavaScript to run.
```js
import {
AstroLogo,
AvatarList,
SkipToContent,
} from '@astrojs/docs';
```
### All Dynamic UI Components
These components may require JavaScript to run. You can include JavaScript automatically by using them with `client:*` directives. Check out Astro's documentation on [Partial Hydration](https://docs.astro.build/core-concepts/component-hydration) for more information.
```js
import {
LanguageSelect,
Search,
SidebarToggle,
ThemeToggle,
TableOfContents,
} from '@astrojs/docs';
```

View file

@ -0,0 +1,17 @@
// Full Astro Configuration API Documentation:
// https://docs.astro.build/reference/configuration-reference
// @type-check enabled!
// VSCode and other TypeScript-enabled text editors will provide auto-completion,
// helpful tooltips, and warnings if your exported object is invalid.
// You can disable this by removing "@ts-check" and `@type` comments below.
// @ts-check
export default /** @type {import('astro').AstroUserConfig} */ ({
renderers: [
// Enable the Preact renderer to support Preact JSX components.
'@astrojs/renderer-preact',
// Enable the React renderer, for the Algolia search component
'@astrojs/renderer-react',
],
});

View file

@ -0,0 +1,21 @@
{
"name": "@astrojs/docs",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"main": "./src/index.js",
"type": "module",
"dependencies": {
"@docsearch/react": "^1.0.0-alpha.27"
},
"devDependencies": {
"astro": "^0.20.3"
},
"snowpack": {
"workspaceRoot": "../../.."
}
}

View file

@ -1,14 +1,10 @@
---
import { getLanguageFromURL, KNOWN_LANGUAGE_CODES } from '../../languages.ts';
import * as CONFIG from '../../config.ts';
import AstroLogo from './AstroLogo.astro';
import SkipToContent from './SkipToContent.astro';
import SidebarToggle from './SidebarToggle.tsx';
import LanguageSelect from './LanguageSelect.jsx';
import Search from "./Search.jsx";
const {currentPage} = Astro.props;
const lang = currentPage && getLanguageFromURL(currentPage);
import AstroLogo from '../components/AstroLogo.astro';
import SkipToContent from '../components/SkipToContent.astro';
import SidebarToggle from '../components/SidebarToggle.js';
import LanguageSelect from '../components/LanguageSelect.js';
import Search from '../components/Search.js';
const {currentPage, lang, languages, search} = Astro.props;
---
<style>
header {
@ -126,7 +122,12 @@ const lang = currentPage && getLanguageFromURL(currentPage);
</a>
</div>
<div style="flex-grow: 1;"></div>
{KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle />}
{CONFIG.ALGOLIA && <div class="search-item"><Search client:idle /></div>}
{Object.keys(languages).length > 1 && <LanguageSelect lang={lang} options={languages} client:idle />}
{search && <div class="search-item">
<Search
client:idle
indexName={search.indexName}
apiKey={search.apiKey} />
</div>}
</nav>
</header>

View file

@ -1,11 +1,8 @@
---
import { getLanguageFromURL } from '../../languages.ts';
import { SIDEBAR } from '../../config.ts';
const {currentPage} = Astro.props;
const {currentPage, navigation} = 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) => {
const sidebarSections = navigation.reduce((col, item) => {
if (item.header) {
col.push({...item, children: []});
} else {

View file

@ -0,0 +1,80 @@
---
import TableOfContents from '../components/TableOfContents.jsx';
const {content} = Astro.props;
const title = content.title;
const headers = content.astro.headers;
---
<style>
.nav {
display: block;
margin: 2rem 0 2rem -1rem;
}
footer {
margin-top: auto;
padding: 2rem 0;
border-top: 3px solid var(--theme-divider);
}
@media (min-width: 72em) {
.sm\:hidden {
display: none;
}
}
</style>
<style global>
.content {
padding: 0;
max-width: 75ch;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
margin-bottom: 0rem;
}
.content ul {
line-height: 1.1em;
color: var(--theme-text-light);
}
.content > section > * + * {
margin-top: 1.25rem;
}
.content > section > :first-child {
margin-top: 0;
}
.content > section :is(ul, ol) > * + * {
margin-top: 0.75rem;
}
.content > section nav :is(ul, ol) > * + * {
margin-top: inherit;
}
.content > section li > :is(p, pre, blockquote):not(:first-child) {
margin-top: 1rem;
}
.content > section :is(ul, ol) {
padding-left: 1em;
}
.content > section nav :is(ul, ol) {
padding-left: inherit;
}
.content > section nav {
margin-top: 1rem;
margin-bottom: 2rem;
}
.content > section ::marker {
font-weight: bold;
color: var(--theme-text-light);
}
.content > section iframe {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
</style>
<article id="article" class="content">
<section class="main-section">
<h1 class="content-title" id="overview">{title}</h1>
<nav class="nav sm:hidden">
<TableOfContents client:media="(max-width: 50em)" headers={headers}/>
</nav>
<slot />
</section>
</article>

View file

@ -0,0 +1,115 @@
---
const { dir, lang } = Astro.props;
---
<html dir="{dir || 'ltr'}" lang="{lang || 'en'}" class="initial">
<head>
<slot name="head" />
<style>
body {
width: 100%;
display: grid;
grid-template-rows: var(--theme-navbar-height) 1fr;
--gutter: 0.5rem;
--doc-padding: 2rem;
}
.layout {
display: grid;
grid-auto-flow: column;
grid-template-columns:
minmax(var(--gutter), 1fr)
minmax(0, var(--max-width))
minmax(var(--gutter), 1fr);
overflow-x: hidden;
}
.grid-sidebar {
height: 100vh;
position: sticky;
top: 0;
padding: 0;
width: 100%;
height: 100%;
}
#grid-left {
position: fixed;
background-color: var(--theme-bg);
z-index: 10;
display: none;
}
#grid-main {
padding: var(--doc-padding) var(--gutter);
grid-column: 2;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
height: 100%;
}
#grid-right {
grid-column: 2;
}
:global(.mobile-sidebar-toggle) {
overflow: hidden;
}
:global(.mobile-sidebar-toggle) #grid-left {
display: block;
top: 2rem;
}
@media (min-width: 50em) {
.layout {
overflow: initial;
grid-template-columns:
20rem
minmax(0, var(--max-width));
gap: 1em;
}
#grid-left {
display: flex;
padding-left: 2rem;
position: sticky;
grid-column: 1;
}
}
@media (min-width: 72em) {
.layout {
grid-template-columns:
20rem
minmax(0, var(--max-width))
18rem;
padding-left: 0;
padding-right: 0;
margin: 0 auto;
}
#grid-right {
grid-column: 3;
display: flex;
width: 100%;
height: 100%;
}
}
</style>
</head>
<body>
<slot name="header" />
<main class="layout">
<aside id="grid-left" class="grid-sidebar" title="Site Navigation">
<slot name="left" />
</aside>
<div id="grid-main">
<slot name="main" />
</div>
<aside id="grid-right" class="grid-right-sidebar" title="Table of Contents">
<slot name="right" />
</aside>
</main>
<!-- Scrollable a11y code helper -->
<script type="module">
Array.from(document.getElementsByTagName('pre')).forEach((element) => {
element.setAttribute('tabindex', '0');
});
</script>
</body>
</html>

View file

@ -0,0 +1,34 @@
---
import TableOfContents from '../components/TableOfContents.jsx';
import MoreMenu from '../components/MoreMenu.astro';
const {content, editUrl, inviteUrl} = Astro.props;
---
<style>
.lg\:hidden {
display: none;
}
@media (min-width: 72em) {
.sidebar-nav {
width: 100%;
position: sticky;
top: 0;
}
.sidebar-nav-inner {
height: 100%;
padding: 0;
padding-top: var(--doc-padding);
overflow: auto;
}
.lg\:hidden {
display: block;
}
}
</style>
<nav class="sidebar-nav" aria-labelledby="grid-right">
<div class="sidebar-nav-inner">
<div class="lg:hidden" >
<TableOfContents client:media="(min-width: 50em)" headers={content.astro.headers} />
</div>
<MoreMenu editUrl={editUrl} inviteUrl={inviteUrl} />
</div>
</nav>

View file

@ -1,8 +1,7 @@
---
// 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}`;
const url = `https://api.github.com/repos/${Astro.props.repo}/commits?path=${Astro.props.file}`;
const commitsURL = `https://github.com/${Astro.props.repo}/commits/${Astro.props.branch || 'main'}/${Astro.props.file}`;
async function getCommits(url) {
try {
@ -34,8 +33,7 @@ async function getCommits(url) {
return data;
} catch (e) {
console.warn(`[error] /src/components/AvatarList.astro
${e?.message ?? e}`);
console.warn(`[error] AvatarList: ${e && e.message || e}`);
return new Array();
}
}

View file

@ -1,9 +1,8 @@
import type { FunctionalComponent } from 'preact';
import { h } from 'preact';
import type { FunctionalComponent } from 'preact';
import './LanguageSelect.css';
import { KNOWN_LANGUAGES, langPathRegex } from '../../languages';
const LanguageSelect: FunctionalComponent<{ lang: string }> = ({ lang }) => {
const LanguageSelect: FunctionalComponent<{ lang: string, options: Record<string, string> }> = ({ lang, options }) => {
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">
@ -18,14 +17,12 @@ const LanguageSelect: FunctionalComponent<{ lang: string }> = ({ lang }) => {
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;
window.location.pathname = '/' + newLang + '/introduction';
}}
>
{Object.keys(KNOWN_LANGUAGES).map((key) => {
{Object.keys(options).map((key) => {
return (
<option value={KNOWN_LANGUAGES[key]}>
<option value={options[key]}>
<span>{key}</span>
</option>
);

View file

@ -1,8 +1,7 @@
---
import ThemeToggleButton from './ThemeToggleButton.jsx';
import * as CONFIG from '../../config.js';
const {editHref} = Astro.props;
const showMoreSection = (CONFIG.COMMUNITY_INVITE_URL || editHref);
import ThemeToggle from './ThemeToggle.astro';
const {editUrl, inviteUrl} = Astro.props;
const showMoreSection = (inviteUrl || editUrl);
---
<style>
.edit-on-github {
@ -14,9 +13,9 @@ const showMoreSection = (CONFIG.COMMUNITY_INVITE_URL || editHref);
</style>
{showMoreSection && <h2 class="heading">More</h2>}
<ul>
{editHref &&
{editUrl &&
<li class={`header-link depth-2`}>
<a class="edit-on-github" href={editHref} target="_blank">
<a class="edit-on-github" href={editUrl} target="_blank">
<svg
aria-hidden="true"
focusable="false"
@ -38,7 +37,7 @@ const showMoreSection = (CONFIG.COMMUNITY_INVITE_URL || editHref);
</a>
</li>
}
{CONFIG.COMMUNITY_INVITE_URL &&
{inviteUrl &&
<li class={`header-link depth-2`}>
<a href="https://astro.build/chat" target="_blank">
<svg
@ -64,5 +63,5 @@ const showMoreSection = (CONFIG.COMMUNITY_INVITE_URL || editHref);
}
</ul>
<div style="margin: 2rem 0; text-align: center;">
<ThemeToggleButton client:visible />
<ThemeToggle />
</div>

View file

@ -2,11 +2,10 @@
import { useState, useCallback, useRef } from 'react';
import { createPortal } from 'react-dom';
import { DocSearchModal, useDocSearchKeyboardEvents } from '@docsearch/react';
import * as CONFIG from '../../config.js';
import '@docsearch/css/dist/style.css';
import './Search.css';
export default function Search() {
export default function Search({indexName, apiKey}) {
const [isOpen, setIsOpen] = useState(false);
const searchButtonRef = useRef();
const [initialQuery, setInitialQuery] = useState(null);
@ -54,8 +53,8 @@ export default function Search() {
initialQuery={initialQuery}
initialScrollY={window.scrollY}
onClose={onClose}
indexName={(CONFIG as any).ALGOLIA.indexName}
apiKey={(CONFIG as any).ALGOLIA.apiKey}
indexName={indexName}
apiKey={apiKey}
transformItems={(items) => {
return items.map((item) => {
// We transform the absolute URL into a relative URL to

View file

@ -0,0 +1,15 @@
---
import ThemeToggleButton from './ThemeToggleButton.js';
---
<!-- This is intentionally inlined to avoid FOUC -->
<script hoist>
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>
<ThemeToggleButton client:visible />

View file

@ -0,0 +1,16 @@
/* Layout Components */
export { default as PageLayout } from './PageLayout/PageLayout.astro';
export { default as Header } from './PageLayout/Header.astro';
export { default as LeftSidebar } from './PageLayout/LeftSidebar.astro';
export { default as PageContent } from './PageLayout/PageContent.astro';
export { default as RightSidebar } from './PageLayout/RightSidebar.astro';
/* Reusable UI Components */
export { default as AvatarList } from './components/AvatarList.astro';
export { default as AstroLogo } from './components/AstroLogo.astro';
export { default as SkipToContent } from './components/SkipToContent.astro';
export { default as SidebarToggle } from './components/SidebarToggle.jsx';
export { default as LanguageSelect } from './components/LanguageSelect.jsx';
export { default as Search } from './components/Search.jsx';
export { default as ThemeToggle } from './components/ThemeToggle.astro';
export { default as TableOfContents } from './components/TableOfContents.jsx';

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"target": "ES2019",
"module": "CommonJS",
"outDir": "./dist"
}
}