@contentrain/astro-kit 0.1.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 +89 -0
- package/catalog.json +1780 -0
- package/components/_shared/Button.astro +37 -0
- package/components/_shared/KitImage.astro +21 -0
- package/components/_shared/Section.astro +43 -0
- package/components/_shared/types.ts +26 -0
- package/components/breadcrumb/Breadcrumb.astro +56 -0
- package/components/breadcrumb/fixtures.json +5 -0
- package/components/breadcrumb/meta.json +27 -0
- package/components/card-grid/CardGrid.astro +82 -0
- package/components/card-grid/fixtures.json +6 -0
- package/components/card-grid/meta.json +123 -0
- package/components/contact-form/ContactForm.astro +79 -0
- package/components/contact-form/fixtures.json +18 -0
- package/components/contact-form/meta.json +28 -0
- package/components/cta/Cta.astro +64 -0
- package/components/cta/fixtures.json +6 -0
- package/components/cta/meta.json +28 -0
- package/components/faq/Faq.astro +79 -0
- package/components/faq/fixtures.json +36 -0
- package/components/faq/meta.json +30 -0
- package/components/footer/Footer.astro +87 -0
- package/components/footer/fixtures.json +5 -0
- package/components/footer/meta.json +31 -0
- package/components/gallery/Gallery.astro +88 -0
- package/components/gallery/fixtures.json +191 -0
- package/components/gallery/meta.json +30 -0
- package/components/header/Header.astro +62 -0
- package/components/header/fixtures.json +125 -0
- package/components/header/meta.json +34 -0
- package/components/hero/Hero.astro +84 -0
- package/components/hero/fixtures.json +95 -0
- package/components/hero/meta.json +127 -0
- package/components/nav/Nav.astro +102 -0
- package/components/nav/fixtures.json +80 -0
- package/components/nav/meta.json +30 -0
- package/components/pagination/Pagination.astro +69 -0
- package/components/pagination/fixtures.json +6 -0
- package/components/pagination/meta.json +31 -0
- package/components/post-card/PostCard.astro +67 -0
- package/components/post-card/fixtures.json +54 -0
- package/components/post-card/meta.json +32 -0
- package/components/prose/Prose.astro +30 -0
- package/components/prose/fixtures.json +4 -0
- package/components/prose/meta.json +69 -0
- package/components/slider/Slider.astro +136 -0
- package/components/slider/fixtures.json +5 -0
- package/components/slider/meta.json +32 -0
- package/components/tabs/Tabs.astro +101 -0
- package/components/tabs/fixtures.json +37 -0
- package/components/tabs/meta.json +28 -0
- package/components/testimonial/Testimonial.astro +70 -0
- package/components/testimonial/fixtures.json +26 -0
- package/components/testimonial/meta.json +97 -0
- package/dist/index.d.mts +150 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +199 -0
- package/dist/index.mjs.map +1 -0
- package/mapping/classic.json +261 -0
- package/mapping/divi.json +295 -0
- package/mapping/elementor.json +322 -0
- package/mapping/gutenberg.json +395 -0
- package/package.json +63 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "slider",
|
|
3
|
+
"name": "Slider",
|
|
4
|
+
"description": "A carousel of images with optional title, text and button per slide. Without JavaScript it is a snapping horizontal scroller; Embla adds paging, arrows and dots.",
|
|
5
|
+
"category": "section",
|
|
6
|
+
"uses": [],
|
|
7
|
+
"shared": ["Button.astro", "KitImage.astro", "types.ts"],
|
|
8
|
+
"variants": {
|
|
9
|
+
"perView": { "options": ["1", "2", "3"], "default": "1", "description": "Slides visible at once on wide screens." },
|
|
10
|
+
"aspect": { "options": ["wide", "standard", "square"], "default": "wide", "description": "Image aspect ratio: 21:9, 3:2 or 1:1." },
|
|
11
|
+
"caption": { "options": ["below", "overlay"], "default": "overlay", "description": "Caption under the image, or over its lower edge on a gradient." }
|
|
12
|
+
},
|
|
13
|
+
"props": {
|
|
14
|
+
"slides": { "type": "array", "required": true, "items": { "type": "object", "fields": { "image": { "type": "object", "required": true, "fields": { "src": { "type": "image", "required": true }, "alt": { "type": "string", "required": true }, "width": { "type": "integer" }, "height": { "type": "integer" } } }, "title": { "type": "string" }, "text": { "type": "text" }, "action": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "href": { "type": "url", "required": true }, "newTab": { "type": "boolean" }, "style": { "type": "select", "options": ["primary", "secondary", "ghost"] } } } } }, "description": "Slides; image is an ImageInput, action an ActionInput." },
|
|
15
|
+
"label": { "type": "string", "required": true, "description": "Accessible name of the carousel." },
|
|
16
|
+
"prevLabel": { "type": "string", "description": "ui-strings slider.prev." },
|
|
17
|
+
"nextLabel": { "type": "string", "description": "ui-strings slider.next." },
|
|
18
|
+
"slideLabel": { "type": "string", "description": "ui-strings slider.slide." },
|
|
19
|
+
"ofLabel": { "type": "string", "description": "ui-strings slider.of." },
|
|
20
|
+
"loop": { "type": "boolean", "default": false, "description": "Wrap from the last slide to the first." }
|
|
21
|
+
},
|
|
22
|
+
"slots": {},
|
|
23
|
+
"js": "embla",
|
|
24
|
+
"dependencies": { "embla-carousel": "^8.6.0" },
|
|
25
|
+
"sources": {
|
|
26
|
+
"gutenberg": ["core/gallery:carousel"],
|
|
27
|
+
"elementor": ["elementor/image-carousel", "elementor/slides", "elementor/media-carousel"],
|
|
28
|
+
"divi": ["divi/et_pb_slider", "divi/et_pb_fullwidth_slider", "divi/et_pb_post_slider"],
|
|
29
|
+
"classic": ["classic/revslider", "classic/metaslider", "classic/smart-slider"]
|
|
30
|
+
},
|
|
31
|
+
"a11y": "WAI-ARIA carousel pattern: a labelled region with aria-roledescription=carousel, slides as labelled groups ('Slide 2 of 5'), labelled previous/next buttons disabled at the ends, and dot buttons marking the current slide. No autoplay; with prefers-reduced-motion slides change without animation. Without JavaScript every slide is reachable by scrolling."
|
|
32
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Tabbed panels with the WAI-ARIA tabs pattern (arrow keys, Home/End,
|
|
3
|
+
// roving focus) from Zag.js. Without script every panel shows, one after the
|
|
4
|
+
// other under its own title — nothing is ever unreachable; the script turns
|
|
5
|
+
// the same markup into tabs.
|
|
6
|
+
import { tv } from 'tailwind-variants'
|
|
7
|
+
import Section from '../_shared/Section.astro'
|
|
8
|
+
|
|
9
|
+
export interface TabItem {
|
|
10
|
+
label: string
|
|
11
|
+
/** The panel as HTML (the rich text the tab came with). */
|
|
12
|
+
content: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const tabs = tv({
|
|
16
|
+
slots: {
|
|
17
|
+
heading: 'mb-8 text-2xl font-bold tracking-tight text-balance md:text-3xl',
|
|
18
|
+
root: 'group/tabs flex flex-col gap-6',
|
|
19
|
+
list: 'hidden gap-1 group-data-[ready]/tabs:flex',
|
|
20
|
+
trigger: 'cursor-pointer rounded-md px-4 py-2 text-sm font-medium whitespace-nowrap opacity-75 transition hover:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent data-[selected]:opacity-100',
|
|
21
|
+
panel: 'prose-site prose max-w-none text-current',
|
|
22
|
+
title: 'mb-3 text-lg font-semibold group-data-[ready]/tabs:sr-only',
|
|
23
|
+
},
|
|
24
|
+
variants: {
|
|
25
|
+
style: {
|
|
26
|
+
underline: { list: 'border-b border-line', trigger: '-mb-px rounded-none border-b-2 border-transparent data-[selected]:border-accent' },
|
|
27
|
+
pills: { trigger: 'data-[selected]:bg-accent data-[selected]:text-accent-ink' },
|
|
28
|
+
},
|
|
29
|
+
orientation: {
|
|
30
|
+
horizontal: { list: 'overflow-x-auto' },
|
|
31
|
+
vertical: { root: 'md:data-[ready]:flex-row md:data-[ready]:items-start md:data-[ready]:gap-10', list: 'md:w-56 md:shrink-0 md:flex-col', trigger: 'text-left' },
|
|
32
|
+
},
|
|
33
|
+
tone: { default: {}, muted: {}, inverse: { panel: 'prose-invert' }, accent: { panel: 'prose-invert', trigger: 'opacity-100' } },
|
|
34
|
+
},
|
|
35
|
+
compoundVariants: [
|
|
36
|
+
{ style: 'underline', orientation: 'vertical', class: { list: 'md:border-b-0 md:border-l', trigger: 'md:mb-0 md:-ml-px md:border-b-0 md:border-l-2' } },
|
|
37
|
+
],
|
|
38
|
+
defaultVariants: { style: 'underline', orientation: 'horizontal', tone: 'default' },
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
interface Props {
|
|
42
|
+
items: TabItem[]
|
|
43
|
+
heading?: string | undefined
|
|
44
|
+
/** Accessible name of the tab list when there is no heading. */
|
|
45
|
+
label?: string | undefined
|
|
46
|
+
style?: 'underline' | 'pills' | undefined
|
|
47
|
+
orientation?: 'horizontal' | 'vertical' | undefined
|
|
48
|
+
tone?: 'default' | 'muted' | 'inverse' | 'accent' | undefined
|
|
49
|
+
id?: string | undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { items, heading, label, style, orientation = 'horizontal', tone, id = 'tabs' } = Astro.props
|
|
53
|
+
const s = tabs({ style, orientation, tone })
|
|
54
|
+
const headingId = heading ? `${id}-title` : undefined
|
|
55
|
+
const value = (i: number) => `tab-${i + 1}`
|
|
56
|
+
---
|
|
57
|
+
{items.length > 0 && (
|
|
58
|
+
<Section tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
|
|
59
|
+
{heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
|
|
60
|
+
<div class={s.root()} data-kit-tabs={id} data-orientation={orientation} data-label={label ?? heading ?? undefined}>
|
|
61
|
+
<div class={s.list()} data-part="list">
|
|
62
|
+
{items.map((item, i) => <button type="button" class={s.trigger()} data-part="trigger" data-value={value(i)}>{item.label}</button>)}
|
|
63
|
+
</div>
|
|
64
|
+
{items.map((item, i) => (
|
|
65
|
+
<div data-part="content" data-value={value(i)} class="min-w-0 flex-1">
|
|
66
|
+
<h3 class={s.title()}>{item.label}</h3>
|
|
67
|
+
<div class={s.panel()} set:html={item.content} />
|
|
68
|
+
</div>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
</Section>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
import * as tabs from '@zag-js/tabs'
|
|
76
|
+
import { normalizeProps, spreadProps, VanillaMachine } from '@zag-js/vanilla'
|
|
77
|
+
|
|
78
|
+
for (const root of document.querySelectorAll<HTMLElement>('[data-kit-tabs]')) {
|
|
79
|
+
const list = root.querySelector<HTMLElement>('[data-part="list"]')
|
|
80
|
+
const triggers = [...root.querySelectorAll<HTMLElement>('[data-part="trigger"]')]
|
|
81
|
+
const contents = [...root.querySelectorAll<HTMLElement>('[data-part="content"]')]
|
|
82
|
+
if (!list || triggers.length === 0) continue
|
|
83
|
+
const machine = new VanillaMachine(tabs.machine, {
|
|
84
|
+
id: root.dataset.kitTabs ?? 'tabs',
|
|
85
|
+
defaultValue: triggers[0]!.dataset.value ?? null,
|
|
86
|
+
orientation: root.dataset.orientation === 'vertical' ? 'vertical' : 'horizontal',
|
|
87
|
+
})
|
|
88
|
+
const render = () => {
|
|
89
|
+
const api = tabs.connect(machine.service, normalizeProps)
|
|
90
|
+
spreadProps(root, api.getRootProps())
|
|
91
|
+
spreadProps(list, { ...api.getListProps(), ...(root.dataset.label ? { 'aria-label': root.dataset.label } : {}) })
|
|
92
|
+
for (const trigger of triggers) spreadProps(trigger, api.getTriggerProps({ value: trigger.dataset.value! }))
|
|
93
|
+
for (const content of contents) spreadProps(content, api.getContentProps({ value: content.dataset.value! }))
|
|
94
|
+
}
|
|
95
|
+
machine.subscribe(render)
|
|
96
|
+
machine.start()
|
|
97
|
+
render()
|
|
98
|
+
// Tabs only once they work: before this, every panel shows under its own title.
|
|
99
|
+
root.dataset.ready = ''
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "underline",
|
|
4
|
+
"props": {
|
|
5
|
+
"heading": "Getting there",
|
|
6
|
+
"id": "getting-there",
|
|
7
|
+
"items": [
|
|
8
|
+
{ "label": "By train", "content": "<p>The night train from Madrid arrives at 7:40. Book a couchette a month ahead in summer.</p>" },
|
|
9
|
+
{ "label": "By air", "content": "<p>The airport is 20 minutes from the centre by metro (line red).</p>" },
|
|
10
|
+
{ "label": "By car", "content": "<p>Parking in the old town is expensive; use the park-and-ride at <strong>Campo Grande</strong>.</p>" }
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "pills-vertical-muted",
|
|
16
|
+
"props": {
|
|
17
|
+
"style": "pills",
|
|
18
|
+
"orientation": "vertical",
|
|
19
|
+
"tone": "muted",
|
|
20
|
+
"label": "Seasons",
|
|
21
|
+
"id": "seasons",
|
|
22
|
+
"items": [
|
|
23
|
+
{ "label": "Spring", "content": "<p>Mild, green, and the best light for photographs.</p>" },
|
|
24
|
+
{ "label": "Summer", "content": "<p>Hot and busy; start early and rest at midday.</p>" },
|
|
25
|
+
{ "label": "Autumn", "content": "<p>Harvest festivals and warm sea.</p>" }
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "underline-inverse",
|
|
31
|
+
"props": { "tone": "inverse", "label": "Plans", "id": "plans", "items": [{ "label": "Free", "content": "<p>The newsletter and every guide.</p>" }, { "label": "Member", "content": "<p>Plus the seasonal print issue.</p>" }] }
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "pills-accent",
|
|
35
|
+
"props": { "style": "pills", "tone": "accent", "label": "Formats", "id": "formats", "items": [{ "label": "Online", "content": "<p>Read on the site.</p>" }, { "label": "Print", "content": "<p>Delivered each season.</p>" }] }
|
|
36
|
+
}
|
|
37
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "tabs",
|
|
3
|
+
"name": "Tabs",
|
|
4
|
+
"description": "Tabbed panels with the WAI-ARIA tabs pattern from Zag.js (arrow keys, Home/End, roving focus). Without script every panel shows in turn under its own title, so no content is ever hidden from a reader or a crawler.",
|
|
5
|
+
"category": "content",
|
|
6
|
+
"uses": [],
|
|
7
|
+
"shared": ["Section.astro"],
|
|
8
|
+
"variants": {
|
|
9
|
+
"style": { "options": ["underline", "pills"], "default": "underline", "description": "underline: a rule under the list, the selected tab underlined in accent. pills: the selected tab filled with accent." },
|
|
10
|
+
"orientation": { "options": ["horizontal", "vertical"], "default": "horizontal", "description": "vertical: the tab list beside the panel on wide screens (Elementor vertical tabs)." },
|
|
11
|
+
"tone": { "options": ["default", "muted", "inverse", "accent"], "default": "default", "description": "Section background." }
|
|
12
|
+
},
|
|
13
|
+
"props": {
|
|
14
|
+
"items": { "type": "array", "required": true, "items": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "content": { "type": "richtext", "required": true } } }, "description": "Tabs in order (Elementor tabs tab_title/tab_content, Divi tab title + content)." },
|
|
15
|
+
"heading": { "type": "string", "description": "Section heading; also names the tab list." },
|
|
16
|
+
"label": { "type": "string", "description": "Accessible name of the tab list when there is no heading." },
|
|
17
|
+
"id": { "type": "string", "description": "Section id, unique per page: the tabs machine and its element ids derive from it." }
|
|
18
|
+
},
|
|
19
|
+
"slots": {},
|
|
20
|
+
"js": "zag",
|
|
21
|
+
"dependencies": { "@zag-js/tabs": "^1.44.0", "@zag-js/vanilla": "^1.44.0" },
|
|
22
|
+
"sources": {
|
|
23
|
+
"elementor": ["elementor/tabs", "elementor/nested-tabs"],
|
|
24
|
+
"divi": ["divi/et_pb_tabs"],
|
|
25
|
+
"classic": ["classic/tabs"]
|
|
26
|
+
},
|
|
27
|
+
"a11y": "Zag.js tabs: role=tablist/tab/tabpanel with aria-selected, aria-controls and aria-labelledby; arrow keys (per orientation), Home and End move between tabs. Before the script runs every panel is visible with its own h3 title; after, the titles become screen-reader-only and the tabs label the panels."
|
|
28
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
// What people say: one large quote, or several as cards. Each is a <figure>
|
|
3
|
+
// with the quote and who said it. A rotating carousel of quotes is the
|
|
4
|
+
// slider's job; this one shows them all.
|
|
5
|
+
import { tv } from 'tailwind-variants'
|
|
6
|
+
import KitImage from '../_shared/KitImage.astro'
|
|
7
|
+
import Section from '../_shared/Section.astro'
|
|
8
|
+
import type { ImageInput } from '../_shared/types'
|
|
9
|
+
|
|
10
|
+
export interface TestimonialItem {
|
|
11
|
+
quote: string
|
|
12
|
+
name: string
|
|
13
|
+
role?: string | undefined
|
|
14
|
+
avatar?: ImageInput | undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const testimonial = tv({
|
|
18
|
+
slots: {
|
|
19
|
+
heading: 'mb-10 text-2xl font-bold tracking-tight text-balance md:text-3xl',
|
|
20
|
+
list: 'grid gap-6',
|
|
21
|
+
card: 'flex h-full flex-col gap-6',
|
|
22
|
+
quote: 'text-pretty',
|
|
23
|
+
person: 'mt-auto flex items-center gap-3 not-italic',
|
|
24
|
+
name: 'font-semibold',
|
|
25
|
+
role: 'text-sm opacity-75',
|
|
26
|
+
},
|
|
27
|
+
variants: {
|
|
28
|
+
layout: {
|
|
29
|
+
single: { heading: 'text-center', list: 'mx-auto max-w-3xl', card: 'items-center text-center', quote: 'text-2xl leading-snug font-medium md:text-3xl', person: 'mt-0' },
|
|
30
|
+
grid: { list: 'md:grid-cols-2 lg:grid-cols-3', card: 'rounded-card border border-line p-6', quote: 'text-base' },
|
|
31
|
+
},
|
|
32
|
+
tone: { default: {}, muted: { card: 'bg-surface' }, inverse: { card: 'border-surface/20' }, accent: { card: 'border-accent-ink/30', role: 'opacity-100' } },
|
|
33
|
+
},
|
|
34
|
+
compoundVariants: [
|
|
35
|
+
{ layout: 'grid', tone: 'muted', class: { card: 'border-transparent text-ink' } },
|
|
36
|
+
],
|
|
37
|
+
defaultVariants: { layout: 'grid', tone: 'default' },
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
interface Props {
|
|
41
|
+
items: TestimonialItem[]
|
|
42
|
+
heading?: string | undefined
|
|
43
|
+
layout?: 'single' | 'grid' | undefined
|
|
44
|
+
tone?: 'default' | 'muted' | 'inverse' | 'accent' | undefined
|
|
45
|
+
id?: string | undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { items, heading, layout, tone, id } = Astro.props
|
|
49
|
+
const s = testimonial({ layout, tone })
|
|
50
|
+
const headingId = heading && id ? `${id}-title` : undefined
|
|
51
|
+
---
|
|
52
|
+
{items.length > 0 && (
|
|
53
|
+
<Section tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
|
|
54
|
+
{heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
|
|
55
|
+
<div class={s.list()}>
|
|
56
|
+
{items.map(item => (
|
|
57
|
+
<figure class={s.card()}>
|
|
58
|
+
<blockquote class={s.quote()}><p>“{item.quote}”</p></blockquote>
|
|
59
|
+
<figcaption class={s.person()}>
|
|
60
|
+
{item.avatar && <KitImage image={item.avatar} sizes="48px" class="size-12 shrink-0 rounded-full object-cover" />}
|
|
61
|
+
<span class="flex flex-col text-left">
|
|
62
|
+
<span class={s.name()}>{item.name}</span>
|
|
63
|
+
{item.role && <span class={s.role()}>{item.role}</span>}
|
|
64
|
+
</span>
|
|
65
|
+
</figcaption>
|
|
66
|
+
</figure>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
</Section>
|
|
70
|
+
)}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "grid",
|
|
4
|
+
"props": {
|
|
5
|
+
"heading": "What readers say",
|
|
6
|
+
"id": "readers",
|
|
7
|
+
"items": [
|
|
8
|
+
{ "quote": "The only travel site I read to the end. Every guide feels written by someone who stayed.", "name": "Ayşe Demir", "role": "Reader since 2019", "avatar": { "src": "/kit-fixtures/avatar-1.svg", "alt": "", "width": 96, "height": 96 } },
|
|
9
|
+
{ "quote": "Their Lisbon guide saved our trip.", "name": "Tom Becker", "avatar": { "src": "/kit-fixtures/avatar-2.svg", "alt": "", "width": 96, "height": 96 } },
|
|
10
|
+
{ "quote": "Honest, slow, and beautifully photographed. I recommend it to every friend who asks where to go next.", "name": "Mei Tanaka", "role": "Photographer", "avatar": { "src": "/kit-fixtures/avatar-3.svg", "alt": "", "width": 96, "height": 96 } }
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "single-muted",
|
|
16
|
+
"props": { "layout": "single", "tone": "muted", "items": [{ "quote": "A journal that made me want to travel less, and better.", "name": "Ayşe Demir", "role": "Editor, Slow Places", "avatar": { "src": "/kit-fixtures/avatar-1.svg", "alt": "", "width": 96, "height": 96 } }] }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "grid-inverse",
|
|
20
|
+
"props": { "tone": "inverse", "items": [{ "quote": "Clear, kind and useful.", "name": "Tom Becker" }, { "quote": "Finally, guides without a hundred ads.", "name": "Mei Tanaka", "role": "Reader" }] }
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "single-accent",
|
|
24
|
+
"props": { "layout": "single", "tone": "accent", "items": [{ "quote": "Worth every minute.", "name": "Jonas Weber" }] }
|
|
25
|
+
}
|
|
26
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "testimonial",
|
|
3
|
+
"name": "Testimonial",
|
|
4
|
+
"description": "Quotes from customers or readers with who said them: one large quote, or several as cards. Every quote is shown; rotating quotes are the slider's job.",
|
|
5
|
+
"category": "section",
|
|
6
|
+
"uses": [],
|
|
7
|
+
"shared": [
|
|
8
|
+
"KitImage.astro",
|
|
9
|
+
"Section.astro",
|
|
10
|
+
"types.ts"
|
|
11
|
+
],
|
|
12
|
+
"variants": {
|
|
13
|
+
"layout": {
|
|
14
|
+
"options": [
|
|
15
|
+
"single",
|
|
16
|
+
"grid"
|
|
17
|
+
],
|
|
18
|
+
"default": "grid",
|
|
19
|
+
"description": "single: one large centred quote (the first item stands alone; more items stack). grid: cards, up to three per row."
|
|
20
|
+
},
|
|
21
|
+
"tone": {
|
|
22
|
+
"options": [
|
|
23
|
+
"default",
|
|
24
|
+
"muted",
|
|
25
|
+
"inverse",
|
|
26
|
+
"accent"
|
|
27
|
+
],
|
|
28
|
+
"default": "default",
|
|
29
|
+
"description": "Section background."
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"props": {
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"required": true,
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"fields": {
|
|
39
|
+
"quote": {
|
|
40
|
+
"type": "text",
|
|
41
|
+
"required": true
|
|
42
|
+
},
|
|
43
|
+
"name": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"required": true
|
|
46
|
+
},
|
|
47
|
+
"role": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
"avatar": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"fields": {
|
|
53
|
+
"src": {
|
|
54
|
+
"type": "image",
|
|
55
|
+
"required": true
|
|
56
|
+
},
|
|
57
|
+
"alt": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"required": true
|
|
60
|
+
},
|
|
61
|
+
"width": {
|
|
62
|
+
"type": "integer"
|
|
63
|
+
},
|
|
64
|
+
"height": {
|
|
65
|
+
"type": "integer"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"description": "The quotes (Elementor testimonial content/name/job/image, Divi testimonial body/author/job_title/portrait_url)."
|
|
72
|
+
},
|
|
73
|
+
"heading": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "Section heading above the quotes."
|
|
76
|
+
},
|
|
77
|
+
"id": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Anchor id of the section; the heading gets `<id>-title`."
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"slots": {},
|
|
83
|
+
"js": "none",
|
|
84
|
+
"dependencies": {},
|
|
85
|
+
"sources": {
|
|
86
|
+
"elementor": [
|
|
87
|
+
"elementor/testimonial"
|
|
88
|
+
],
|
|
89
|
+
"divi": [
|
|
90
|
+
"divi/et_pb_testimonial"
|
|
91
|
+
],
|
|
92
|
+
"classic": [
|
|
93
|
+
"classic/testimonial"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"a11y": "Each quote is a <figure> whose <figcaption> names the speaker; the text is in a <blockquote>. Avatars are decorative when their alt is empty. The section is labelled by its heading when it has one."
|
|
97
|
+
}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { FieldDef } from "@contentrain/types";
|
|
2
|
+
|
|
3
|
+
//#region src/catalog.d.ts
|
|
4
|
+
declare const CATALOG_FORMAT = "astro-kit-catalog@1";
|
|
5
|
+
/** The WordPress builders whose elements a component is fed from. */
|
|
6
|
+
type KitBuilder = 'gutenberg' | 'elementor' | 'divi' | 'classic';
|
|
7
|
+
declare const KIT_BUILDERS: readonly KitBuilder[];
|
|
8
|
+
/** Element-name prefix per builder, as `facts.json` names elements (`core/cover`, `elementor/heading`, `divi/et_pb_blurb`). */
|
|
9
|
+
declare const BUILDER_PREFIX: Record<KitBuilder, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Whether an element name belongs to a builder. Gutenberg blocks are namespaced by their plugin
|
|
12
|
+
* (`core/cover`, `yoast-seo/breadcrumbs`, `uagb/container`), so any `<ns>/<name>` that is not another
|
|
13
|
+
* builder's is a Gutenberg block.
|
|
14
|
+
*/
|
|
15
|
+
declare function isBuilderElement(builder: KitBuilder, name: string): boolean;
|
|
16
|
+
type KitCategory = 'chrome' | 'section' | 'content' | 'primitive';
|
|
17
|
+
/** Client JavaScript a component ships: none, Embla (carousel), Zag.js (accessible widget) or the Studio runtime. */
|
|
18
|
+
type KitJs = 'none' | 'embla' | 'zag' | 'studio';
|
|
19
|
+
type KitProp = FieldDef & {
|
|
20
|
+
description: string;
|
|
21
|
+
};
|
|
22
|
+
interface KitVariant {
|
|
23
|
+
options: string[];
|
|
24
|
+
default: string;
|
|
25
|
+
description: string;
|
|
26
|
+
}
|
|
27
|
+
/** One component as written in `components/<id>/meta.json`. */
|
|
28
|
+
interface KitComponentMeta {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
category: KitCategory;
|
|
33
|
+
/** Kit components this one renders. */
|
|
34
|
+
uses: string[];
|
|
35
|
+
/** Files it imports from `components/_shared/`. */
|
|
36
|
+
shared: string[];
|
|
37
|
+
variants: Record<string, KitVariant>;
|
|
38
|
+
props: Record<string, KitProp>;
|
|
39
|
+
/** Named slots → what goes in them. `default` is the unnamed slot. */
|
|
40
|
+
slots: Record<string, string>;
|
|
41
|
+
js: KitJs;
|
|
42
|
+
/** npm packages the site needs for this component, name → range. */
|
|
43
|
+
dependencies: Record<string, string>;
|
|
44
|
+
/** Builder elements this component replaces, by builder — what the mapping tables map from. */
|
|
45
|
+
sources: Partial<Record<KitBuilder, string[]>>;
|
|
46
|
+
/** How it meets its accessibility pattern. */
|
|
47
|
+
a11y: string;
|
|
48
|
+
}
|
|
49
|
+
/** A catalog entry: the meta plus the files that make the component. */
|
|
50
|
+
interface KitComponent extends KitComponentMeta {
|
|
51
|
+
/** Paths relative to `components/<id>/`. */
|
|
52
|
+
files: string[];
|
|
53
|
+
}
|
|
54
|
+
interface KitCatalog {
|
|
55
|
+
format: typeof CATALOG_FORMAT;
|
|
56
|
+
/** Design tokens (Tailwind @theme names) the components read; the site must define them. */
|
|
57
|
+
tokens: string[];
|
|
58
|
+
/** npm packages every kit component needs. */
|
|
59
|
+
dependencies: Record<string, string>;
|
|
60
|
+
components: KitComponent[];
|
|
61
|
+
}
|
|
62
|
+
/** Problems with a catalog, one line each; empty when it is sound. */
|
|
63
|
+
declare function validateCatalog(catalog: KitCatalog): string[];
|
|
64
|
+
/** Components that replace a builder element, e.g. `core/cover` → hero. */
|
|
65
|
+
declare function componentsForSource(catalog: KitCatalog, element: string): KitComponent[];
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/mapping.d.ts
|
|
68
|
+
declare const MAPPING_FORMAT = "astro-kit-mapping@1";
|
|
69
|
+
declare const MAPPING_VALUE_PATTERN: RegExp;
|
|
70
|
+
interface MappingRule {
|
|
71
|
+
/** Builder element, as the fact pack names it; a `:qualifier` narrows it (`core/template-part:header`). */
|
|
72
|
+
match: string;
|
|
73
|
+
/** Extra conditions on the element. */
|
|
74
|
+
when?: {
|
|
75
|
+
attr?: Record<string, string | number | boolean>;
|
|
76
|
+
class?: string;
|
|
77
|
+
};
|
|
78
|
+
/** Kit component id. */
|
|
79
|
+
component: string;
|
|
80
|
+
variant?: Record<string, string>;
|
|
81
|
+
/** Content the element lists rather than holds: one component per entry of the collection (`collection:posts`). */
|
|
82
|
+
bind?: `collection:${string}`;
|
|
83
|
+
/** Consecutive sibling matches form one component (a row of Elementor icon boxes is one card grid). */
|
|
84
|
+
group?: 'siblings';
|
|
85
|
+
props?: Record<string, string>;
|
|
86
|
+
/** Array prop filled from repeated parts of the element (`each`) or the grouped siblings / bound entries. */
|
|
87
|
+
into?: string;
|
|
88
|
+
/** Selector of the repeated parts inside the element, for `into`. */
|
|
89
|
+
each?: string;
|
|
90
|
+
/** Props of one repeated part / sibling / entry, with sources relative to it. */
|
|
91
|
+
item?: Record<string, string>;
|
|
92
|
+
}
|
|
93
|
+
interface MappingTable {
|
|
94
|
+
format: typeof MAPPING_FORMAT;
|
|
95
|
+
builder: KitBuilder;
|
|
96
|
+
/** Bumped on any rule change: plans record the version they applied. */
|
|
97
|
+
version: string;
|
|
98
|
+
/** What an element no rule covers becomes when no decision is made: rich text. */
|
|
99
|
+
fallback: 'prose';
|
|
100
|
+
rules: MappingRule[];
|
|
101
|
+
}
|
|
102
|
+
/** Problems with a mapping table against the catalog, one line each; empty when it is sound. */
|
|
103
|
+
declare function validateMapping(table: MappingTable, catalog: KitCatalog): string[];
|
|
104
|
+
/** Catalog sources of a builder that no rule of its table matches. */
|
|
105
|
+
declare function unmappedSources(table: MappingTable, catalog: KitCatalog): string[];
|
|
106
|
+
/** Rules for an element, most specific (with `when` / qualifier) first. */
|
|
107
|
+
declare function rulesFor(table: MappingTable, element: string): MappingRule[];
|
|
108
|
+
/** A builder element as the fact pack gives it (`FactBlock`): name, attributes, children. */
|
|
109
|
+
interface MappingNode {
|
|
110
|
+
name: string;
|
|
111
|
+
attrs?: Record<string, unknown>;
|
|
112
|
+
children?: MappingNode[];
|
|
113
|
+
}
|
|
114
|
+
/** Whether a rule's `match` (with an optional `:qualifier`) and `when.attr` fit an element. */
|
|
115
|
+
declare function ruleMatches(rule: MappingRule, node: MappingNode): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Apply a table to a tree: the outermost matching element owns its whole subtree, and nothing inside
|
|
118
|
+
* it is matched again — a header template part holding the site title and logo is one header, not three.
|
|
119
|
+
*/
|
|
120
|
+
declare function claimMatches(nodes: MappingNode[], table: MappingTable, path?: string): {
|
|
121
|
+
path: string;
|
|
122
|
+
node: MappingNode;
|
|
123
|
+
rule: MappingRule;
|
|
124
|
+
}[];
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/copy.d.ts
|
|
127
|
+
/** Where kit files land in a site, relative to the site root. */
|
|
128
|
+
declare const KIT_DIR = "src/components/kit";
|
|
129
|
+
interface CopyPlan {
|
|
130
|
+
/** Components copied, the requested ones and everything they render, sorted. */
|
|
131
|
+
components: string[];
|
|
132
|
+
/** Site-relative destination → path relative to the kit's `components/` directory. */
|
|
133
|
+
files: Record<string, string>;
|
|
134
|
+
/** npm packages the site needs, name → range. */
|
|
135
|
+
dependencies: Record<string, string>;
|
|
136
|
+
}
|
|
137
|
+
declare function planCopy(catalog: KitCatalog, ids: readonly string[]): CopyPlan;
|
|
138
|
+
/** Carry out a plan: `kitRoot` is the kit's `components/` directory, `siteRoot` the site. */
|
|
139
|
+
declare function copyComponents(plan: CopyPlan, kitRoot: string, siteRoot: string): Promise<void>;
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/index.d.ts
|
|
142
|
+
/** The kit's `components/` directory — the source `copyComponents` copies from. */
|
|
143
|
+
declare const KIT_COMPONENTS_DIR: string;
|
|
144
|
+
/** A builder's mapping table (`mapping/<builder>.json`). */
|
|
145
|
+
declare function loadMapping(builder: KitBuilder): Promise<MappingTable>;
|
|
146
|
+
/** The published catalog (`catalog.json`). */
|
|
147
|
+
declare function loadCatalog(): Promise<KitCatalog>;
|
|
148
|
+
//#endregion
|
|
149
|
+
export { BUILDER_PREFIX, CATALOG_FORMAT, CopyPlan, KIT_BUILDERS, KIT_COMPONENTS_DIR, KIT_DIR, KitBuilder, KitCatalog, KitCategory, KitComponent, KitComponentMeta, KitJs, KitProp, KitVariant, MAPPING_FORMAT, MAPPING_VALUE_PATTERN, MappingNode, MappingRule, MappingTable, claimMatches, componentsForSource, copyComponents, isBuilderElement, loadCatalog, loadMapping, planCopy, ruleMatches, rulesFor, unmappedSources, validateCatalog, validateMapping };
|
|
150
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/catalog.ts","../src/mapping.ts","../src/copy.ts","../src/index.ts"],"mappings":";;;cAWa,cAAA;;KAGD,UAAA;AAAA,cACC,YAAA,WAAuB,UAAA;;cAGvB,cAAA,EAAgB,MAAA,CAAO,UAAA;;AAJpC;;;;iBAgBgB,gBAAA,CAAiB,OAAA,EAAS,UAAA,EAAY,IAAA;AAAA,KAK1C,WAAA;;KAGA,KAAA;AAAA,KAEA,OAAA,GAAU,QAAA;EAAa,WAAA;AAAA;AAAA,UAElB,UAAA;EACf,OAAA;EACA,OAAA;EACA,WAAA;AAAA;;UAIe,gBAAA;EACf,EAAA;EACA,IAAA;EACA,WAAA;EACA,QAAA,EAAU,WAAA;EAvBsD;EAyBhE,IAAA;EApBU;EAsBV,MAAA;EACA,QAAA,EAAU,MAAA,SAAe,UAAA;EACzB,KAAA,EAAO,MAAA,SAAe,OAAA;EAxBD;EA0BrB,KAAA,EAAO,MAAA;EACP,EAAA,EAAI,KAAA;;EAEJ,YAAA,EAAc,MAAA;EA1BC;EA4Bf,OAAA,EAAS,OAAA,CAAQ,MAAA,CAAO,UAAA;EA1BP;EA4BjB,IAAA;AAAA;;UAIe,YAAA,SAAqB,gBAAA;EA9BX;EAgCzB,KAAA;AAAA;AAAA,UAGe,UAAA;EACf,MAAA,SAAe,cAAA;EAjCf;EAmCA,MAAA;EAnCW;EAqCX,YAAA,EAAc,MAAA;EACd,UAAA,EAAY,YAAA;AAAA;;iBAIE,eAAA,CAAgB,OAAA,EAAS,UAAA;;iBA6BzB,mBAAA,CAAoB,OAAA,EAAS,UAAA,EAAY,OAAA,WAAkB,YAAA;;;cC5F9D,cAAA;AAAA,cAEA,qBAAA,EAAqB,MAAA;AAAA,UAEjB,WAAA;EDjBU;ECmBzB,KAAA;EDnByB;ECqBzB,IAAA;IAAS,IAAA,GAAO,MAAA;IAA2C,KAAA;EAAA;EDlBvC;ECoBpB,SAAA;EACA,OAAA,GAAU,MAAA;EDpBoF;ECsB9F,IAAA;EDtBkC;ECwBlC,KAAA;EACA,KAAA,GAAQ,MAAA;EDjBT;ECmBC,IAAA;EDxB2B;EC0B3B,IAAA;EDdc;ECgBd,IAAA,GAAO,MAAA;AAAA;AAAA,UAGQ,YAAA;EACf,MAAA,SAAe,cAAA;EACf,OAAA,EAAS,UAAA;EDrB2C;ECuBpD,OAAA;EDvBgE;ECyBhE,QAAA;EACA,KAAA,EAAO,WAAA;AAAA;;iBAIO,eAAA,CAAgB,KAAA,EAAO,YAAA,EAAc,OAAA,EAAS,UAAA;ADtB9D;AAAA,iBC+DgB,eAAA,CAAgB,KAAA,EAAO,YAAA,EAAc,OAAA,EAAS,UAAA;;iBAO9C,QAAA,CAAS,KAAA,EAAO,YAAA,EAAc,OAAA,WAAkB,WAAA;;UAO/C,WAAA;EACf,IAAA;EACA,KAAA,GAAQ,MAAA;EACR,QAAA,GAAW,WAAA;AAAA;AD5Eb;AAAA,iBCgFgB,WAAA,CAAY,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,WAAA;;;;;iBAerC,YAAA,CAAa,KAAA,EAAO,WAAA,IAAe,KAAA,EAAO,YAAA,EAAc,IAAA;EAAe,IAAA;EAAc,IAAA,EAAM,WAAA;EAAa,IAAA,EAAM,WAAA;AAAA;;;;cC9HjH,OAAA;AAAA,UAEI,QAAA;EFFU;EEIzB,UAAA;EFJyB;EEMzB,KAAA,EAAO,MAAA;EFHG;EEKV,YAAA,EAAc,MAAA;AAAA;AAAA,iBAGA,QAAA,CAAS,OAAA,EAAS,UAAA,EAAY,GAAA,sBAAyB,QAAA;;iBAwBjD,cAAA,CAAe,IAAA,EAAM,QAAA,EAAU,OAAA,UAAiB,QAAA,WAAmB,OAAA;;;;cCjC5E,kBAAA;;iBAGS,WAAA,CAAY,OAAA,EAAS,UAAA,GAAa,OAAA,CAAQ,YAAA;;iBAK1C,WAAA,CAAA,GAAe,OAAA,CAAQ,UAAA"}
|