@contentrain/astro-kit 0.2.0 → 0.3.1
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 +13 -0
- package/catalog.json +9 -3
- package/components/_shared/KitImage.astro +32 -5
- package/components/_shared/Section.astro +1 -1
- package/components/card-grid/CardGrid.astro +1 -1
- package/components/cta/Cta.astro +1 -1
- package/components/faq/Faq.astro +3 -1
- package/components/faq/fixtures.json +4 -0
- package/components/faq/meta.json +1 -1
- package/components/footer/Footer.astro +2 -2
- package/components/header/Header.astro +1 -1
- package/components/hero/Hero.astro +2 -2
- package/mapping/gutenberg.json +5 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -60,6 +60,19 @@ unchanged:
|
|
|
60
60
|
- `--spacing-gutter`: the page frame's side padding, which falls back to 1rem
|
|
61
61
|
on phones and 2rem from `md` up.
|
|
62
62
|
|
|
63
|
+
The theme's type scale and rhythm are not read by components. The site's
|
|
64
|
+
theme applies them to the kit's markers, and only when the site sets them:
|
|
65
|
+
- `--text-heading-1` to `--text-heading-3`: headings inside `main`.
|
|
66
|
+
- `--text-body`: `[data-kit-text]`, the lead and body paragraphs of hero, CTA
|
|
67
|
+
and card grid.
|
|
68
|
+
- `--text-nav`: the header navigation, `[data-cr-part="nav-header"]`.
|
|
69
|
+
- `--spacing-section`: the distance between two consecutive sections; each
|
|
70
|
+
`[data-kit-section]` gets half of it as vertical padding, except
|
|
71
|
+
where `data-kit-spacing="none"`.
|
|
72
|
+
- `data-kit-brand` (the site title in Header and Footer) and `data-kit-tagline`
|
|
73
|
+
(Footer) take the theme's own sizes: the title from where it sits (body in
|
|
74
|
+
the header, a heading in the footer), the tagline from its preset.
|
|
75
|
+
|
|
63
76
|
The full list is in `catalog.json` under `tokens`.
|
|
64
77
|
|
|
65
78
|
## API
|
package/catalog.json
CHANGED
|
@@ -20,7 +20,12 @@
|
|
|
20
20
|
"--leading-heading",
|
|
21
21
|
"--radius-control",
|
|
22
22
|
"--radius-image",
|
|
23
|
-
"--spacing-gutter"
|
|
23
|
+
"--spacing-gutter",
|
|
24
|
+
"--text-nav",
|
|
25
|
+
"--text-heading-1",
|
|
26
|
+
"--text-heading-2",
|
|
27
|
+
"--text-heading-3",
|
|
28
|
+
"--spacing-section"
|
|
24
29
|
],
|
|
25
30
|
"dependencies": {
|
|
26
31
|
"tailwind-merge": "^3.7.0",
|
|
@@ -554,10 +559,11 @@
|
|
|
554
559
|
"style": {
|
|
555
560
|
"options": [
|
|
556
561
|
"lines",
|
|
557
|
-
"cards"
|
|
562
|
+
"cards",
|
|
563
|
+
"plain"
|
|
558
564
|
],
|
|
559
565
|
"default": "lines",
|
|
560
|
-
"description": "lines: rows divided by rules. cards: each question in a rounded box."
|
|
566
|
+
"description": "lines: rows divided by rules. cards: each question in a rounded box. plain: the browser's own disclosure triangle without rules or boxes (WordPress's details block)."
|
|
561
567
|
},
|
|
562
568
|
"tone": {
|
|
563
569
|
"options": [
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
// An image through astro:assets
|
|
3
|
-
//
|
|
2
|
+
// An image through astro:assets, optimized and sized so the layout never
|
|
3
|
+
// shifts, from either place a site's media lives:
|
|
4
|
+
//
|
|
5
|
+
// - the repository, under public/ (`/media/2026/01/cover.jpg`): the file is
|
|
6
|
+
// read at build time and gets a srcset of resized copies;
|
|
7
|
+
// - a media host the site allows (Contentrain Studio's delivery URLs, in
|
|
8
|
+
// astro.config `image.remotePatterns`): fetched at build time when its
|
|
9
|
+
// size is known, and resized the same way.
|
|
10
|
+
//
|
|
11
|
+
// Anything else — a vector, a host the site does not allow, a remote image of
|
|
12
|
+
// unknown size — is a plain lazy <img>: nothing is fetched from a host the
|
|
13
|
+
// site did not name.
|
|
14
|
+
import type { ImageMetadata } from 'astro'
|
|
4
15
|
import { Image } from 'astro:assets'
|
|
5
16
|
import type { ImageInput } from './types'
|
|
6
17
|
|
|
@@ -13,9 +24,25 @@ interface Props {
|
|
|
13
24
|
priority?: boolean | undefined
|
|
14
25
|
}
|
|
15
26
|
|
|
27
|
+
// Raster files under public/, loaded only when a page shows one.
|
|
28
|
+
const LOCAL = import.meta.glob<ImageMetadata>('/public/**/*.{avif,gif,jpeg,jpg,png,tif,tiff,webp,AVIF,GIF,JPEG,JPG,PNG,TIF,TIFF,WEBP}', { import: 'default' })
|
|
29
|
+
|
|
16
30
|
const { image, sizes = '100vw', class: className, priority = false } = Astro.props
|
|
17
31
|
const loading = priority ? 'eager' : 'lazy'
|
|
32
|
+
const fetchpriority = priority ? 'high' : 'auto'
|
|
33
|
+
const path = image.src.startsWith('/') && !image.src.startsWith('//') ? image.src.split(/[?#]/)[0]! : undefined
|
|
34
|
+
// A malformed escape (`%E0%A4%A`) is no file in public/: the image stays a plain <img> rather than failing the build.
|
|
35
|
+
const decoded = (() => { try { return path === undefined ? undefined : decodeURI(path) } catch { return undefined } })()
|
|
36
|
+
const load = decoded === undefined ? undefined : LOCAL[`/public${decoded}`]
|
|
37
|
+
const local = load ? await load() : undefined
|
|
38
|
+
// The stored size (WordPress's chosen size of the file), else the file's own.
|
|
39
|
+
const size = image.width && image.height ? { width: image.width, height: image.height } : {}
|
|
40
|
+
// Candidate widths for the srcset, up to the image's own: the browser picks one by `sizes`.
|
|
41
|
+
const STEPS = [320, 640, 960, 1280, 1600, 1920, 2560]
|
|
42
|
+
const widthsUpTo = (max: number) => [...STEPS.filter(step => step < max), max]
|
|
18
43
|
---
|
|
19
|
-
{
|
|
20
|
-
? <Image src={
|
|
21
|
-
:
|
|
44
|
+
{local
|
|
45
|
+
? <Image src={local} alt={image.alt} {...size} widths={widthsUpTo(image.width ?? local.width)} sizes={sizes} class={className} loading={loading} fetchpriority={fetchpriority} />
|
|
46
|
+
: image.width && image.height && path === undefined
|
|
47
|
+
? <Image src={image.src} alt={image.alt} width={image.width} height={image.height} widths={widthsUpTo(image.width)} sizes={sizes} class={className} loading={loading} fetchpriority={fetchpriority} />
|
|
48
|
+
: <img src={image.src} alt={image.alt} width={image.width} height={image.height} class={className} loading={loading} fetchpriority={fetchpriority} decoding="async" />}
|
|
@@ -38,6 +38,6 @@ interface Props {
|
|
|
38
38
|
|
|
39
39
|
const { tone, spacing, width = 'page', as: Tag = 'section', id, class: className, 'aria-labelledby': labelledBy } = Astro.props
|
|
40
40
|
---
|
|
41
|
-
<Tag id={id} aria-labelledby={labelledBy} class={section({ tone, spacing, class: className })} data-tone={tone ?? 'default'}>
|
|
41
|
+
<Tag id={id} aria-labelledby={labelledBy} class={section({ tone, spacing, class: className })} data-tone={tone ?? 'default'} data-kit-section data-kit-spacing={spacing ?? 'md'}>
|
|
42
42
|
{width === 'none' ? <slot /> : <div class={width === 'prose' ? 'container-prose' : 'container-page'}><slot /></div>}
|
|
43
43
|
</Tag>
|
|
@@ -75,7 +75,7 @@ const headingId = title ? `cards-${title.toLowerCase().replace(/[^a-z0-9]+/g, '-
|
|
|
75
75
|
? <a href={item.href} class="text-current no-underline after:absolute after:inset-0 after:content-[''] hover:text-accent">{item.title}</a>
|
|
76
76
|
: item.title}
|
|
77
77
|
</CardHeading>
|
|
78
|
-
{item.text && <p class={text({ class: dim })}>{item.text}</p>}
|
|
78
|
+
{item.text && <p class={text({ class: dim })} data-kit-text>{item.text}</p>}
|
|
79
79
|
</li>
|
|
80
80
|
))}
|
|
81
81
|
</ul>
|
package/components/cta/Cta.astro
CHANGED
|
@@ -57,7 +57,7 @@ const shown = actions.map(action => (tone === 'accent' && (action.style ?? 'prim
|
|
|
57
57
|
<div class={s.box()}>
|
|
58
58
|
<div class={s.copy()}>
|
|
59
59
|
<h2 id={headingId} class={s.heading()}>{heading}</h2>
|
|
60
|
-
{body && <p class={s.body()}>{body}</p>}
|
|
60
|
+
{body && <p class={s.body()} data-kit-text>{body}</p>}
|
|
61
61
|
</div>
|
|
62
62
|
{shown.length > 0 && <div class={s.actions()}>{shown.map(action => <Button action={action} size="lg" />)}</div>}
|
|
63
63
|
</div>
|
package/components/faq/Faq.astro
CHANGED
|
@@ -25,6 +25,8 @@ const faq = tv({
|
|
|
25
25
|
style: {
|
|
26
26
|
lines: { list: 'divide-y divide-line border-y border-line' },
|
|
27
27
|
cards: { list: 'gap-3', item: 'rounded-card border border-line px-5', summary: 'py-5' },
|
|
28
|
+
// WordPress's details block: the browser's own disclosure triangle, no rules or box.
|
|
29
|
+
plain: { list: 'gap-2', summary: 'list-item list-[revert] py-1 text-base [font-weight:inherit] [&::-webkit-details-marker]:inline', marker: 'hidden', answer: 'pt-2 pl-5' },
|
|
28
30
|
},
|
|
29
31
|
tone: { default: {}, muted: { item: 'bg-surface' }, inverse: { answer: 'prose-invert' }, accent: { answer: 'prose-invert' } },
|
|
30
32
|
},
|
|
@@ -37,7 +39,7 @@ const faq = tv({
|
|
|
37
39
|
interface Props {
|
|
38
40
|
items: FaqItem[]
|
|
39
41
|
heading?: string | undefined
|
|
40
|
-
style?: 'lines' | 'cards' | undefined
|
|
42
|
+
style?: 'lines' | 'cards' | 'plain' | undefined
|
|
41
43
|
/** One answer open at a time (`<details name>`). */
|
|
42
44
|
exclusive?: boolean | undefined
|
|
43
45
|
/** Open the first answer. */
|
|
@@ -32,5 +32,9 @@
|
|
|
32
32
|
{
|
|
33
33
|
"name": "cards-accent",
|
|
34
34
|
"props": { "style": "cards", "tone": "accent", "id": "faq-accent", "items": [{ "question": "How do I cancel my membership?", "answer": "<p>From your account page, at any time.</p>" }] }
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "plain",
|
|
38
|
+
"props": { "style": "plain", "id": "faq-plain", "items": [{ "question": "Do you accept guest essays?", "answer": "<p>Yes, send a short pitch first.</p>" }, { "question": "How often do you publish?", "answer": "<p>Two or three pieces a month.</p>" }] }
|
|
35
39
|
}
|
|
36
40
|
]
|
package/components/faq/meta.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"uses": [],
|
|
7
7
|
"shared": ["Section.astro"],
|
|
8
8
|
"variants": {
|
|
9
|
-
"style": { "options": ["lines", "cards"], "default": "lines", "description": "lines: rows divided by rules. cards: each question in a rounded box." },
|
|
9
|
+
"style": { "options": ["lines", "cards", "plain"], "default": "lines", "description": "lines: rows divided by rules. cards: each question in a rounded box. plain: the browser's own disclosure triangle without rules or boxes (WordPress's details block)." },
|
|
10
10
|
"tone": { "options": ["default", "muted", "inverse", "accent"], "default": "default", "description": "Section background." }
|
|
11
11
|
},
|
|
12
12
|
"props": {
|
|
@@ -48,8 +48,8 @@ const line = copyright ?? `© ${new Date().getFullYear()} ${siteName}`
|
|
|
48
48
|
<footer class={root()}>
|
|
49
49
|
<div class={inner()} style={layout === 'columns' ? `--footer-columns: ${Math.max(columns.length, 1)}` : undefined}>
|
|
50
50
|
<div class="flex flex-col gap-3">
|
|
51
|
-
<p class="text-base font-semibold">{siteName}</p>
|
|
52
|
-
{tagline && <p class={muted()}>{tagline}</p>}
|
|
51
|
+
<p class="text-base font-semibold" data-kit-brand>{siteName}</p>
|
|
52
|
+
{tagline && <p class={muted()} data-kit-tagline>{tagline}</p>}
|
|
53
53
|
{layout === 'columns' && <p class:list={[muted(), 'mt-auto pt-6']}>{line}</p>}
|
|
54
54
|
</div>
|
|
55
55
|
{layout === 'columns'
|
|
@@ -50,7 +50,7 @@ const { root, inner, brand } = header({ layout, tone, position })
|
|
|
50
50
|
---
|
|
51
51
|
<header class={root()}>
|
|
52
52
|
<div class={inner()}>
|
|
53
|
-
<a href={homeHref} class={brand()} aria-label={showName ? undefined : siteName}>
|
|
53
|
+
<a href={homeHref} class={brand()} aria-label={showName ? undefined : siteName} data-kit-brand>
|
|
54
54
|
{logo && <KitImage image={logo} priority class="h-8 w-auto" sizes="160px" />}
|
|
55
55
|
{showName && <span>{siteName}</span>}
|
|
56
56
|
</a>
|
|
@@ -65,7 +65,7 @@ const cover = layout === 'cover' && image
|
|
|
65
65
|
<div class={s.copy()}>
|
|
66
66
|
{eyebrow && <p class={s.eyebrow()}>{eyebrow}</p>}
|
|
67
67
|
<Heading id={headingId} class={s.heading()}>{heading}</Heading>
|
|
68
|
-
{lead && <p class={s.lead()}>{lead}</p>}
|
|
68
|
+
{lead && <p class={s.lead()} data-kit-text>{lead}</p>}
|
|
69
69
|
{actions.length > 0 && <div class={s.actions()}>{actions.map(action => <Button action={action} size="lg" />)}</div>}
|
|
70
70
|
</div>
|
|
71
71
|
</div>
|
|
@@ -78,7 +78,7 @@ const cover = layout === 'cover' && image
|
|
|
78
78
|
<div class={s.copy()}>
|
|
79
79
|
{eyebrow && <p class={s.eyebrow()}>{eyebrow}</p>}
|
|
80
80
|
<Heading id={headingId} class={s.heading()}>{heading}</Heading>
|
|
81
|
-
{lead && <p class={s.lead()}>{lead}</p>}
|
|
81
|
+
{lead && <p class={s.lead()} data-kit-text>{lead}</p>}
|
|
82
82
|
{actions.length > 0 && <div class={s.actions()}>{actions.map(action => <Button action={action} size="lg" />)}</div>}
|
|
83
83
|
</div>
|
|
84
84
|
{image && (
|
package/mapping/gutenberg.json
CHANGED
|
@@ -215,7 +215,8 @@
|
|
|
215
215
|
"match": "core/cover",
|
|
216
216
|
"component": "hero",
|
|
217
217
|
"variant": {
|
|
218
|
-
"layout": "cover"
|
|
218
|
+
"layout": "cover",
|
|
219
|
+
"align": "class:is-position-*-left=start|center"
|
|
219
220
|
},
|
|
220
221
|
"props": {
|
|
221
222
|
"heading": "dom:h1,h2",
|
|
@@ -233,6 +234,9 @@
|
|
|
233
234
|
{
|
|
234
235
|
"match": "core/details",
|
|
235
236
|
"component": "faq",
|
|
237
|
+
"variant": {
|
|
238
|
+
"style": "plain"
|
|
239
|
+
},
|
|
236
240
|
"group": "siblings",
|
|
237
241
|
"into": "items",
|
|
238
242
|
"item": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/astro-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Copy-in Astro components for Contentrain sites (shadcn model): typed props, tailwind-variants, zero JavaScript by default — with a machine-readable catalog",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"mapping"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@contentrain/types": "1.
|
|
45
|
+
"@contentrain/types": "1.27.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^22.10.0",
|