@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/astro",
3
- "version": "2.1.0",
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": "2.1.0",
53
- "@getvitops/generator": "2.1.0",
54
- "@getvitops/utils": "2.1.0",
55
- "@getvitops/vite": "2.1.0"
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 (`getvitops({ consent })`)? */
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 getvitops()
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 getvitops() integration at build time
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 getvitops() integration with `consent` enabled, which is what loads
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 getvitops() integration at build time
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 `getvitops()` now does this centrally, so
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 `getvitops()` now does this centrally, so
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 getvitops() integration (it provides 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 getvitops({ fonts }), and the web-component runtime scripts in
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 getvitops() integration at build time
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 getvitops({ fonts }). Astro's `fonts:` config resolves and
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 getvitops({ sitemap }); null when no sitemap was registered. */}
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 (getvitops({ editor: true })).
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
- `[getvitops] <Icon name="${name}"> — "${qualified}" not found. Is ` +
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
- `[getvitops] <Icon name="${name}"> rendered but icons are not configured. ` +
76
- 'Add `icons: true` to getvitops() in astro.config.',
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}`}>&times;</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 `getvitops()` now does this centrally, so
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.
@@ -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 `getvitops({ seo: … })` via the
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 getvitops() integration at build time
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 `getvitops({ seo })` and baked into the
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/site.ts`) so a `SiteConfig` → these adapter would
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.