@rxova/astro-ui 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +76 -2
- package/package.json +64 -6
- package/src/components/BackLink.astro +24 -0
- package/src/components/Header.astro +148 -0
- package/src/components/PageHeader.astro +63 -0
- package/src/components/ProjectSwitcher.astro +95 -0
- package/src/components/ShowMore.astro +82 -0
- package/src/components/SiteFooter.astro +149 -0
- package/src/components/SiteShell.astro +155 -0
- package/src/components/ThemeScript.astro +31 -0
- package/src/components/ThemeToggle.astro +163 -0
- package/src/components/VisuallyHidden.astro +23 -0
- package/src/env.d.ts +15 -0
- package/src/lib/entries.ts +72 -0
- package/src/scripts/show-more.ts +46 -0
- package/src/starlight/Footer.astro +16 -0
- package/src/starlight/SiteTitle.astro +34 -0
- package/src/starlight/SocialIcons.astro +18 -0
- package/src/starlight/ThemeSelect.astro +42 -0
- package/src/starlight/index.ts +103 -0
- package/src/styles/chrome.css +8 -0
- package/src/styles/document.css +72 -0
- package/src/styles/footer.css +124 -0
- package/src/styles/starlight.css +162 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonatan Kruszewski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,2 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<h1 align="center">@rxova/astro-ui</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
Astro components, the Starlight preset and the shared chrome for every
|
|
5
|
+
<a href="https://rxova.org">rxova.org</a> surface, built on
|
|
6
|
+
<a href="../brand"><code>@rxova/brand</code></a>.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
### In a Starlight docs site
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// astro.config.mjs
|
|
17
|
+
import { defineConfig } from 'astro/config'
|
|
18
|
+
import starlight from '@astrojs/starlight'
|
|
19
|
+
import { sharedStarlightConfig } from '@rxova/astro-ui/starlight'
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
site: process.env.DOCS_URL ?? 'https://rxova.org',
|
|
23
|
+
base: process.env.DOCS_BASE_URL ?? '/',
|
|
24
|
+
integrations: [
|
|
25
|
+
starlight(
|
|
26
|
+
sharedStarlightConfig({
|
|
27
|
+
project: 'use-everywhere',
|
|
28
|
+
sidebar: [{ label: 'Learn', items: [{ autogenerate: { directory: 'learn' } }] }],
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
31
|
+
],
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That gets you the tokens, the typefaces, the rxova mark linking back to the
|
|
36
|
+
umbrella site, the cross-project switcher, the shared footer and Pagefind search.
|
|
37
|
+
|
|
38
|
+
### In a plain Astro site
|
|
39
|
+
|
|
40
|
+
```astro
|
|
41
|
+
---
|
|
42
|
+
import '@rxova/brand/fonts.css'
|
|
43
|
+
import '@rxova/astro-ui/styles/document.css'
|
|
44
|
+
import SiteFooter from '@rxova/astro-ui/components/SiteFooter.astro'
|
|
45
|
+
---
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`document.css` is `chrome.css` plus a reset and base element styles. Load
|
|
49
|
+
`chrome.css` alone where something else owns the document: its reset is
|
|
50
|
+
unlayered and would flatten a Starlight page.
|
|
51
|
+
|
|
52
|
+
## What's in it
|
|
53
|
+
|
|
54
|
+
| Export | What it is |
|
|
55
|
+
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| `@rxova/astro-ui/starlight` | `sharedStarlightConfig()`, the preset every docs site spreads |
|
|
57
|
+
| `@rxova/astro-ui/starlight/*.astro` | The Starlight overrides: `SiteTitle`, `SocialIcons`, `Footer`, `ThemeSelect` |
|
|
58
|
+
| `@rxova/astro-ui/components/*.astro` | Chrome (`SiteShell`, `Header`, `SiteFooter`, …) and primitives (`PageHeader`, `BackLink`, `ShowMore`, `VisuallyHidden`) |
|
|
59
|
+
| `@rxova/astro-ui/scripts/show-more` | `enhanceShowMore()`: batches a `[data-reveal-list]` behind a `ShowMore` |
|
|
60
|
+
| `@rxova/astro-ui/lib/entries` | Ordering, bylines, dates and excerpts shared by /blog and /updates |
|
|
61
|
+
| `@rxova/astro-ui/styles/document.css` | For sites that own their document: `chrome.css`, a reset, base element styling |
|
|
62
|
+
| `@rxova/astro-ui/styles/chrome.css` | What the header and footer need, with nothing document-level |
|
|
63
|
+
| `@rxova/astro-ui/styles/starlight.css` | Maps `--rx-*` onto Starlight's `--sl-*`, plus the footer |
|
|
64
|
+
|
|
65
|
+
There is no barrel: each component has its own path, so a page only loads the
|
|
66
|
+
CSS of the components it imports.
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
Run everything from the repo root. `apps/preview` renders the Starlight preset
|
|
71
|
+
and the plain-Astro chrome; `pnpm run verify` runs `astro check`, the tests and
|
|
72
|
+
the export checks for this package.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT © Jonatan Kruszewski
|
package/package.json
CHANGED
|
@@ -1,12 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rxova/astro-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Astro components, Starlight preset and shared chrome for rxova.org",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"rxova",
|
|
8
|
+
"astro",
|
|
9
|
+
"starlight",
|
|
10
|
+
"components"
|
|
11
|
+
],
|
|
5
12
|
"license": "MIT",
|
|
6
|
-
"author": "Jonatan Kruszewski",
|
|
13
|
+
"author": "Jonatan Kruszewski <jonakrusze@gmail.com>",
|
|
14
|
+
"homepage": "https://rxova.org",
|
|
7
15
|
"repository": {
|
|
8
16
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/rxova/
|
|
17
|
+
"url": "git+https://github.com/rxova/rxova-website.git",
|
|
18
|
+
"directory": "packages/astro-ui"
|
|
10
19
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
20
|
+
"bugs": "https://github.com/rxova/rxova-website/issues",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=24.0.0"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"*.css"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"exports": {
|
|
31
|
+
"./components/*": "./src/components/*",
|
|
32
|
+
"./lib/entries": "./src/lib/entries.ts",
|
|
33
|
+
"./scripts/show-more": "./src/scripts/show-more.ts",
|
|
34
|
+
"./starlight": "./src/starlight/index.ts",
|
|
35
|
+
"./starlight/*": "./src/starlight/*",
|
|
36
|
+
"./styles/*": "./src/styles/*",
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@astrojs/starlight": ">=0.36.0",
|
|
41
|
+
"astro": ">=5.6.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"@astrojs/starlight": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@rxova/brand": "^1.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@astrojs/check": "^0.9.10",
|
|
53
|
+
"@astrojs/starlight": "^0.42.1",
|
|
54
|
+
"@rxova/repo-tooling": "0.0.0",
|
|
55
|
+
"@types/node": "^26.6.1",
|
|
56
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
57
|
+
"astro": "^7.3.2",
|
|
58
|
+
"happy-dom": "^20.14.5",
|
|
59
|
+
"publint": "^0.3.24",
|
|
60
|
+
"tsx": "^4.23.13",
|
|
61
|
+
"typescript": "6.0.3",
|
|
62
|
+
"vitest": "^5.0.1"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"check:astro": "astro check",
|
|
66
|
+
"check:exports": "publint --strict",
|
|
67
|
+
"pack:smoke": "node --import tsx ../brand/scripts/pack-smoke.ts",
|
|
68
|
+
"test": "vitest run --coverage"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
/** An "← up one level" link; the caller's class sets its spacing. */
|
|
5
|
+
type Props = HTMLAttributes<'a'> & { href: string }
|
|
6
|
+
|
|
7
|
+
const { href, class: className, ...rest } = Astro.props
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<a class:list={['back-link', className]} href={href} {...rest}>
|
|
11
|
+
← <slot />
|
|
12
|
+
</a>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
.back-link {
|
|
16
|
+
display: inline-block;
|
|
17
|
+
color: var(--rx-muted);
|
|
18
|
+
text-decoration: none;
|
|
19
|
+
font-size: 0.95rem;
|
|
20
|
+
}
|
|
21
|
+
.back-link:hover {
|
|
22
|
+
color: var(--rx-fg);
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The single rxova.org header, rendered by `SiteShell` and the website landing; callers pass
|
|
4
|
+
* the menu fully resolved. Not for the Starlight docs sites, which use `sharedStarlightConfig`.
|
|
5
|
+
*/
|
|
6
|
+
import ThemeToggle from './ThemeToggle.astro'
|
|
7
|
+
|
|
8
|
+
export interface HeaderItem {
|
|
9
|
+
label: string
|
|
10
|
+
/** Fully resolved by the caller — absolute or root-relative as its base needs. */
|
|
11
|
+
href: string
|
|
12
|
+
/** Marks the item for the surface the reader is on. */
|
|
13
|
+
current?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Where the brand mark links — the site root, resolved by the caller. */
|
|
18
|
+
homeHref: string
|
|
19
|
+
/** Logo `src`, resolved by the caller for its base. */
|
|
20
|
+
logoSrc: string
|
|
21
|
+
/** The menu, in the one fixed order every surface shows it. */
|
|
22
|
+
items: readonly HeaderItem[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { homeHref, logoSrc, items } = Astro.props
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
{/* Outside <main> so it spans the viewport; sticky, not fixed, so nothing needs a top margin. */}
|
|
29
|
+
<header class="site">
|
|
30
|
+
<div class="site__inner">
|
|
31
|
+
<a class="brand" href={homeHref}>
|
|
32
|
+
<img src={logoSrc} alt="" width="28" height="28" />
|
|
33
|
+
<span>Rxova</span>
|
|
34
|
+
</a>
|
|
35
|
+
<nav aria-label="Sections">
|
|
36
|
+
{items.map((item) => (
|
|
37
|
+
<a href={item.href} aria-current={item.current ? 'page' : undefined}>
|
|
38
|
+
{item.label}
|
|
39
|
+
</a>
|
|
40
|
+
))}
|
|
41
|
+
</nav>
|
|
42
|
+
{/* In the header, not floating over it — see ThemeToggle's `floating`. */}
|
|
43
|
+
<ThemeToggle floating={false} />
|
|
44
|
+
</div>
|
|
45
|
+
</header>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
/* Sticky, translucent, full-bleed. */
|
|
49
|
+
.site {
|
|
50
|
+
position: sticky;
|
|
51
|
+
top: 0;
|
|
52
|
+
z-index: 10;
|
|
53
|
+
border-bottom: 1px solid var(--rx-rule);
|
|
54
|
+
background: color-mix(in srgb, var(--rx-bg) 88%, transparent);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* The blur keeps text legible under the bar; where unsupported, the 88% mix above stays
|
|
58
|
+
nearly opaque. */
|
|
59
|
+
@supports (backdrop-filter: blur(8px)) or (-webkit-backdrop-filter: blur(8px)) {
|
|
60
|
+
.site {
|
|
61
|
+
background: color-mix(in srgb, var(--rx-bg) 72%, transparent);
|
|
62
|
+
-webkit-backdrop-filter: blur(10px);
|
|
63
|
+
backdrop-filter: blur(10px);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.site__inner {
|
|
68
|
+
max-width: var(--rx-max-wide);
|
|
69
|
+
margin: 0 auto;
|
|
70
|
+
padding: 0.75rem 1.5rem;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 1rem;
|
|
74
|
+
/* No wrapping: a two-line header would eat a phone's viewport, and the row
|
|
75
|
+
is sized to fit at the narrowest widths we support. */
|
|
76
|
+
min-height: 3.5rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.brand {
|
|
80
|
+
display: inline-flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 0.55rem;
|
|
83
|
+
color: var(--rx-fg);
|
|
84
|
+
text-decoration: none;
|
|
85
|
+
font-weight: 640;
|
|
86
|
+
letter-spacing: -0.01em;
|
|
87
|
+
}
|
|
88
|
+
.brand img {
|
|
89
|
+
display: block;
|
|
90
|
+
border-radius: 6px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* The nav takes the space between the brand and the toggle, and pushes itself
|
|
94
|
+
to the right of it. */
|
|
95
|
+
nav {
|
|
96
|
+
margin-left: auto;
|
|
97
|
+
display: flex;
|
|
98
|
+
gap: 1.4rem;
|
|
99
|
+
}
|
|
100
|
+
nav a {
|
|
101
|
+
color: var(--rx-muted);
|
|
102
|
+
text-decoration: none;
|
|
103
|
+
font-size: 0.95rem;
|
|
104
|
+
white-space: nowrap;
|
|
105
|
+
}
|
|
106
|
+
nav a:hover {
|
|
107
|
+
color: var(--rx-fg);
|
|
108
|
+
}
|
|
109
|
+
/* The current section is marked for everyone, not only for assistive tech —
|
|
110
|
+
aria-current on its own is invisible. */
|
|
111
|
+
nav a[aria-current='page'] {
|
|
112
|
+
color: var(--rx-fg);
|
|
113
|
+
text-decoration: underline;
|
|
114
|
+
text-underline-offset: 5px;
|
|
115
|
+
}
|
|
116
|
+
nav a:focus-visible {
|
|
117
|
+
outline: 2px solid var(--rx-fg);
|
|
118
|
+
outline-offset: 3px;
|
|
119
|
+
border-radius: 2px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Brand mark, the links and the toggle stay on one row at the narrowest phones
|
|
123
|
+
by dropping the wordmark — the logo carries the identity in the space left. */
|
|
124
|
+
@media (max-width: 34rem) {
|
|
125
|
+
.site__inner {
|
|
126
|
+
gap: 0.75rem;
|
|
127
|
+
padding-inline: 1rem;
|
|
128
|
+
min-height: 3.25rem;
|
|
129
|
+
}
|
|
130
|
+
nav {
|
|
131
|
+
gap: 0.9rem;
|
|
132
|
+
}
|
|
133
|
+
nav a {
|
|
134
|
+
font-size: 0.875rem;
|
|
135
|
+
}
|
|
136
|
+
.brand span {
|
|
137
|
+
display: none;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
141
|
+
|
|
142
|
+
<!-- Global because `html` sits outside Astro's scoping. Without it a jump to an
|
|
143
|
+
in-page anchor would land under the sticky header. -->
|
|
144
|
+
<style is:global>
|
|
145
|
+
html {
|
|
146
|
+
scroll-padding-top: 4.5rem;
|
|
147
|
+
}
|
|
148
|
+
</style>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* A page's title and lede, with an optional back link. The default slot is the lede.
|
|
4
|
+
* `detail` keeps the repo pages' heavier title, which still inherits the body's line height.
|
|
5
|
+
*/
|
|
6
|
+
import BackLink from './BackLink.astro'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
title: string
|
|
10
|
+
back?: { href: string; label: string }
|
|
11
|
+
variant?: 'index' | 'detail'
|
|
12
|
+
/** The lede's measure: `narrow` is 46ch, `wide` 56ch. */
|
|
13
|
+
measure?: 'narrow' | 'wide'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { title, back, variant = 'index', measure = 'wide' } = Astro.props
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<header class:list={['head', variant]}>
|
|
20
|
+
{back && (
|
|
21
|
+
<BackLink class="back" href={back.href}>
|
|
22
|
+
{back.label}
|
|
23
|
+
</BackLink>
|
|
24
|
+
)}
|
|
25
|
+
<h1>{title}</h1>
|
|
26
|
+
<p class:list={['lede', measure]}>
|
|
27
|
+
<slot />
|
|
28
|
+
</p>
|
|
29
|
+
</header>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.head {
|
|
33
|
+
margin-bottom: clamp(2rem, 6vh, 3rem);
|
|
34
|
+
}
|
|
35
|
+
.back {
|
|
36
|
+
margin-bottom: 1.25rem;
|
|
37
|
+
}
|
|
38
|
+
h1 {
|
|
39
|
+
font-size: clamp(1.8rem, 5vw, 2.4rem);
|
|
40
|
+
letter-spacing: -0.02em;
|
|
41
|
+
margin-bottom: 0.6rem;
|
|
42
|
+
}
|
|
43
|
+
/* One title across /about, /blog and /updates, so switching pages does not move it. */
|
|
44
|
+
.index h1 {
|
|
45
|
+
line-height: 1.15;
|
|
46
|
+
font-weight: 650;
|
|
47
|
+
}
|
|
48
|
+
.detail h1 {
|
|
49
|
+
font-weight: 660;
|
|
50
|
+
}
|
|
51
|
+
.lede {
|
|
52
|
+
color: var(--rx-muted);
|
|
53
|
+
}
|
|
54
|
+
.narrow {
|
|
55
|
+
max-width: 46ch;
|
|
56
|
+
}
|
|
57
|
+
.wide {
|
|
58
|
+
max-width: 56ch;
|
|
59
|
+
}
|
|
60
|
+
.lede :global(a) {
|
|
61
|
+
color: var(--rx-fg);
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Cross-project navigation as a zero-JS `<details>`. Every href is absolute: a relative one would
|
|
4
|
+
* resolve under the current site's base path.
|
|
5
|
+
*/
|
|
6
|
+
import { PROJECTS, RXOVA_ORIGIN, type ProjectId } from '@rxova/brand'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** Which project this site is, so it can be marked as current. */
|
|
10
|
+
current?: ProjectId
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { current } = Astro.props
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<details class="rx-switcher">
|
|
17
|
+
<summary class="rx-switcher__trigger" aria-label="Switch project">
|
|
18
|
+
Projects
|
|
19
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" width="12" height="12">
|
|
20
|
+
<path d="M4 6l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.75"></path>
|
|
21
|
+
</svg>
|
|
22
|
+
</summary>
|
|
23
|
+
<ul class="rx-switcher__menu">
|
|
24
|
+
<li>
|
|
25
|
+
<a href={RXOVA_ORIGIN}>rxova.org</a>
|
|
26
|
+
</li>
|
|
27
|
+
{PROJECTS.map((project) => (
|
|
28
|
+
<li>
|
|
29
|
+
<a
|
|
30
|
+
href={`${RXOVA_ORIGIN}${project.mount}`}
|
|
31
|
+
class={project.id === current ? 'rx-switcher__current' : undefined}
|
|
32
|
+
aria-current={project.id === current ? 'page' : undefined}
|
|
33
|
+
>
|
|
34
|
+
{project.label}
|
|
35
|
+
</a>
|
|
36
|
+
</li>
|
|
37
|
+
))}
|
|
38
|
+
</ul>
|
|
39
|
+
</details>
|
|
40
|
+
|
|
41
|
+
<style>
|
|
42
|
+
.rx-switcher__trigger {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: 0.3rem;
|
|
46
|
+
padding: 0.25rem 0.5rem;
|
|
47
|
+
border-radius: var(--rx-radius-sm);
|
|
48
|
+
color: var(--sl-color-gray-2);
|
|
49
|
+
font-size: var(--sl-text-sm);
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
list-style: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.rx-switcher__trigger::-webkit-details-marker {
|
|
55
|
+
display: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.rx-switcher__trigger:hover {
|
|
59
|
+
color: var(--sl-color-white);
|
|
60
|
+
background: var(--sl-color-gray-6);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.rx-switcher[open] > .rx-switcher__trigger svg {
|
|
64
|
+
transform: rotate(180deg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.rx-switcher__menu {
|
|
68
|
+
position: absolute;
|
|
69
|
+
z-index: var(--sl-z-index-navbar, 20);
|
|
70
|
+
inset-inline-end: 0;
|
|
71
|
+
min-width: 12rem;
|
|
72
|
+
margin: 0.4rem 0 0;
|
|
73
|
+
padding: 0.35rem;
|
|
74
|
+
border: 1px solid var(--sl-color-hairline);
|
|
75
|
+
border-radius: var(--rx-radius);
|
|
76
|
+
background: var(--sl-color-bg-nav);
|
|
77
|
+
box-shadow: var(--sl-shadow-md);
|
|
78
|
+
list-style: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.rx-switcher__menu a {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
padding: 0.4rem 0.6rem;
|
|
85
|
+
border-radius: var(--rx-radius-sm);
|
|
86
|
+
color: var(--sl-color-gray-2);
|
|
87
|
+
font-size: var(--sl-text-sm);
|
|
88
|
+
text-decoration: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.rx-switcher__menu a:hover {
|
|
92
|
+
color: var(--sl-color-white);
|
|
93
|
+
background: var(--sl-color-gray-6);
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* "Show more" controls for a batched list; they ship `hidden` until a script drives them.
|
|
4
|
+
* `enhanceShowMore` from `@rxova/astro-ui/scripts/show-more` batches a plain list.
|
|
5
|
+
*/
|
|
6
|
+
import VisuallyHidden from './VisuallyHidden.astro'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** What the list holds, for the button's accessible name: "Show more posts". */
|
|
10
|
+
noun: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { noun } = Astro.props
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<div class="more" data-reveal-controls hidden>
|
|
17
|
+
<button class="more-btn" type="button" data-reveal-more>
|
|
18
|
+
Show more<VisuallyHidden>{` ${noun}`}</VisuallyHidden>
|
|
19
|
+
</button>
|
|
20
|
+
<button class="show-all" type="button" data-reveal-all>
|
|
21
|
+
Show all
|
|
22
|
+
</button>
|
|
23
|
+
<p class="progress" data-reveal-progress></p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<style>
|
|
27
|
+
.more {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: baseline;
|
|
30
|
+
gap: 0.75rem;
|
|
31
|
+
flex-wrap: wrap;
|
|
32
|
+
margin-top: 2.5rem;
|
|
33
|
+
padding-top: 1.5rem;
|
|
34
|
+
border-top: 1px solid var(--rx-rule);
|
|
35
|
+
}
|
|
36
|
+
/* `display: flex` outranks the UA's `[hidden]` rule. */
|
|
37
|
+
.more[hidden] {
|
|
38
|
+
display: none;
|
|
39
|
+
}
|
|
40
|
+
.more-btn {
|
|
41
|
+
font: inherit;
|
|
42
|
+
font-size: 0.9rem;
|
|
43
|
+
color: var(--rx-fg);
|
|
44
|
+
background: var(--rx-tag-bg);
|
|
45
|
+
border: 1px solid var(--rx-rule);
|
|
46
|
+
border-radius: 999px;
|
|
47
|
+
padding: 0.35rem 1rem;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
.more-btn:hover {
|
|
51
|
+
border-color: var(--rx-fg);
|
|
52
|
+
}
|
|
53
|
+
.show-all {
|
|
54
|
+
font: inherit;
|
|
55
|
+
font-size: 0.85rem;
|
|
56
|
+
color: var(--rx-muted);
|
|
57
|
+
background: none;
|
|
58
|
+
border: 0;
|
|
59
|
+
padding: 0;
|
|
60
|
+
text-decoration: underline;
|
|
61
|
+
text-underline-offset: 3px;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
}
|
|
64
|
+
.show-all:hover {
|
|
65
|
+
color: var(--rx-fg);
|
|
66
|
+
}
|
|
67
|
+
.more-btn:focus-visible,
|
|
68
|
+
.show-all:focus-visible {
|
|
69
|
+
outline: 2px solid var(--rx-fg);
|
|
70
|
+
outline-offset: 2px;
|
|
71
|
+
}
|
|
72
|
+
.progress {
|
|
73
|
+
margin-left: auto;
|
|
74
|
+
color: var(--rx-faint);
|
|
75
|
+
font-size: 0.85rem;
|
|
76
|
+
}
|
|
77
|
+
@media print {
|
|
78
|
+
.more {
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
</style>
|