@contentrain/astro-kit 0.29.4 → 0.31.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 +25 -2
- package/catalog.json +1055 -7
- package/components/_shared/KitImage.astro +6 -4
- package/components/_shared/Section.astro +18 -3
- package/components/_shared/measured.ts +142 -0
- package/components/_shared/types.ts +40 -0
- package/components/band/Band.astro +132 -0
- package/components/band/fixtures.json +8 -0
- package/components/band/meta.json +323 -0
- package/components/card-grid/CardGrid.astro +7 -3
- package/components/card-grid/fixtures.json +2 -1
- package/components/card-grid/meta.json +111 -0
- package/components/contact-details/ContactDetails.astro +7 -2
- package/components/contact-details/meta.json +113 -1
- package/components/contact-form/meta.json +1 -1
- package/components/cta/meta.json +1 -1
- package/components/faq/meta.json +1 -1
- package/components/feature-list/FeatureList.astro +7 -3
- package/components/feature-list/fixtures.json +2 -1
- package/components/feature-list/meta.json +113 -1
- package/components/gallery/meta.json +1 -1
- package/components/hero/Hero.astro +14 -8
- package/components/hero/fixtures.json +2 -1
- package/components/hero/meta.json +111 -0
- package/components/logo-cloud/meta.json +1 -0
- package/components/nav/Nav.astro +7 -1
- package/components/newsletter/Newsletter.astro +5 -1
- package/components/newsletter/meta.json +113 -1
- package/components/post-card/PostCard.astro +9 -2
- package/components/post-card/meta.json +4 -1
- package/components/pricing/meta.json +1 -0
- package/components/split/meta.json +1 -0
- package/components/stats/meta.json +3 -1
- package/components/steps/meta.json +1 -0
- package/components/tabs/Tabs.astro +5 -2
- package/components/tabs/meta.json +2 -1
- package/components/team/meta.json +1 -0
- package/components/testimonial/meta.json +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "band",
|
|
3
|
+
"name": "Band",
|
|
4
|
+
"description": "The styled fallback section: an optional heading, lead, image, a grid of items and buttons, in the section's measured frame and fill. Used where no closer component fits (an unrecognised or low-confidence section), so it still reads as a designed section, never bare text.",
|
|
5
|
+
"category": "section",
|
|
6
|
+
"uses": [],
|
|
7
|
+
"shared": [
|
|
8
|
+
"Button.astro",
|
|
9
|
+
"KitImage.astro",
|
|
10
|
+
"Section.astro",
|
|
11
|
+
"measured.ts",
|
|
12
|
+
"types.ts"
|
|
13
|
+
],
|
|
14
|
+
"variants": {
|
|
15
|
+
"layout": {
|
|
16
|
+
"options": [
|
|
17
|
+
"stack",
|
|
18
|
+
"split"
|
|
19
|
+
],
|
|
20
|
+
"default": "stack",
|
|
21
|
+
"description": "stack: one column. split: the copy beside the image on wide screens (only with an image)."
|
|
22
|
+
},
|
|
23
|
+
"columns": {
|
|
24
|
+
"options": [
|
|
25
|
+
"1",
|
|
26
|
+
"2",
|
|
27
|
+
"3",
|
|
28
|
+
"4"
|
|
29
|
+
],
|
|
30
|
+
"default": "3",
|
|
31
|
+
"description": "Item columns on wide screens when nothing is measured; one on phones, two on tablets."
|
|
32
|
+
},
|
|
33
|
+
"align": {
|
|
34
|
+
"options": [
|
|
35
|
+
"start",
|
|
36
|
+
"center"
|
|
37
|
+
],
|
|
38
|
+
"default": "start",
|
|
39
|
+
"description": "Alignment of the copy and the items."
|
|
40
|
+
},
|
|
41
|
+
"tone": {
|
|
42
|
+
"options": [
|
|
43
|
+
"default",
|
|
44
|
+
"muted",
|
|
45
|
+
"inverse",
|
|
46
|
+
"accent"
|
|
47
|
+
],
|
|
48
|
+
"default": "default",
|
|
49
|
+
"description": "Section background when nothing is measured."
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"props": {
|
|
53
|
+
"heading": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"label": "Heading",
|
|
56
|
+
"description": "Section heading."
|
|
57
|
+
},
|
|
58
|
+
"eyebrow": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"label": "Eyebrow",
|
|
61
|
+
"description": "Short line above the heading."
|
|
62
|
+
},
|
|
63
|
+
"lead": {
|
|
64
|
+
"type": "text",
|
|
65
|
+
"label": "Lead",
|
|
66
|
+
"description": "Paragraph under the heading."
|
|
67
|
+
},
|
|
68
|
+
"media": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"label": "Image",
|
|
71
|
+
"fields": {
|
|
72
|
+
"src": {
|
|
73
|
+
"type": "image",
|
|
74
|
+
"label": "Image",
|
|
75
|
+
"required": true,
|
|
76
|
+
"description": "The image file."
|
|
77
|
+
},
|
|
78
|
+
"alt": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"label": "Image description",
|
|
81
|
+
"description": "What the image shows, for readers who cannot see it; empty when it is decoration."
|
|
82
|
+
},
|
|
83
|
+
"width": {
|
|
84
|
+
"type": "integer",
|
|
85
|
+
"label": "Width",
|
|
86
|
+
"description": "Width in pixels, as uploaded."
|
|
87
|
+
},
|
|
88
|
+
"height": {
|
|
89
|
+
"type": "integer",
|
|
90
|
+
"label": "Height",
|
|
91
|
+
"description": "Height in pixels, as uploaded."
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"description": "The section's image, under or beside the copy."
|
|
95
|
+
},
|
|
96
|
+
"items": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"label": "Items",
|
|
99
|
+
"items": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"fields": {
|
|
102
|
+
"value": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"label": "Figure",
|
|
105
|
+
"description": "A figure drawn large above the title (a stat with its prefix or suffix, a price)."
|
|
106
|
+
},
|
|
107
|
+
"title": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"label": "Title",
|
|
110
|
+
"description": "The item heading."
|
|
111
|
+
},
|
|
112
|
+
"meta": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"label": "Detail",
|
|
115
|
+
"description": "A short line under the title (a role, a date, a period)."
|
|
116
|
+
},
|
|
117
|
+
"text": {
|
|
118
|
+
"type": "text",
|
|
119
|
+
"label": "Text",
|
|
120
|
+
"description": "A sentence or two under the title."
|
|
121
|
+
},
|
|
122
|
+
"href": {
|
|
123
|
+
"type": "url",
|
|
124
|
+
"label": "Link",
|
|
125
|
+
"description": "Where the item goes; without a link text the whole item is the link."
|
|
126
|
+
},
|
|
127
|
+
"linkLabel": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"label": "Link text",
|
|
130
|
+
"description": "The link's own text, shown under the item."
|
|
131
|
+
},
|
|
132
|
+
"media": {
|
|
133
|
+
"type": "image",
|
|
134
|
+
"label": "Image",
|
|
135
|
+
"description": "Image on top of the item."
|
|
136
|
+
},
|
|
137
|
+
"mediaAlt": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"label": "Image description",
|
|
140
|
+
"description": "What the image shows, for readers who cannot see it; empty when it is decoration."
|
|
141
|
+
},
|
|
142
|
+
"icon": {
|
|
143
|
+
"type": "image",
|
|
144
|
+
"label": "Icon",
|
|
145
|
+
"description": "A small image above the title (decoration)."
|
|
146
|
+
},
|
|
147
|
+
"points": {
|
|
148
|
+
"type": "array",
|
|
149
|
+
"label": "Points",
|
|
150
|
+
"items": "string",
|
|
151
|
+
"description": "A short list under the text (features, facts)."
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"description": "Items in a grid (cards, features, logos), in order."
|
|
156
|
+
},
|
|
157
|
+
"actions": {
|
|
158
|
+
"type": "array",
|
|
159
|
+
"label": "Buttons",
|
|
160
|
+
"items": {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"fields": {
|
|
163
|
+
"label": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"label": "Label",
|
|
166
|
+
"required": true,
|
|
167
|
+
"description": "The button text."
|
|
168
|
+
},
|
|
169
|
+
"href": {
|
|
170
|
+
"type": "url",
|
|
171
|
+
"label": "Link",
|
|
172
|
+
"required": true,
|
|
173
|
+
"description": "Where the button goes: a page on this site or a full address."
|
|
174
|
+
},
|
|
175
|
+
"newTab": {
|
|
176
|
+
"type": "boolean",
|
|
177
|
+
"label": "Open in a new tab",
|
|
178
|
+
"description": "Open the link in a new tab (an external site the reader should not leave for)."
|
|
179
|
+
},
|
|
180
|
+
"style": {
|
|
181
|
+
"type": "select",
|
|
182
|
+
"label": "Style",
|
|
183
|
+
"options": [
|
|
184
|
+
"primary",
|
|
185
|
+
"secondary",
|
|
186
|
+
"ghost"
|
|
187
|
+
],
|
|
188
|
+
"description": "primary for the main action, secondary or ghost for the ones beside it."
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"description": "Buttons, in order."
|
|
193
|
+
},
|
|
194
|
+
"headingLevel": {
|
|
195
|
+
"type": "integer",
|
|
196
|
+
"min": 2,
|
|
197
|
+
"max": 3,
|
|
198
|
+
"default": 2,
|
|
199
|
+
"content": false,
|
|
200
|
+
"description": "Level of the section heading; item titles are one below, or at this level when there is no heading."
|
|
201
|
+
},
|
|
202
|
+
"measured": {
|
|
203
|
+
"type": "object",
|
|
204
|
+
"content": false,
|
|
205
|
+
"description": "The source section's measured style (MeasuredStyle): frame width, padding, fill, image, columns per width, gap, text tone, radius, minimum height — all in pixels. Absent, the site's tokens; present, it wins over tone and columns. Values that are not a number, a colour or a known keyword are dropped.",
|
|
206
|
+
"fields": {
|
|
207
|
+
"containerPx": {
|
|
208
|
+
"type": "integer",
|
|
209
|
+
"min": 0,
|
|
210
|
+
"max": 2560,
|
|
211
|
+
"description": "The frame's content width."
|
|
212
|
+
},
|
|
213
|
+
"padY": {
|
|
214
|
+
"type": "integer",
|
|
215
|
+
"min": 0,
|
|
216
|
+
"max": 400,
|
|
217
|
+
"description": "Top and bottom padding."
|
|
218
|
+
},
|
|
219
|
+
"padX": {
|
|
220
|
+
"type": "integer",
|
|
221
|
+
"min": 0,
|
|
222
|
+
"max": 200,
|
|
223
|
+
"description": "The frame's side padding (gutter)."
|
|
224
|
+
},
|
|
225
|
+
"bg": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"description": "The section's background.",
|
|
228
|
+
"fields": {
|
|
229
|
+
"color": {
|
|
230
|
+
"type": "string",
|
|
231
|
+
"description": "Fill colour: hex, rgb(a) or hsl(a)."
|
|
232
|
+
},
|
|
233
|
+
"image": {
|
|
234
|
+
"type": "string",
|
|
235
|
+
"description": "Background image address (http(s) or root-relative), drawn behind the content."
|
|
236
|
+
},
|
|
237
|
+
"overlay": {
|
|
238
|
+
"type": "string",
|
|
239
|
+
"description": "Colour or linear/radial gradient laid over the image or the fill."
|
|
240
|
+
},
|
|
241
|
+
"size": {
|
|
242
|
+
"type": "string",
|
|
243
|
+
"description": "cover, contain or auto; anything else draws as cover."
|
|
244
|
+
},
|
|
245
|
+
"position": {
|
|
246
|
+
"type": "string",
|
|
247
|
+
"description": "One or two of center/top/bottom/left/right or percentages."
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"columns": {
|
|
252
|
+
"type": "object",
|
|
253
|
+
"description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.",
|
|
254
|
+
"fields": {
|
|
255
|
+
"390": {
|
|
256
|
+
"type": "integer",
|
|
257
|
+
"min": 1,
|
|
258
|
+
"max": 6,
|
|
259
|
+
"description": "Phone."
|
|
260
|
+
},
|
|
261
|
+
"768": {
|
|
262
|
+
"type": "integer",
|
|
263
|
+
"min": 1,
|
|
264
|
+
"max": 6,
|
|
265
|
+
"description": "Tablet."
|
|
266
|
+
},
|
|
267
|
+
"1280": {
|
|
268
|
+
"type": "integer",
|
|
269
|
+
"min": 1,
|
|
270
|
+
"max": 6,
|
|
271
|
+
"description": "Desktop."
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"gap": {
|
|
276
|
+
"type": "integer",
|
|
277
|
+
"min": 0,
|
|
278
|
+
"max": 200,
|
|
279
|
+
"description": "Gap between items."
|
|
280
|
+
},
|
|
281
|
+
"tone": {
|
|
282
|
+
"type": "select",
|
|
283
|
+
"options": [
|
|
284
|
+
"light",
|
|
285
|
+
"dark"
|
|
286
|
+
],
|
|
287
|
+
"description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides."
|
|
288
|
+
},
|
|
289
|
+
"align": {
|
|
290
|
+
"type": "select",
|
|
291
|
+
"options": [
|
|
292
|
+
"start",
|
|
293
|
+
"center",
|
|
294
|
+
"end"
|
|
295
|
+
],
|
|
296
|
+
"description": "Where the heading and lead sit; read by a component whose own align is not set."
|
|
297
|
+
},
|
|
298
|
+
"radius": {
|
|
299
|
+
"type": "integer",
|
|
300
|
+
"min": 0,
|
|
301
|
+
"max": 200,
|
|
302
|
+
"description": "Card and item corner radius."
|
|
303
|
+
},
|
|
304
|
+
"minHeight": {
|
|
305
|
+
"type": "integer",
|
|
306
|
+
"min": 0,
|
|
307
|
+
"max": 2000,
|
|
308
|
+
"description": "The section's minimum height."
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"id": {
|
|
313
|
+
"type": "string",
|
|
314
|
+
"content": false,
|
|
315
|
+
"description": "Anchor id of the section; the heading gets `<id>-title`."
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
"slots": {},
|
|
319
|
+
"js": "none",
|
|
320
|
+
"dependencies": {},
|
|
321
|
+
"sources": {},
|
|
322
|
+
"a11y": "A labelled region (aria-labelledby its heading) when it has one. Item titles sit one level under the heading. An item with a link and no link text is clickable as a whole through its title's link; icons are decoration (alt=\"\")."
|
|
323
|
+
}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { tv } from 'tailwind-variants'
|
|
5
5
|
import KitImage from '../_shared/KitImage.astro'
|
|
6
6
|
import Section, { type Tone } from '../_shared/Section.astro'
|
|
7
|
+
import { measuredGrid } from '../_shared/measured'
|
|
8
|
+
import type { MeasuredStyle } from '../_shared/types'
|
|
7
9
|
import { imageOf, type ImageSource } from '../_shared/image'
|
|
8
10
|
|
|
9
11
|
const grid = tv({
|
|
@@ -50,9 +52,11 @@ interface Props {
|
|
|
50
52
|
style?: 'plain' | 'bordered' | 'elevated' | undefined
|
|
51
53
|
align?: 'start' | 'center' | undefined
|
|
52
54
|
tone?: Tone | undefined
|
|
55
|
+
/** The source section's measured style (`MeasuredStyle`): frame, padding, fill, background image, columns per width, gap. Absent, the site's tokens; present, it wins over tone and columns. */
|
|
56
|
+
measured?: MeasuredStyle | undefined
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
const { title, intro, items, headingLevel = 2, columns, style, align, tone } = Astro.props
|
|
59
|
+
const { title, intro, items, headingLevel = 2, columns, style, align, tone, measured } = Astro.props
|
|
56
60
|
const { list, card, media, title: cardTitle, text } = grid({ columns, style, align })
|
|
57
61
|
// Accent backgrounds have the least contrast headroom: text on them is never dimmed.
|
|
58
62
|
const dim = tone === 'accent' ? 'opacity-100' : undefined
|
|
@@ -61,14 +65,14 @@ const Heading = `h${headingLevel}` as 'h2' | 'h3'
|
|
|
61
65
|
const CardHeading = `h${title ? headingLevel + 1 : headingLevel}` as 'h2' | 'h3' | 'h4'
|
|
62
66
|
const headingId = title ? `cards-${title.toLowerCase().replace(/[^a-z0-9]+/g, '-')}` : undefined
|
|
63
67
|
---
|
|
64
|
-
<Section tone={tone} aria-labelledby={headingId}>
|
|
68
|
+
<Section measured={measured} tone={tone} aria-labelledby={headingId}>
|
|
65
69
|
{(title || intro) && (
|
|
66
70
|
<header class:list={['mb-10 flex max-w-2xl flex-col gap-3', align === 'center' && 'mx-auto items-center text-center']}>
|
|
67
71
|
{title && <Heading id={headingId} class="text-3xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-4xl">{title}</Heading>}
|
|
68
72
|
{intro && <p class:list={['text-lg', tone === 'accent' ? undefined : 'opacity-80']}>{intro}</p>}
|
|
69
73
|
</header>
|
|
70
74
|
)}
|
|
71
|
-
<ul class={list()}>
|
|
75
|
+
<ul class={list({ class: measuredGrid(measured) })}>
|
|
72
76
|
{items.map(item => (
|
|
73
77
|
<li class={card()}>
|
|
74
78
|
{imageOf(item.image, item.imageAlt) && <div class={media()}><KitImage image={imageOf(item.image, item.imageAlt)!} sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw" class="aspect-[3/2] w-full object-cover transition duration-300 group-hover:scale-[1.02]" /></div>}
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
{ "name": "three-plain", "props": { "title": "What we do", "intro": "Three things, done carefully.", "items": [{ "title": "Design systems", "text": "Tokens, components and documentation your team keeps using.", "href": "/services/design/", "image": { "src": "/kit-fixtures/photo-1.svg", "alt": "", "width": 1200, "height": 800 } }, { "title": "Migrations", "text": "WordPress to Astro without losing an address or a ranking.", "href": "/services/migrations/", "image": { "src": "/kit-fixtures/photo-2.svg", "alt": "", "width": 1200, "height": 800 } }, { "title": "Performance", "text": "Measured, budgeted and kept fast after launch.", "href": "/services/performance/", "image": { "src": "/kit-fixtures/photo-3.svg", "alt": "", "width": 1200, "height": 800 } }] } },
|
|
3
3
|
{ "name": "two-bordered-center", "props": { "columns": "2", "style": "bordered", "align": "center", "tone": "muted", "title": "Plans", "items": [{ "eyebrow": "Starter", "title": "One site", "text": "Everything you need for a single site." }, { "eyebrow": "Team", "title": "Many sites", "text": "Shared components across a portfolio." }] } },
|
|
4
4
|
{ "name": "four-elevated-inverse", "props": { "columns": "4", "style": "elevated", "tone": "inverse", "headingLevel": 3, "title": "Team", "items": [{ "title": "Mira Kovač", "text": "Editor", "image": { "src": "/kit-fixtures/avatar-1.svg", "alt": "Mira Kovač", "width": 96, "height": 96 } }, { "title": "Jon Ade", "text": "Engineer", "image": { "src": "/kit-fixtures/avatar-2.svg", "alt": "Jon Ade", "width": 96, "height": 96 } }, { "title": "Lea Park", "text": "Designer", "image": { "src": "/kit-fixtures/avatar-3.svg", "alt": "Lea Park", "width": 96, "height": 96 } }, { "title": "Sam Ortiz", "text": "Producer" }] } },
|
|
5
|
-
{ "name": "accent", "props": { "tone": "accent", "columns": "3", "items": [{ "title": "Fast", "text": "Static HTML, one stylesheet." }, { "title": "Editable", "text": "Content in Contentrain Studio." }, { "title": "Kept", "text": "Every address survives." }] } }
|
|
5
|
+
{ "name": "accent", "props": { "tone": "accent", "columns": "3", "items": [{ "title": "Fast", "text": "Static HTML, one stylesheet." }, { "title": "Editable", "text": "Content in Contentrain Studio." }, { "title": "Kept", "text": "Every address survives." }] } },
|
|
6
|
+
{"name": "measured-columns", "props": {"title": "Services", "measured": {"containerPx": 1140, "padY": 80, "columns": {"390": 1, "768": 2, "1280": 4}, "gap": 30, "radius": 4, "bg": {"color": "#f4f1ea"}}, "items": [{"title": "Audits", "text": "Where a site loses readers."}, {"title": "Builds", "text": "Static and fast."}, {"title": "Hosting", "text": "Managed for you."}, {"title": "Care", "text": "Kept up to date."}]}}
|
|
6
7
|
]
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"KitImage.astro",
|
|
9
9
|
"Section.astro",
|
|
10
10
|
"image.ts",
|
|
11
|
+
"measured.ts",
|
|
11
12
|
"types.ts"
|
|
12
13
|
],
|
|
13
14
|
"variants": {
|
|
@@ -108,6 +109,116 @@
|
|
|
108
109
|
"default": 2,
|
|
109
110
|
"content": false,
|
|
110
111
|
"description": "Level of the section heading; card titles are one below, or at this level when there is no title."
|
|
112
|
+
},
|
|
113
|
+
"measured": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"content": false,
|
|
116
|
+
"description": "The source section's measured style (MeasuredStyle): frame width, padding, fill, image, columns per width, gap, text tone, radius, minimum height — all in pixels. Absent, the site's tokens; present, it wins over tone and columns. Values that are not a number, a colour or a known keyword are dropped.",
|
|
117
|
+
"fields": {
|
|
118
|
+
"containerPx": {
|
|
119
|
+
"type": "integer",
|
|
120
|
+
"min": 0,
|
|
121
|
+
"max": 2560,
|
|
122
|
+
"description": "The frame's content width."
|
|
123
|
+
},
|
|
124
|
+
"padY": {
|
|
125
|
+
"type": "integer",
|
|
126
|
+
"min": 0,
|
|
127
|
+
"max": 400,
|
|
128
|
+
"description": "Top and bottom padding."
|
|
129
|
+
},
|
|
130
|
+
"padX": {
|
|
131
|
+
"type": "integer",
|
|
132
|
+
"min": 0,
|
|
133
|
+
"max": 200,
|
|
134
|
+
"description": "The frame's side padding (gutter)."
|
|
135
|
+
},
|
|
136
|
+
"bg": {
|
|
137
|
+
"type": "object",
|
|
138
|
+
"description": "The section's background.",
|
|
139
|
+
"fields": {
|
|
140
|
+
"color": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"description": "Fill colour: hex, rgb(a) or hsl(a)."
|
|
143
|
+
},
|
|
144
|
+
"image": {
|
|
145
|
+
"type": "string",
|
|
146
|
+
"description": "Background image address (http(s) or root-relative), drawn behind the content."
|
|
147
|
+
},
|
|
148
|
+
"overlay": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"description": "Colour or linear/radial gradient laid over the image or the fill."
|
|
151
|
+
},
|
|
152
|
+
"size": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"description": "cover, contain or auto; anything else draws as cover."
|
|
155
|
+
},
|
|
156
|
+
"position": {
|
|
157
|
+
"type": "string",
|
|
158
|
+
"description": "One or two of center/top/bottom/left/right or percentages."
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"columns": {
|
|
163
|
+
"type": "object",
|
|
164
|
+
"description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.",
|
|
165
|
+
"fields": {
|
|
166
|
+
"390": {
|
|
167
|
+
"type": "integer",
|
|
168
|
+
"min": 1,
|
|
169
|
+
"max": 6,
|
|
170
|
+
"description": "Phone."
|
|
171
|
+
},
|
|
172
|
+
"768": {
|
|
173
|
+
"type": "integer",
|
|
174
|
+
"min": 1,
|
|
175
|
+
"max": 6,
|
|
176
|
+
"description": "Tablet."
|
|
177
|
+
},
|
|
178
|
+
"1280": {
|
|
179
|
+
"type": "integer",
|
|
180
|
+
"min": 1,
|
|
181
|
+
"max": 6,
|
|
182
|
+
"description": "Desktop."
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"gap": {
|
|
187
|
+
"type": "integer",
|
|
188
|
+
"min": 0,
|
|
189
|
+
"max": 200,
|
|
190
|
+
"description": "Gap between items."
|
|
191
|
+
},
|
|
192
|
+
"tone": {
|
|
193
|
+
"type": "select",
|
|
194
|
+
"options": [
|
|
195
|
+
"light",
|
|
196
|
+
"dark"
|
|
197
|
+
],
|
|
198
|
+
"description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides."
|
|
199
|
+
},
|
|
200
|
+
"align": {
|
|
201
|
+
"type": "select",
|
|
202
|
+
"options": [
|
|
203
|
+
"start",
|
|
204
|
+
"center",
|
|
205
|
+
"end"
|
|
206
|
+
],
|
|
207
|
+
"description": "Where the heading and lead sit; read by a component whose own align is not set."
|
|
208
|
+
},
|
|
209
|
+
"radius": {
|
|
210
|
+
"type": "integer",
|
|
211
|
+
"min": 0,
|
|
212
|
+
"max": 200,
|
|
213
|
+
"description": "Card and item corner radius."
|
|
214
|
+
},
|
|
215
|
+
"minHeight": {
|
|
216
|
+
"type": "integer",
|
|
217
|
+
"min": 0,
|
|
218
|
+
"max": 2000,
|
|
219
|
+
"description": "The section's minimum height."
|
|
220
|
+
}
|
|
221
|
+
}
|
|
111
222
|
}
|
|
112
223
|
},
|
|
113
224
|
"slots": {},
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// widgets that list the same things by hand.
|
|
6
6
|
import { tv } from 'tailwind-variants'
|
|
7
7
|
import Section from '../_shared/Section.astro'
|
|
8
|
+
import { measuredGrid } from '../_shared/measured'
|
|
9
|
+
import type { MeasuredStyle } from '../_shared/types'
|
|
8
10
|
|
|
9
11
|
const details = tv({
|
|
10
12
|
slots: {
|
|
@@ -40,12 +42,15 @@ interface Props {
|
|
|
40
42
|
mapLabel?: string | undefined
|
|
41
43
|
layout?: 'list' | 'columns' | undefined
|
|
42
44
|
id?: string | undefined
|
|
45
|
+
/** The source section's measured style (`MeasuredStyle`): frame, padding, fill, background image, columns per width, gap. Absent, the site's tokens; present, it wins over tone and columns. */
|
|
46
|
+
measured?: MeasuredStyle | undefined
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
const {
|
|
46
50
|
heading, email, phone, address, hours, mapUrl,
|
|
47
51
|
emailLabel = 'Email', phoneLabel = 'Phone', addressLabel = 'Address', hoursLabel = 'Opening hours', mapLabel = 'View on the map',
|
|
48
52
|
layout, id = 'contact-details',
|
|
53
|
+
measured,
|
|
49
54
|
} = Astro.props
|
|
50
55
|
const s = details({ layout })
|
|
51
56
|
const headingId = heading ? `${id}-title` : undefined
|
|
@@ -54,9 +59,9 @@ const tel = phone ? `tel:${phone.replace(/[^\d+]/g, '')}` : undefined
|
|
|
54
59
|
const shown = Boolean(email || phone || address || hours || mapUrl)
|
|
55
60
|
---
|
|
56
61
|
{shown && (
|
|
57
|
-
<Section spacing="md" id={id} aria-labelledby={headingId}>
|
|
62
|
+
<Section measured={measured} spacing="md" id={id} aria-labelledby={headingId}>
|
|
58
63
|
{heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
|
|
59
|
-
<dl class={s.list()}>
|
|
64
|
+
<dl class={s.list({ class: measuredGrid(measured) })}>
|
|
60
65
|
{email && <div><dt class={s.term()}>{emailLabel}</dt><dd class={s.value()}><a href={`mailto:${email}`} class={s.link()}>{email}</a></dd></div>}
|
|
61
66
|
{phone && <div><dt class={s.term()}>{phoneLabel}</dt><dd class={s.value()}><a href={tel} class={s.link()}>{phone}</a></dd></div>}
|
|
62
67
|
{(address || mapUrl) && (
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
"category": "section",
|
|
6
6
|
"uses": [],
|
|
7
7
|
"shared": [
|
|
8
|
-
"Section.astro"
|
|
8
|
+
"Section.astro",
|
|
9
|
+
"measured.ts",
|
|
10
|
+
"types.ts"
|
|
9
11
|
],
|
|
10
12
|
"variants": {
|
|
11
13
|
"layout": {
|
|
@@ -82,6 +84,116 @@
|
|
|
82
84
|
"type": "string",
|
|
83
85
|
"content": false,
|
|
84
86
|
"description": "Section id; the heading gets `<id>-title`."
|
|
87
|
+
},
|
|
88
|
+
"measured": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"content": false,
|
|
91
|
+
"description": "The source section's measured style (MeasuredStyle): frame width, padding, fill, image, columns per width, gap, text tone, radius, minimum height — all in pixels. Absent, the site's tokens; present, it wins over tone and columns. Values that are not a number, a colour or a known keyword are dropped.",
|
|
92
|
+
"fields": {
|
|
93
|
+
"containerPx": {
|
|
94
|
+
"type": "integer",
|
|
95
|
+
"min": 0,
|
|
96
|
+
"max": 2560,
|
|
97
|
+
"description": "The frame's content width."
|
|
98
|
+
},
|
|
99
|
+
"padY": {
|
|
100
|
+
"type": "integer",
|
|
101
|
+
"min": 0,
|
|
102
|
+
"max": 400,
|
|
103
|
+
"description": "Top and bottom padding."
|
|
104
|
+
},
|
|
105
|
+
"padX": {
|
|
106
|
+
"type": "integer",
|
|
107
|
+
"min": 0,
|
|
108
|
+
"max": 200,
|
|
109
|
+
"description": "The frame's side padding (gutter)."
|
|
110
|
+
},
|
|
111
|
+
"bg": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"description": "The section's background.",
|
|
114
|
+
"fields": {
|
|
115
|
+
"color": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "Fill colour: hex, rgb(a) or hsl(a)."
|
|
118
|
+
},
|
|
119
|
+
"image": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Background image address (http(s) or root-relative), drawn behind the content."
|
|
122
|
+
},
|
|
123
|
+
"overlay": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"description": "Colour or linear/radial gradient laid over the image or the fill."
|
|
126
|
+
},
|
|
127
|
+
"size": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"description": "cover, contain or auto; anything else draws as cover."
|
|
130
|
+
},
|
|
131
|
+
"position": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"description": "One or two of center/top/bottom/left/right or percentages."
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"columns": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.",
|
|
140
|
+
"fields": {
|
|
141
|
+
"390": {
|
|
142
|
+
"type": "integer",
|
|
143
|
+
"min": 1,
|
|
144
|
+
"max": 6,
|
|
145
|
+
"description": "Phone."
|
|
146
|
+
},
|
|
147
|
+
"768": {
|
|
148
|
+
"type": "integer",
|
|
149
|
+
"min": 1,
|
|
150
|
+
"max": 6,
|
|
151
|
+
"description": "Tablet."
|
|
152
|
+
},
|
|
153
|
+
"1280": {
|
|
154
|
+
"type": "integer",
|
|
155
|
+
"min": 1,
|
|
156
|
+
"max": 6,
|
|
157
|
+
"description": "Desktop."
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"gap": {
|
|
162
|
+
"type": "integer",
|
|
163
|
+
"min": 0,
|
|
164
|
+
"max": 200,
|
|
165
|
+
"description": "Gap between items."
|
|
166
|
+
},
|
|
167
|
+
"tone": {
|
|
168
|
+
"type": "select",
|
|
169
|
+
"options": [
|
|
170
|
+
"light",
|
|
171
|
+
"dark"
|
|
172
|
+
],
|
|
173
|
+
"description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides."
|
|
174
|
+
},
|
|
175
|
+
"align": {
|
|
176
|
+
"type": "select",
|
|
177
|
+
"options": [
|
|
178
|
+
"start",
|
|
179
|
+
"center",
|
|
180
|
+
"end"
|
|
181
|
+
],
|
|
182
|
+
"description": "Where the heading and lead sit; read by a component whose own align is not set."
|
|
183
|
+
},
|
|
184
|
+
"radius": {
|
|
185
|
+
"type": "integer",
|
|
186
|
+
"min": 0,
|
|
187
|
+
"max": 200,
|
|
188
|
+
"description": "Card and item corner radius."
|
|
189
|
+
},
|
|
190
|
+
"minHeight": {
|
|
191
|
+
"type": "integer",
|
|
192
|
+
"min": 0,
|
|
193
|
+
"max": 2000,
|
|
194
|
+
"description": "The section's minimum height."
|
|
195
|
+
}
|
|
196
|
+
}
|
|
85
197
|
}
|
|
86
198
|
},
|
|
87
199
|
"slots": {},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "The contact section: heading, introduction, contact details (email, phone, address) and a Contentrain Studio form — fields, spam protection and notifications set in Studio, submissions reviewed there. Replaces form plugins; renders the details alone until the site is bound to a Studio project (a section left with only its heading is not drawn: the heading would title a form that is not there). When the owner chose another home at migration (site.config.ts `features.forms`) the form posts to their form service, or the section shows an address to write to or a link to the form on WordPress.",
|
|
5
5
|
"category": "section",
|
|
6
6
|
"uses": [],
|
|
7
|
-
"shared": ["Section.astro"],
|
|
7
|
+
"shared": ["Section.astro", "measured.ts", "types.ts"],
|
|
8
8
|
"variants": {
|
|
9
9
|
"layout": { "options": ["split", "stacked"], "default": "split", "description": "split: details beside the form on wide screens. stacked: one column (see align)." },
|
|
10
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." },
|