@contentrain/astro-kit 0.17.0 → 0.19.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/catalog.json CHANGED
@@ -520,7 +520,15 @@
520
520
  "stacked"
521
521
  ],
522
522
  "default": "split",
523
- "description": "split: details beside the form on wide screens. stacked: one centred column."
523
+ "description": "split: details beside the form on wide screens. stacked: one column (see align)."
524
+ },
525
+ "align": {
526
+ "options": [
527
+ "center",
528
+ "start"
529
+ ],
530
+ "default": "center",
531
+ "description": "Stacked only, and the details alone before a Studio form. center: one centred column at a reading width. start: the section's whole column from the start edge, as a page builder's container whose text sets no alignment."
524
532
  },
525
533
  "tone": {
526
534
  "options": [
@@ -653,10 +661,11 @@
653
661
  "muted",
654
662
  "inverse",
655
663
  "accent",
664
+ "brand",
656
665
  "none"
657
666
  ],
658
667
  "default": "accent",
659
- "description": "Background. On accent, primary buttons are drawn in the secondary style so they stay visible. none: no fill of its own; the page background shows through (a source colour the palette does not have)."
668
+ "description": "Background. On accent, primary buttons are drawn in the secondary style so they stay visible. brand: the site's brand colour role (`--color-brand`, the builder's own colour on a band) with its ink; buttons keep their style. none: no fill of its own; the page background shows through (a source colour the palette does not have)."
660
669
  },
661
670
  "actionsAlign": {
662
671
  "options": [
@@ -3432,6 +3441,14 @@
3432
3441
  ],
3433
3442
  "default": "auto",
3434
3443
  "description": "The quote's text size. auto: the layout's own (body size in a grid, 2xl–3xl for a single quote). base, lg, xl: that size on every width."
3444
+ },
3445
+ "frame": {
3446
+ "options": [
3447
+ "card",
3448
+ "plain"
3449
+ ],
3450
+ "default": "card",
3451
+ "description": "card: each quote in its box (a bordered, padded card in the grid; the surface colour in a muted section). plain: the quote and who said it straight on the section, across the column, with no box, as Elementor's testimonial widget draws them."
3435
3452
  }
3436
3453
  },
3437
3454
  "props": {
@@ -12,6 +12,8 @@ const section = tv({
12
12
  muted: 'bg-surface-muted text-ink',
13
13
  inverse: 'bg-ink text-surface',
14
14
  accent: 'bg-accent text-accent-ink',
15
+ // The site's brand colour where it is not the accent (a band the source paints in the builder's own colour).
16
+ brand: 'bg-brand text-brand-ink',
15
17
  // No fill of its own: the page's background and text colour show through.
16
18
  none: '',
17
19
  },
@@ -28,7 +30,7 @@ const section = tv({
28
30
  export type Tone = 'default' | 'muted' | 'inverse' | 'accent'
29
31
 
30
32
  interface Props {
31
- tone?: Tone | 'none' | undefined
33
+ tone?: Tone | 'brand' | 'none' | undefined
32
34
  spacing?: 'none' | 'sm' | 'md' | 'lg' | undefined
33
35
  /** `page` is the full frame, `prose` the reading column, `none` for full-bleed content. */
34
36
  width?: 'page' | 'prose' | 'none' | undefined
@@ -37,12 +37,18 @@ const contact = tv({
37
37
  variants: {
38
38
  layout: {
39
39
  split: { grid: 'md:grid-cols-[2fr_3fr] md:items-start' },
40
- stacked: { grid: 'mx-auto max-w-2xl' },
40
+ stacked: {},
41
41
  },
42
+ // The stacked column: centred in a reading width, or the section's whole column from its start edge (a page
43
+ // builder's container whose text has no alignment of its own sits left, full width).
44
+ align: { center: {}, start: {} },
42
45
  // accent/accent-ink is only ~5:1 — dimming text on it drops body copy under 4.5:1 (WCAG 1.4.3), so accent keeps it at full strength.
43
46
  tone: { default: { form: 'border border-line bg-surface p-6 md:p-8' }, muted: { form: 'bg-surface p-6 text-ink md:p-8' }, inverse: { form: 'bg-surface p-6 text-ink md:p-8' }, accent: { form: 'bg-surface p-6 text-ink md:p-8', intro: 'opacity-100', term: 'opacity-100' } },
44
47
  },
45
- defaultVariants: { layout: 'split', tone: 'default' },
48
+ compoundVariants: [
49
+ { layout: 'stacked', align: 'center', class: { grid: 'mx-auto max-w-2xl' } },
50
+ ],
51
+ defaultVariants: { layout: 'split', tone: 'default', align: 'center' },
46
52
  })
47
53
 
48
54
  interface Props {
@@ -54,14 +60,16 @@ interface Props {
54
60
  formHeading?: string | undefined
55
61
  details?: ContactDetail[] | undefined
56
62
  layout?: 'split' | 'stacked' | undefined
63
+ /** Stacked only (and the details alone, before a Studio form): a centred column, or the whole column from the start edge. */
64
+ align?: 'center' | 'start' | undefined
57
65
  tone?: 'default' | 'muted' | 'inverse' | 'accent' | undefined
58
66
  id?: string | undefined
59
67
  }
60
68
 
61
- const { model, heading, intro, formHeading, details = [], layout, tone, id = 'contact' } = Astro.props
69
+ const { model, heading, intro, formHeading, details = [], layout, align, tone, id = 'contact' } = Astro.props
62
70
  // Nothing to render (Studio not bound yet): a split would leave an empty column — the details stand alone.
63
71
  const formShown = formHome(model) !== null
64
- const s = contact({ layout: formShown ? layout : 'stacked', tone })
72
+ const s = contact({ layout: formShown ? layout : 'stacked', align, tone })
65
73
  const headingId = heading ? `${id}-title` : undefined
66
74
  // A description list needs a term for every detail; an icon list carried from the source has values only.
67
75
  const labelled = details.every(detail => detail.label)
@@ -13,6 +13,7 @@
13
13
  }
14
14
  },
15
15
  { "name": "stacked-muted", "props": { "model": "contact-submissions", "heading": "Write to us", "layout": "stacked", "tone": "muted", "id": "write" } },
16
+ { "name": "stacked-start", "props": { "model": "contact-submissions", "intro": "Tell us what you make and when you want to launch.", "details": [{ "value": "Studio 4, Harbour Lane" }], "layout": "stacked", "align": "start", "id": "start" } },
16
17
  { "name": "split-inverse", "props": { "model": "contact-submissions", "heading": "Press", "tone": "inverse", "id": "press", "details": [{ "label": "Press office", "value": "press@northwind.example", "href": "mailto:press@northwind.example" }] } },
17
18
  { "name": "stacked-accent", "props": { "model": "newsletter-requests", "heading": "Request a print copy", "layout": "stacked", "tone": "accent", "id": "print" } },
18
19
  { "name": "form-heading", "props": { "model": "contact-submissions", "heading": "Contact", "intro": "Tell us where you are and what you need.", "formHeading": "Tell us about your project", "details": [{ "label": "Address", "value": "12 Harbour Street, Leith" }] } }
@@ -6,7 +6,8 @@
6
6
  "uses": [],
7
7
  "shared": ["Section.astro"],
8
8
  "variants": {
9
- "layout": { "options": ["split", "stacked"], "default": "split", "description": "split: details beside the form on wide screens. stacked: one centred column." },
9
+ "layout": { "options": ["split", "stacked"], "default": "split", "description": "split: details beside the form on wide screens. stacked: one column (see align)." },
10
+ "align": { "options": ["center", "start"], "default": "center", "description": "Stacked only, and the details alone before a Studio form. center: one centred column at a reading width. start: the section's whole column from the start edge, as a page builder's container whose text sets no alignment." },
10
11
  "tone": { "options": ["default", "muted", "inverse", "accent"], "default": "default", "description": "Section background; the form always sits on a surface card." }
11
12
  },
12
13
  "props": {
@@ -27,7 +27,7 @@ const cta = tv({
27
27
  },
28
28
  // accent/accent-ink is only ~5:1 — dimming text on it drops body copy under 4.5:1 (WCAG 1.4.3), so accent keeps it at full strength.
29
29
  // none: no fill of its own — the page background and text colour show through.
30
- tone: { default: {}, muted: {}, inverse: {}, accent: { body: 'opacity-100' }, none: {} },
30
+ tone: { default: {}, muted: {}, inverse: {}, accent: { body: 'opacity-100' }, brand: { body: 'opacity-100' }, none: {} },
31
31
  // The buttons' own alignment: `start` keeps them at the start edge under centred text (a WordPress buttons block
32
32
  // with no justification sits left in a group whose heading and paragraph are centred). The row is the text's
33
33
  // column, as WordPress's content width is, not the whole section. Only centred needs it: inline and stacked
@@ -40,6 +40,7 @@ const cta = tv({
40
40
  { frame: 'card', tone: 'muted', class: { box: 'border-transparent bg-surface-muted text-ink' } },
41
41
  { frame: 'card', tone: 'inverse', class: { box: 'border-transparent bg-ink text-surface' } },
42
42
  { frame: 'card', tone: 'accent', class: { box: 'border-transparent bg-accent text-accent-ink' } },
43
+ { frame: 'card', tone: 'brand', class: { box: 'border-transparent bg-brand text-brand-ink' } },
43
44
  { layout: 'centered', actionsAlign: 'start', class: { actions: 'w-full max-w-2xl justify-start' } },
44
45
  ],
45
46
  defaultVariants: { layout: 'inline', frame: 'full', tone: 'accent', actionsAlign: 'inherit' },
@@ -51,7 +52,7 @@ interface Props {
51
52
  actions: ActionInput[]
52
53
  layout?: 'inline' | 'centered' | 'stacked' | undefined
53
54
  frame?: 'full' | 'card' | undefined
54
- tone?: 'default' | 'muted' | 'inverse' | 'accent' | 'none' | undefined
55
+ tone?: 'default' | 'muted' | 'inverse' | 'accent' | 'brand' | 'none' | undefined
55
56
  /** The buttons' alignment: as the layout sets it, or at the start edge under centred text (layout centered). */
56
57
  actionsAlign?: 'inherit' | 'start' | undefined
57
58
  id?: string | undefined
@@ -4,5 +4,6 @@
4
4
  { "name": "card-default", "props": { "heading": "Write for the journal", "body": "We publish reader essays every season.", "frame": "card", "tone": "default", "actions": [{ "label": "Pitch an essay", "href": "/pitch/" }] } },
5
5
  { "name": "card-muted", "props": { "heading": "Support independent travel writing", "frame": "card", "tone": "muted", "layout": "centered", "actions": [{ "label": "Become a member", "href": "/members/" }] } },
6
6
  { "name": "stacked-none", "props": { "heading": "Ready when you are", "body": "Tell us where you want to go and we will send a first plan within a week.", "layout": "stacked", "tone": "none", "actions": [{ "label": "Start planning", "href": "/contact/" }] } },
7
- { "name": "centered-actions-start", "props": { "heading": "See what is on this week", "body": "New films every Thursday, and a late show on Saturdays.", "layout": "centered", "tone": "inverse", "actionsAlign": "start", "actions": [{ "label": "Programme", "href": "/programme/" }] } }
7
+ { "name": "centered-actions-start", "props": { "heading": "See what is on this week", "body": "New films every Thursday, and a late show on Saturdays.", "layout": "centered", "tone": "inverse", "actionsAlign": "start", "actions": [{ "label": "Programme", "href": "/programme/" }] } },
8
+ { "name": "centered-brand", "props": { "heading": "Ready when you are", "body": "Two launch slots left this quarter.", "layout": "centered", "tone": "brand", "actions": [{ "label": "Book a launch call", "href": "/contact/" }] } }
8
9
  ]
@@ -8,7 +8,7 @@
8
8
  "variants": {
9
9
  "layout": { "options": ["inline", "centered", "stacked"], "default": "inline", "description": "inline: text left, buttons right on wide screens. centered: everything centred. stacked: heading, text and buttons in one column at the start edge on every width." },
10
10
  "frame": { "options": ["full", "card"], "default": "full", "description": "full: the tone fills the section. card: a rounded box in a plain section carries the tone." },
11
- "tone": { "options": ["default", "muted", "inverse", "accent", "none"], "default": "accent", "description": "Background. On accent, primary buttons are drawn in the secondary style so they stay visible. none: no fill of its own; the page background shows through (a source colour the palette does not have)." },
11
+ "tone": { "options": ["default", "muted", "inverse", "accent", "brand", "none"], "default": "accent", "description": "Background. On accent, primary buttons are drawn in the secondary style so they stay visible. brand: the site's brand colour role (`--color-brand`, the builder's own colour on a band) with its ink; buttons keep their style. none: no fill of its own; the page background shows through (a source colour the palette does not have)." },
12
12
  "actionsAlign": { "options": ["inherit", "start"], "default": "inherit", "description": "The buttons' alignment. inherit: as the layout sets it. start: at the start edge under centred heading and text (layout centered; a WordPress buttons block with no justification in a centred group). inline and stacked ignore it." }
13
13
  },
14
14
  "props": {
@@ -29,8 +29,10 @@ const faq = tv({
29
29
  plain: { list: 'gap-2', summary: 'list-item list-[revert] py-1 text-[length:var(--text-body,1rem)] [font-weight:inherit] [&::-webkit-details-marker]:inline', marker: 'hidden', answer: 'pt-2 ps-5' },
30
30
  },
31
31
  tone: { default: {}, muted: { item: 'bg-surface' }, inverse: { answer: 'prose-invert' }, accent: { answer: 'prose-invert' } },
32
- // heading: the question as a small heading. bold: body size in bold, as accordion and toggle titles often are.
33
- questionStyle: { heading: {}, bold: { summary: 'text-[length:var(--text-body,1rem)] font-bold' } },
32
+ // heading: the question as a small heading. bold: body size in bold, as accordion and toggle titles often are. The
33
+ // weight is an arbitrary property like the summary's own: twMerge replaces one only with another, and a `font-bold`
34
+ // beside it lost in the CSS (IP-4 elementor G9: the question stayed at the heading weight).
35
+ questionStyle: { heading: {}, bold: { summary: 'text-[length:var(--text-body,1rem)] [font-weight:700]' } },
34
36
  },
35
37
  compoundVariants: [
36
38
  { style: 'cards', tone: 'muted', class: { item: 'border-transparent' } },
@@ -39,11 +39,15 @@ const testimonial = tv({
39
39
  lg: { quote: 'text-lg leading-relaxed md:text-lg' },
40
40
  xl: { quote: 'text-xl leading-snug md:text-xl' },
41
41
  },
42
+ // card: a quote in its box (the grid's border and padding, a muted section's surface). plain: the quote and who said
43
+ // it on the section itself, across the column, as Elementor's testimonial widget draws them.
44
+ frame: { card: {}, plain: {} },
42
45
  },
43
46
  compoundVariants: [
44
47
  { layout: 'grid', tone: 'muted', class: { card: 'border-transparent text-ink' } },
48
+ { frame: 'plain', class: { card: 'rounded-none border-0 bg-transparent p-0' } },
45
49
  ],
46
- defaultVariants: { layout: 'grid', tone: 'default', quoteSize: 'auto' },
50
+ defaultVariants: { layout: 'grid', tone: 'default', quoteSize: 'auto', frame: 'card' },
47
51
  })
48
52
 
49
53
  interface Props {
@@ -51,12 +55,13 @@ interface Props {
51
55
  heading?: string | undefined
52
56
  layout?: 'single' | 'grid' | undefined
53
57
  quoteSize?: 'auto' | 'base' | 'lg' | 'xl' | undefined
58
+ frame?: 'card' | 'plain' | undefined
54
59
  tone?: 'default' | 'muted' | 'inverse' | 'accent' | undefined
55
60
  id?: string | undefined
56
61
  }
57
62
 
58
- const { items, heading, layout, quoteSize, tone, id } = Astro.props
59
- const s = testimonial({ layout, quoteSize, tone })
63
+ const { items, heading, layout, quoteSize, frame, tone, id } = Astro.props
64
+ const s = testimonial({ layout, quoteSize, frame, tone })
60
65
  const headingId = heading && id ? `${id}-title` : undefined
61
66
  // Two quotes share the row: a third column would stand empty beside them and narrow both (IP-4 elementor launch G9).
62
67
  const list = s.list(items.length === 2 && (layout ?? 'grid') === 'grid' ? { class: 'lg:grid-cols-2' } : {})
@@ -35,6 +35,14 @@
35
35
  "name": "single-quote-lg",
36
36
  "props": { "layout": "single", "quoteSize": "lg", "items": [{ "quote": "We planned less and saw more.", "name": "Jonas Weber", "role": "Reader" }] }
37
37
  },
38
+ {
39
+ "name": "grid-plain",
40
+ "props": { "frame": "plain", "quoteSize": "xl", "items": [{ "quote": "We sold out our first kiln batch on launch day.", "name": "Ruth Adeyemi", "role": "Potter" }, { "quote": "Six weeks, one price, and they answered every email.", "name": "Daniel Okafor", "role": "Leatherworker" }] }
41
+ },
42
+ {
43
+ "name": "single-plain-muted",
44
+ "props": { "layout": "single", "frame": "plain", "tone": "muted", "items": [{ "quote": "Calm pages and a fast checkout.", "name": "Ruth Adeyemi" }] }
45
+ },
38
46
  {
39
47
  "name": "grid-source-quoted",
40
48
  "props": { "items": [{ "quote": "“We set it up before lunch.”", "name": "Grace Lin", "role": "Head of Support, Fernhill" }, { "quote": "People just read the page.", "name": "Omar Haddad", "role": "Support lead" }] }
@@ -38,6 +38,14 @@
38
38
  ],
39
39
  "default": "auto",
40
40
  "description": "The quote's text size. auto: the layout's own (body size in a grid, 2xl–3xl for a single quote). base, lg, xl: that size on every width."
41
+ },
42
+ "frame": {
43
+ "options": [
44
+ "card",
45
+ "plain"
46
+ ],
47
+ "default": "card",
48
+ "description": "card: each quote in its box (a bordered, padded card in the grid; the surface colour in a muted section). plain: the quote and who said it straight on the section, across the column, with no box, as Elementor's testimonial widget draws them."
41
49
  }
42
50
  },
43
51
  "props": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "format": "astro-kit-mapping@1",
3
3
  "builder": "elementor",
4
- "version": "10",
4
+ "version": "11",
5
5
  "fallback": "prose",
6
6
  "defaults": {
7
7
  "elementor/video": {
@@ -276,6 +276,9 @@
276
276
  {
277
277
  "match": "elementor/testimonial",
278
278
  "component": "testimonial",
279
+ "variant": {
280
+ "frame": "plain"
281
+ },
279
282
  "group": "siblings",
280
283
  "into": "items",
281
284
  "item": {
@@ -855,7 +858,8 @@
855
858
  "id": "testimonial.quotes",
856
859
  "component": "testimonial",
857
860
  "variant": {
858
- "quoteSize": "xl"
861
+ "quoteSize": "xl",
862
+ "frame": "plain"
859
863
  },
860
864
  "slots": {
861
865
  "title": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentrain/astro-kit",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
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.36.0"
45
+ "@contentrain/types": "1.37.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.10.0",