@contentrain/astro-kit 0.19.0 → 0.21.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 +34 -1
- package/components/footer/Footer.astro +35 -10
- package/components/footer/fixtures.json +3 -1
- package/components/footer/meta.json +34 -1
- package/mapping/gutenberg.json +11 -2
- package/package.json +2 -2
package/catalog.json
CHANGED
|
@@ -1154,6 +1154,22 @@
|
|
|
1154
1154
|
],
|
|
1155
1155
|
"default": "start",
|
|
1156
1156
|
"description": "start: brand and links to the sides (left-aligned on phones). center: everything centred in one column, as themes with a centred footer print it."
|
|
1157
|
+
},
|
|
1158
|
+
"pack": {
|
|
1159
|
+
"options": [
|
|
1160
|
+
"spread",
|
|
1161
|
+
"end"
|
|
1162
|
+
],
|
|
1163
|
+
"default": "spread",
|
|
1164
|
+
"description": "columns layout only. spread: the link columns share the footer's width after the brand column. end: they are packed at the end edge at their own width, as a theme whose footer is the brand left and its menus on the right."
|
|
1165
|
+
},
|
|
1166
|
+
"narrow": {
|
|
1167
|
+
"options": [
|
|
1168
|
+
"stack",
|
|
1169
|
+
"row"
|
|
1170
|
+
],
|
|
1171
|
+
"default": "stack",
|
|
1172
|
+
"description": "Packed columns only, below the md breakpoint. stack: one column. row: still side by side from the start edge, under the brand."
|
|
1157
1173
|
}
|
|
1158
1174
|
},
|
|
1159
1175
|
"props": {
|
|
@@ -1241,7 +1257,7 @@
|
|
|
1241
1257
|
},
|
|
1242
1258
|
"copyright": {
|
|
1243
1259
|
"type": "string",
|
|
1244
|
-
"description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName
|
|
1260
|
+
"description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName>; the component also takes false for no line (the cookie settings control stays)."
|
|
1245
1261
|
},
|
|
1246
1262
|
"consentLabel": {
|
|
1247
1263
|
"type": "string",
|
|
@@ -1250,6 +1266,23 @@
|
|
|
1250
1266
|
"navLabel": {
|
|
1251
1267
|
"type": "string",
|
|
1252
1268
|
"description": "Accessible name of the footer navigation (ui-strings nav.footer)."
|
|
1269
|
+
},
|
|
1270
|
+
"packGap": {
|
|
1271
|
+
"type": "string",
|
|
1272
|
+
"description": "Packed columns only: the space between columns, a CSS length such as 98px."
|
|
1273
|
+
},
|
|
1274
|
+
"packGapNarrow": {
|
|
1275
|
+
"type": "string",
|
|
1276
|
+
"description": "Packed columns in a row only: the space between them below the md breakpoint, a CSS length."
|
|
1277
|
+
},
|
|
1278
|
+
"linkSize": {
|
|
1279
|
+
"type": "string",
|
|
1280
|
+
"description": "Font size of the footer's links, a CSS length such as 17.88px; absent, the footer's own."
|
|
1281
|
+
},
|
|
1282
|
+
"border": {
|
|
1283
|
+
"type": "boolean",
|
|
1284
|
+
"content": false,
|
|
1285
|
+
"description": "A border on the footer's top side; absent, the default tone has one and the others do not."
|
|
1253
1286
|
}
|
|
1254
1287
|
},
|
|
1255
1288
|
"slots": {},
|
|
@@ -17,16 +17,25 @@ const footer = tv({
|
|
|
17
17
|
columns: { inner: 'grid gap-10 md:grid-cols-[2fr_repeat(var(--footer-columns),1fr)] md:py-16' },
|
|
18
18
|
},
|
|
19
19
|
tone: {
|
|
20
|
-
default: { root: '
|
|
20
|
+
default: { root: 'bg-surface text-ink' },
|
|
21
21
|
muted: { root: 'bg-surface-muted text-ink' },
|
|
22
22
|
inverse: { root: 'bg-ink text-surface' },
|
|
23
23
|
},
|
|
24
|
+
// The border on the footer's top side: the default tone's own unless `border` says otherwise.
|
|
25
|
+
border: { on: { root: 'border-t border-line' }, off: {} },
|
|
24
26
|
align: { start: {}, center: { inner: 'items-center justify-items-center text-center md:items-center [&_ul]:justify-center' } },
|
|
27
|
+
// The `columns` layout's link columns: spread from the brand column over the footer's width, or packed at the end
|
|
28
|
+
// edge at their own width (a theme whose footer is the brand left and its menus on the right).
|
|
29
|
+
pack: { spread: {}, end: {} },
|
|
30
|
+
// Packed columns below `md`: stacked, or still side by side from the start edge under the brand.
|
|
31
|
+
narrow: { stack: {}, row: { inner: 'grid-cols-[repeat(var(--footer-columns),auto)] justify-start gap-x-[var(--footer-gap-narrow,1.5rem)]' } },
|
|
25
32
|
},
|
|
26
33
|
compoundVariants: [
|
|
27
34
|
{ layout: 'simple', align: 'center', class: { inner: 'md:flex-col md:justify-center' } },
|
|
35
|
+
{ layout: 'columns', pack: 'end', class: { inner: 'md:grid-cols-[1fr_repeat(var(--footer-columns),auto)] md:gap-x-[var(--footer-gap,2.5rem)] md:[&>div:first-child]:col-span-1' } },
|
|
36
|
+
{ layout: 'columns', pack: 'end', narrow: 'row', class: { inner: '[&>div:first-child]:col-span-full' } },
|
|
28
37
|
],
|
|
29
|
-
defaultVariants: { layout: 'simple', tone: 'default', align: 'start' },
|
|
38
|
+
defaultVariants: { layout: 'simple', tone: 'default', align: 'start', pack: 'spread', narrow: 'stack', border: 'off' },
|
|
30
39
|
})
|
|
31
40
|
|
|
32
41
|
interface Props {
|
|
@@ -39,8 +48,8 @@ interface Props {
|
|
|
39
48
|
social?: LinkInput[] | undefined
|
|
40
49
|
/** Contact details printed in an `<address>` block; each is optional, and the block is left out when none is set. */
|
|
41
50
|
contact?: { address?: string | undefined, phone?: string | undefined, email?: string | undefined } | undefined
|
|
42
|
-
/** The copyright line, e.g. "© 2026 Northwind Studio"; `{year}` is the current year. */
|
|
43
|
-
copyright?: string | undefined
|
|
51
|
+
/** The copyright line, e.g. "© 2026 Northwind Studio"; `{year}` is the current year; `false` prints none. Absent: "© year Site". */
|
|
52
|
+
copyright?: string | false | undefined
|
|
44
53
|
/** Label of the cookie settings link after the copyright line ("Cookie settings"); it reopens the consent bar, so a visitor can withdraw consent as easily as they gave it. Only with a consent bar on the page. */
|
|
45
54
|
consentLabel?: string | undefined
|
|
46
55
|
/** Accessible name of the footer navigation ("Footer"). */
|
|
@@ -48,18 +57,34 @@ interface Props {
|
|
|
48
57
|
layout?: 'simple' | 'columns' | undefined
|
|
49
58
|
tone?: 'default' | 'muted' | 'inverse' | undefined
|
|
50
59
|
align?: 'start' | 'center' | undefined
|
|
60
|
+
/** `columns` layout only: `end` packs the link columns at the end edge, right-aligned, instead of spreading them. */
|
|
61
|
+
pack?: 'spread' | 'end' | undefined
|
|
62
|
+
/** Packed columns only: the space between columns, a CSS length (`98px`). */
|
|
63
|
+
packGap?: string | undefined
|
|
64
|
+
/** Packed columns only: below `md`, `stack` (one column) or `row` (still side by side from the start edge, under the brand). */
|
|
65
|
+
narrow?: 'stack' | 'row' | undefined
|
|
66
|
+
/** Packed columns in a `row` only: the space between them below `md`, a CSS length. */
|
|
67
|
+
packGapNarrow?: string | undefined
|
|
68
|
+
/** The links' font size, a CSS length (`17.88px`); absent, the footer's own. */
|
|
69
|
+
linkSize?: string | undefined
|
|
70
|
+
/** A border on the footer's top side: absent, the default tone has one and the others do not. */
|
|
71
|
+
border?: boolean | undefined
|
|
51
72
|
}
|
|
52
73
|
|
|
53
|
-
const { siteName, tagline, columns = [], links = [], social = [], contact, copyright, consentLabel, navLabel = 'Footer', layout = 'simple', tone, align } = Astro.props
|
|
54
|
-
const { root, inner, muted, link } = footer({ layout, tone, align })
|
|
74
|
+
const { siteName, tagline, columns = [], links = [], social = [], contact, copyright, consentLabel, navLabel = 'Footer', layout = 'simple', tone, align, pack, packGap, narrow, packGapNarrow, linkSize, border } = Astro.props
|
|
75
|
+
const { root, inner, muted, link } = footer({ layout, tone, align, pack: layout === 'columns' ? pack : undefined, narrow: layout === 'columns' && pack === 'end' ? narrow : undefined, border: (border ?? (tone ?? 'default') === 'default') ? 'on' : 'off' })
|
|
76
|
+
// Custom properties the packed columns and sized links read; none when none is set.
|
|
77
|
+
const vars = [layout === 'columns' ? `--footer-columns: ${Math.max(columns.length, 1)}` : '', layout === 'columns' && pack === 'end' && packGap ? `--footer-gap: ${packGap}` : '', layout === 'columns' && pack === 'end' && narrow === 'row' && packGapNarrow ? `--footer-gap-narrow: ${packGapNarrow}` : '', linkSize ? `--footer-link-size: ${linkSize}` : ''].filter(Boolean).join('; ')
|
|
55
78
|
const year = String(new Date().getFullYear())
|
|
56
79
|
const phoneHref = contact?.phone?.replace(/[^\d+]/g, '')
|
|
57
80
|
const hasContact = Boolean(contact?.address || contact?.phone || contact?.email)
|
|
58
|
-
const line = copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
|
|
81
|
+
const line = copyright === false ? undefined : copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
|
|
82
|
+
// The line and the cookie settings control share one paragraph; neither: no paragraph.
|
|
83
|
+
const hasBottom = Boolean(line || consentLabel)
|
|
59
84
|
---
|
|
60
85
|
<!-- The cookie settings control is a button, not a link: the Consent component's listener reopens its bar on any [data-kit-consent-open]. -->
|
|
61
86
|
<footer class={root()}>
|
|
62
|
-
<div class={inner()} data-kit-footer style={
|
|
87
|
+
<div class:list={[inner(), linkSize && '[&_nav_a]:text-[length:var(--footer-link-size)]']} data-kit-footer style={vars || undefined}>
|
|
63
88
|
<div class="flex flex-col gap-3">
|
|
64
89
|
<p class="text-base font-semibold" data-kit-brand>{siteName}</p>
|
|
65
90
|
{tagline && <p class={muted()} data-kit-tagline>{tagline}</p>}
|
|
@@ -70,7 +95,7 @@ const line = copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
|
|
|
70
95
|
{contact?.email && <a href={`mailto:${contact.email}`} class={link()}>{contact.email}</a>}
|
|
71
96
|
</address>
|
|
72
97
|
)}
|
|
73
|
-
{layout === 'columns' && <p class:list={[muted(), 'mt-auto pt-6']}>{line}{consentLabel && <> · <button type="button" data-kit-consent-open class={link()}>{consentLabel}</button></>}</p>}
|
|
98
|
+
{layout === 'columns' && hasBottom && <p class:list={[muted(), 'mt-auto pt-6']}>{line}{consentLabel && <>{line && ' · '}<button type="button" data-kit-consent-open class={link()}>{consentLabel}</button></>}</p>}
|
|
74
99
|
</div>
|
|
75
100
|
{layout === 'columns'
|
|
76
101
|
? columns.map(column => (
|
|
@@ -95,7 +120,7 @@ const line = copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
|
|
|
95
120
|
</ul>
|
|
96
121
|
</nav>
|
|
97
122
|
)}
|
|
98
|
-
<p class={muted()}>{line}{consentLabel && <> · <button type="button" data-kit-consent-open class={link()}>{consentLabel}</button></>}</p>
|
|
123
|
+
{hasBottom && <p class={muted()}>{line}{consentLabel && <>{line && ' · '}<button type="button" data-kit-consent-open class={link()}>{consentLabel}</button></>}</p>}
|
|
99
124
|
</div>
|
|
100
125
|
)}
|
|
101
126
|
{layout === 'columns' && social.length > 0 && (
|
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
{ "name": "columns-inverse", "props": { "siteName": "Northwind Studio", "tagline": "Calm, fast websites for teams who write.", "layout": "columns", "tone": "inverse", "copyright": "© 2026 Northwind Studio", "columns": [{ "title": "Studio", "links": [{ "label": "About", "href": "/about/" }, { "label": "Team", "href": "/about/team/" }] }, { "title": "Journal", "links": [{ "label": "Guides", "href": "/category/guides/" }, { "label": "News", "href": "/category/news/" }] }, { "title": "Contact", "links": [{ "label": "Email", "href": "mailto:hello@example.com" }] }], "social": [{ "label": "GitHub", "href": "https://github.com/contentrain" }, { "label": "Mastodon", "href": "https://mastodon.social/@contentrain" }] } },
|
|
4
4
|
{ "name": "muted", "props": { "siteName": "Northwind", "tone": "muted", "copyright": "© 2026 Northwind", "links": [{ "label": "Imprint", "href": "/imprint/" }] } },
|
|
5
5
|
{ "name": "centered", "props": { "siteName": "Golden Agency", "tagline": "Websites for makers", "align": "center", "copyright": "© 2026 Golden Agency", "links": [{ "label": "Services", "href": "/services/" }, { "label": "Contact", "href": "/contact/" }], "social": [{ "label": "Instagram", "href": "https://instagram.com/" }] } },
|
|
6
|
-
{ "name": "consent-settings", "props": { "siteName": "Golden Agency", "copyright": "© 2026 Golden Agency", "consentLabel": "Cookie settings", "links": [{ "label": "Privacy", "href": "/privacy/" }] } }
|
|
6
|
+
{ "name": "consent-settings", "props": { "siteName": "Golden Agency", "copyright": "© 2026 Golden Agency", "consentLabel": "Cookie settings", "links": [{ "label": "Privacy", "href": "/privacy/" }] } },
|
|
7
|
+
{ "name": "columns-end", "props": { "siteName": "Golden Works", "tagline": "Websites for makers", "layout": "columns", "pack": "end", "packGap": "128px", "linkSize": "17.88px", "copyright": "© 2026 Golden Works", "columns": [{ "label": "Studio", "links": [{ "label": "About", "href": "/about/" }, { "label": "Work", "href": "/work/" }] }, { "label": "Contact", "links": [{ "label": "Email", "href": "mailto:hello@example.com" }, { "label": "Press", "href": "/press/" }] }] } },
|
|
8
|
+
{ "name": "columns-end-row", "props": { "siteName": "Golden Works", "layout": "columns", "pack": "end", "narrow": "row", "packGap": "128px", "packGapNarrow": "48px", "linkSize": "17.88px", "copyright": "© 2026 Golden Works", "columns": [{ "label": "Studio", "links": [{ "label": "About", "href": "/about/" }, { "label": "Work", "href": "/work/" }] }, { "label": "Contact", "links": [{ "label": "Email", "href": "mailto:hello@example.com" }] }] } }
|
|
7
9
|
]
|
|
@@ -32,6 +32,22 @@
|
|
|
32
32
|
],
|
|
33
33
|
"default": "start",
|
|
34
34
|
"description": "start: brand and links to the sides (left-aligned on phones). center: everything centred in one column, as themes with a centred footer print it."
|
|
35
|
+
},
|
|
36
|
+
"pack": {
|
|
37
|
+
"options": [
|
|
38
|
+
"spread",
|
|
39
|
+
"end"
|
|
40
|
+
],
|
|
41
|
+
"default": "spread",
|
|
42
|
+
"description": "columns layout only. spread: the link columns share the footer's width after the brand column. end: they are packed at the end edge at their own width, as a theme whose footer is the brand left and its menus on the right."
|
|
43
|
+
},
|
|
44
|
+
"narrow": {
|
|
45
|
+
"options": [
|
|
46
|
+
"stack",
|
|
47
|
+
"row"
|
|
48
|
+
],
|
|
49
|
+
"default": "stack",
|
|
50
|
+
"description": "Packed columns only, below the md breakpoint. stack: one column. row: still side by side from the start edge, under the brand."
|
|
35
51
|
}
|
|
36
52
|
},
|
|
37
53
|
"props": {
|
|
@@ -110,7 +126,7 @@
|
|
|
110
126
|
},
|
|
111
127
|
"copyright": {
|
|
112
128
|
"type": "string",
|
|
113
|
-
"description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName
|
|
129
|
+
"description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName>; the component also takes false for no line (the cookie settings control stays)."
|
|
114
130
|
},
|
|
115
131
|
"consentLabel": {
|
|
116
132
|
"type": "string",
|
|
@@ -119,6 +135,23 @@
|
|
|
119
135
|
"navLabel": {
|
|
120
136
|
"type": "string",
|
|
121
137
|
"description": "Accessible name of the footer navigation (ui-strings nav.footer)."
|
|
138
|
+
},
|
|
139
|
+
"packGap": {
|
|
140
|
+
"type": "string",
|
|
141
|
+
"description": "Packed columns only: the space between columns, a CSS length such as 98px."
|
|
142
|
+
},
|
|
143
|
+
"packGapNarrow": {
|
|
144
|
+
"type": "string",
|
|
145
|
+
"description": "Packed columns in a row only: the space between them below the md breakpoint, a CSS length."
|
|
146
|
+
},
|
|
147
|
+
"linkSize": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"description": "Font size of the footer's links, a CSS length such as 17.88px; absent, the footer's own."
|
|
150
|
+
},
|
|
151
|
+
"border": {
|
|
152
|
+
"type": "boolean",
|
|
153
|
+
"content": false,
|
|
154
|
+
"description": "A border on the footer's top side; absent, the default tone has one and the others do not."
|
|
122
155
|
}
|
|
123
156
|
},
|
|
124
157
|
"slots": {},
|
package/mapping/gutenberg.json
CHANGED
|
@@ -552,7 +552,11 @@
|
|
|
552
552
|
},
|
|
553
553
|
"when": {
|
|
554
554
|
"position": "first",
|
|
555
|
-
"columns": 1
|
|
555
|
+
"columns": 1,
|
|
556
|
+
"order": [
|
|
557
|
+
"title",
|
|
558
|
+
"media"
|
|
559
|
+
]
|
|
556
560
|
},
|
|
557
561
|
"slots": {
|
|
558
562
|
"title": {
|
|
@@ -567,11 +571,16 @@
|
|
|
567
571
|
"button": {
|
|
568
572
|
"match": "core/button",
|
|
569
573
|
"max": 2
|
|
574
|
+
},
|
|
575
|
+
"media": {
|
|
576
|
+
"match": "core/image",
|
|
577
|
+
"max": 1
|
|
570
578
|
}
|
|
571
579
|
},
|
|
572
580
|
"props": {
|
|
573
581
|
"heading": "@title dom:",
|
|
574
|
-
"lead": "@lead dom:"
|
|
582
|
+
"lead": "@lead dom:",
|
|
583
|
+
"image": "@media img:img"
|
|
575
584
|
},
|
|
576
585
|
"into": "actions",
|
|
577
586
|
"each": "@button",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/astro-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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.
|
|
45
|
+
"@contentrain/types": "1.39.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^22.10.0",
|