diff --git a/examples/docs/astro.config.mjs b/examples/docs/astro.config.mjs index 68499b3fa..120106f3e 100644 --- a/examples/docs/astro.config.mjs +++ b/examples/docs/astro.config.mjs @@ -8,6 +8,10 @@ // @ts-check export default /** @type {import('astro').AstroUserConfig} */ ({ - // Enable the Preact renderer to support Preact JSX components. - renderers: ['@astrojs/renderer-preact'], + 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', + ], }); diff --git a/examples/docs/package.json b/examples/docs/package.json index cfaa832ac..9893e6496 100644 --- a/examples/docs/package.json +++ b/examples/docs/package.json @@ -8,10 +8,21 @@ "build": "astro build", "preview": "astro preview" }, + "dependencies": { + "@docsearch/react": "^1.0.0-alpha.27" + }, "devDependencies": { - "astro": "^0.20.0" + "astro": "^0.20.0", + "@snowpack/plugin-dotenv": "^2.1.0" }, "snowpack": { + "alias": { + "components": "./src/components", + "~": "./src" + }, + "plugins": [ + "@snowpack/plugin-dotenv" + ], "workspaceRoot": "../.." } } diff --git a/examples/docs/public/code.css b/examples/docs/public/code.css index ec735a676..3fbb26626 100644 --- a/examples/docs/public/code.css +++ b/examples/docs/public/code.css @@ -8,148 +8,89 @@ opacity: 0.7; } -.token.atrule { - color: #c792ea; +.token.plain-text, +[class*='language-bash'] span.token, +[class*='language-shell'] span.token { + color: hsla(var(--color-gray-90), 1); } -.token.attr-name { - color: #ffcb6b; +[class*='language-bash'] span.token, +[class*='language-shell'] span.token { + font-style: bold; } -.token.attr-value { - color: #a5e844; -} - -.token.attribute { - color: #a5e844; -} - -.token.boolean { - color: #c792ea; -} - -.token.builtin { - color: #ffcb6b; -} - -.token.cdata { - color: #80cbc4; -} - -.token.char { - color: #80cbc4; -} - -.token.class { - color: #ffcb6b; -} - -.token.class-name { - color: #f2ff00; -} - -.token.comment { - color: #616161; -} - -.token.constant { - color: #c792ea; +.token.prolog, +.token.comment, +[class*='language-bash'] span.token.comment, +[class*='language-shell'] span.token.comment { + color: hsla(var(--color-gray-70), 1); } +.token.selector, +.token.tag, +.token.unit, +.token.url, +.token.variable, +.token.entity, .token.deleted { - color: #ff6666; + color: #fa5e5b; } -.token.doctype { - color: #616161; +.token.boolean, +.token.constant, +.token.doctype, +.token.number, +.token.regex, +.token.builtin, +.token.class, +.token.hexcode, +.token.class-name, +.token.attr-name { + color: hsla(var(--color-yellow), 1); } -.token.entity { - color: #ff6666; -} - -.token.function { - color: #c792ea; -} - -.token.hexcode { - color: #f2ff00; -} - -.token.id { - color: #c792ea; - font-weight: bold; +.token.atrule, +.token.attribute, +.token.attr-value .token.punctuation, +.token.attr-value, +.token.pseudo-class, +.token.pseudo-element, +.token.string { + color: hsla(var(--color-green), 1); } +.token.symbol, +.token.function, +.token.id, .token.important { - color: #c792ea; + color: hsla(var(--color-blue), 1); +} + +.token.important, +.token.id { font-weight: bold; } +.token.cdata, +.token.char, +.token.property { + color: #23b1af; +} + .token.inserted { - color: #80cbc4; + color: hsla(var(--color-green), 1); } .token.keyword { - color: #c792ea; -} - -.token.number { - color: #fd9170; + color: #ff657c; + font-style: italic; } .token.operator { - color: #89ddff; -} - -.token.prolog { - color: #616161; -} - -.token.property { - color: #80cbc4; -} - -.token.pseudo-class { - color: #a5e844; -} - -.token.pseudo-element { - color: #a5e844; + color: hsla(var(--color-gray-70), 1); } +.token.attr-value .token.attr-equals, .token.punctuation { - color: #89ddff; -} - -.token.regex { - color: #f2ff00; -} - -.token.selector { - color: #ff6666; -} - -.token.string { - color: #a5e844; -} - -.token.symbol { - color: #c792ea; -} - -.token.tag { - color: #ff6666; -} - -.token.unit { - color: #fd9170; -} - -.token.url { - color: #ff6666; -} - -.token.variable { - color: #ff6666; + color: hsla(var(--color-gray-80), 1); } diff --git a/examples/docs/public/default-og-image.png b/examples/docs/public/default-og-image.png new file mode 100644 index 000000000..97903207e Binary files /dev/null and b/examples/docs/public/default-og-image.png differ diff --git a/examples/docs/public/index.css b/examples/docs/public/index.css index 358f5fdf4..aadc5c2f5 100644 --- a/examples/docs/public/index.css +++ b/examples/docs/public/index.css @@ -3,14 +3,19 @@ margin: 0; } +/* Global focus outline reset */ +*:focus:not(:focus-visible) { + outline: none; +} + :root { --user-font-scale: 1rem - 16px; - --max-width: calc(100% - 2rem); + --max-width: calc(100% - 1rem); } @media (min-width: 50em) { :root { - --max-width: 48em; + --max-width: 46em; } } @@ -20,8 +25,9 @@ body { min-height: 100vh; font-family: var(--font-body); font-size: 1rem; - font-size: clamp(0.875rem, 0.4626rem + 1.0309vw + var(--user-font-scale), 1.125rem); - line-height: 1.625; + font-size: clamp(0.9rem, 0.75rem + 0.375vw + var(--user-font-scale), 1rem); + line-height: 1.5; + max-width: 100vw; } nav ul { @@ -29,18 +35,28 @@ nav ul { padding: 0; } -.content main > * + * { - margin-top: 1rem; +.content > section > * + * { + margin-top: 1.25rem; +} + +.content > section > :first-child { + margin-top: 0; } /* Typography */ -:is(h1, h2, h3, h4, h5, h6) { - margin-bottom: 1.38rem; - font-weight: 400; - line-height: 1.3; +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: 1rem; + font-weight: bold; + line-height: 1; } -:is(h1, h2) { +h1, +h2 { max-width: 40ch; } @@ -48,27 +64,41 @@ nav ul { margin-top: 3rem; } +:is(h4, h5, h6):not(:first-child) { + margin-top: 2rem; +} + h1 { - font-size: clamp(2.488rem, 1.924rem + 1.41vw, 3.052rem); + font-size: 3.25rem; + font-weight: 800; } h2 { - font-size: clamp(2.074rem, 1.707rem + 0.9175vw, 2.441rem); + font-size: 2.5rem; } h3 { - font-size: clamp(1.728rem, 1.503rem + 0.5625vw, 1.953rem); + font-size: 1.75rem; } h4 { - font-size: clamp(1.44rem, 1.317rem + 0.3075vw, 1.563rem); + font-size: 1.3rem; } h5 { - font-size: clamp(1.2rem, 1.15rem + 0.125vw, 1.25rem); + font-size: 1rem; } p { + line-height: 1.65em; +} + +.content ul { + line-height: 1.1em; +} + +p, +.content ul { color: var(--theme-text-light); } @@ -78,18 +108,52 @@ small, } a { - color: var(--theme-accent); + color: var(--theme-text-accent); font-weight: 400; text-underline-offset: 0.08em; - text-decoration: none; - display: inline-flex; 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-accent); + color: var(--theme-text-accent); background: transparent; text-underline-offset: var(--padding-block); } @@ -123,19 +187,21 @@ strong { } /* 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.33rem; - - font-family: var(--font-mono); - font-size: 0.85em; - color: inherit; + --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']) { @@ -146,37 +212,78 @@ pre > code:not([class*='language']) { color: inherit; } +pre > code { + font-size: 1em; +} + +table, pre { position: relative; - background-color: var(--theme-code-bg); - color: var(--theme-code-text); --padding-block: 1rem; --padding-inline: 2rem; padding: var(--padding-block) var(--padding-inline); padding-right: calc(var(--padding-inline) * 2); - margin-left: calc(50vw - var(--padding-inline)); - transform: translateX(-50vw); + margin-left: calc(var(--padding-inline) * -1); + margin-right: calc(var(--padding-inline) * -1); + font-family: var(--font-mono); - line-height: 1.414; - width: calc(100vw + 4px); - max-width: calc(100% + (var(--padding-inline) * 2)); + 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: 0.5em 1rem; - border-left: 3px solid rgba(0, 0, 0, 0.35); - background-color: rgba(0, 0, 0, 0.05); + 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 { @@ -197,92 +304,43 @@ button { align-items: center; gap: 0.25em; border-radius: 99em; - background-color: var(--theme-bg); -} -button:hover { -} - -#theme-toggle { - display: flex; - align-items: center; - gap: 0.25em; - padding: 0.33em 0.67em; - margin-left: -0.67em; - margin-right: -0.67em; - border-radius: 99em; + color: var(--theme-text); background-color: var(--theme-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; +h2.heading { + font-size: 1rem; + font-weight: 700; + padding: 0.1rem 1rem; + text-transform: uppercase; + margin-bottom: 0.5rem; } -#theme-toggle > label { - position: relative; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.5; - transition: transform 120ms ease-out, opacity 120ms ease-out; -} - -#theme-toggle > label:hover, -#theme-toggle > label:focus { - transform: scale(1.125); - opacity: 1; -} - -#theme-toggle .checked { - color: var(--theme-accent); - transform: scale(1.125); - opacity: 1; -} - -input[name='theme-toggle'] { - position: absolute; - opacity: 0; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: -1; -} - -nav h4 { - font-weight: 400; - font-size: 1.25rem; - margin: 0; - margin-bottom: 1em; -} - -.edit-on-github, .header-link { font-size: 1rem; - padding-left: 1rem; + padding: 0.1rem 0 0.1rem 1rem; border-left: 4px solid var(--theme-divider); } -.edit-on-github:hover, -.edit-on-github:focus, .header-link:hover, .header-link:focus { - color: var(--theme-text-light); - border-left-color: var(--theme-text-lighter); -} - -.header-link:focus-within { - color: var(--theme-text-light); - border-left-color: var(--theme-text-lighter); -} - -.header-link.active { border-left-color: var(--theme-accent); color: var(--theme-accent); } - -.header-link.depth-2 { - font-weight: 600; +.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 { @@ -292,88 +350,37 @@ nav h4 { padding-left: 3rem; } -.edit-on-github, .header-link a { font: inherit; color: inherit; text-decoration: none; } -.edit-on-github { - margin-top: 2rem; - text-decoration: none; -} -.edit-on-github > * { - 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; } -.nav-link { - font-size: 1rem; - margin-bottom: 0; - transform: translateX(0); - transition: 120ms transform ease-out; +.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; } -.nav-link:hover, -.nav-link:focus { - color: var(--theme-text-lighter); - transform: translateX(0.25em); +:target { + scroll-margin: calc(var(--theme-sidebar-offset, 5rem) + 2rem) 0 2rem; } - -.nav-link:focus-within { - color: var(--theme-text-lighter); - transform: translateX(0.25em); -} - -.nav-link a { - font: inherit; - color: inherit; - text-decoration: none; -} - -.nav-groups { - padding-bottom: 2rem; - max-height: calc(100% - 3rem); - overflow-y: auto; - overflow-x: hidden; -} - -.nav-groups > li + li { - margin-top: 2rem; -} - -/* Scrollbar */ - -/* Firefox */ -body { - scrollbar-width: thin; - scrollbar-color: var(--theme-text-lighter) var(--theme-divider); -} - -/* width */ -::-webkit-scrollbar { - width: 0.5rem; -} - -/* Track */ -::-webkit-scrollbar-track { - background: var(--theme-divider); - border-radius: 1rem; -} - -/* Handle */ -::-webkit-scrollbar-thumb { - background: var(--theme-text-lighter); - border-radius: 1rem; -} - -/* Handle on hover */ -::-webkit-scrollbar-thumb:hover { - background: var(--theme-text-light); -} - -/* Buttons */ -::-webkit-scrollbar-button { - display: none; -} -/* Scrollbar - End */ diff --git a/examples/docs/public/make-scrollable-code-focusable.js b/examples/docs/public/make-scrollable-code-focusable.js new file mode 100644 index 000000000..35f104923 --- /dev/null +++ b/examples/docs/public/make-scrollable-code-focusable.js @@ -0,0 +1,3 @@ +Array.from(document.getElementsByTagName('pre')).forEach((element) => { + element.setAttribute('tabindex', '0'); +}); diff --git a/examples/docs/public/theme.css b/examples/docs/public/theme.css index 7a4613188..587ddf29d 100644 --- a/examples/docs/public/theme.css +++ b/examples/docs/public/theme.css @@ -1,51 +1,81 @@ :root { --font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; --font-body: system-ui, var(--font-fallback); - --font-mono: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + --font-mono: 'IBM Plex Mono', Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', + 'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace; - --color-white: #fff; - --color-black: #000014; + /* + * Variables with --color-base prefix define + * the hue, and saturation values to be used for + * hsla colors. + * + * ex: + * + * --color-base-{color}: {hue}, {saturation}; + * + */ - --color-gray-50: #f9fafb; - --color-gray-100: #f3f4f6; - --color-gray-200: #e5e7eb; - --color-gray-300: #d1d5db; - --color-gray-400: #9ca3af; - --color-gray-500: #6b7280; - --color-gray-600: #4b5563; - --color-gray-700: #374151; - --color-gray-800: #1f2937; - --color-gray-900: #111827; + --color-base-white: 0, 0%; + --color-base-black: 240, 100%; + --color-base-gray: 215, 14%; + --color-base-blue: 212, 100%; + --color-base-blue-dark: 212, 72%; + --color-base-green: 158, 79%; + --color-base-orange: 22, 100%; + --color-base-purple: 269, 79%; + --color-base-red: 351, 100%; + --color-base-yellow: 41, 100%; - --color-blue: #3894ff; - --color-blue-rgb: 56, 148, 255; - --color-green: #17c083; - --color-green-rgb: 23, 192, 131; - --color-orange: #ff5d01; - --color-orange-rgb: 255, 93, 1; - --color-purple: #882de7; - --color-purple-rgb: 136, 45, 231; - --color-red: #ff1639; - --color-red-rgb: 255, 22, 57; - --color-yellow: #ffbe2d; - --color-yellow-rgb: 255, 190, 45; + /* + * Color palettes are made using --color-base + * variables, along with a lightness value to + * define different variants. + * + */ + + --color-gray-5: var(--color-base-gray), 5%; + --color-gray-10: var(--color-base-gray), 10%; + --color-gray-20: var(--color-base-gray), 20%; + --color-gray-30: var(--color-base-gray), 30%; + --color-gray-40: var(--color-base-gray), 40%; + --color-gray-50: var(--color-base-gray), 50%; + --color-gray-60: var(--color-base-gray), 60%; + --color-gray-70: var(--color-base-gray), 70%; + --color-gray-80: var(--color-base-gray), 80%; + --color-gray-90: var(--color-base-gray), 90%; + --color-gray-95: var(--color-base-gray), 95%; + + --color-blue: var(--color-base-blue), 61%; + --color-blue-dark: var(--color-base-blue-dark), 39%; + --color-green: var(--color-base-green), 42%; + --color-orange: var(--color-base-orange), 50%; + --color-purple: var(--color-base-purple), 54%; + --color-red: var(--color-base-red), 54%; + --color-yellow: var(--color-base-yellow), 59%; } :root { color-scheme: light; - --theme-accent: var(--color-blue); - --theme-accent-rgb: var(--color-blue-rgb); + --theme-accent: hsla(var(--color-orange), 1); + --theme-text-accent: hsla(var(--color-orange), 1); --theme-accent-opacity: 0.1; - --theme-divider: var(--color-gray-100); - --theme-text: var(--color-gray-800); - --theme-text-light: var(--color-gray-600); - --theme-text-lighter: var(--color-gray-400); - --theme-bg: var(--color-white); - --theme-bg-offset: var(--color-gray-100); - --theme-bg-accent: rgba(var(--theme-accent-rgb), var(--theme-accent-opacity)); - --theme-code-inline-bg: var(--color-gray-100); - --theme-code-text: var(--color-gray-100); - --theme-code-bg: var(--color-gray-700); + --theme-divider: hsla(var(--color-gray-95), 1); + --theme-text: hsla(var(--color-gray-10), 1); + --theme-text-light: hsla(var(--color-gray-40), 1); + /* @@@: not used anywhere */ + --theme-text-lighter: hsla(var(--color-gray-80), 1); + --theme-bg: hsla(var(--color-base-white), 100%, 1); + --theme-bg-hover: hsla(var(--color-gray-95), 1); + --theme-bg-offset: hsla(var(--color-gray-90), 1); + --theme-bg-accent: hsla(var(--color-orange), var(--theme-accent-opacity)); + --theme-code-inline-bg: hsla(var(--color-gray-95), 1); + --theme-code-inline-text: var(--theme-text); + --theme-code-bg: hsla(217, 19%, 27%, 1); + --theme-code-text: hsla(var(--color-gray-95), 1); + --theme-navbar-bg: hsla(var(--color-base-white), 100%, 1); + --theme-navbar-height: 6rem; + --theme-selection-color: hsla(var(--color-orange), 1); + --theme-selection-bg: hsla(var(--color-orange), var(--theme-accent-opacity)); } body { @@ -55,19 +85,39 @@ body { :root.theme-dark { color-scheme: dark; - --theme-accent-opacity: 0.3; - --theme-divider: var(--color-gray-900); - --theme-text: var(--color-gray-200); - --theme-text-light: var(--color-gray-400); - --theme-text-lighter: var(--color-gray-600); - --theme-bg: var(--color-black); - --theme-bg-offset: var(--color-gray-900); - --theme-code-inline-bg: var(--color-gray-800); - --theme-code-text: var(--color-gray-200); - --theme-code-bg: var(--color-gray-900); + --theme-accent-opacity: 0.4; + --theme-accent: hsla(var(--color-orange), 1); + --theme-text-accent: hsla(var(--color-orange), 1); + --theme-divider: hsla(var(--color-gray-10), 1); + --theme-text: hsla(var(--color-gray-90), 1); + --theme-text-light: hsla(var(--color-gray-80), 1); + + /* @@@: not used anywhere */ + --theme-text-lighter: hsla(var(--color-gray-40), 1); + --theme-bg: hsla(215, 28%, 17%, 1); + --theme-bg-hover: hsla(var(--color-gray-40), 1); + --theme-bg-offset: hsla(var(--color-gray-5), 1); + --theme-code-inline-bg: hsla(var(--color-gray-10), 1); + --theme-code-inline-text: hsla(var(--color-base-white), 100%, 1); + --theme-code-bg: hsla(var(--color-gray-5), 1); + --theme-code-text: hsla(var(--color-base-white), 100%, 1); + --theme-navbar-bg: hsla(215, 28%, 17%, 1); + --theme-selection-color: hsla(var(--color-base-white), 100%, 1); + --theme-selection-bg: hsla(var(--color-purple), var(--theme-accent-opacity)); + + /* DocSearch [Algolia] */ + --docsearch-modal-background: var(--theme-bg); + --docsearch-searchbox-focus-background: var(--theme-divider); + --docsearch-footer-background: var(--theme-divider); + --docsearch-text-color: var(--theme-text); + --docsearch-hit-background: var(--theme-divider); + --docsearch-hit-shadow: none; + --docsearch-hit-color: var(--theme-text); + --docsearch-footer-shadow: inset 0 2px 10px #000; + --docsearch-modal-shadow: inset 0 0 8px #000; } ::selection { - color: var(--theme-accent); - background-color: rgba(var(--theme-accent-rgb), var(--theme-accent-opacity)); + color: var(--theme-selection-color); + background-color: var(--theme-selection-bg); } diff --git a/examples/docs/public/theme.js b/examples/docs/public/theme.js deleted file mode 100644 index d75d0bf99..000000000 --- a/examples/docs/public/theme.js +++ /dev/null @@ -1,8 +0,0 @@ -(() => { - const root = document.documentElement; - if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { - root.classList.add('theme-dark'); - } else { - root.classList.remove('theme-dark'); - } -})(); 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 @@ - - - - - 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(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 ( - - ); -}; - -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 ( - - - - - - - Edit on GitHub - - ); -}; - -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 + +--- + +
+ + {additionalContributors > 0 && {`and ${additionalContributors} additional contributor${additionalContributors > 1 ? 's' : ''}.`}} + {unique.length === 0 && Contributors} +
+ + diff --git a/examples/docs/src/components/ArticleFooter.astro b/examples/docs/src/components/Footer/Footer.astro similarity index 76% rename from examples/docs/src/components/ArticleFooter.astro rename to 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; --- + Logo + + + \ 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); +--- + +
+ + +
\ 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 ( +
+ + +
+ ); +}; + +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 ( + <> + + {isOpen && + createPortal( + { + 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 ( + + ); +}; + +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 @@ + + 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; +}, []); + +--- + + + + + + 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; ---- - - - - 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'; +--- + +
+
+

{title}

+ + +
+ +
\ 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; +--- + +

More

+ +
+ +
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; +--- + + \ 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(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 ( + <> +

On this page

+
    + + {headers + .filter(({ depth }) => depth > 1 && depth < 4) + .map((header) => ( + + ))} +
+ + ); +}; + +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 similarity index 53% rename from examples/docs/src/components/ThemeToggle.tsx rename to 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,17 +1,11 @@ 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 = [ - - - , { - 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 ( -
+
{themes.map((t, i) => { const icon = icons[i]; const checked = t === theme; return ( -