@contentrain/astro-kit 0.31.0 → 0.33.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.
Files changed (102) hide show
  1. package/catalog.json +4600 -1584
  2. package/components/_shared/Button.astro +2 -1
  3. package/components/_shared/KitImage.astro +9 -4
  4. package/components/_shared/Section.astro +3 -1
  5. package/components/_shared/SectionHeader.astro +27 -0
  6. package/components/_shared/measured.ts +23 -0
  7. package/components/_shared/safe-url.ts +67 -0
  8. package/components/_shared/social.ts +73 -0
  9. package/components/announcement/Announcement.astro +2 -1
  10. package/components/announcement/meta.json +1 -1
  11. package/components/band/Band.astro +3 -2
  12. package/components/band/meta.json +1 -0
  13. package/components/breadcrumb/Breadcrumb.astro +2 -1
  14. package/components/breadcrumb/meta.json +1 -1
  15. package/components/card-grid/CardGrid.astro +2 -1
  16. package/components/card-grid/meta.json +1 -0
  17. package/components/consent/Consent.astro +2 -1
  18. package/components/consent/meta.json +1 -1
  19. package/components/contact-details/ContactDetails.astro +3 -2
  20. package/components/contact-details/meta.json +1 -0
  21. package/components/contact-form/ContactForm.astro +51 -14
  22. package/components/contact-form/fixtures.json +169 -9
  23. package/components/contact-form/meta.json +284 -15
  24. package/components/cta/Cta.astro +52 -12
  25. package/components/cta/fixtures.json +5 -1
  26. package/components/cta/meta.json +11 -2
  27. package/components/dialog/Dialog.astro +113 -0
  28. package/components/dialog/fixtures.json +84 -0
  29. package/components/dialog/meta.json +252 -0
  30. package/components/embed/meta.json +1 -1
  31. package/components/faq/Faq.astro +60 -17
  32. package/components/faq/fixtures.json +79 -0
  33. package/components/faq/meta.json +9 -3
  34. package/components/feature-list/FeatureList.astro +28 -4
  35. package/components/feature-list/meta.json +16 -3
  36. package/components/footer/Footer.astro +5 -4
  37. package/components/footer/meta.json +1 -0
  38. package/components/gallery/Gallery.astro +49 -14
  39. package/components/gallery/fixtures.json +275 -0
  40. package/components/gallery/meta.json +267 -14
  41. package/components/header/Header.astro +17 -5
  42. package/components/header/fixtures.json +90 -1
  43. package/components/header/meta.json +5 -3
  44. package/components/hero/meta.json +1 -0
  45. package/components/language-switcher/LanguageSwitcher.astro +68 -0
  46. package/components/language-switcher/fixtures.json +51 -0
  47. package/components/language-switcher/meta.json +73 -0
  48. package/components/logo-cloud/LogoCloud.astro +86 -21
  49. package/components/logo-cloud/fixtures.json +239 -0
  50. package/components/logo-cloud/meta.json +145 -5
  51. package/components/nav/Nav.astro +77 -24
  52. package/components/nav/NavTree.astro +40 -0
  53. package/components/nav/fixtures.json +165 -0
  54. package/components/nav/meta.json +11 -2
  55. package/components/newsletter/Newsletter.astro +2 -1
  56. package/components/newsletter/meta.json +1 -0
  57. package/components/pagination/Pagination.astro +4 -3
  58. package/components/pagination/meta.json +1 -1
  59. package/components/post-card/PostCard.astro +8 -5
  60. package/components/post-card/meta.json +3 -2
  61. package/components/post-list/PostList.astro +115 -0
  62. package/components/post-list/fixtures.json +387 -0
  63. package/components/post-list/meta.json +309 -0
  64. package/components/pricing/Pricing.astro +128 -24
  65. package/components/pricing/fixtures.json +288 -4
  66. package/components/pricing/meta.json +188 -1
  67. package/components/search-box/SearchBox.astro +51 -0
  68. package/components/search-box/fixtures.json +31 -0
  69. package/components/search-box/meta.json +75 -0
  70. package/components/slider/Slider.astro +19 -115
  71. package/components/slider/SliderTrack.astro +178 -0
  72. package/components/slider/fixtures.json +2 -1
  73. package/components/slider/meta.json +11 -5
  74. package/components/social-links/SocialLinks.astro +74 -0
  75. package/components/social-links/fixtures.json +67 -0
  76. package/components/social-links/meta.json +94 -0
  77. package/components/split/Split.astro +76 -14
  78. package/components/split/fixtures.json +8 -1
  79. package/components/split/meta.json +226 -4
  80. package/components/stats/Stats.astro +54 -11
  81. package/components/stats/fixtures.json +260 -0
  82. package/components/stats/meta.json +151 -4
  83. package/components/steps/Steps.astro +36 -8
  84. package/components/steps/fixtures.json +217 -5
  85. package/components/steps/meta.json +157 -3
  86. package/components/tabs/Tabs.astro +10 -3
  87. package/components/tabs/fixtures.json +4 -0
  88. package/components/tabs/meta.json +5 -2
  89. package/components/team/Team.astro +54 -9
  90. package/components/team/fixtures.json +217 -4
  91. package/components/team/meta.json +169 -3
  92. package/components/team/team.ts +4 -0
  93. package/components/testimonial/Testimonial.astro +71 -21
  94. package/components/testimonial/fixtures.json +73 -0
  95. package/components/testimonial/meta.json +168 -6
  96. package/dist/index.mjs +1 -1
  97. package/dist/index.mjs.map +1 -1
  98. package/mapping/classic.json +124 -3
  99. package/mapping/divi.json +26 -8
  100. package/mapping/elementor.json +133 -14
  101. package/mapping/gutenberg.json +38 -10
  102. package/package.json +2 -2
@@ -187,5 +187,280 @@
187
187
  }
188
188
  ]
189
189
  }
190
+ },
191
+ {
192
+ "name": "row",
193
+ "props": {
194
+ "layout": "row",
195
+ "heading": "Strip",
196
+ "id": "strip",
197
+ "items": [
198
+ {
199
+ "image": {
200
+ "src": "/kit-fixtures/photo-1.svg",
201
+ "alt": "Photo 1",
202
+ "width": 1200,
203
+ "height": 800
204
+ },
205
+ "caption": "Porto at dawn"
206
+ },
207
+ {
208
+ "image": {
209
+ "src": "/kit-fixtures/photo-2.svg",
210
+ "alt": "Photo 2",
211
+ "width": 1200,
212
+ "height": 800
213
+ },
214
+ "caption": "Train to Coimbra"
215
+ },
216
+ {
217
+ "image": {
218
+ "src": "/kit-fixtures/photo-3.svg",
219
+ "alt": "Photo 3",
220
+ "width": 1200,
221
+ "height": 800
222
+ },
223
+ "caption": "Market day"
224
+ },
225
+ {
226
+ "image": {
227
+ "src": "/kit-fixtures/photo-4.svg",
228
+ "alt": "Photo 4",
229
+ "width": 1200,
230
+ "height": 800
231
+ },
232
+ "caption": "Harbour"
233
+ },
234
+ {
235
+ "image": {
236
+ "src": "/kit-fixtures/photo-5.svg",
237
+ "alt": "Photo 5",
238
+ "width": 1200,
239
+ "height": 800
240
+ },
241
+ "caption": "Old town"
242
+ },
243
+ {
244
+ "image": {
245
+ "src": "/kit-fixtures/photo-6.svg",
246
+ "alt": "Photo 6",
247
+ "width": 1200,
248
+ "height": 800
249
+ },
250
+ "caption": "Last light"
251
+ }
252
+ ]
253
+ }
254
+ },
255
+ {
256
+ "name": "mosaic-wide-overlay",
257
+ "props": {
258
+ "layout": "mosaic",
259
+ "aspect": "16:9",
260
+ "captionStyle": "overlay",
261
+ "columns": "4",
262
+ "items": [
263
+ {
264
+ "image": {
265
+ "src": "/kit-fixtures/photo-1.svg",
266
+ "alt": "Photo 1",
267
+ "width": 1200,
268
+ "height": 800
269
+ },
270
+ "caption": "Porto at dawn"
271
+ },
272
+ {
273
+ "image": {
274
+ "src": "/kit-fixtures/photo-2.svg",
275
+ "alt": "Photo 2",
276
+ "width": 1200,
277
+ "height": 800
278
+ },
279
+ "caption": "Train to Coimbra"
280
+ },
281
+ {
282
+ "image": {
283
+ "src": "/kit-fixtures/photo-3.svg",
284
+ "alt": "Photo 3",
285
+ "width": 1200,
286
+ "height": 800
287
+ },
288
+ "caption": "Market day"
289
+ },
290
+ {
291
+ "image": {
292
+ "src": "/kit-fixtures/photo-4.svg",
293
+ "alt": "Photo 4",
294
+ "width": 1200,
295
+ "height": 800
296
+ },
297
+ "caption": "Harbour"
298
+ },
299
+ {
300
+ "image": {
301
+ "src": "/kit-fixtures/photo-5.svg",
302
+ "alt": "Photo 5",
303
+ "width": 1200,
304
+ "height": 800
305
+ },
306
+ "caption": "Old town"
307
+ },
308
+ {
309
+ "image": {
310
+ "src": "/kit-fixtures/photo-6.svg",
311
+ "alt": "Photo 6",
312
+ "width": 1200,
313
+ "height": 800
314
+ },
315
+ "caption": "Last light"
316
+ }
317
+ ]
318
+ }
319
+ },
320
+ {
321
+ "name": "aspect-43-zoom",
322
+ "props": {
323
+ "aspect": "4:3",
324
+ "hoverEffect": "zoom",
325
+ "gap": "lg",
326
+ "columns": "5",
327
+ "items": [
328
+ {
329
+ "image": {
330
+ "src": "/kit-fixtures/photo-1.svg",
331
+ "alt": "Photo 1",
332
+ "width": 1200,
333
+ "height": 800
334
+ },
335
+ "caption": "Porto at dawn"
336
+ },
337
+ {
338
+ "image": {
339
+ "src": "/kit-fixtures/photo-2.svg",
340
+ "alt": "Photo 2",
341
+ "width": 1200,
342
+ "height": 800
343
+ },
344
+ "caption": "Train to Coimbra"
345
+ },
346
+ {
347
+ "image": {
348
+ "src": "/kit-fixtures/photo-3.svg",
349
+ "alt": "Photo 3",
350
+ "width": 1200,
351
+ "height": 800
352
+ },
353
+ "caption": "Market day"
354
+ },
355
+ {
356
+ "image": {
357
+ "src": "/kit-fixtures/photo-4.svg",
358
+ "alt": "Photo 4",
359
+ "width": 1200,
360
+ "height": 800
361
+ },
362
+ "caption": "Harbour"
363
+ },
364
+ {
365
+ "image": {
366
+ "src": "/kit-fixtures/photo-5.svg",
367
+ "alt": "Photo 5",
368
+ "width": 1200,
369
+ "height": 800
370
+ },
371
+ "caption": "Old town"
372
+ },
373
+ {
374
+ "image": {
375
+ "src": "/kit-fixtures/photo-6.svg",
376
+ "alt": "Photo 6",
377
+ "width": 1200,
378
+ "height": 800
379
+ },
380
+ "caption": "Last light"
381
+ }
382
+ ]
383
+ }
384
+ },
385
+ {
386
+ "name": "gap-small-auto",
387
+ "props": {
388
+ "aspect": "auto",
389
+ "gap": "sm",
390
+ "columns": "6",
391
+ "open": "link",
392
+ "items": [
393
+ {
394
+ "image": {
395
+ "src": "/kit-fixtures/photo-1.svg",
396
+ "alt": "Photo 1",
397
+ "width": 1200,
398
+ "height": 800
399
+ },
400
+ "caption": "Porto at dawn"
401
+ },
402
+ {
403
+ "image": {
404
+ "src": "/kit-fixtures/photo-2.svg",
405
+ "alt": "Photo 2",
406
+ "width": 1200,
407
+ "height": 800
408
+ },
409
+ "caption": "Train to Coimbra"
410
+ },
411
+ {
412
+ "image": {
413
+ "src": "/kit-fixtures/photo-3.svg",
414
+ "alt": "Photo 3",
415
+ "width": 1200,
416
+ "height": 800
417
+ },
418
+ "caption": "Market day"
419
+ },
420
+ {
421
+ "image": {
422
+ "src": "/kit-fixtures/photo-4.svg",
423
+ "alt": "Photo 4",
424
+ "width": 1200,
425
+ "height": 800
426
+ },
427
+ "caption": "Harbour"
428
+ },
429
+ {
430
+ "image": {
431
+ "src": "/kit-fixtures/photo-5.svg",
432
+ "alt": "Photo 5",
433
+ "width": 1200,
434
+ "height": 800
435
+ },
436
+ "caption": "Old town"
437
+ },
438
+ {
439
+ "image": {
440
+ "src": "/kit-fixtures/photo-6.svg",
441
+ "alt": "Photo 6",
442
+ "width": 1200,
443
+ "height": 800
444
+ },
445
+ "caption": "Last light"
446
+ }
447
+ ]
448
+ }
449
+ },
450
+ {
451
+ "name": "single",
452
+ "props": {
453
+ "items": [
454
+ {
455
+ "image": {
456
+ "src": "/kit-fixtures/photo-1.svg",
457
+ "alt": "Photo 1",
458
+ "width": 1200,
459
+ "height": 800
460
+ },
461
+ "caption": "Porto at dawn"
462
+ }
463
+ ]
464
+ }
190
465
  }
191
466
  ]
@@ -4,27 +4,280 @@
4
4
  "description": "Images in a square grid or in masonry columns, with optional captions. A click shows the image large in a native popover (no script), opens the image file, or does nothing.",
5
5
  "category": "section",
6
6
  "uses": [],
7
- "shared": ["KitImage.astro", "Section.astro", "image.ts", "measured.ts", "types.ts"],
7
+ "shared": [
8
+ "KitImage.astro",
9
+ "Section.astro",
10
+ "image.ts",
11
+ "measured.ts",
12
+ "safe-url.ts",
13
+ "types.ts"
14
+ ],
8
15
  "variants": {
9
- "layout": { "options": ["grid", "masonry"], "default": "grid", "description": "grid: square crops in rows. masonry: columns that keep each image's shape." },
10
- "columns": { "options": ["2", "3", "4"], "default": "3", "description": "Columns on wide screens; phones show two." },
11
- "open": { "options": ["popover", "link", "none"], "default": "popover", "description": "popover: the image opens large in place (Popover API; Escape or a click outside closes it). link: the thumbnail links to the image file (the WordPress 'link to media file'). none: images only." }
16
+ "layout": {
17
+ "options": [
18
+ "grid",
19
+ "masonry",
20
+ "row",
21
+ "mosaic"
22
+ ],
23
+ "default": "grid",
24
+ "description": "grid: equal tiles. masonry: columns keeping each image's height. row: a scroll-snap strip (a focusable region). mosaic: the first tile spans 2×2."
25
+ },
26
+ "columns": {
27
+ "options": [
28
+ "2",
29
+ "3",
30
+ "4",
31
+ "5",
32
+ "6"
33
+ ],
34
+ "default": "3",
35
+ "description": "Images per row on wide screens; two on phones."
36
+ },
37
+ "open": {
38
+ "options": [
39
+ "popover",
40
+ "link",
41
+ "none"
42
+ ],
43
+ "default": "popover",
44
+ "description": "popover: the image opens large in place (Popover API; Escape or a click outside closes it). link: the thumbnail links to the image file (the WordPress 'link to media file'). none: images only."
45
+ },
46
+ "aspect": {
47
+ "options": [
48
+ "square",
49
+ "4:3",
50
+ "16:9",
51
+ "auto"
52
+ ],
53
+ "default": "square",
54
+ "description": "Tile shape (a masonry keeps each image's own)."
55
+ },
56
+ "gap": {
57
+ "options": [
58
+ "sm",
59
+ "md",
60
+ "lg"
61
+ ],
62
+ "default": "md",
63
+ "description": "Space between tiles."
64
+ },
65
+ "captionStyle": {
66
+ "options": [
67
+ "below",
68
+ "overlay"
69
+ ],
70
+ "default": "below",
71
+ "description": "below: caption under the image. overlay: over the image foot on hover and keyboard focus, always shown below md."
72
+ },
73
+ "hoverEffect": {
74
+ "options": [
75
+ "none",
76
+ "zoom"
77
+ ],
78
+ "default": "none",
79
+ "description": "zoom: a slight scale on hover, only where reduced motion is not requested."
80
+ }
12
81
  },
13
82
  "props": {
14
- "items": { "type": "array", "label": "Images", "required": true, "items": { "type": "object", "fields": { "image": { "type": "image", "label": "Image", "required": true, "description": "The image." }, "imageAlt": { "type": "string", "label": "Image description", "description": "What the image shows, for readers who cannot see it." }, "caption": { "type": "string", "label": "Caption", "description": "Caption under the enlarged image." } } }, "description": "The images in order (core/gallery inner images, Elementor gallery items, Divi gallery_ids)." },
15
- "heading": { "type": "string", "label": "Heading", "description": "Section heading above the images." },
16
- "closeLabel": { "type": "string", "content": false, "description": "Text of the viewer's close button (ui-strings gallery.close)." },
17
- "tone": { "type": "select", "options": ["default", "muted", "inverse", "accent"], "content": false, "description": "Section background." },
18
- "id": { "type": "string", "content": false, "description": "Section id, unique per page: viewer ids derive from it (`<id>-image-<n>`)." }
83
+ "items": {
84
+ "type": "array",
85
+ "label": "Images",
86
+ "required": true,
87
+ "items": {
88
+ "type": "object",
89
+ "fields": {
90
+ "image": {
91
+ "type": "image",
92
+ "label": "Image",
93
+ "required": true,
94
+ "description": "The image."
95
+ },
96
+ "imageAlt": {
97
+ "type": "string",
98
+ "label": "Image description",
99
+ "description": "What the image shows, for readers who cannot see it."
100
+ },
101
+ "caption": {
102
+ "type": "string",
103
+ "label": "Caption",
104
+ "description": "Caption under the enlarged image."
105
+ },
106
+ "full": {
107
+ "type": "image",
108
+ "label": "Full-size image",
109
+ "description": "The original file the viewer shows large, where the source links to it; otherwise the image itself."
110
+ }
111
+ }
112
+ },
113
+ "description": "The images in order (core/gallery inner images, Elementor gallery items, Divi gallery_ids)."
114
+ },
115
+ "heading": {
116
+ "type": "string",
117
+ "label": "Heading",
118
+ "description": "Section heading above the images."
119
+ },
120
+ "closeLabel": {
121
+ "type": "string",
122
+ "content": false,
123
+ "description": "Text of the viewer's close button (ui-strings gallery.close)."
124
+ },
125
+ "tone": {
126
+ "type": "select",
127
+ "options": [
128
+ "default",
129
+ "muted",
130
+ "inverse",
131
+ "accent"
132
+ ],
133
+ "content": false,
134
+ "description": "Section background."
135
+ },
136
+ "id": {
137
+ "type": "string",
138
+ "content": false,
139
+ "description": "Section id, unique per page: viewer ids derive from it (`<id>-image-<n>`)."
140
+ },
141
+ "enlargeLabel": {
142
+ "type": "string",
143
+ "default": "Enlarge",
144
+ "content": false,
145
+ "description": "Prefix of each thumbnail button's name for screen readers (\"Enlarge: <description>\")."
146
+ },
147
+ "regionLabel": {
148
+ "type": "string",
149
+ "default": "Images",
150
+ "content": false,
151
+ "description": "Name of the scrolling row's region when the gallery has no heading."
152
+ },
153
+ "measured": {
154
+ "type": "object",
155
+ "content": false,
156
+ "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.",
157
+ "fields": {
158
+ "containerPx": {
159
+ "type": "integer",
160
+ "min": 0,
161
+ "max": 2560,
162
+ "description": "The frame's content width."
163
+ },
164
+ "padY": {
165
+ "type": "integer",
166
+ "min": 0,
167
+ "max": 400,
168
+ "description": "Top and bottom padding."
169
+ },
170
+ "padX": {
171
+ "type": "integer",
172
+ "min": 0,
173
+ "max": 200,
174
+ "description": "The frame's side padding (gutter)."
175
+ },
176
+ "bg": {
177
+ "type": "object",
178
+ "description": "The section's background.",
179
+ "fields": {
180
+ "color": {
181
+ "type": "string",
182
+ "description": "Fill colour: hex, rgb(a) or hsl(a)."
183
+ },
184
+ "image": {
185
+ "type": "string",
186
+ "description": "Background image address (http(s) or root-relative), drawn behind the content."
187
+ },
188
+ "overlay": {
189
+ "type": "string",
190
+ "description": "Colour or linear/radial gradient laid over the image or the fill."
191
+ },
192
+ "size": {
193
+ "type": "string",
194
+ "description": "cover, contain or auto; anything else draws as cover."
195
+ },
196
+ "position": {
197
+ "type": "string",
198
+ "description": "One or two of center/top/bottom/left/right or percentages."
199
+ }
200
+ }
201
+ },
202
+ "columns": {
203
+ "type": "object",
204
+ "description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.",
205
+ "fields": {
206
+ "390": {
207
+ "type": "integer",
208
+ "min": 1,
209
+ "max": 6,
210
+ "description": "Phone."
211
+ },
212
+ "768": {
213
+ "type": "integer",
214
+ "min": 1,
215
+ "max": 6,
216
+ "description": "Tablet."
217
+ },
218
+ "1280": {
219
+ "type": "integer",
220
+ "min": 1,
221
+ "max": 6,
222
+ "description": "Desktop."
223
+ }
224
+ }
225
+ },
226
+ "gap": {
227
+ "type": "integer",
228
+ "min": 0,
229
+ "max": 200,
230
+ "description": "Gap between items."
231
+ },
232
+ "tone": {
233
+ "type": "select",
234
+ "options": [
235
+ "light",
236
+ "dark"
237
+ ],
238
+ "description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides."
239
+ },
240
+ "align": {
241
+ "type": "select",
242
+ "options": [
243
+ "start",
244
+ "center",
245
+ "end"
246
+ ],
247
+ "description": "Where the heading and lead sit; read by a component whose own align is not set."
248
+ },
249
+ "radius": {
250
+ "type": "integer",
251
+ "min": 0,
252
+ "max": 200,
253
+ "description": "Card and item corner radius."
254
+ },
255
+ "minHeight": {
256
+ "type": "integer",
257
+ "min": 0,
258
+ "max": 2000,
259
+ "description": "The section's minimum height."
260
+ }
261
+ }
262
+ }
19
263
  },
20
264
  "slots": {},
21
265
  "js": "none",
22
266
  "dependencies": {},
23
267
  "sources": {
24
- "gutenberg": ["core/gallery"],
25
- "elementor": ["elementor/image-gallery", "elementor/gallery"],
26
- "divi": ["divi/et_pb_gallery"],
27
- "classic": ["classic/gallery"]
268
+ "gutenberg": [
269
+ "core/gallery"
270
+ ],
271
+ "elementor": [
272
+ "elementor/image-gallery",
273
+ "elementor/gallery"
274
+ ],
275
+ "divi": [
276
+ "divi/et_pb_gallery"
277
+ ],
278
+ "classic": [
279
+ "classic/gallery"
280
+ ]
28
281
  },
29
- "a11y": "A list of figures. In popover mode each thumbnail is a <button> named by the image's alt (or caption) that opens the viewer; the viewer has a visible close button, and Escape closes it, returning focus to the thumbnail. Captions are <figcaption>s."
282
+ "a11y": "A list of figures. In popover mode each thumbnail is a <button> named by the image's alt (or caption) that opens the viewer; the viewer has a visible close button, and Escape closes it, returning focus to the thumbnail. Captions are <figcaption>s. A scrolling row is a labelled region with tabindex 0 so the keyboard can scroll it; thumbnail buttons are named \"Enlarge: …\". The viewer has no next/previous (it would need script)."
30
283
  }
@@ -6,6 +6,8 @@ import Button from '../_shared/Button.astro'
6
6
  import KitImage from '../_shared/KitImage.astro'
7
7
  import type { ActionInput, ImageInput, NavItem } from '../_shared/types'
8
8
  import Nav from '../nav/Nav.astro'
9
+ import SearchBox from '../search-box/SearchBox.astro'
10
+ import { safeHref } from '../_shared/safe-url'
9
11
 
10
12
  const header = tv({
11
13
  slots: {
@@ -54,28 +56,33 @@ interface Props {
54
56
  homeHref?: string | undefined
55
57
  items: NavItem[]
56
58
  action?: ActionInput | undefined
59
+ /** A search field in the bar (a GET form to the site's search page); below the bar until lg. */
60
+ search?: { label?: string | undefined, placeholder?: string | undefined, submitLabel?: string | undefined, action?: string | undefined } | undefined
57
61
  navLabel?: string | undefined
58
62
  menuLabel?: string | undefined
59
63
  closeLabel?: string | undefined
60
64
  layout?: 'split' | 'centered' | 'brand-left' | undefined
61
65
  tone?: 'default' | 'inverse' | 'transparent' | undefined
62
66
  position?: 'static' | 'sticky' | undefined
67
+ /** How submenus open on wide screens: a list under the item, or a wide panel of columns. */
68
+ menu?: 'flyout' | 'mega' | undefined
63
69
  }
64
70
 
65
- const { siteName, tagline, brandSize, brandWeight, brandTracking, bordered, offsetTop, padTop, padBottom, linkNav, logo, showName = true, homeHref = '/', items, action, navLabel, menuLabel, closeLabel, layout, tone, position } = Astro.props
71
+ const { siteName, tagline, brandSize, brandWeight, brandTracking, bordered, offsetTop, padTop, padBottom, linkNav, logo, showName = true, homeHref = '/', items, action, search, navLabel, menuLabel, closeLabel, layout, tone, position, menu } = Astro.props
66
72
  const { root, inner, brand } = header({ layout, tone, position, bordered })
67
73
  const rootStyle = offsetTop ? `margin-top: ${offsetTop}` : undefined
68
74
  const innerStyle = [padTop && `padding-top: ${padTop}`, padBottom && `padding-bottom: ${padBottom}`].filter(Boolean).join('; ') || undefined
69
75
  const brandStyle = [brandSize && `font-size: ${brandSize}`, brandWeight && `font-weight: ${brandWeight}`, brandTracking && `letter-spacing: ${brandTracking}`].filter(Boolean).join('; ') || undefined
70
76
  // A tagline under the name keeps its line on a phone, as themes print it: the navigation wraps below.
71
- const wrap = tagline ? 'flex-wrap md:flex-nowrap' : undefined
77
+ // The search field sits in the bar from lg; at md the bar already holds the navigation, so the field keeps its own row.
78
+ const wrap = search ? 'flex-wrap lg:flex-nowrap' : tagline ? 'flex-wrap md:flex-nowrap' : undefined
72
79
  ---
73
80
  <header class={root()} style={rootStyle}>
74
81
  <div class={inner({ class: wrap })} style={innerStyle}>
75
82
  {tagline
76
83
  ? (
77
84
  <div class="flex min-w-0 basis-full flex-col gap-1 md:basis-auto">
78
- <a href={homeHref} class={brand()} aria-label={showName ? undefined : siteName} style={brandStyle} data-kit-brand>
85
+ <a href={safeHref(homeHref)} class={brand()} aria-label={showName ? undefined : siteName} style={brandStyle} data-kit-brand>
79
86
  {logo && <KitImage image={logo} priority class="h-8 w-auto" sizes="160px" />}
80
87
  {showName && <span>{siteName}</span>}
81
88
  </a>
@@ -83,13 +90,18 @@ const wrap = tagline ? 'flex-wrap md:flex-nowrap' : undefined
83
90
  </div>
84
91
  )
85
92
  : (
86
- <a href={homeHref} class={brand()} aria-label={showName ? undefined : siteName} style={brandStyle} data-kit-brand>
93
+ <a href={safeHref(homeHref)} class={brand()} aria-label={showName ? undefined : siteName} style={brandStyle} data-kit-brand>
87
94
  {logo && <KitImage image={logo} priority class="h-8 w-auto" sizes="160px" />}
88
95
  {showName && <span>{siteName}</span>}
89
96
  </a>
90
97
  )}
98
+ {search && (
99
+ <div class="order-last basis-full lg:order-none lg:ms-auto lg:basis-auto">
100
+ <SearchBox action={search.action} label={search.label} placeholder={search.placeholder} submitLabel={search.submitLabel} size="sm" id="header-search" />
101
+ </div>
102
+ )}
91
103
  <div class:list={['flex items-center gap-4', layout === 'centered' ? 'justify-center' : 'ms-auto']}>
92
- <Nav items={items} label={navLabel} menuLabel={menuLabel} closeLabel={closeLabel} part="nav-header" color={linkNav ? 'link' : 'current'} align={layout === 'centered' ? 'center' : 'end'} />
104
+ <Nav items={items} menu={menu} label={navLabel} menuLabel={menuLabel} closeLabel={closeLabel} part="nav-header" color={linkNav ? 'link' : 'current'} align={layout === 'centered' ? 'center' : 'end'} />
93
105
  {action && <Button action={action} size="sm" class="hidden md:inline-flex" />}
94
106
  </div>
95
107
  </div>