basic astro docs
This commit is contained in:
parent
61657bd1b1
commit
bc7a84e462
48 changed files with 14926 additions and 2 deletions
79
website/src/components/RightSidebar/MoreMenu.astro
Normal file
79
website/src/components/RightSidebar/MoreMenu.astro
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
import ThemeToggleButton from './ThemeToggleButton';
|
||||
import * as CONFIG from '../../config';
|
||||
|
||||
type Props = {
|
||||
editHref: string;
|
||||
};
|
||||
|
||||
const { editHref } = Astro.props as Props;
|
||||
const showMoreSection = CONFIG.COMMUNITY_INVITE_URL;
|
||||
---
|
||||
|
||||
{showMoreSection && <h2 class="heading">More</h2>}
|
||||
<ul>
|
||||
{
|
||||
editHref && (
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
<span>Edit this page</span>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
CONFIG.COMMUNITY_INVITE_URL && (
|
||||
<li class={`header-link depth-2`}>
|
||||
<a href={CONFIG.COMMUNITY_INVITE_URL} 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"
|
||||
/>
|
||||
</svg>
|
||||
<span>Join our community</span>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
<div style="margin: 2rem 0; text-align: center;">
|
||||
<ThemeToggleButton client:visible />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.edit-on-github {
|
||||
text-decoration: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
34
website/src/components/RightSidebar/RightSidebar.astro
Normal file
34
website/src/components/RightSidebar/RightSidebar.astro
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
import TableOfContents from './TableOfContents';
|
||||
import MoreMenu from './MoreMenu.astro';
|
||||
import type { MarkdownHeading } from 'astro';
|
||||
|
||||
type Props = {
|
||||
headings: MarkdownHeading[];
|
||||
githubEditUrl: string;
|
||||
};
|
||||
|
||||
const { headings, githubEditUrl } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<nav class="sidebar-nav" aria-labelledby="grid-right">
|
||||
<div class="sidebar-nav-inner">
|
||||
<TableOfContents client:media="(min-width: 50em)" headings={headings} />
|
||||
<MoreMenu editHref={githubEditUrl} />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.sidebar-nav {
|
||||
width: 100%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.sidebar-nav-inner {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
padding-top: var(--doc-padding);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
93
website/src/components/RightSidebar/TableOfContents.tsx
Normal file
93
website/src/components/RightSidebar/TableOfContents.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { unescape } from 'html-escaper';
|
||||
import type { MarkdownHeading } from 'astro';
|
||||
import type { FunctionalComponent } from 'preact';
|
||||
import { useState, useEffect, useRef } from 'preact/hooks';
|
||||
|
||||
type ItemOffsets = {
|
||||
id: string;
|
||||
topOffset: number;
|
||||
};
|
||||
|
||||
const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({
|
||||
headings = [],
|
||||
}) => {
|
||||
const toc = useRef<HTMLUListElement>();
|
||||
const onThisPageID = 'on-this-page-heading';
|
||||
const itemOffsets = useRef<ItemOffsets[]>([]);
|
||||
const [currentID, setCurrentID] = useState('overview');
|
||||
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);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!toc.current) return;
|
||||
|
||||
const setCurrent: IntersectionObserverCallback = (entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
const { id } = entry.target;
|
||||
if (id === onThisPageID) continue;
|
||||
setCurrentID(entry.target.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const observerOptions: IntersectionObserverInit = {
|
||||
// Negative top margin accounts for `scroll-margin`.
|
||||
// Negative bottom margin means heading needs to be towards top of viewport to trigger intersection.
|
||||
rootMargin: '-100px 0% -66%',
|
||||
threshold: 1,
|
||||
};
|
||||
|
||||
const headingsObserver = new IntersectionObserver(setCurrent, observerOptions);
|
||||
|
||||
// Observe all the headings in the main page content.
|
||||
document.querySelectorAll('article :is(h1,h2,h3)').forEach((h) => headingsObserver.observe(h));
|
||||
|
||||
// Stop observing when the component is unmounted.
|
||||
return () => headingsObserver.disconnect();
|
||||
}, [toc.current]);
|
||||
|
||||
const onLinkClick = (e) => {
|
||||
setCurrentID(e.target.getAttribute('href').replace('#', ''));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 id={onThisPageID} className="heading">
|
||||
On this page
|
||||
</h2>
|
||||
<ul ref={toc}>
|
||||
{headings
|
||||
.filter(({ depth }) => depth > 1 && depth < 4)
|
||||
.map((heading) => (
|
||||
<li
|
||||
className={`header-link depth-${heading.depth} ${
|
||||
currentID === heading.slug ? 'current-header-link' : ''
|
||||
}`.trim()}
|
||||
>
|
||||
<a href={`#${heading.slug}`} onClick={onLinkClick}>
|
||||
{unescape(heading.text)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableOfContents;
|
||||
37
website/src/components/RightSidebar/ThemeToggleButton.css
Normal file
37
website/src/components/RightSidebar/ThemeToggleButton.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
82
website/src/components/RightSidebar/ThemeToggleButton.tsx
Normal file
82
website/src/components/RightSidebar/ThemeToggleButton.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import type { FunctionalComponent } from 'preact';
|
||||
import { useState, useEffect } from 'preact/hooks';
|
||||
import './ThemeToggleButton.css';
|
||||
|
||||
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
|
||||
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"
|
||||
/>
|
||||
</svg>,
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
||||
</svg>,
|
||||
];
|
||||
|
||||
const ThemeToggle: FunctionalComponent = () => {
|
||||
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 === 'light') {
|
||||
root.classList.remove('theme-dark');
|
||||
} else {
|
||||
root.classList.add('theme-dark');
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<div className="theme-toggle">
|
||||
{themes.map((t, i) => {
|
||||
const icon = icons[i];
|
||||
const checked = t === theme;
|
||||
return (
|
||||
<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={() => {
|
||||
localStorage.setItem('theme', t);
|
||||
setTheme(t);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeToggle;
|
||||
Loading…
Add table
Add a link
Reference in a new issue