@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.
- package/catalog.json +4600 -1584
- package/components/_shared/Button.astro +2 -1
- package/components/_shared/KitImage.astro +9 -4
- package/components/_shared/Section.astro +3 -1
- package/components/_shared/SectionHeader.astro +27 -0
- package/components/_shared/measured.ts +23 -0
- package/components/_shared/safe-url.ts +67 -0
- package/components/_shared/social.ts +73 -0
- package/components/announcement/Announcement.astro +2 -1
- package/components/announcement/meta.json +1 -1
- package/components/band/Band.astro +3 -2
- package/components/band/meta.json +1 -0
- package/components/breadcrumb/Breadcrumb.astro +2 -1
- package/components/breadcrumb/meta.json +1 -1
- package/components/card-grid/CardGrid.astro +2 -1
- package/components/card-grid/meta.json +1 -0
- package/components/consent/Consent.astro +2 -1
- package/components/consent/meta.json +1 -1
- package/components/contact-details/ContactDetails.astro +3 -2
- package/components/contact-details/meta.json +1 -0
- package/components/contact-form/ContactForm.astro +51 -14
- package/components/contact-form/fixtures.json +169 -9
- package/components/contact-form/meta.json +284 -15
- package/components/cta/Cta.astro +52 -12
- package/components/cta/fixtures.json +5 -1
- package/components/cta/meta.json +11 -2
- package/components/dialog/Dialog.astro +113 -0
- package/components/dialog/fixtures.json +84 -0
- package/components/dialog/meta.json +252 -0
- package/components/embed/meta.json +1 -1
- package/components/faq/Faq.astro +60 -17
- package/components/faq/fixtures.json +79 -0
- package/components/faq/meta.json +9 -3
- package/components/feature-list/FeatureList.astro +28 -4
- package/components/feature-list/meta.json +16 -3
- package/components/footer/Footer.astro +5 -4
- package/components/footer/meta.json +1 -0
- package/components/gallery/Gallery.astro +49 -14
- package/components/gallery/fixtures.json +275 -0
- package/components/gallery/meta.json +267 -14
- package/components/header/Header.astro +17 -5
- package/components/header/fixtures.json +90 -1
- package/components/header/meta.json +5 -3
- package/components/hero/meta.json +1 -0
- package/components/language-switcher/LanguageSwitcher.astro +68 -0
- package/components/language-switcher/fixtures.json +51 -0
- package/components/language-switcher/meta.json +73 -0
- package/components/logo-cloud/LogoCloud.astro +86 -21
- package/components/logo-cloud/fixtures.json +239 -0
- package/components/logo-cloud/meta.json +145 -5
- package/components/nav/Nav.astro +77 -24
- package/components/nav/NavTree.astro +40 -0
- package/components/nav/fixtures.json +165 -0
- package/components/nav/meta.json +11 -2
- package/components/newsletter/Newsletter.astro +2 -1
- package/components/newsletter/meta.json +1 -0
- package/components/pagination/Pagination.astro +4 -3
- package/components/pagination/meta.json +1 -1
- package/components/post-card/PostCard.astro +8 -5
- package/components/post-card/meta.json +3 -2
- package/components/post-list/PostList.astro +115 -0
- package/components/post-list/fixtures.json +387 -0
- package/components/post-list/meta.json +309 -0
- package/components/pricing/Pricing.astro +128 -24
- package/components/pricing/fixtures.json +288 -4
- package/components/pricing/meta.json +188 -1
- package/components/search-box/SearchBox.astro +51 -0
- package/components/search-box/fixtures.json +31 -0
- package/components/search-box/meta.json +75 -0
- package/components/slider/Slider.astro +19 -115
- package/components/slider/SliderTrack.astro +178 -0
- package/components/slider/fixtures.json +2 -1
- package/components/slider/meta.json +11 -5
- package/components/social-links/SocialLinks.astro +74 -0
- package/components/social-links/fixtures.json +67 -0
- package/components/social-links/meta.json +94 -0
- package/components/split/Split.astro +76 -14
- package/components/split/fixtures.json +8 -1
- package/components/split/meta.json +226 -4
- package/components/stats/Stats.astro +54 -11
- package/components/stats/fixtures.json +260 -0
- package/components/stats/meta.json +151 -4
- package/components/steps/Steps.astro +36 -8
- package/components/steps/fixtures.json +217 -5
- package/components/steps/meta.json +157 -3
- package/components/tabs/Tabs.astro +10 -3
- package/components/tabs/fixtures.json +4 -0
- package/components/tabs/meta.json +5 -2
- package/components/team/Team.astro +54 -9
- package/components/team/fixtures.json +217 -4
- package/components/team/meta.json +169 -3
- package/components/team/team.ts +4 -0
- package/components/testimonial/Testimonial.astro +71 -21
- package/components/testimonial/fixtures.json +73 -0
- package/components/testimonial/meta.json +168 -6
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/mapping/classic.json +124 -3
- package/mapping/divi.json +26 -8
- package/mapping/elementor.json +133 -14
- package/mapping/gutenberg.json +38 -10
- package/package.json +2 -2
package/components/cta/Cta.astro
CHANGED
|
@@ -3,13 +3,21 @@
|
|
|
3
3
|
// across the full section, or as a card inside it.
|
|
4
4
|
import { tv } from 'tailwind-variants'
|
|
5
5
|
import Button from '../_shared/Button.astro'
|
|
6
|
+
import KitImage from '../_shared/KitImage.astro'
|
|
6
7
|
import Section from '../_shared/Section.astro'
|
|
7
|
-
import type { ActionInput } from '../_shared/types'
|
|
8
|
+
import type { ActionInput, ImageInput, MeasuredStyle } from '../_shared/types'
|
|
8
9
|
|
|
9
10
|
const cta = tv({
|
|
10
11
|
slots: {
|
|
11
12
|
box: 'flex flex-col gap-6',
|
|
12
13
|
copy: 'flex flex-col gap-3',
|
|
14
|
+
eyebrow: 'text-sm font-semibold tracking-wide uppercase opacity-80',
|
|
15
|
+
// The text and buttons when a picture stands beside them.
|
|
16
|
+
text: 'flex min-w-0 flex-1 flex-col gap-6',
|
|
17
|
+
// The buttons with the small print under them.
|
|
18
|
+
cta: 'flex flex-col gap-2 md:shrink-0',
|
|
19
|
+
note: 'text-sm opacity-75',
|
|
20
|
+
media: 'w-full max-w-sm shrink-0 overflow-hidden rounded-[var(--radius-image,var(--radius-card))] md:w-2/5',
|
|
13
21
|
heading: 'text-2xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-3xl',
|
|
14
22
|
body: 'max-w-2xl text-pretty opacity-85',
|
|
15
23
|
actions: 'flex flex-wrap gap-3',
|
|
@@ -21,13 +29,19 @@ const cta = tv({
|
|
|
21
29
|
// Heading, text and buttons in one column at the start edge, on every width.
|
|
22
30
|
stacked: { box: 'items-start text-start' },
|
|
23
31
|
},
|
|
32
|
+
// The heading's scale; `lg` also gives the band more room.
|
|
33
|
+
size: { md: {}, lg: { heading: 'text-3xl md:text-5xl' } },
|
|
34
|
+
// The text block's alignment (`centered` already sets its own).
|
|
35
|
+
align: { start: {}, center: { copy: 'items-center text-center', body: 'mx-auto' } },
|
|
36
|
+
// With a picture: which side it takes on wide screens (text first in the source).
|
|
37
|
+
mediaSide: { end: {}, start: { media: 'md:order-first' } },
|
|
24
38
|
frame: {
|
|
25
39
|
full: {},
|
|
26
40
|
card: { box: 'rounded-card border border-line p-8 md:p-12' },
|
|
27
41
|
},
|
|
28
42
|
// 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
43
|
// none: no fill of its own — the page background and text colour show through.
|
|
30
|
-
tone: { default: {}, muted: {}, inverse: {}, accent: { body: 'opacity-100' }, brand: { body: 'opacity-100' }, none: {} },
|
|
44
|
+
tone: { default: {}, muted: {}, inverse: {}, accent: { body: 'opacity-100', eyebrow: 'opacity-100', note: 'opacity-100' }, brand: { body: 'opacity-100', eyebrow: 'opacity-100', note: 'opacity-100' }, none: {} },
|
|
31
45
|
// The buttons' own alignment: `start` keeps them at the start edge under centred text (a WordPress buttons block
|
|
32
46
|
// with no justification sits left in a group whose heading and paragraph are centred). The row is the text's
|
|
33
47
|
// column, as WordPress's content width is, not the whole section. Only centred needs it: inline and stacked
|
|
@@ -42,35 +56,61 @@ const cta = tv({
|
|
|
42
56
|
{ frame: 'card', tone: 'accent', class: { box: 'border-transparent bg-accent text-accent-ink' } },
|
|
43
57
|
{ frame: 'card', tone: 'brand', class: { box: 'border-transparent bg-brand text-brand-ink' } },
|
|
44
58
|
{ layout: 'centered', actionsAlign: 'start', class: { actions: 'w-full max-w-2xl justify-start' } },
|
|
59
|
+
{ layout: 'centered', class: { cta: 'items-center' } },
|
|
45
60
|
],
|
|
46
|
-
defaultVariants: { layout: 'inline', frame: 'full', tone: 'accent', actionsAlign: 'inherit' },
|
|
61
|
+
defaultVariants: { layout: 'inline', frame: 'full', size: 'md', align: 'start', mediaSide: 'end', tone: 'accent', actionsAlign: 'inherit' },
|
|
47
62
|
})
|
|
48
63
|
|
|
49
64
|
interface Props {
|
|
50
65
|
heading: string
|
|
66
|
+
eyebrow?: string | undefined
|
|
51
67
|
body?: string | undefined
|
|
68
|
+
/** The small print under the buttons ("No credit card required"). */
|
|
69
|
+
note?: string | undefined
|
|
70
|
+
/** A small picture beside the text. A large one is a `split`. */
|
|
71
|
+
image?: ImageInput | undefined
|
|
52
72
|
actions: ActionInput[]
|
|
53
73
|
layout?: 'inline' | 'centered' | 'stacked' | undefined
|
|
54
74
|
frame?: 'full' | 'card' | undefined
|
|
75
|
+
size?: 'md' | 'lg' | undefined
|
|
76
|
+
align?: 'start' | 'center' | undefined
|
|
77
|
+
mediaSide?: 'start' | 'end' | undefined
|
|
55
78
|
tone?: 'default' | 'muted' | 'inverse' | 'accent' | 'brand' | 'none' | undefined
|
|
56
79
|
/** The buttons' alignment: as the layout sets it, or at the start edge under centred text (layout centered). */
|
|
57
80
|
actionsAlign?: 'inherit' | 'start' | undefined
|
|
81
|
+
/** A picture behind the band (its address); the text turns light and an overlay keeps it readable. For `frame` full. */
|
|
82
|
+
bgImage?: string | undefined
|
|
83
|
+
/** The colour laid over `bgImage` (default: black at 55 %). */
|
|
84
|
+
bgOverlay?: string | undefined
|
|
85
|
+
/** 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. */
|
|
86
|
+
measured?: MeasuredStyle | undefined
|
|
58
87
|
id?: string | undefined
|
|
59
88
|
}
|
|
60
89
|
|
|
61
|
-
const { heading, body, actions, layout, frame, tone = 'accent', actionsAlign, id } = Astro.props
|
|
62
|
-
const s = cta({ layout, frame, tone, actionsAlign })
|
|
90
|
+
const { heading, eyebrow, body, note, image, actions, layout, frame, size, align, mediaSide, tone = 'accent', actionsAlign, bgImage, bgOverlay, measured: given, id } = Astro.props
|
|
91
|
+
const s = cta({ layout, frame, size, align, mediaSide, tone, actionsAlign })
|
|
92
|
+
// A background picture is the section's measured background: Section draws the image and its overlay, and the text
|
|
93
|
+
// turns light so it reads on either.
|
|
94
|
+
const measured: MeasuredStyle | undefined = bgImage
|
|
95
|
+
? { ...given, tone: 'dark', bg: { ...given?.bg, image: bgImage, overlay: bgOverlay ?? given?.bg?.overlay ?? 'rgba(0, 0, 0, 0.55)' } }
|
|
96
|
+
: given
|
|
63
97
|
const headingId = id ? `${id}-title` : undefined
|
|
64
98
|
// On the accent background the primary button (accent) would vanish: it is drawn as the secondary style there.
|
|
65
99
|
const onAccent = (action: ActionInput): ActionInput => ({ label: action.label, href: action.href, newTab: action.newTab, style: 'secondary' })
|
|
66
|
-
const shown = actions.map(action => (tone === 'accent' && (action.style ?? 'primary') === 'primary' ? onAccent(action) : action))
|
|
100
|
+
const shown = actions.map(action => (tone === 'accent' && !bgImage && (action.style ?? 'primary') === 'primary' ? onAccent(action) : action))
|
|
67
101
|
---
|
|
68
|
-
<Section tone={frame === 'card' ? 'default' : tone} spacing=
|
|
69
|
-
<div class={s.box()}>
|
|
70
|
-
<div class={s.
|
|
71
|
-
<
|
|
72
|
-
|
|
102
|
+
<Section measured={measured} tone={frame === 'card' ? 'default' : tone} spacing={size === 'lg' ? 'lg' : 'md'} id={id} aria-labelledby={headingId}>
|
|
103
|
+
<div class:list={[s.box(), image && 'md:flex-row md:items-center md:justify-between md:gap-12']}>
|
|
104
|
+
<div class={image ? s.text() : 'contents'}>
|
|
105
|
+
<div class={s.copy()}>
|
|
106
|
+
{eyebrow && <p class={s.eyebrow()}>{eyebrow}</p>}
|
|
107
|
+
<h2 id={headingId} class={s.heading()}>{heading}</h2>
|
|
108
|
+
{body && <p class={s.body()} data-kit-text>{body}</p>}
|
|
109
|
+
</div>
|
|
110
|
+
{note
|
|
111
|
+
? <div class={s.cta()}>{shown.length > 0 && <div class={s.actions()}>{shown.map(action => <Button action={action} size="lg" />)}</div>}<p class={s.note()} data-kit-text>{note}</p></div>
|
|
112
|
+
: shown.length > 0 && <div class={s.actions()}>{shown.map(action => <Button action={action} size="lg" />)}</div>}
|
|
73
113
|
</div>
|
|
74
|
-
{
|
|
114
|
+
{image && <div class={s.media()}><KitImage image={image} sizes="(min-width: 768px) 30vw, 100vw" class="h-auto w-full object-cover" /></div>}
|
|
75
115
|
</div>
|
|
76
116
|
</Section>
|
|
@@ -5,5 +5,9 @@
|
|
|
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
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
|
+
{ "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/" }] } },
|
|
9
|
+
{ "name": "eyebrow-note-lg", "props": { "eyebrow": "Free trial", "heading": "Start building today", "body": "Ship a first page in an afternoon.", "note": "No credit card required", "actions": [{ "label": "Subscribe", "href": "/subscribe/" }], "size": "lg", "tone": "muted", "align": "center" } },
|
|
10
|
+
{ "name": "media-end", "props": { "heading": "See it in action", "body": "A short tour of the editor.", "actions": [{ "label": "Subscribe", "href": "/subscribe/" }], "image": { "src": "/kit-fixtures/photo-1.svg", "alt": "The editor", "width": 640, "height": 480 }, "tone": "default", "frame": "card" } },
|
|
11
|
+
{ "name": "media-start-centered", "props": { "heading": "Join the newsletter", "body": "One email a month.", "actions": [{ "label": "Subscribe", "href": "/subscribe/" }], "image": { "src": "/kit-fixtures/photo-2.svg", "alt": "", "width": 640, "height": 480 }, "mediaSide": "start", "layout": "centered", "tone": "inverse" } },
|
|
12
|
+
{ "name": "background-image", "props": { "heading": "Planning a trip?", "body": "Start with the guides readers use most.", "actions": [{ "label": "Subscribe", "href": "/subscribe/" }], "layout": "centered", "bgImage": "/kit-fixtures/photo-3.svg", "tone": "inverse", "size": "lg" } }
|
|
9
13
|
]
|
package/components/cta/meta.json
CHANGED
|
@@ -4,17 +4,26 @@
|
|
|
4
4
|
"description": "A call to action: a short heading, an optional line of text and one or two buttons — across the whole section, or as a card inside it.",
|
|
5
5
|
"category": "section",
|
|
6
6
|
"uses": [],
|
|
7
|
-
"shared": ["Button.astro", "Section.astro", "measured.ts", "types.ts"],
|
|
7
|
+
"shared": ["Button.astro", "KitImage.astro", "Section.astro", "measured.ts", "safe-url.ts", "types.ts"],
|
|
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
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
|
-
"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." }
|
|
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
|
+
"size": { "options": ["md", "lg"], "default": "md", "description": "md: the usual band. lg: a larger heading and more room above and below." },
|
|
14
|
+
"align": { "options": ["start", "center"], "default": "start", "description": "Alignment of the heading and text (the centred layout already centres them)." },
|
|
15
|
+
"mediaSide": { "options": ["end", "start"], "default": "end", "description": "With a picture: the side it takes on wide screens." }
|
|
13
16
|
},
|
|
14
17
|
"props": {
|
|
15
18
|
"heading": { "type": "string", "label": "Heading", "required": true, "description": "The call's heading (Divi CTA title, Elementor call-to-action title)." },
|
|
19
|
+
"eyebrow": { "type": "string", "label": "Eyebrow", "description": "A short line above the heading." },
|
|
16
20
|
"body": { "type": "text", "label": "Text", "description": "One or two sentences under the heading." },
|
|
21
|
+
"note": { "type": "string", "label": "Small print", "description": "A line under the buttons (No credit card required)." },
|
|
22
|
+
"image": { "type": "image", "label": "Image", "description": "A small picture beside the text; a large one belongs in a split section." },
|
|
23
|
+
"bgImage": { "type": "image", "label": "Background image", "description": "A picture behind the band; the text turns light and an overlay keeps it readable." },
|
|
24
|
+
"bgOverlay": { "type": "string", "content": false, "description": "The colour laid over the background image (a hex or rgb colour); default black at 55 %." },
|
|
17
25
|
"actions": { "type": "array", "label": "Buttons", "required": true, "items": { "type": "object", "fields": { "label": { "type": "string", "label": "Label", "required": true, "description": "The button text." }, "href": { "type": "url", "label": "Link", "required": true, "description": "Where the button goes: a page on this site or a full address." }, "newTab": { "type": "boolean", "label": "Open in a new tab", "description": "Open the link in a new tab (an external site the reader should not leave for)." }, "style": { "type": "select", "label": "Style", "options": ["primary", "secondary", "ghost"], "description": "primary for the main action, secondary or ghost for the ones beside it." } } }, "description": "Buttons, in order; usually one." },
|
|
26
|
+
"measured": { "type": "object", "content": false, "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.", "fields": { "containerPx": { "type": "integer", "min": 0, "max": 2560, "description": "The frame's content width." }, "padY": { "type": "integer", "min": 0, "max": 400, "description": "Top and bottom padding." }, "padX": { "type": "integer", "min": 0, "max": 200, "description": "The frame's side padding (gutter)." }, "bg": { "type": "object", "description": "The section's background.", "fields": { "color": { "type": "string", "description": "Fill colour: hex, rgb(a) or hsl(a)." }, "image": { "type": "string", "description": "Background image address (http(s) or root-relative), drawn behind the content." }, "overlay": { "type": "string", "description": "Colour or linear/radial gradient laid over the image or the fill." }, "size": { "type": "string", "description": "cover, contain or auto; anything else draws as cover." }, "position": { "type": "string", "description": "One or two of center/top/bottom/left/right or percentages." } } }, "columns": { "type": "object", "description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.", "fields": { "390": { "type": "integer", "min": 1, "max": 6, "description": "Phone." }, "768": { "type": "integer", "min": 1, "max": 6, "description": "Tablet." }, "1280": { "type": "integer", "min": 1, "max": 6, "description": "Desktop." } } }, "gap": { "type": "integer", "min": 0, "max": 200, "description": "Gap between items." }, "tone": { "type": "select", "options": ["light", "dark"], "description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides." }, "align": { "type": "select", "options": ["start", "center", "end"], "description": "Where the heading and lead sit; read by a component whose own align is not set." }, "radius": { "type": "integer", "min": 0, "max": 200, "description": "Card and item corner radius." }, "minHeight": { "type": "integer", "min": 0, "max": 2000, "description": "The section's minimum height." } } },
|
|
18
27
|
"id": { "type": "string", "content": false, "description": "Anchor id of the section; the heading gets `<id>-title`." }
|
|
19
28
|
},
|
|
20
29
|
"slots": {},
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
// A popup, as a dialog the visitor opens: a button in a section of the page and a modal <dialog> with a heading, text
|
|
3
|
+
// and buttons. Replaces Elementor popups and Popup Maker overlays. Never opens by itself (no timers, no exit intent):
|
|
4
|
+
// content that a source popup showed on load or on scroll is reachable from the button instead, and sits in the HTML
|
|
5
|
+
// for crawlers. The section is the kit's (Section: tone, spacing, measured style), like every other section.
|
|
6
|
+
//
|
|
7
|
+
// Modal behaviour is the browser's (the APG dialog pattern as `showModal()` implements it): focus moves into the
|
|
8
|
+
// dialog and is kept there, the page behind is inert, Escape closes it and focus returns to the button. The button
|
|
9
|
+
// opens it with no script where Invoker Commands exist (`command`/`commandfor`); a small script covers browsers
|
|
10
|
+
// without them and closes it on a click outside. With scripting off altogether, the dialog's content is laid out in
|
|
11
|
+
// the section itself, under where the button was: nothing is hidden and nothing blocks the page.
|
|
12
|
+
import { tv } from 'tailwind-variants'
|
|
13
|
+
import Button from '../_shared/Button.astro'
|
|
14
|
+
import Section from '../_shared/Section.astro'
|
|
15
|
+
import type { ActionInput, MeasuredStyle } from '../_shared/types'
|
|
16
|
+
import Prose from '../prose/Prose.astro'
|
|
17
|
+
|
|
18
|
+
const dialog = tv({
|
|
19
|
+
slots: {
|
|
20
|
+
root: 'flex flex-wrap gap-6',
|
|
21
|
+
trigger: 'inline-flex cursor-pointer items-center justify-center gap-2 rounded-[var(--radius-control,var(--radius-md))] px-5 py-2.5 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2',
|
|
22
|
+
// A dialog is not positioned by the page: UA styles centre it; width and surface come from here. Open but not
|
|
23
|
+
// modal (the `open` preview prop) it is a block in the flow under the button, not a box over it. The inner box
|
|
24
|
+
// scrolls, not the dialog: a click on the scrollbar then never lands on the dialog itself (the backdrop test). The
|
|
25
|
+
// display is set only when open (`open:flex`): an author `display` would override the UA's `display: none` when closed.
|
|
26
|
+
box: 'm-auto w-[min(92vw,var(--dialog-width))] max-h-[85dvh] flex-col open:flex rounded-card border border-line bg-surface p-0 text-ink shadow-xl backdrop:bg-ink/50 [&[open]:not(:modal)]:relative [&[open]:not(:modal)]:m-0 [&[open]:not(:modal)]:basis-full',
|
|
27
|
+
inner: 'flex min-h-0 flex-col gap-4 overflow-y-auto p-6 md:p-8',
|
|
28
|
+
heading: 'pe-10 text-xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-2xl',
|
|
29
|
+
actions: 'flex flex-wrap gap-3',
|
|
30
|
+
close: 'absolute end-3 top-3 inline-flex size-9 cursor-pointer items-center justify-center rounded-md hover:bg-surface-muted focus-visible:outline-2',
|
|
31
|
+
},
|
|
32
|
+
variants: {
|
|
33
|
+
triggerStyle: {
|
|
34
|
+
primary: { trigger: 'bg-accent text-accent-ink hover:bg-ink hover:text-surface' },
|
|
35
|
+
secondary: { trigger: 'bg-ink text-surface hover:bg-accent hover:text-accent-ink' },
|
|
36
|
+
ghost: { trigger: 'border border-current bg-transparent text-current hover:bg-ink/5' },
|
|
37
|
+
link: { trigger: 'bg-transparent p-0 text-[color:var(--color-link,var(--color-accent))] underline underline-offset-4' },
|
|
38
|
+
},
|
|
39
|
+
size: { sm: { box: '[--dialog-width:28rem]' }, md: { box: '[--dialog-width:40rem]' }, lg: { box: '[--dialog-width:56rem]' } },
|
|
40
|
+
// Where the button sits in the section.
|
|
41
|
+
align: { start: { root: 'justify-start' }, center: { root: 'justify-center' }, end: { root: 'justify-end' } },
|
|
42
|
+
},
|
|
43
|
+
defaultVariants: { triggerStyle: 'primary', size: 'md', align: 'start' },
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
interface Props {
|
|
47
|
+
/** Unique on the page; the button points at the dialog by it. */
|
|
48
|
+
id?: string | undefined
|
|
49
|
+
/** Text of the button that opens the dialog. */
|
|
50
|
+
triggerLabel: string
|
|
51
|
+
heading?: string | undefined
|
|
52
|
+
/** The dialog's text (rendered HTML). */
|
|
53
|
+
html?: string | undefined
|
|
54
|
+
actions?: ActionInput[] | undefined
|
|
55
|
+
/** Accessible name of the close button (ui-strings dialog.close). */
|
|
56
|
+
closeLabel?: string | undefined
|
|
57
|
+
triggerStyle?: 'primary' | 'secondary' | 'ghost' | 'link' | undefined
|
|
58
|
+
size?: 'sm' | 'md' | 'lg' | undefined
|
|
59
|
+
align?: 'start' | 'center' | 'end' | undefined
|
|
60
|
+
tone?: 'default' | 'muted' | 'inverse' | 'accent' | undefined
|
|
61
|
+
/** The source section's measured style (`MeasuredStyle`): frame, padding, fill, background image. Absent, the site's tokens; present, it wins over tone. */
|
|
62
|
+
measured?: MeasuredStyle | undefined
|
|
63
|
+
/** Rendered already open, in the flow of the section and not modal: for previews and visual baselines only, never for a page. */
|
|
64
|
+
open?: boolean | undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { id: givenId = 'dialog', triggerLabel, heading, html, actions = [], closeLabel = 'Close', triggerStyle, size, align: given, tone, measured, open = false } = Astro.props
|
|
68
|
+
// The id goes into attributes and into the scripting-off style sheet, so it is a plain token or the default.
|
|
69
|
+
const id = /^[A-Za-z][\w-]*$/.test(givenId) ? givenId : 'dialog'
|
|
70
|
+
const align = given ?? measured?.align
|
|
71
|
+
// On the accent band the primary button (accent) would vanish: drawn as the secondary style there, as in Cta. The
|
|
72
|
+
// dialog's own actions sit on the dialog's surface, not the band, so they keep their styles.
|
|
73
|
+
const onAccent = tone === 'accent' && !measured?.bg?.image
|
|
74
|
+
const s = dialog({ triggerStyle: onAccent && (triggerStyle ?? 'primary') === 'primary' ? 'secondary' : triggerStyle, size, align })
|
|
75
|
+
const headingId = `${id}-title`
|
|
76
|
+
// Invoker Commands attributes are not in Astro's button types.
|
|
77
|
+
const invoker = { command: 'show-modal', commandfor: id }
|
|
78
|
+
// Scripting off: the content takes the button's place in the section, as a plain block, and the close button goes
|
|
79
|
+
// (it can only close an open dialog). Styles scoped by the dialog's id (a checked token, nothing from content); the selectors win over the UA sheet by specificity.
|
|
80
|
+
const noscript = `<style>[data-kit-dialog="${id}"]>[data-kit-dialog-open],#${id} form[method=dialog]{display:none}#${id}:not([open]){display:block;position:static;margin:0;width:100%;max-width:none;max-height:none;box-shadow:none}</style>`
|
|
81
|
+
---
|
|
82
|
+
<Section measured={measured} tone={tone} spacing="md" id={`${id}-section`}>
|
|
83
|
+
<div class={s.root()} data-kit-dialog={id}>
|
|
84
|
+
<noscript set:html={noscript} />
|
|
85
|
+
<button type="button" class={s.trigger()} data-kit-dialog-open aria-haspopup="dialog" {...invoker}>{triggerLabel}</button>
|
|
86
|
+
<dialog id={id} class={s.box()} open={open || undefined} aria-labelledby={heading ? headingId : undefined} aria-label={heading ? undefined : triggerLabel}>
|
|
87
|
+
<div class={s.inner()}>
|
|
88
|
+
{heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
|
|
89
|
+
{html && <Prose html={html} searchable={false} />}
|
|
90
|
+
{actions.length > 0 && <div class={s.actions()}>{actions.map(action => <Button action={action} />)}</div>}
|
|
91
|
+
</div>
|
|
92
|
+
<form method="dialog">
|
|
93
|
+
<button type="submit" class={s.close()} aria-label={closeLabel}>
|
|
94
|
+
<svg aria-hidden="true" viewBox="0 0 24 24" class="size-5" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 6l12 12M18 6L6 18" /></svg>
|
|
95
|
+
</button>
|
|
96
|
+
</form>
|
|
97
|
+
</dialog>
|
|
98
|
+
</div>
|
|
99
|
+
</Section>
|
|
100
|
+
|
|
101
|
+
<script>
|
|
102
|
+
// Browsers without Invoker Commands: the button opens the dialog here. Everywhere, a click on the backdrop closes it.
|
|
103
|
+
const invokers = 'commandForElement' in HTMLButtonElement.prototype
|
|
104
|
+
for (const root of document.querySelectorAll<HTMLElement>('[data-kit-dialog]')) {
|
|
105
|
+
const dialog = root.querySelector('dialog')
|
|
106
|
+
const open = root.querySelector<HTMLButtonElement>('[data-kit-dialog-open]')
|
|
107
|
+
if (!dialog || !open || root.dataset.ready !== undefined) continue
|
|
108
|
+
root.dataset.ready = ''
|
|
109
|
+
if (!invokers) open.addEventListener('click', () => { if (!dialog.open) dialog.showModal() })
|
|
110
|
+
// The inner box fills the dialog and owns the scrollbar, so a click whose target is the dialog itself landed on the backdrop.
|
|
111
|
+
dialog.addEventListener('click', (event) => { if (event.target === dialog) dialog.close() })
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "default",
|
|
4
|
+
"props": {
|
|
5
|
+
"id": "fx-dialog-a",
|
|
6
|
+
"triggerLabel": "Open the opening hours",
|
|
7
|
+
"heading": "Opening hours",
|
|
8
|
+
"html": "<p>We are open Monday to Friday, <strong>9:00 to 17:00</strong>.</p><p>On public holidays we are closed.</p>",
|
|
9
|
+
"actions": [
|
|
10
|
+
{
|
|
11
|
+
"label": "Contact us",
|
|
12
|
+
"href": "/contact/"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"closeLabel": "Close"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "link-large",
|
|
20
|
+
"props": {
|
|
21
|
+
"id": "fx-dialog-b",
|
|
22
|
+
"triggerLabel": "Read the terms",
|
|
23
|
+
"triggerStyle": "link",
|
|
24
|
+
"size": "lg",
|
|
25
|
+
"heading": "Terms of the offer",
|
|
26
|
+
"html": "<p>The offer runs while stocks last.</p><ul><li>One per customer</li><li>Not combinable</li></ul>",
|
|
27
|
+
"align": "end"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "small-no-heading",
|
|
32
|
+
"props": {
|
|
33
|
+
"id": "fx-dialog-c",
|
|
34
|
+
"triggerLabel": "Subscribe",
|
|
35
|
+
"triggerStyle": "ghost",
|
|
36
|
+
"size": "sm",
|
|
37
|
+
"html": "<p>Thank you for your interest.</p>",
|
|
38
|
+
"tone": "inverse"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "secondary",
|
|
43
|
+
"props": {
|
|
44
|
+
"id": "fx-dialog-d",
|
|
45
|
+
"triggerLabel": "Subscribe to updates",
|
|
46
|
+
"triggerStyle": "secondary",
|
|
47
|
+
"heading": "Stay in the loop",
|
|
48
|
+
"html": "<p>One email a month, never more.</p>",
|
|
49
|
+
"actions": [
|
|
50
|
+
{
|
|
51
|
+
"label": "Subscribe",
|
|
52
|
+
"href": "/newsletter/",
|
|
53
|
+
"style": "secondary"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"align": "center",
|
|
57
|
+
"tone": "muted"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "open-preview",
|
|
62
|
+
"frame": "tall",
|
|
63
|
+
"props": {
|
|
64
|
+
"id": "fx-dialog-e",
|
|
65
|
+
"triggerLabel": "Open the opening hours",
|
|
66
|
+
"heading": "Opening hours",
|
|
67
|
+
"html": "<p>We are open Monday to Friday, <strong>9:00 to 17:00</strong>.</p><p>On public holidays we are closed.</p>",
|
|
68
|
+
"actions": [
|
|
69
|
+
{
|
|
70
|
+
"label": "Contact us",
|
|
71
|
+
"href": "/contact/"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"label": "Call",
|
|
75
|
+
"href": "tel:+10000000000",
|
|
76
|
+
"style": "ghost"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"closeLabel": "Close",
|
|
80
|
+
"open": true,
|
|
81
|
+
"tone": "accent"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "dialog",
|
|
3
|
+
"name": "Dialog",
|
|
4
|
+
"description": "A popup as a dialog the visitor opens: a button in a section of the page and a modal <dialog> with a heading, text and buttons. Never opens by itself. Focus is trapped inside and returns to the button, Escape closes it; the button opens it with no script where Invoker Commands exist, a small script covers other browsers and closes on a backdrop click, and with scripting off the content is laid out in the section itself.",
|
|
5
|
+
"category": "section",
|
|
6
|
+
"uses": [
|
|
7
|
+
"prose"
|
|
8
|
+
],
|
|
9
|
+
"shared": [
|
|
10
|
+
"Button.astro",
|
|
11
|
+
"Section.astro",
|
|
12
|
+
"measured.ts",
|
|
13
|
+
"safe-url.ts",
|
|
14
|
+
"types.ts"
|
|
15
|
+
],
|
|
16
|
+
"variants": {
|
|
17
|
+
"triggerStyle": {
|
|
18
|
+
"options": [
|
|
19
|
+
"primary",
|
|
20
|
+
"secondary",
|
|
21
|
+
"ghost",
|
|
22
|
+
"link"
|
|
23
|
+
],
|
|
24
|
+
"default": "primary",
|
|
25
|
+
"description": "How the opening button looks: a filled or outlined button, or a text link."
|
|
26
|
+
},
|
|
27
|
+
"size": {
|
|
28
|
+
"options": [
|
|
29
|
+
"sm",
|
|
30
|
+
"md",
|
|
31
|
+
"lg"
|
|
32
|
+
],
|
|
33
|
+
"default": "md",
|
|
34
|
+
"description": "Width of the dialog: 28, 40 or 56 rem (never wider than the screen)."
|
|
35
|
+
},
|
|
36
|
+
"align": {
|
|
37
|
+
"options": [
|
|
38
|
+
"start",
|
|
39
|
+
"center",
|
|
40
|
+
"end"
|
|
41
|
+
],
|
|
42
|
+
"default": "start",
|
|
43
|
+
"description": "Where the button sits in the section; the measured align when not set."
|
|
44
|
+
},
|
|
45
|
+
"tone": {
|
|
46
|
+
"options": [
|
|
47
|
+
"default",
|
|
48
|
+
"muted",
|
|
49
|
+
"inverse",
|
|
50
|
+
"accent"
|
|
51
|
+
],
|
|
52
|
+
"default": "default",
|
|
53
|
+
"description": "Section background."
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"props": {
|
|
57
|
+
"triggerLabel": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"required": true,
|
|
60
|
+
"label": "Button text",
|
|
61
|
+
"description": "Text of the button that opens the dialog (the popup's trigger link or button)."
|
|
62
|
+
},
|
|
63
|
+
"heading": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"label": "Heading",
|
|
66
|
+
"description": "The dialog's title; it names the dialog for screen readers."
|
|
67
|
+
},
|
|
68
|
+
"html": {
|
|
69
|
+
"type": "richtext",
|
|
70
|
+
"label": "Text",
|
|
71
|
+
"description": "The popup's content as rendered HTML (the popup template's text)."
|
|
72
|
+
},
|
|
73
|
+
"actions": {
|
|
74
|
+
"type": "array",
|
|
75
|
+
"label": "Buttons",
|
|
76
|
+
"required": false,
|
|
77
|
+
"items": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"fields": {
|
|
80
|
+
"label": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"label": "Label",
|
|
83
|
+
"required": true,
|
|
84
|
+
"description": "The button text."
|
|
85
|
+
},
|
|
86
|
+
"href": {
|
|
87
|
+
"type": "url",
|
|
88
|
+
"label": "Link",
|
|
89
|
+
"required": true,
|
|
90
|
+
"description": "Where the button goes: a page on this site or a full address."
|
|
91
|
+
},
|
|
92
|
+
"newTab": {
|
|
93
|
+
"type": "boolean",
|
|
94
|
+
"label": "Open in a new tab",
|
|
95
|
+
"description": "Open the link in a new tab (an external site the reader should not leave for)."
|
|
96
|
+
},
|
|
97
|
+
"style": {
|
|
98
|
+
"type": "select",
|
|
99
|
+
"label": "Style",
|
|
100
|
+
"options": [
|
|
101
|
+
"primary",
|
|
102
|
+
"secondary",
|
|
103
|
+
"ghost"
|
|
104
|
+
],
|
|
105
|
+
"description": "primary for the main action, secondary or ghost for the ones beside it."
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"description": "The popup's own buttons, under its text, in order; none by default."
|
|
110
|
+
},
|
|
111
|
+
"id": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"default": "dialog",
|
|
114
|
+
"content": false,
|
|
115
|
+
"description": "Unique on the page: the button points at the dialog by it."
|
|
116
|
+
},
|
|
117
|
+
"closeLabel": {
|
|
118
|
+
"type": "string",
|
|
119
|
+
"default": "Close",
|
|
120
|
+
"content": false,
|
|
121
|
+
"description": "Accessible name of the close button (ui-strings dialog.close)."
|
|
122
|
+
},
|
|
123
|
+
"measured": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"content": false,
|
|
126
|
+
"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.",
|
|
127
|
+
"fields": {
|
|
128
|
+
"containerPx": {
|
|
129
|
+
"type": "integer",
|
|
130
|
+
"min": 0,
|
|
131
|
+
"max": 2560,
|
|
132
|
+
"description": "The frame's content width."
|
|
133
|
+
},
|
|
134
|
+
"padY": {
|
|
135
|
+
"type": "integer",
|
|
136
|
+
"min": 0,
|
|
137
|
+
"max": 400,
|
|
138
|
+
"description": "Top and bottom padding."
|
|
139
|
+
},
|
|
140
|
+
"padX": {
|
|
141
|
+
"type": "integer",
|
|
142
|
+
"min": 0,
|
|
143
|
+
"max": 200,
|
|
144
|
+
"description": "The frame's side padding (gutter)."
|
|
145
|
+
},
|
|
146
|
+
"bg": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"description": "The section's background.",
|
|
149
|
+
"fields": {
|
|
150
|
+
"color": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"description": "Fill colour: hex, rgb(a) or hsl(a)."
|
|
153
|
+
},
|
|
154
|
+
"image": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"description": "Background image address (http(s) or root-relative), drawn behind the content."
|
|
157
|
+
},
|
|
158
|
+
"overlay": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"description": "Colour or linear/radial gradient laid over the image or the fill."
|
|
161
|
+
},
|
|
162
|
+
"size": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"description": "cover, contain or auto; anything else draws as cover."
|
|
165
|
+
},
|
|
166
|
+
"position": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"description": "One or two of center/top/bottom/left/right or percentages."
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"columns": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.",
|
|
175
|
+
"fields": {
|
|
176
|
+
"390": {
|
|
177
|
+
"type": "integer",
|
|
178
|
+
"min": 1,
|
|
179
|
+
"max": 6,
|
|
180
|
+
"description": "Phone."
|
|
181
|
+
},
|
|
182
|
+
"768": {
|
|
183
|
+
"type": "integer",
|
|
184
|
+
"min": 1,
|
|
185
|
+
"max": 6,
|
|
186
|
+
"description": "Tablet."
|
|
187
|
+
},
|
|
188
|
+
"1280": {
|
|
189
|
+
"type": "integer",
|
|
190
|
+
"min": 1,
|
|
191
|
+
"max": 6,
|
|
192
|
+
"description": "Desktop."
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"gap": {
|
|
197
|
+
"type": "integer",
|
|
198
|
+
"min": 0,
|
|
199
|
+
"max": 200,
|
|
200
|
+
"description": "Gap between items."
|
|
201
|
+
},
|
|
202
|
+
"tone": {
|
|
203
|
+
"type": "select",
|
|
204
|
+
"options": [
|
|
205
|
+
"light",
|
|
206
|
+
"dark"
|
|
207
|
+
],
|
|
208
|
+
"description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides."
|
|
209
|
+
},
|
|
210
|
+
"align": {
|
|
211
|
+
"type": "select",
|
|
212
|
+
"options": [
|
|
213
|
+
"start",
|
|
214
|
+
"center",
|
|
215
|
+
"end"
|
|
216
|
+
],
|
|
217
|
+
"description": "Where the heading and lead sit; read by a component whose own align is not set."
|
|
218
|
+
},
|
|
219
|
+
"radius": {
|
|
220
|
+
"type": "integer",
|
|
221
|
+
"min": 0,
|
|
222
|
+
"max": 200,
|
|
223
|
+
"description": "Card and item corner radius."
|
|
224
|
+
},
|
|
225
|
+
"minHeight": {
|
|
226
|
+
"type": "integer",
|
|
227
|
+
"min": 0,
|
|
228
|
+
"max": 2000,
|
|
229
|
+
"description": "The section's minimum height."
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"open": {
|
|
234
|
+
"type": "boolean",
|
|
235
|
+
"default": false,
|
|
236
|
+
"content": false,
|
|
237
|
+
"description": "Rendered already open, in the flow of the section and not modal: for previews and visual baselines only, never for a page."
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"slots": {},
|
|
241
|
+
"js": "vanilla",
|
|
242
|
+
"dependencies": {},
|
|
243
|
+
"sources": {
|
|
244
|
+
"elementor": [
|
|
245
|
+
"elementor/popup"
|
|
246
|
+
],
|
|
247
|
+
"classic": [
|
|
248
|
+
"classic/popup-maker"
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
"a11y": "A native modal <dialog> (the APG dialog pattern as the platform implements it): focus moves in and stays in, the page behind is inert, Escape closes and focus returns to the button. The heading names it (aria-labelledby; the button text when there is no heading). The close button is named by closeLabel. Nothing opens on load, on scroll or on exit intent; the text is in the HTML, so crawlers read it, and with scripting off it is shown in the section as a plain block, so nothing is hidden or blocks the page."
|
|
252
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "A video, map or player from another site (YouTube, Vimeo, Google Maps, OpenStreetMap, Spotify, SoundCloud, Calendly, Typeform, Google Forms, Loom, Wistia), loaded only on click: until then a poster link to the provider's page, with no iframe, no third-party request and no cookie (YouTube through youtube-nocookie.com). Unknown providers are never framed; they stay a plain link.",
|
|
5
5
|
"category": "content",
|
|
6
6
|
"uses": [],
|
|
7
|
-
"shared": ["KitImage.astro", "types.ts"],
|
|
7
|
+
"shared": ["KitImage.astro", "safe-url.ts", "types.ts"],
|
|
8
8
|
"variants": {
|
|
9
9
|
"aspect": { "options": ["auto", "video", "portrait", "square", "map"], "default": "auto", "description": "auto: 16:9 for videos, 9:16 for YouTube Shorts, 4:3 (16:9 wide) for maps, a fixed strip for audio players. The others force a ratio." },
|
|
10
10
|
"width": { "options": ["prose", "page"], "default": "prose", "description": "prose: as wide as the reading column. page: the full page frame." }
|