@rxova/astro-ui 0.1.0 → 0.3.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,16 +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`, …) 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 |
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 |
64
67
 
65
68
  There is no barrel: each component has its own path, so a page only loads the
66
69
  CSS of the components it imports.
@@ -71,6 +74,15 @@ Run everything from the repo root. `apps/preview` renders the Starlight preset
71
74
  and the plain-Astro chrome; `pnpm run verify` runs `astro check`, the tests and
72
75
  the export checks for this package.
73
76
 
77
+ `apps/preview` also carries the gallery: one page per component under
78
+ `/gallery/`, each state in a `Story` frame. `pnpm --filter @rxova/preview
79
+ screenshots` rebuilds it and writes `screenshots/<component>.png` here, light and
80
+ dark, for the pull request that adds or changes a component.
81
+
82
+ `apps/storybook` is the Storybook: a story file per component with its states,
83
+ a Docs page built from the component's frontmatter, and a light/dark toolbar.
84
+ `pnpm --filter @rxova/storybook dev` serves it with live controls.
85
+
74
86
  ## License
75
87
 
76
88
  MIT © Jonatan Kruszewski
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxova/astro-ui",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Astro components, Starlight preset and shared chrome for rxova.org",
6
6
  "keywords": [
@@ -46,7 +46,8 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "@rxova/brand": "^1.0.0"
49
+ "@rxova/brand": "^1.1.1",
50
+ "lucide-static": "^1.45.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@astrojs/check": "^0.9.10",
@@ -0,0 +1,67 @@
1
+ ---
2
+ /** Titled source listings, one per recipe: a heading linking to the library, then the source in a code frame. */
3
+ export interface Recipe {
4
+ id: string
5
+ label: string
6
+ href: string
7
+ source: string
8
+ }
9
+
10
+ interface Props {
11
+ recipes: readonly Recipe[]
12
+ /** Heading ids are `<idPrefix>-<recipe.id>`, so a page can link to one recipe. */
13
+ idPrefix?: string
14
+ }
15
+
16
+ const { recipes, idPrefix = 'recipe' } = Astro.props
17
+ ---
18
+
19
+ <div class="rx-recipes">
20
+ {recipes.map(({ id, label, href, source }) => (
21
+ <section class="rx-recipe">
22
+ <h3 class="rx-recipe__title" id={`${idPrefix}-${id}`}>
23
+ <a href={href}>{label}</a>
24
+ </h3>
25
+ <pre class="rx-recipe__source"><code>{source}</code></pre>
26
+ </section>
27
+ ))}
28
+ </div>
29
+
30
+ <style>
31
+ .rx-recipes {
32
+ display: grid;
33
+ gap: 2rem;
34
+ }
35
+
36
+ .rx-recipe__title {
37
+ margin: 0 0 0.75rem;
38
+ font-size: 1.05rem;
39
+ font-weight: 620;
40
+ letter-spacing: -0.015em;
41
+ }
42
+
43
+ .rx-recipe__title a {
44
+ color: var(--rx-fg);
45
+ text-decoration: none;
46
+ }
47
+
48
+ .rx-recipe__title a:hover {
49
+ color: var(--rx-primary);
50
+ text-decoration: underline;
51
+ text-underline-offset: 3px;
52
+ }
53
+
54
+ .rx-recipe__source {
55
+ margin: 0;
56
+ padding: 1.1rem 1.25rem;
57
+ background: var(--rx-card);
58
+ border: 1px solid var(--rx-rule);
59
+ border-radius: var(--rx-radius);
60
+ overflow-x: auto;
61
+ color: var(--rx-fg);
62
+ font-family: var(--rx-font-mono);
63
+ font-size: 0.86rem;
64
+ line-height: 1.65;
65
+ tab-size: 2;
66
+ }
67
+ </style>
@@ -0,0 +1,121 @@
1
+ ---
2
+ /**
3
+ * A centred call to action: title, lede and a row of pill buttons. Internal hrefs must arrive
4
+ * base-aware; external ones get `rel="noopener"` and a visible marker.
5
+ */
6
+ export interface CtaAction {
7
+ text: string
8
+ href: string
9
+ variant?: 'primary' | 'minimal'
10
+ external?: boolean
11
+ }
12
+
13
+ interface Props {
14
+ title: string
15
+ lede: string
16
+ actions: readonly CtaAction[]
17
+ }
18
+
19
+ const { title, lede, actions } = Astro.props
20
+ ---
21
+
22
+ <aside class="rx-cta not-content">
23
+ <h2 class="rx-cta__title">{title}</h2>
24
+ <p class="rx-cta__lede">{lede}</p>
25
+ <div class="rx-cta__actions">
26
+ {actions.map((action) => (
27
+ <a
28
+ class:list={['rx-btn', `rx-btn--${action.variant ?? 'minimal'}`]}
29
+ href={action.href}
30
+ rel={action.external ? 'noopener' : undefined}
31
+ >
32
+ {action.text}
33
+ {action.external && (
34
+ <svg
35
+ viewBox="0 0 24 24"
36
+ width="14"
37
+ height="14"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ stroke-width="2"
41
+ stroke-linecap="round"
42
+ stroke-linejoin="round"
43
+ aria-hidden="true"
44
+ >
45
+ <path d="M14 4h6v6M20 4l-9 9M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5" />
46
+ </svg>
47
+ )}
48
+ </a>
49
+ ))}
50
+ </div>
51
+ </aside>
52
+
53
+ <style>
54
+ .rx-cta {
55
+ margin-block: 6rem 1rem;
56
+ padding: clamp(2rem, 1rem + 4vw, 3.5rem);
57
+ text-align: center;
58
+ background: var(--rx-card);
59
+ border: 1px solid var(--rx-rule);
60
+ border-radius: var(--rx-radius);
61
+ box-shadow: var(--rx-shadow-soft);
62
+ }
63
+
64
+ .rx-cta__title {
65
+ margin: 0;
66
+ color: var(--rx-fg);
67
+ font-size: clamp(1.5rem, 1.15rem + 1.5vw, 2.1rem);
68
+ font-weight: 640;
69
+ line-height: 1.15;
70
+ letter-spacing: -0.02em;
71
+ text-wrap: balance;
72
+ }
73
+
74
+ .rx-cta__lede {
75
+ max-width: 46ch;
76
+ margin: 0.9rem auto 0;
77
+ color: var(--rx-muted);
78
+ font-size: 1rem;
79
+ line-height: 1.6;
80
+ text-wrap: pretty;
81
+ }
82
+
83
+ .rx-cta__actions {
84
+ display: flex;
85
+ flex-wrap: wrap;
86
+ gap: 0.75rem;
87
+ justify-content: center;
88
+ margin-top: 2rem;
89
+ }
90
+
91
+ .rx-btn {
92
+ display: inline-flex;
93
+ align-items: center;
94
+ gap: 0.5rem;
95
+ padding: 0.7rem 1.4rem;
96
+ border: 1px solid transparent;
97
+ border-radius: var(--rx-radius-pill);
98
+ font-size: 0.95rem;
99
+ font-weight: 600;
100
+ text-decoration: none;
101
+ }
102
+
103
+ /* --rx-on-primary, not #fff: white on the lightened dark-mode violet is about 2.9:1 as a fill. */
104
+ .rx-btn--primary {
105
+ color: var(--rx-on-primary);
106
+ background: var(--rx-primary);
107
+ }
108
+
109
+ .rx-btn--primary:hover {
110
+ background: var(--rx-primary-dark);
111
+ }
112
+
113
+ .rx-btn--minimal {
114
+ color: var(--rx-fg);
115
+ border-color: var(--rx-rule-strong);
116
+ }
117
+
118
+ .rx-btn--minimal:hover {
119
+ border-color: var(--rx-primary);
120
+ }
121
+ </style>
@@ -0,0 +1,114 @@
1
+ ---
2
+ /**
3
+ * Facts a page reads from somewhere else, as a framed table: column headings, a row heading per
4
+ * row, cells that may render as code. Scrollable and focusable when narrow, so a keyboard can reach it.
5
+ */
6
+ export type Cell = string | { code: string }
7
+
8
+ export interface DataRow {
9
+ label: string
10
+ cells: readonly Cell[]
11
+ }
12
+
13
+ interface Props {
14
+ /** The accessible name of the scrollable region: "Framework compatibility". */
15
+ label: string
16
+ columns: readonly string[]
17
+ rows: readonly DataRow[]
18
+ /** Where the facts come from, under the table. */
19
+ caption?: string
20
+ }
21
+
22
+ const { label, columns, rows, caption } = Astro.props
23
+ ---
24
+
25
+ <div class="rx-table" tabindex="0" role="region" aria-label={label}>
26
+ <table class="rx-table__table">
27
+ {caption && <caption class="rx-table__caption">{caption}</caption>}
28
+ <thead>
29
+ <tr>
30
+ {columns.map((column) => (
31
+ <th scope="col">{column}</th>
32
+ ))}
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ {rows.map((row) => (
37
+ <tr>
38
+ <th scope="row" class="rx-table__label">
39
+ {row.label}
40
+ </th>
41
+ {row.cells.map((cell) => (
42
+ <td>{typeof cell === 'string' ? cell : <code>{cell.code}</code>}</td>
43
+ ))}
44
+ </tr>
45
+ ))}
46
+ </tbody>
47
+ </table>
48
+ </div>
49
+
50
+ <style>
51
+ .rx-table {
52
+ overflow-x: auto;
53
+ border: 1px solid var(--rx-rule);
54
+ border-radius: var(--rx-radius);
55
+ }
56
+
57
+ .rx-table__table {
58
+ width: 100%;
59
+ margin: 0;
60
+ border-collapse: collapse;
61
+ font-size: 0.9rem;
62
+ line-height: 1.5;
63
+ }
64
+
65
+ .rx-table__caption {
66
+ padding: 1rem 1.25rem;
67
+ color: var(--rx-muted);
68
+ font-size: 0.82rem;
69
+ line-height: 1.5;
70
+ text-align: start;
71
+ caption-side: bottom;
72
+ border-top: 1px solid var(--rx-rule);
73
+ }
74
+
75
+ .rx-table__table th,
76
+ .rx-table__table td {
77
+ padding: 0.85rem 1.25rem;
78
+ text-align: start;
79
+ vertical-align: top;
80
+ color: var(--rx-fg);
81
+ border-bottom: 1px solid var(--rx-rule);
82
+ }
83
+
84
+ .rx-table__table thead th {
85
+ background: var(--rx-card);
86
+ color: var(--rx-muted);
87
+ font-size: 0.75rem;
88
+ font-weight: 700;
89
+ letter-spacing: 0.06em;
90
+ text-transform: uppercase;
91
+ white-space: nowrap;
92
+ }
93
+
94
+ .rx-table__table tbody tr:last-child th,
95
+ .rx-table__table tbody tr:last-child td {
96
+ border-bottom: 0;
97
+ }
98
+
99
+ .rx-table__label {
100
+ font-weight: 600;
101
+ white-space: nowrap;
102
+ }
103
+
104
+ .rx-table__table td {
105
+ color: var(--rx-muted);
106
+ }
107
+
108
+ .rx-table__table code {
109
+ color: var(--rx-fg);
110
+ font-family: var(--rx-font-mono);
111
+ font-size: 0.84rem;
112
+ white-space: nowrap;
113
+ }
114
+ </style>
@@ -0,0 +1,15 @@
1
+ ---
2
+ /** A stack of `DocAccordionItem`s: `<details>` does the collapsing, so there is no client script. */
3
+ ---
4
+
5
+ <div class="doc-accordion">
6
+ <slot />
7
+ </div>
8
+
9
+ <style>
10
+ .doc-accordion {
11
+ display: grid;
12
+ gap: 1rem;
13
+ margin: 1.5rem 0 2rem;
14
+ }
15
+ </style>
@@ -0,0 +1,83 @@
1
+ ---
2
+ /** One collapsible block of a `DocAccordion`: a title, an optional one-line summary, and the slot. */
3
+ interface Props {
4
+ title: string
5
+ summary?: string
6
+ defaultOpen?: boolean
7
+ }
8
+
9
+ const { title, summary, defaultOpen = false } = Astro.props
10
+ ---
11
+
12
+ <details class="doc-accordion-item" open={defaultOpen}>
13
+ <summary>
14
+ <span class="summary-text">
15
+ <span class="title">{title}</span>
16
+ {summary && <span class="summary-copy">{summary}</span>}
17
+ </span>
18
+ </summary>
19
+ <div class="content">
20
+ <slot />
21
+ </div>
22
+ </details>
23
+
24
+ <style>
25
+ .doc-accordion-item {
26
+ border: 1px solid var(--rx-rule);
27
+ border-radius: 1rem;
28
+ background: var(--rx-bg);
29
+ overflow: hidden;
30
+ }
31
+
32
+ summary {
33
+ list-style: none;
34
+ cursor: pointer;
35
+ padding: 1rem 1.1rem 1rem 1.25rem;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ gap: 1rem;
40
+ font-weight: 600;
41
+ color: var(--rx-fg);
42
+ }
43
+
44
+ summary::-webkit-details-marker {
45
+ display: none;
46
+ }
47
+
48
+ summary::after {
49
+ content: '+';
50
+ flex: 0 0 auto;
51
+ font-size: 1.3rem;
52
+ line-height: 1;
53
+ color: var(--rx-primary);
54
+ }
55
+
56
+ .doc-accordion-item[open] summary::after {
57
+ content: '−';
58
+ }
59
+
60
+ .summary-text {
61
+ display: grid;
62
+ gap: 0.18rem;
63
+ }
64
+
65
+ .title {
66
+ font-size: 1.02rem;
67
+ }
68
+
69
+ .summary-copy {
70
+ font-size: 0.92rem;
71
+ font-weight: 500;
72
+ color: var(--rx-muted);
73
+ }
74
+
75
+ .content {
76
+ border-top: 1px solid var(--rx-rule);
77
+ padding: 1rem 1.25rem 1.25rem;
78
+ }
79
+
80
+ .content > :last-child {
81
+ margin-bottom: 0;
82
+ }
83
+ </style>
@@ -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,29 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ /** The frame every glyph in `icons/` draws in: a 16px grid in the text colour. Decorative: label the button. */
5
+ type Props = HTMLAttributes<'svg'> & {
6
+ /** Width and height, in pixels. */
7
+ size?: number
8
+ /** Fills the shape instead of stroking it. */
9
+ solid?: boolean
10
+ }
11
+
12
+ const { size = 16, solid = false, class: className, ...rest } = Astro.props
13
+ ---
14
+
15
+ <svg
16
+ class:list={['icon', className]}
17
+ viewBox="0 0 16 16"
18
+ width={size}
19
+ height={size}
20
+ fill={solid ? 'currentColor' : 'none'}
21
+ stroke={solid ? 'none' : 'currentColor'}
22
+ stroke-width="1.6"
23
+ stroke-linecap="round"
24
+ stroke-linejoin="round"
25
+ aria-hidden="true"
26
+ {...rest}
27
+ >
28
+ <slot />
29
+ </svg>
@@ -0,0 +1,124 @@
1
+ ---
2
+ /**
3
+ * A numbered progression of modes, each a prose column beside a code pane. Panes are named slots
4
+ * (one per `mode.slot`), so a page writes fenced blocks and gets the docs' Shiki highlighting.
5
+ */
6
+ export interface Mode {
7
+ name: string
8
+ /** The named slot holding this mode's code. */
9
+ slot: string
10
+ summary: string
11
+ }
12
+
13
+ interface Props {
14
+ modes: readonly Mode[]
15
+ }
16
+
17
+ const { modes } = Astro.props
18
+ const panes = await Promise.all(
19
+ modes.map(async (mode) => ({
20
+ ...mode,
21
+ html: Astro.slots.has(mode.slot) ? await Astro.slots.render(mode.slot) : '',
22
+ })),
23
+ )
24
+ ---
25
+
26
+ <ol class="rx-modes">
27
+ {panes.map((mode, index) => (
28
+ <li class="rx-modes__item">
29
+ <div class="rx-modes__prose">
30
+ <div class="rx-modes__head">
31
+ <span class="rx-modes__step" aria-hidden="true">
32
+ {index + 1}
33
+ </span>
34
+ <h3 class="rx-modes__name">{mode.name}</h3>
35
+ </div>
36
+ <p class="rx-modes__summary">{mode.summary}</p>
37
+ </div>
38
+ <div class="rx-modes__code" set:html={mode.html} />
39
+ </li>
40
+ ))}
41
+ </ol>
42
+
43
+ <style>
44
+ .rx-modes {
45
+ display: grid;
46
+ gap: 1.25rem;
47
+ margin: 0;
48
+ padding: 0;
49
+ list-style: none;
50
+ }
51
+
52
+ /* Full-width rows, not columns: snippets are wider than a third of the page and would truncate. */
53
+ .rx-modes__item {
54
+ display: grid;
55
+ gap: 1.25rem;
56
+ min-width: 0;
57
+ padding: 1.5rem;
58
+ background: var(--rx-card);
59
+ border: 1px solid var(--rx-rule);
60
+ border-radius: var(--rx-radius);
61
+ grid-template-columns: 1fr;
62
+ }
63
+
64
+ @media (min-width: 50rem) {
65
+ .rx-modes__item {
66
+ grid-template-columns: 17rem 1fr;
67
+ align-items: start;
68
+ gap: 2rem;
69
+ }
70
+ }
71
+
72
+ .rx-modes__head {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 0.7rem;
76
+ }
77
+
78
+ .rx-modes__step {
79
+ display: inline-flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ width: 1.6rem;
83
+ height: 1.6rem;
84
+ flex: none;
85
+ color: var(--rx-primary);
86
+ background: var(--rx-tag-bg);
87
+ border-radius: var(--rx-radius-pill);
88
+ font-family: var(--rx-font-mono);
89
+ font-size: 0.8rem;
90
+ font-weight: 500;
91
+ }
92
+
93
+ .rx-modes__name {
94
+ margin: 0;
95
+ color: var(--rx-fg);
96
+ font-size: 1.05rem;
97
+ font-weight: 620;
98
+ letter-spacing: -0.01em;
99
+ }
100
+
101
+ .rx-modes__summary {
102
+ margin: 0.7rem 0 0;
103
+ color: var(--rx-muted);
104
+ font-size: 0.92rem;
105
+ line-height: 1.6;
106
+ text-wrap: pretty;
107
+ }
108
+
109
+ /* min-width: 0 so a long line scrolls inside the pane instead of widening the card past the page. */
110
+ .rx-modes__code {
111
+ min-width: 0;
112
+ }
113
+
114
+ .rx-modes__code :global(pre) {
115
+ margin: 0;
116
+ padding: 0.95rem 1.1rem;
117
+ background: var(--rx-bg);
118
+ border: 1px solid var(--rx-rule);
119
+ border-radius: var(--rx-radius-sm);
120
+ overflow-x: auto;
121
+ font-size: 0.8rem;
122
+ line-height: 1.6;
123
+ }
124
+ </style>