@rxova/astro-ui 0.2.0 → 0.4.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/README.md CHANGED
@@ -51,19 +51,19 @@ unlayered and would flatten a Starlight page.
51
51
 
52
52
  ## What's in it
53
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`, …), primitives (`PageHeader`, `BackLink`, `ShowMore`, `VisuallyHidden`, `Icon`) and the components in the gallery |
59
- | `@rxova/astro-ui/components/icons/*.astro` | Glyphs in the `Icon` frame: `Prev`, `Next`, `Play`, `Pause`, `Replay`, `Expand`, `Collapse` |
60
- | `@rxova/astro-ui/scripts/show-more` | `enhanceShowMore()`: batches a `[data-reveal-list]` behind a `ShowMore` |
61
- | `@rxova/astro-ui/lib/entries` | Ordering, bylines, dates and excerpts shared by /blog and /updates |
62
- | `@rxova/astro-ui/styles/document.css` | For sites that own their document: `chrome.css`, a reset, base element styling |
63
- | `@rxova/astro-ui/styles/chrome.css` | What the header and footer need, with nothing document-level |
64
- | `@rxova/astro-ui/styles/starlight.css` | Maps `--rx-*` onto Starlight's `--sl-*`, plus the footer |
65
- | `@rxova/astro-ui/styles/landing.css` | The Starlight overrides a splash landing page needs; load it through `customCss` |
66
- | `@rxova/astro-ui/styles/mermaid.css` | Restyles `rehype-mermaid`'s build-time SVG with the tokens, so diagrams follow the theme |
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 (`Header`, `SiteFooter`, …), primitives (`PageHeader`, `BackLink`, `ShowMore`, `VisuallyHidden`, `Icon`) and the components in the gallery |
59
+ | `@rxova/astro-ui/components/icons/*.astro` | Glyphs in the `Icon` frame: `Prev`, `Next`, `Play`, `Pause`, `Replay`, `Expand`, `Collapse` |
60
+ | `@rxova/astro-ui/scripts/show-more` | `enhanceShowMore()`: batches a `[data-reveal-list]` behind a `ShowMore` |
61
+ | `@rxova/astro-ui/lib/entries` | Ordering, bylines, dates and excerpts shared by /blog and /updates |
62
+ | `@rxova/astro-ui/styles/document.css` | For sites that own their document: `chrome.css`, a reset, base element styling |
63
+ | `@rxova/astro-ui/styles/chrome.css` | What the header and footer need, with nothing document-level |
64
+ | `@rxova/astro-ui/styles/starlight.css` | Maps `--rx-*` onto Starlight's `--sl-*`, plus the footer |
65
+ | `@rxova/astro-ui/styles/landing.css` | The Starlight overrides a splash landing page needs; load it through `customCss` |
66
+ | `@rxova/astro-ui/styles/mermaid.css` | Restyles `rehype-mermaid`'s build-time SVG with the tokens, so diagrams follow the theme |
67
67
 
68
68
  There is no barrel: each component has its own path, so a page only loads the
69
69
  CSS of the components it imports.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxova/astro-ui",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Astro components, Starlight preset and shared chrome for rxova.org",
6
6
  "keywords": [
@@ -46,7 +46,7 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "@rxova/brand": "^1.1.0",
49
+ "@rxova/brand": "^1.2.0",
50
50
  "lucide-static": "^1.45.0"
51
51
  },
52
52
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * The single rxova.org header, rendered by `SiteShell` and the website landing; callers pass
3
+ * The single rxova.org header, rendered by the website's layout; callers pass
4
4
  * the menu fully resolved. Not for the Starlight docs sites, which use `sharedStarlightConfig`.
5
5
  */
6
6
  import ThemeToggle from './ThemeToggle.astro'
@@ -0,0 +1,26 @@
1
+ ---
2
+ /**
3
+ * The umbrella navigation for a docs site: the project switcher, then the rxova.org sections.
4
+ * Starlight shows it in the header bar on wide screens and in the menu on phones.
5
+ */
6
+ import { RXOVA_ORIGIN, SECTIONS, type ProjectId } from '@rxova/brand'
7
+ import ProjectSwitcher from './ProjectSwitcher.astro'
8
+
9
+ interface Props {
10
+ /** The project whose docs these are, marked in the switcher. */
11
+ current?: ProjectId
12
+ /** Prefix for the section links. '' links root-relative, for a build served on rxova.org. */
13
+ origin?: string
14
+ }
15
+
16
+ const { current, origin = RXOVA_ORIGIN } = Astro.props
17
+ ---
18
+
19
+ <nav class="rx-sections" aria-label="rxova.org">
20
+ <ProjectSwitcher current={current} />
21
+ {SECTIONS.map((section) => (
22
+ <a class="rx-sections__link" href={`${origin}${section.path}`}>
23
+ {section.label}
24
+ </a>
25
+ ))}
26
+ </nav>
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  /**
3
3
  * The rxova footer on every surface: brand block, link columns, then copyright and legal links.
4
- * Styles live in `../styles/footer.css`; all cross-project hrefs are absolute.
4
+ * Styles live in `../styles/footer.css`. Hrefs are absolute unless `origin` is '' (on rxova.org).
5
5
  */
6
- import { PROJECTS, RXOVA_ORIGIN, getProject, siteUrl, type ProjectId } from '@rxova/brand'
6
+ import { PROJECTS, RXOVA_ORIGIN, SECTIONS, getProject, type ProjectId } from '@rxova/brand'
7
7
 
8
8
  /** A standalone surface of rxova.org, for the "Site" column. */
9
9
  export interface SiteLink {
10
10
  label: string
11
- /** Absolute — see sites.ts in @rxova/brand. */
11
+ /** A page URL ending in `/`: absolute, or root-relative when `origin` is ''. */
12
12
  href: string
13
13
  }
14
14
 
@@ -27,27 +27,26 @@ interface Props {
27
27
  * Defaults to every project, which is right for a docs site.
28
28
  */
29
29
  projects?: readonly SiteLink[]
30
+ /** Prefix for every rxova.org link. '' on rxova.org itself, so links stay on the serving origin. */
31
+ origin?: string
30
32
  }
31
33
 
32
- const { project, docs, site, projects } = Astro.props
34
+ const { project, docs, site, projects, origin = RXOVA_ORIGIN } = Astro.props
33
35
  const self = project ? getProject(project) : undefined
34
36
  const year = new Date().getFullYear()
35
37
 
36
38
  const projectLinks: readonly SiteLink[] =
37
- projects ?? PROJECTS.map((p) => ({ label: p.label, href: `${RXOVA_ORIGIN}${p.mount}` }))
39
+ projects ?? PROJECTS.map((p) => ({ label: p.label, href: `${origin}${p.mount}` }))
38
40
 
39
- const surfaces: readonly SiteLink[] = site ?? [
40
- { label: 'Blog', href: siteUrl('/blog') },
41
- { label: 'Updates', href: siteUrl('/updates') },
42
- { label: 'About', href: siteUrl('/about') },
43
- ]
41
+ const surfaces: readonly SiteLink[] =
42
+ site ?? SECTIONS.map((section) => ({ label: section.label, href: `${origin}${section.path}` }))
44
43
  ---
45
44
 
46
45
  <footer class="rx-footer">
47
46
  <div class="rx-footer__top">
48
47
  <div class="rx-footer__brand">
49
- <a class="rx-footer__mark" href={RXOVA_ORIGIN}>
50
- <img src={siteUrl('/rxova-logo-256.png')} alt="" width="28" height="28" loading="lazy" />
48
+ <a class="rx-footer__mark" href={`${origin}/`}>
49
+ <img src={`${origin}/rxova-logo-256.png`} alt="" width="28" height="28" loading="lazy" />
51
50
  <span>Rxova</span>
52
51
  </a>
53
52
  {/* Not "React libraries": most ship a framework-agnostic core; some are extensions/CLIs. */}
@@ -112,7 +111,7 @@ const surfaces: readonly SiteLink[] = site ?? [
112
111
  <h2 class="rx-footer__title">Site</h2>
113
112
  <ul class="rx-footer__list">
114
113
  <li>
115
- <a href={RXOVA_ORIGIN}>Home</a>
114
+ <a href={`${origin}/`}>Home</a>
116
115
  </li>
117
116
  {surfaces.map((s) => (
118
117
  <li>
@@ -142,8 +141,8 @@ const surfaces: readonly SiteLink[] = site ?? [
142
141
  <div class="rx-footer__legal">
143
142
  <p>© {year} Rxova · MIT licensed</p>
144
143
  <div class="rx-footer__legal-links">
145
- <a href={siteUrl('/privacy')}>Privacy</a>
146
- <a href={siteUrl('/terms')}>Terms</a>
144
+ <a href={`${origin}/privacy/`}>Privacy</a>
145
+ <a href={`${origin}/terms/`}>Terms</a>
147
146
  </div>
148
147
  </div>
149
148
  </footer>
@@ -59,7 +59,7 @@ const { floating = true } = Astro.props
59
59
  position: fixed;
60
60
  top: 1rem;
61
61
  right: 1rem;
62
- /* Above SiteShell's sticky header (z-index 10), so the fixed button stays reachable. */
62
+ /* Above the site's sticky header (z-index 10), so the fixed button stays reachable. */
63
63
  z-index: 20;
64
64
  width: 2.25rem;
65
65
  height: 2.25rem;
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  /**
3
- * Starlight `SocialIcons` override: the stock icons plus the project switcher, which lands just
4
- * before search and the theme toggle.
3
+ * Starlight `SocialIcons` override: the umbrella navigation, then the stock icons. Starlight
4
+ * renders this slot in the header on wide screens and in the mobile menu, so one header serves both.
5
5
  */
6
6
  import Default from '@astrojs/starlight/components/SocialIcons.astro'
7
- import ProjectSwitcher from '../components/ProjectSwitcher.astro'
7
+ import SectionNav from '../components/SectionNav.astro'
8
8
  import { projectFromBase } from '@rxova/brand'
9
9
 
10
10
  // Overrides receive no props, so the current project is inferred from the base
@@ -12,7 +12,7 @@ import { projectFromBase } from '@rxova/brand'
12
12
  const current = projectFromBase(import.meta.env.BASE_URL)
13
13
  ---
14
14
 
15
- <ProjectSwitcher current={current} />
15
+ <SectionNav current={current} />
16
16
  <Default>
17
17
  <slot />
18
18
  </Default>
@@ -94,8 +94,7 @@ export function sharedStarlightConfig({
94
94
  },
95
95
  ],
96
96
 
97
- // Pagefind ships with Starlight and replaces the third-party search plugin
98
- // journey was carrying.
97
+ // Pagefind ships with Starlight, so docs sites need no third-party search plugin.
99
98
  pagefind: true,
100
99
 
101
100
  sidebar,
@@ -157,6 +157,34 @@ html[data-rxova-shell] .sl-menu-button {
157
157
  background: var(--rx-gradient);
158
158
  }
159
159
 
160
+ /* Umbrella navigation: the switcher and the rxova.org sections, beside the social icons. */
161
+ .rx-sections {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 1rem;
165
+ }
166
+
167
+ .rx-sections__link {
168
+ color: var(--sl-color-gray-2);
169
+ font-size: var(--sl-text-sm);
170
+ text-decoration: none;
171
+ white-space: nowrap;
172
+ }
173
+
174
+ .rx-sections__link:hover {
175
+ color: var(--sl-color-white);
176
+ }
177
+
178
+ /* In the mobile menu the icons share a row with the theme picker; the navigation takes a row
179
+ of its own above them. */
180
+ .mobile-preferences .social-icons {
181
+ flex-wrap: wrap;
182
+ }
183
+
184
+ .mobile-preferences .rx-sections {
185
+ flex-basis: 100%;
186
+ }
187
+
160
188
  /* --- Footer -------------------------------------------------------------- */
161
189
 
162
190
  /* Footer styles live in footer.css (imported above) because /blog and /updates need them too. */
@@ -1,155 +0,0 @@
1
- ---
2
- /**
3
- * Artifact document for rxova.org's non-Starlight pages: metadata and assets only, no header,
4
- * footer, analytics or theme bootstrap. rxova-website supplies the public shell at deploy time.
5
- */
6
- import '@rxova/brand/fonts.css'
7
- import '../styles/document.css'
8
-
9
- import { canonicalUrl, siteUrl } from '@rxova/brand'
10
-
11
- interface Props {
12
- title: string
13
- description: string
14
- /** Canonical path, e.g. `/blog/why-rxova-has-a-blog`. Normalised below. */
15
- path: string
16
- ogType?: 'website' | 'article'
17
- /** Absolute URL of the social card, chosen by the caller; defaults to the umbrella card. */
18
- image?: string
19
- /** Advertises an RSS feed for this surface, as `rel="alternate"`. */
20
- feed?: { href: string; title: string }
21
- /** JSON-LD for this page, emitted verbatim as `application/ld+json`. */
22
- jsonLd?: unknown
23
- }
24
-
25
- const { title, description, path, ogType = 'website', image, feed, jsonLd } = Astro.props
26
-
27
- // Normalised here so no caller can declare a canonical URL without the trailing slash (a 301).
28
- const canonical = canonicalUrl(path)
29
-
30
- const ogImage = image ?? siteUrl('/og/rxova.png')
31
-
32
- /** Serialise JSON-LD for an inline `<script>`; `<` is escaped so a `</script>` cannot close it. */
33
- const serialiseJsonLd = (value: unknown): string => JSON.stringify(value).replace(/</g, '\\u003c')
34
- ---
35
-
36
- <!doctype html>
37
- <html lang="en">
38
- <head>
39
- <meta charset="utf-8" />
40
- <meta name="viewport" content="width=device-width, initial-scale=1" />
41
- <link rel="canonical" href={canonical} />
42
- <title>{title}</title>
43
- <meta name="description" content={description} />
44
- <meta property="og:title" content={title} />
45
- <meta property="og:description" content={description} />
46
- <meta property="og:type" content={ogType} />
47
- <meta property="og:url" content={canonical} />
48
- <meta property="og:image" content={ogImage} />
49
- <meta name="twitter:card" content="summary_large_image" />
50
- {feed && (
51
- <link rel="alternate" type="application/rss+xml" title={feed.title} href={feed.href} />
52
- )}
53
- {jsonLd && (
54
- <script type="application/ld+json" is:inline set:html={serialiseJsonLd(jsonLd)}></script>
55
- )}
56
- </head>
57
- <body>
58
- <main>
59
- <slot />
60
- </main>
61
-
62
- <style>
63
- main {
64
- max-width: var(--rx-max-wide);
65
- margin: 0 auto;
66
- padding: clamp(2rem, 6vh, 3.5rem) 1.5rem 4rem;
67
- }
68
- </style>
69
-
70
- <!-- Prose styling for rendered markdown. Global because <Content /> markup carries no Astro
71
- scoping attributes. -->
72
- <style is:global>
73
- .prose {
74
- color: var(--rx-muted);
75
- line-height: 1.7;
76
- }
77
- .prose > * + * {
78
- margin-top: 1.1rem;
79
- }
80
- .prose h2 {
81
- color: var(--rx-fg);
82
- font-size: 1.25rem;
83
- font-weight: 640;
84
- letter-spacing: -0.01em;
85
- margin-top: 2.5rem;
86
- }
87
- .prose h3 {
88
- color: var(--rx-fg);
89
- font-size: 1.05rem;
90
- font-weight: 620;
91
- margin-top: 2rem;
92
- }
93
- .prose a {
94
- color: var(--rx-fg);
95
- text-decoration: underline;
96
- text-underline-offset: 2px;
97
- }
98
- .prose strong {
99
- color: var(--rx-fg);
100
- font-weight: 620;
101
- }
102
- .prose ul,
103
- .prose ol {
104
- margin-left: 1.2rem;
105
- }
106
- .prose li + li {
107
- margin-top: 0.35rem;
108
- }
109
- .prose code {
110
- font-family: var(--rx-font-mono);
111
- font-size: 0.88em;
112
- background: var(--rx-tag-bg);
113
- padding: 0.05rem 0.35rem;
114
- border-radius: 4px;
115
- }
116
- .prose pre {
117
- background: var(--rx-card);
118
- border: 1px solid var(--rx-rule);
119
- border-radius: 10px;
120
- padding: 1rem 1.1rem;
121
- /* Long lines scroll inside the block; the page itself never does. */
122
- overflow-x: auto;
123
- }
124
- .prose pre code {
125
- background: none;
126
- padding: 0;
127
- }
128
- .prose blockquote {
129
- border-left: 2px solid var(--rx-rule);
130
- padding-left: 1rem;
131
- color: var(--rx-faint);
132
- }
133
- .prose img {
134
- max-width: 100%;
135
- height: auto;
136
- border-radius: 10px;
137
- }
138
- .prose table {
139
- width: 100%;
140
- border-collapse: collapse;
141
- font-size: 0.95rem;
142
- }
143
- .prose th,
144
- .prose td {
145
- border-bottom: 1px solid var(--rx-rule);
146
- padding: 0.5rem 0.6rem;
147
- text-align: left;
148
- }
149
- .prose th {
150
- color: var(--rx-fg);
151
- font-weight: 620;
152
- }
153
- </style>
154
- </body>
155
- </html>