@getvitops/astro 2.1.0 → 3.0.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/CHANGELOG.md +178 -0
- package/README.md +1 -1
- package/dist/index.d.mts +8 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +23 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -5
- package/src/analytics.ts +1 -1
- package/src/components/Analytics.astro +2 -2
- package/src/components/CookieConsent.astro +2 -2
- package/src/components/Details.astro +1 -1
- package/src/components/Drawer.astro +1 -1
- package/src/components/Head.astro +6 -6
- package/src/components/Icon.astro +3 -3
- package/src/components/NavShell.astro +123 -0
- package/src/components/NavShellToggle.astro +45 -0
- package/src/components/Popover.astro +1 -1
- package/src/components/Seo.astro +2 -2
- package/src/seo.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/astro",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Astro integration for the Vitops design system — favicons/PWA, web-component scripts, and CSS, via a getvitops() integration + <Head /> component.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"src/components/Popover.astro",
|
|
20
20
|
"src/components/Details.astro",
|
|
21
21
|
"src/components/Drawer.astro",
|
|
22
|
+
"src/components/NavShell.astro",
|
|
23
|
+
"src/components/NavShellToggle.astro",
|
|
22
24
|
"src/schemas",
|
|
23
25
|
"virtual.d.ts",
|
|
24
26
|
"CHANGELOG.md"
|
|
@@ -42,6 +44,8 @@
|
|
|
42
44
|
"./components/Popover.astro": "./src/components/Popover.astro",
|
|
43
45
|
"./components/Details.astro": "./src/components/Details.astro",
|
|
44
46
|
"./components/Drawer.astro": "./src/components/Drawer.astro",
|
|
47
|
+
"./components/NavShell.astro": "./src/components/NavShell.astro",
|
|
48
|
+
"./components/NavShellToggle.astro": "./src/components/NavShellToggle.astro",
|
|
45
49
|
"./schemas/*": "./src/schemas/*",
|
|
46
50
|
"./package.json": "./package.json"
|
|
47
51
|
},
|
|
@@ -49,10 +53,10 @@
|
|
|
49
53
|
"access": "public"
|
|
50
54
|
},
|
|
51
55
|
"dependencies": {
|
|
52
|
-
"@getvitops/core": "
|
|
53
|
-
"@getvitops/
|
|
54
|
-
"@getvitops/
|
|
55
|
-
"@getvitops/vite": "
|
|
56
|
+
"@getvitops/core": "3.0.0",
|
|
57
|
+
"@getvitops/utils": "3.0.0",
|
|
58
|
+
"@getvitops/generator": "3.0.0",
|
|
59
|
+
"@getvitops/vite": "3.0.0"
|
|
56
60
|
},
|
|
57
61
|
"devDependencies": {
|
|
58
62
|
"@astrojs/sitemap": "^3.7.3",
|
package/src/analytics.ts
CHANGED
|
@@ -136,7 +136,7 @@ export interface ResolvedAnalytics {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
export interface ResolveAnalyticsContext {
|
|
139
|
-
/** Is the consent gate enabled on this site (`
|
|
139
|
+
/** Is the consent gate enabled on this site (`vitops({ consent })`)? */
|
|
140
140
|
consent?: boolean;
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* <Analytics /> — the site's analytics tags, off the critical path and behind the
|
|
4
|
-
* consent gate. Place in your layout's <head>; requires the
|
|
4
|
+
* consent gate. Place in your layout's <head>; requires the vitops()
|
|
5
5
|
* integration, which supplies `virtual:getvitops/head`.
|
|
6
6
|
*
|
|
7
7
|
* A dumb renderer over `resolveAnalytics()` (src/analytics.ts) — every decision
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* the critical-path cost the `idle` strategy exists to avoid.
|
|
20
20
|
*/
|
|
21
21
|
import { type AnalyticsStrategy, resolveAnalytics } from '../analytics.ts';
|
|
22
|
-
// @ts-ignore — virtual module provided by the
|
|
22
|
+
// @ts-ignore — virtual module provided by the vitops() integration at build time
|
|
23
23
|
import head from 'virtual:getvitops/head';
|
|
24
24
|
|
|
25
25
|
interface Props {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* <CookieConsent /> — the consent banner. Place near the end of <body>; requires
|
|
4
|
-
* the
|
|
4
|
+
* the vitops() integration with `consent` enabled, which is what loads
|
|
5
5
|
* `@getvitops/core/consent` (the gate + `<wc-consent>`).
|
|
6
6
|
*
|
|
7
7
|
* This renders the *fallback*: a real, semantic form with real buttons. The
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* this banner offers therefore comes from the integration, not from
|
|
21
21
|
* `<Analytics />`.
|
|
22
22
|
*/
|
|
23
|
-
// @ts-ignore — virtual module provided by the
|
|
23
|
+
// @ts-ignore — virtual module provided by the vitops() integration at build time
|
|
24
24
|
import head from 'virtual:getvitops/head';
|
|
25
25
|
import type { OptionalConsentCategory } from '../analytics.ts';
|
|
26
26
|
|
|
@@ -15,7 +15,7 @@ interface Props {
|
|
|
15
15
|
/**
|
|
16
16
|
* Map a semantic name onto a fully-qualified one before rendering.
|
|
17
17
|
*
|
|
18
|
-
* @deprecated The `icons` option on `
|
|
18
|
+
* @deprecated The `icons` option on `vitops()` now does this centrally, so
|
|
19
19
|
* the site's set is configured once instead of threaded through every call
|
|
20
20
|
* site. Left in place because removing it would break consumers passing one;
|
|
21
21
|
* when omitted, `<Icon />` resolves through the site config.
|
|
@@ -19,7 +19,7 @@ interface Props {
|
|
|
19
19
|
/**
|
|
20
20
|
* Map a semantic name onto a fully-qualified one before rendering.
|
|
21
21
|
*
|
|
22
|
-
* @deprecated The `icons` option on `
|
|
22
|
+
* @deprecated The `icons` option on `vitops()` now does this centrally, so
|
|
23
23
|
* the site's set is configured once instead of threaded through every call
|
|
24
24
|
* site. Left in place because removing it would break consumers passing one;
|
|
25
25
|
* when omitted, `<Icon />` resolves through the site config.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* <Head /> — the Vitops design system's <head> contributions. Place inside your
|
|
4
|
-
* layout's <head>. Requires the
|
|
4
|
+
* layout's <head>. Requires the vitops() integration (it provides the
|
|
5
5
|
* `virtual:getvitops/head` config + copies the assets into public/).
|
|
6
6
|
*
|
|
7
7
|
* Renders: favicon + PWA manifest tags, the webfont <Font /> tags for families
|
|
8
|
-
* declared via
|
|
8
|
+
* declared via vitops({ fonts }), and the web-component runtime scripts in
|
|
9
9
|
* canonical order/priority. It does NOT emit a stylesheet <link> — the CSS is
|
|
10
10
|
* imported into Astro's graph by the integration and Astro emits that link.
|
|
11
11
|
*/
|
|
12
|
-
// @ts-ignore — virtual module provided by the
|
|
12
|
+
// @ts-ignore — virtual module provided by the vitops() integration at build time
|
|
13
13
|
import head from 'virtual:getvitops/head';
|
|
14
14
|
// @ts-ignore — `Font` is provided by Astro's Fonts API (astro >= 7)
|
|
15
15
|
import { Font } from 'astro:assets';
|
|
@@ -61,7 +61,7 @@ const THEME_SCRIPT =
|
|
|
61
61
|
{favicons && head.themeColor && <meta name="theme-color" content={head.themeColor} />}
|
|
62
62
|
|
|
63
63
|
{/*
|
|
64
|
-
Webfonts declared via
|
|
64
|
+
Webfonts declared via vitops({ fonts }). Astro's `fonts:` config resolves and
|
|
65
65
|
optimises the files; this component is what actually puts the @font-face on the page,
|
|
66
66
|
so without it the config is inert and every --font-* token falls through to its
|
|
67
67
|
fallback stack.
|
|
@@ -77,7 +77,7 @@ const THEME_SCRIPT =
|
|
|
77
77
|
<Font cssVariable={f.cssVariable} preload={f.preload} />
|
|
78
78
|
))}
|
|
79
79
|
|
|
80
|
-
{/* Set by
|
|
80
|
+
{/* Set by vitops({ sitemap }); null when no sitemap was registered. */}
|
|
81
81
|
{head.sitemap && <link rel="sitemap" type="application/xml" href={head.sitemap} />}
|
|
82
82
|
|
|
83
83
|
{webComponents && (
|
|
@@ -112,7 +112,7 @@ const THEME_SCRIPT =
|
|
|
112
112
|
{head.consentRuntime && <script is:inline type="module" src={`${wc}/consent.js`}></script>}
|
|
113
113
|
|
|
114
114
|
{/*
|
|
115
|
-
The live theme editor — a separate, opt-in bundle (
|
|
115
|
+
The live theme editor — a separate, opt-in bundle (vitops({ editor: true })).
|
|
116
116
|
It is tooling, not part of the runtime, so it stays out of elements.js and off
|
|
117
117
|
every page that doesn't ask for it. Loads independently of `webComponents`:
|
|
118
118
|
<wc-theme-editor> registers itself and pulls in no framework element.
|
|
@@ -67,13 +67,13 @@ if (icons.engine === 'inline' && qualified) {
|
|
|
67
67
|
svg = await loadIconSvg(qualified, { resolveFrom: icons.root });
|
|
68
68
|
if (!svg)
|
|
69
69
|
console.warn(
|
|
70
|
-
`[
|
|
70
|
+
`[vitops] <Icon name="${name}"> — "${qualified}" not found. Is ` +
|
|
71
71
|
`@iconify-json/${qualified.slice(0, qualified.indexOf(':'))} installed?`,
|
|
72
72
|
);
|
|
73
73
|
} else if (icons.engine === 'none') {
|
|
74
74
|
console.warn(
|
|
75
|
-
`[
|
|
76
|
-
'Add `icons: true` to
|
|
75
|
+
`[vitops] <Icon name="${name}"> rendered but icons are not configured. ` +
|
|
76
|
+
'Add `icons: true` to vitops() in astro.config.',
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* NavShell — a navigation aside beside main content at width, collapsing to a
|
|
4
|
+
* toggle + drawer below it. Markup only: every declaration lives in the
|
|
5
|
+
* framework's `navshell` pattern (patterns/navshell.css), which is emitted in
|
|
6
|
+
* all three output formats.
|
|
7
|
+
*
|
|
8
|
+
* It nests. A docs page is typically two of these — a site nav wrapping an
|
|
9
|
+
* on-this-page nav — promoting at different breakpoints:
|
|
10
|
+
*
|
|
11
|
+
* <NavShell id="site-nav" label="Documentation" breakpoint="md">
|
|
12
|
+
* <Fragment slot="nav">…</Fragment>
|
|
13
|
+
* <main>
|
|
14
|
+
* <NavShell id="page-toc" label="On this page" breakpoint="lg" side="end">
|
|
15
|
+
* <Fragment slot="nav">…</Fragment>
|
|
16
|
+
* …prose…
|
|
17
|
+
* </NavShell>
|
|
18
|
+
* </main>
|
|
19
|
+
* </NavShell>
|
|
20
|
+
*
|
|
21
|
+
* Nothing here may reach for a Tailwind utility: Tailwind doesn't scan
|
|
22
|
+
* node_modules, so a class only it can emit is silently never generated — and
|
|
23
|
+
* classes in the generator's TW_CLASH list are stripped from the framework's own
|
|
24
|
+
* tailwind bundle by design. `navshell*` and `drawer*` are in neither list.
|
|
25
|
+
*
|
|
26
|
+
* No runtime JS: the toggle is `<button popovertarget>` against a `[popover]`
|
|
27
|
+
* panel, so it works from the first paint and degrades to a visible panel if the
|
|
28
|
+
* Popover API is missing.
|
|
29
|
+
*/
|
|
30
|
+
interface Props {
|
|
31
|
+
/**
|
|
32
|
+
* The panel's id, and the `popovertarget` the toggle points at.
|
|
33
|
+
*
|
|
34
|
+
* Required, never generated. It IS the toggle↔panel contract, and a generated
|
|
35
|
+
* id would make the one real hazard of nesting — two instances colliding on
|
|
36
|
+
* the same id, so the outer toggle opens the inner panel — invisible rather
|
|
37
|
+
* than impossible. Making the author name each instance makes a collision a
|
|
38
|
+
* thing you can see in the markup.
|
|
39
|
+
*/
|
|
40
|
+
id: string;
|
|
41
|
+
/** Accessible name for the `<nav>`, e.g. "Documentation" / "On this page". */
|
|
42
|
+
label: string;
|
|
43
|
+
/** Container width at which the aside appears. Default `md` (48rem). */
|
|
44
|
+
breakpoint?: 'sm' | 'md' | 'lg' | 'xl';
|
|
45
|
+
/** Which side the aside sits on at width. Default `start`. */
|
|
46
|
+
side?: 'start' | 'end';
|
|
47
|
+
/**
|
|
48
|
+
* Aside column width once promoted, e.g. `'14rem'`. Defaults to the design
|
|
49
|
+
* system's `--width-nav` token rather than a literal, so a site retunes every
|
|
50
|
+
* nav column in one place.
|
|
51
|
+
*/
|
|
52
|
+
size?: string;
|
|
53
|
+
/** Accessible name for the toggle. Defaults to `Open ${label}`. */
|
|
54
|
+
toggleLabel?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Where the toggle lives. `'inline'` renders a `.navshell__bar` row above the
|
|
57
|
+
* content; `'external'` renders none, for when a `<NavShellToggle>` elsewhere
|
|
58
|
+
* (typically the site header) opens this shell instead.
|
|
59
|
+
*
|
|
60
|
+
* Two NESTED shells should not both be `'inline'`: their bars are sticky at
|
|
61
|
+
* the same offset and land on top of each other. Hoist the outer one.
|
|
62
|
+
*/
|
|
63
|
+
toggle?: 'inline' | 'external';
|
|
64
|
+
class?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const {
|
|
68
|
+
id,
|
|
69
|
+
label,
|
|
70
|
+
breakpoint = 'md',
|
|
71
|
+
side = 'start',
|
|
72
|
+
// Not a literal: the default REFERENCES the design system's token, so an
|
|
73
|
+
// unsized instance still tracks `--width-nav`. A literal here would shadow it
|
|
74
|
+
// on every instance and quietly make the token unreachable through this
|
|
75
|
+
// component, which is the only way most consumers will use the pattern.
|
|
76
|
+
size = 'var(--width-nav, 16rem)',
|
|
77
|
+
toggleLabel,
|
|
78
|
+
toggle = 'inline',
|
|
79
|
+
class: className,
|
|
80
|
+
} = Astro.props;
|
|
81
|
+
|
|
82
|
+
// Always emitted, even when the author passes no `size`. `--navshell-size`
|
|
83
|
+
// inherits (it can't be registered as non-inheriting — a registered property's
|
|
84
|
+
// initial-value must be computationally independent, and this one is in rem), so
|
|
85
|
+
// without this an inner instance would silently adopt an outer instance's column
|
|
86
|
+
// width. Writing it on every root severs the inheritance at each boundary.
|
|
87
|
+
const style = `--navshell-size: ${size}`;
|
|
88
|
+
|
|
89
|
+
// The drawer slides in from the side its aside will occupy, so the motion points
|
|
90
|
+
// at where the panel is going to live.
|
|
91
|
+
const edge = side === 'end' ? 'drawer--right' : 'drawer--left';
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
<div
|
|
95
|
+
class:list={['navshell', `navshell--bp-${breakpoint}`, side === 'end' && 'navshell--end', className]}
|
|
96
|
+
style={style}
|
|
97
|
+
>
|
|
98
|
+
{toggle === 'inline' && (
|
|
99
|
+
<div class="navshell__bar">
|
|
100
|
+
<slot name="toggle">
|
|
101
|
+
<button class="navshell__toggle" popovertarget={id} aria-label={toggleLabel ?? `Open ${label}`}>
|
|
102
|
+
<slot name="toggle-label">{label}</slot>
|
|
103
|
+
</button>
|
|
104
|
+
</slot>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
<div id={id} popover class:list={['navshell__panel', 'drawer', edge]}>
|
|
109
|
+
<button
|
|
110
|
+
class="navshell__close"
|
|
111
|
+
popovertarget={id}
|
|
112
|
+
popovertargetaction="hide"
|
|
113
|
+
aria-label={`Close ${label}`}>×</button
|
|
114
|
+
>
|
|
115
|
+
<nav class="navshell__nav" aria-label={label}>
|
|
116
|
+
<slot name="nav" />
|
|
117
|
+
</nav>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="navshell__main">
|
|
121
|
+
<slot />
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* NavShellToggle — a `<NavShell>` toggle rendered outside its shell, so it can
|
|
4
|
+
* sit in a site header or navbar rather than in a row of its own.
|
|
5
|
+
*
|
|
6
|
+
* Two nested shells both rendering their own sticky toggle row put those rows at
|
|
7
|
+
* the same offset, on top of each other. Hoisting the outer one into the header
|
|
8
|
+
* — which is already sticky — leaves a single in-flow bar and no conflict. It is
|
|
9
|
+
* also simply where a site menu button belongs.
|
|
10
|
+
*
|
|
11
|
+
* <header class="navbar">
|
|
12
|
+
* <a href="/">Brand</a>
|
|
13
|
+
* <NavShellToggle for="site-nav" label="Menu" breakpoint="md" />
|
|
14
|
+
* </header>
|
|
15
|
+
* …
|
|
16
|
+
* <NavShell id="site-nav" label="Documentation" breakpoint="md" toggle="external">
|
|
17
|
+
*
|
|
18
|
+
* `breakpoint` MUST match the shell's: outside `.navshell` the toggle cannot read
|
|
19
|
+
* the shell's internal "am I wide?" flag (a style query resolves against an
|
|
20
|
+
* ancestor container, and the header is not one), so it carries its own copy.
|
|
21
|
+
* Mismatch them and you get a toggle visible next to an expanded sidebar, or no
|
|
22
|
+
* way to open the nav at all.
|
|
23
|
+
*
|
|
24
|
+
* No runtime JS: `popovertarget` is an id reference, which works from anywhere in
|
|
25
|
+
* the document.
|
|
26
|
+
*/
|
|
27
|
+
interface Props {
|
|
28
|
+
/** The `id` of the `<NavShell>` this opens. */
|
|
29
|
+
for: string;
|
|
30
|
+
/** Visible text (and accessible name, unless `aria-label` is given). */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Must match the shell's `breakpoint`. Default `md`. */
|
|
33
|
+
breakpoint?: 'sm' | 'md' | 'lg' | 'xl';
|
|
34
|
+
class?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { for: target, label, breakpoint = 'md', class: className } = Astro.props;
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
<button
|
|
41
|
+
class:list={['navshell-toggle', `navshell-toggle--bp-${breakpoint}`, className]}
|
|
42
|
+
popovertarget={target}
|
|
43
|
+
>
|
|
44
|
+
<slot>{label}</slot>
|
|
45
|
+
</button>
|
|
@@ -17,7 +17,7 @@ interface Props {
|
|
|
17
17
|
/**
|
|
18
18
|
* Map a semantic name onto a fully-qualified one before rendering.
|
|
19
19
|
*
|
|
20
|
-
* @deprecated The `icons` option on `
|
|
20
|
+
* @deprecated The `icons` option on `vitops()` now does this centrally, so
|
|
21
21
|
* the site's set is configured once instead of threaded through every call
|
|
22
22
|
* site. Left in place because removing it would break consumers passing one;
|
|
23
23
|
* when omitted, `<Icon />` resolves through the site config.
|
package/src/components/Seo.astro
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* alongside <Head /> (which owns favicons, theme-color and the web-component
|
|
5
5
|
* runtime — the two don't overlap).
|
|
6
6
|
*
|
|
7
|
-
* Site-level defaults come from `
|
|
7
|
+
* Site-level defaults come from `vitops({ seo: … })` via the
|
|
8
8
|
* `virtual:getvitops/head` module; the props here override them per page.
|
|
9
9
|
*
|
|
10
10
|
* It owns <title> and <meta name="description">, so **remove them from your
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* On an EmDash site use <EmDashHead> instead — it covers the same tags from the
|
|
22
22
|
* CMS. Using both double-emits every og:/twitter: tag.
|
|
23
23
|
*/
|
|
24
|
-
// @ts-ignore — virtual module provided by the
|
|
24
|
+
// @ts-ignore — virtual module provided by the vitops() integration at build time
|
|
25
25
|
import head from 'virtual:getvitops/head';
|
|
26
26
|
import { resolveSeo, type SeoProps } from '../seo.ts';
|
|
27
27
|
|
package/src/seo.ts
CHANGED
|
@@ -17,11 +17,11 @@ export type SeoTwitterCard = 'summary' | 'summary_large_image' | 'app' | 'player
|
|
|
17
17
|
export type SeoOgType = 'website' | 'article' | 'profile';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Site-level SEO defaults, passed to `
|
|
20
|
+
* Site-level SEO defaults, passed to `vitops({ seo })` and baked into the
|
|
21
21
|
* `virtual:getvitops/head` module. Per-page `<Seo />` props override these.
|
|
22
22
|
*
|
|
23
23
|
* The vocabulary mirrors the site-config schema's `seo` block
|
|
24
|
-
* (`@getvitops/generator`, `src/
|
|
24
|
+
* (`@getvitops/generator`, `src/config.ts`) so a `Config` → these adapter would
|
|
25
25
|
* be a flat field map — but it is not imported from there. This is the
|
|
26
26
|
* integration's own option surface, and it must stay JSON-serialisable: it is
|
|
27
27
|
* `JSON.stringify`d into the virtual module, so no functions, no `URL`s, no dates.
|