@contentrain/astro-kit 0.32.0 → 0.33.1
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 +850 -19
- 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 +13 -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 +4 -3
- package/components/contact-form/meta.json +1 -0
- package/components/cta/meta.json +1 -1
- 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/fixtures.json +4 -0
- package/components/faq/meta.json +2 -2
- package/components/feature-list/meta.json +1 -0
- package/components/footer/Footer.astro +5 -4
- package/components/footer/meta.json +1 -0
- package/components/gallery/Gallery.astro +2 -1
- package/components/gallery/meta.json +1 -0
- 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 +2 -1
- package/components/logo-cloud/meta.json +1 -0
- 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 +4 -3
- package/components/post-card/meta.json +1 -1
- package/components/post-list/PostList.astro +3 -2
- package/components/post-list/meta.json +1 -0
- package/components/pricing/meta.json +1 -0
- 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/meta.json +1 -0
- package/components/stats/Stats.astro +19 -8
- package/components/stats/fixtures.json +87 -0
- package/components/stats/meta.json +1 -0
- package/components/steps/meta.json +1 -0
- 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 +3 -2
- package/components/team/meta.json +2 -0
- package/components/team/team.ts +3 -8
- package/components/testimonial/meta.json +2 -3
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/mapping/classic.json +117 -2
- package/mapping/divi.json +6 -5
- package/mapping/elementor.json +120 -7
- package/mapping/gutenberg.json +20 -8
- package/package.json +2 -2
|
@@ -77,5 +77,170 @@
|
|
|
77
77
|
}
|
|
78
78
|
]
|
|
79
79
|
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "ps-flyout",
|
|
83
|
+
"props": {
|
|
84
|
+
"id": "fx-nav-ps",
|
|
85
|
+
"items": [
|
|
86
|
+
{
|
|
87
|
+
"label": "Hakkımızda",
|
|
88
|
+
"href": "/hakkimizda/"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"label": "Ürünlerimiz",
|
|
92
|
+
"href": "#",
|
|
93
|
+
"children": [
|
|
94
|
+
{
|
|
95
|
+
"label": "Aracım İçin",
|
|
96
|
+
"href": "#",
|
|
97
|
+
"children": [
|
|
98
|
+
{
|
|
99
|
+
"label": "Trafik Sigortası",
|
|
100
|
+
"href": "/urunlerimiz/trafik-sigortasi/"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"label": "Kasko",
|
|
104
|
+
"href": "/urunlerimiz/kasko/"
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"label": "Evim İçin",
|
|
110
|
+
"href": "#",
|
|
111
|
+
"children": [
|
|
112
|
+
{
|
|
113
|
+
"label": "Dask",
|
|
114
|
+
"href": "/urunlerimiz/dask/"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"label": "Konut Sigortası",
|
|
118
|
+
"href": "/urunlerimiz/konut-sigortasi/"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"label": "Sağlığım İçin",
|
|
124
|
+
"href": "#",
|
|
125
|
+
"children": [
|
|
126
|
+
{
|
|
127
|
+
"label": "Sağlık Sigortası",
|
|
128
|
+
"href": "/urunlerimiz/saglik-sigortasi/"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"label": "Tamamlayıcı Sağlık",
|
|
132
|
+
"href": "/urunlerimiz/tamamlayici-saglik/"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"label": "Diğer",
|
|
138
|
+
"href": "#",
|
|
139
|
+
"children": [
|
|
140
|
+
{
|
|
141
|
+
"label": "İş Yeri Sigortası",
|
|
142
|
+
"href": "/urunlerimiz/is-yeri-sigortasi/"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"label": "Seyahat Sigortası",
|
|
146
|
+
"href": "/urunlerimiz/seyahat-sigortasi/"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"label": "Anlaşmalı Sigorta Şirketleri",
|
|
154
|
+
"href": "/anlasmali-sigorta-sirketleri/"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"label": "İletişim",
|
|
158
|
+
"href": "/iletisim/"
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "ps-mega",
|
|
165
|
+
"props": {
|
|
166
|
+
"id": "fx-nav-ps-mega",
|
|
167
|
+
"menu": "mega",
|
|
168
|
+
"items": [
|
|
169
|
+
{
|
|
170
|
+
"label": "Hakkımızda",
|
|
171
|
+
"href": "/hakkimizda/"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"label": "Ürünlerimiz",
|
|
175
|
+
"href": "#",
|
|
176
|
+
"children": [
|
|
177
|
+
{
|
|
178
|
+
"label": "Aracım İçin",
|
|
179
|
+
"href": "#",
|
|
180
|
+
"children": [
|
|
181
|
+
{
|
|
182
|
+
"label": "Trafik Sigortası",
|
|
183
|
+
"href": "/urunlerimiz/trafik-sigortasi/"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"label": "Kasko",
|
|
187
|
+
"href": "/urunlerimiz/kasko/"
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"label": "Evim İçin",
|
|
193
|
+
"href": "#",
|
|
194
|
+
"children": [
|
|
195
|
+
{
|
|
196
|
+
"label": "Dask",
|
|
197
|
+
"href": "/urunlerimiz/dask/"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"label": "Konut Sigortası",
|
|
201
|
+
"href": "/urunlerimiz/konut-sigortasi/"
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"label": "Sağlığım İçin",
|
|
207
|
+
"href": "#",
|
|
208
|
+
"children": [
|
|
209
|
+
{
|
|
210
|
+
"label": "Sağlık Sigortası",
|
|
211
|
+
"href": "/urunlerimiz/saglik-sigortasi/"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"label": "Tamamlayıcı Sağlık",
|
|
215
|
+
"href": "/urunlerimiz/tamamlayici-saglik/"
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"label": "Diğer",
|
|
221
|
+
"href": "#",
|
|
222
|
+
"children": [
|
|
223
|
+
{
|
|
224
|
+
"label": "İş Yeri Sigortası",
|
|
225
|
+
"href": "/urunlerimiz/is-yeri-sigortasi/"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"label": "Seyahat Sigortası",
|
|
229
|
+
"href": "/urunlerimiz/seyahat-sigortasi/"
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"label": "Anlaşmalı Sigorta Şirketleri",
|
|
237
|
+
"href": "/anlasmali-sigorta-sirketleri/"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"label": "İletişim",
|
|
241
|
+
"href": "/iletisim/"
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
80
245
|
}
|
|
81
246
|
]
|
package/components/nav/meta.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "nav",
|
|
3
3
|
"name": "Navigation",
|
|
4
|
-
"description": "Site navigation: an inline list with hover/focus submenus on wide screens and a toggle on narrow ones that opens a native popover drawer or a <details> dropdown.
|
|
4
|
+
"description": "Site navigation: an inline list with hover/focus submenus on wide screens and a toggle on narrow ones that opens a native popover drawer or a <details> dropdown. Submenus nest to any depth; the mega variant draws them as a column panel. Works without JavaScript; a small script (only when a menu has submenus) keeps aria-expanded true and lets Escape close a panel.",
|
|
5
5
|
"category": "chrome",
|
|
6
6
|
"uses": [],
|
|
7
7
|
"shared": [
|
|
8
|
+
"safe-url.ts",
|
|
8
9
|
"types.ts"
|
|
9
10
|
],
|
|
10
11
|
"variants": {
|
|
@@ -16,6 +17,14 @@
|
|
|
16
17
|
"default": "popover",
|
|
17
18
|
"description": "Narrow-screen menu: a side drawer (Popover API) or a dropdown under the toggle."
|
|
18
19
|
},
|
|
20
|
+
"menu": {
|
|
21
|
+
"options": [
|
|
22
|
+
"flyout",
|
|
23
|
+
"mega"
|
|
24
|
+
],
|
|
25
|
+
"default": "flyout",
|
|
26
|
+
"description": "How a submenu opens on wide screens: a list under the item (flyout) or a wide panel with one column per child and the grandchildren listed under it (mega)."
|
|
27
|
+
},
|
|
19
28
|
"align": {
|
|
20
29
|
"options": [
|
|
21
30
|
"start",
|
|
@@ -107,5 +116,5 @@
|
|
|
107
116
|
"classic/nav-menu"
|
|
108
117
|
]
|
|
109
118
|
},
|
|
110
|
-
"a11y": "A <nav> landmark with a label; the current page carries aria-current.
|
|
119
|
+
"a11y": "A <nav> landmark with a label; the current page carries aria-current. Each parent has a button (aria-controls its panel, labelled by the item) that opens the panel on hover and :focus-within; with the small script aria-expanded follows what is open and Escape closes the panel and returns focus to the button, as in the WAI-ARIA disclosure navigation pattern. Tab leaves a panel and closes it; nothing is hidden from crawlers (the panels are in the HTML). The drawer is a native popover: Escape and light dismiss close it and focus returns to the toggle."
|
|
111
120
|
}
|
|
@@ -12,6 +12,7 @@ import { tv } from 'tailwind-variants'
|
|
|
12
12
|
import Section from '../_shared/Section.astro'
|
|
13
13
|
import type { MeasuredStyle } from '../_shared/types'
|
|
14
14
|
import { newsletterForm } from './newsletter'
|
|
15
|
+
import { safeUrl } from '../_shared/safe-url'
|
|
15
16
|
|
|
16
17
|
const newsletter = tv({
|
|
17
18
|
slots: {
|
|
@@ -91,7 +92,7 @@ const formRel: Record<string, string> = newTab ? { rel: 'noopener noreferrer' }
|
|
|
91
92
|
</div>
|
|
92
93
|
)}
|
|
93
94
|
<div class="flex flex-col gap-2">
|
|
94
|
-
<form method="post" action={form.action} target={newTab ? '_blank' : undefined} {...formRel} class={s.form()} aria-labelledby={headingId} aria-label={headingId ? undefined : submitLabel}>
|
|
95
|
+
<form method="post" action={safeUrl(form.action)} target={newTab ? '_blank' : undefined} {...formRel} class={s.form()} aria-labelledby={headingId} aria-label={headingId ? undefined : submitLabel}>
|
|
95
96
|
{askName && form.name && (
|
|
96
97
|
<div class={s.field()}>
|
|
97
98
|
<label for={`${id}-name`} class={s.label()}>{nameLabel}</label>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// itself for page 1, `<base>page/<n>/` after — so a migrated archive keeps
|
|
4
4
|
// its page URLs.
|
|
5
5
|
import { tv } from 'tailwind-variants'
|
|
6
|
+
import { safeHref } from '../_shared/safe-url'
|
|
6
7
|
|
|
7
8
|
const pagination = tv({
|
|
8
9
|
slots: {
|
|
@@ -52,7 +53,7 @@ const shown = [...new Set([1, current - 2, current - 1, current, current + 1, cu
|
|
|
52
53
|
{total > 1 && (
|
|
53
54
|
<nav aria-label={label} data-cr-part="pagination" class={slots.root()}>
|
|
54
55
|
{current > 1
|
|
55
|
-
? <a href={href(current - 1)} rel="prev" class:list={[slots.item(), slots.link()]}><span aria-hidden="true" class="inline-block rtl:-scale-x-100">←</span> <span class="max-sm:sr-only">{prevLabel}</span></a>
|
|
56
|
+
? <a href={safeHref(href(current - 1))} rel="prev" class:list={[slots.item(), slots.link()]}><span aria-hidden="true" class="inline-block rtl:-scale-x-100">←</span> <span class="max-sm:sr-only">{prevLabel}</span></a>
|
|
56
57
|
: style === 'simple' && <span />}
|
|
57
58
|
{style === 'numbered' && shown.map((page, index) => (
|
|
58
59
|
<>
|
|
@@ -60,10 +61,10 @@ const shown = [...new Set([1, current - 2, current - 1, current, current + 1, cu
|
|
|
60
61
|
{/* Two away from the current page: dropped on phones so the row fits one line. */}
|
|
61
62
|
{page === current
|
|
62
63
|
? <span aria-current="page" class:list={[slots.item(), slots.current()]}><span class="sr-only">{pageLabel} </span>{page}</span>
|
|
63
|
-
: <a href={href(page)} class:list={[slots.item(), slots.link(), Math.abs(page - current) === 2 && page !== 1 && page !== total && 'max-md:hidden']}><span class="sr-only">{pageLabel} </span>{page}</a>}
|
|
64
|
+
: <a href={safeHref(href(page))} class:list={[slots.item(), slots.link(), Math.abs(page - current) === 2 && page !== 1 && page !== total && 'max-md:hidden']}><span class="sr-only">{pageLabel} </span>{page}</a>}
|
|
64
65
|
</>
|
|
65
66
|
))}
|
|
66
67
|
{style === 'simple' && <span class="text-sm text-ink-muted">{pageLabel} {current} / {total}</span>}
|
|
67
|
-
{current < total && <a href={href(current + 1)} rel="next" class:list={[slots.item(), slots.link()]}><span class="max-sm:sr-only">{nextLabel}</span> <span aria-hidden="true" class="inline-block rtl:-scale-x-100">→</span></a>}
|
|
68
|
+
{current < total && <a href={safeHref(href(current + 1))} rel="next" class:list={[slots.item(), slots.link()]}><span class="max-sm:sr-only">{nextLabel}</span> <span aria-hidden="true" class="inline-block rtl:-scale-x-100">→</span></a>}
|
|
68
69
|
</nav>
|
|
69
70
|
)}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "Links between the pages of a post list, at WordPress addresses (<base>page/<n>/).",
|
|
5
5
|
"category": "content",
|
|
6
6
|
"uses": [],
|
|
7
|
-
"shared": [],
|
|
7
|
+
"shared": ["safe-url.ts"],
|
|
8
8
|
"variants": {
|
|
9
9
|
"style": { "options": ["numbered", "simple"], "default": "numbered", "description": "Numbered pages with ellipses, or previous/next with 'Page n / m'." },
|
|
10
10
|
"align": { "options": ["start", "center", "end"], "default": "center", "description": "Alignment of the numbered style." }
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { tv } from 'tailwind-variants'
|
|
8
8
|
import KitImage from '../_shared/KitImage.astro'
|
|
9
9
|
import type { ImageInput, LinkInput } from '../_shared/types'
|
|
10
|
+
import { safeHref } from '../_shared/safe-url'
|
|
10
11
|
|
|
11
12
|
const card = tv({
|
|
12
13
|
slots: {
|
|
@@ -79,18 +80,18 @@ const Heading = `h${headingLevel}` as 'h2' | 'h3'
|
|
|
79
80
|
<div class={slots.body()}>
|
|
80
81
|
{below && category && (
|
|
81
82
|
<p class={slots.meta()}>
|
|
82
|
-
<a href={category.href} class="relative z-10 no-underline hover:underline">{category.label}</a>
|
|
83
|
+
<a href={safeHref(category.href)} class="relative z-10 no-underline hover:underline">{category.label}</a>
|
|
83
84
|
</p>
|
|
84
85
|
)}
|
|
85
86
|
{!below && (category || date) && (
|
|
86
87
|
<p class={slots.meta()}>
|
|
87
|
-
{category && <a href={category.href} class="relative z-10 font-medium text-accent-text no-underline hover:underline">{category.label}</a>}
|
|
88
|
+
{category && <a href={safeHref(category.href)} class="relative z-10 font-medium text-accent-text no-underline hover:underline">{category.label}</a>}
|
|
88
89
|
{category && date && <span aria-hidden="true">·</span>}
|
|
89
90
|
{date && <time datetime={date.toISOString()}>{shown}</time>}
|
|
90
91
|
</p>
|
|
91
92
|
)}
|
|
92
93
|
<Heading class={slots.title({ class: linkTitle ? 'text-[color:var(--color-link,var(--color-accent))]' : undefined })}>
|
|
93
|
-
<a href={href} class="text-current no-underline after:absolute after:inset-0 after:content-[''] hover:text-accent-text">{title}</a>
|
|
94
|
+
<a href={safeHref(href)} class="text-current no-underline after:absolute after:inset-0 after:content-[''] hover:text-accent-text">{title}</a>
|
|
94
95
|
</Heading>
|
|
95
96
|
{below && (author || date) && (
|
|
96
97
|
<p class={slots.meta()}>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "One post in a list (index, archive, related posts): image, category, date, title, excerpt, author. The whole card links to the post.",
|
|
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
|
"layout": { "options": ["vertical", "horizontal", "minimal"], "default": "vertical", "description": "Image on top, image beside the text, or text only with a divider." },
|
|
10
10
|
"surface": { "options": ["plain", "card"], "default": "plain", "description": "plain: no surface. card: its own background (--color-card), radius and shadow (--shadow-card), the text padded (--spacing-card) and the image flush at the edge, as theme cards are drawn on a tinted page." },
|
|
@@ -9,6 +9,7 @@ import { measuredGrid } from '../_shared/measured'
|
|
|
9
9
|
import type { MeasuredStyle } from '../_shared/types'
|
|
10
10
|
import PostCard from '../post-card/PostCard.astro'
|
|
11
11
|
import type { ImageInput, LinkInput } from '../_shared/types'
|
|
12
|
+
import { safeHref } from '../_shared/safe-url'
|
|
12
13
|
|
|
13
14
|
export interface PostItem {
|
|
14
15
|
title: string
|
|
@@ -93,7 +94,7 @@ const cardLayout = (i: number) => layout === 'compact' ? 'minimal' : layout ===
|
|
|
93
94
|
<li>
|
|
94
95
|
<PostCard
|
|
95
96
|
title={post.title}
|
|
96
|
-
href={post.href}
|
|
97
|
+
href={safeHref(post.href)}
|
|
97
98
|
excerpt={showExcerpt && layout !== 'compact' ? post.excerpt : undefined}
|
|
98
99
|
date={post.date}
|
|
99
100
|
locale={locale}
|
|
@@ -109,6 +110,6 @@ const cardLayout = (i: number) => layout === 'compact' ? 'minimal' : layout ===
|
|
|
109
110
|
</li>
|
|
110
111
|
))}
|
|
111
112
|
</ul>
|
|
112
|
-
{moreHref && moreLabel && <p class={s.more()}><a href={moreHref} class="font-medium text-accent-text underline underline-offset-4">{moreLabel}</a></p>}
|
|
113
|
+
{moreHref && moreLabel && <p class={s.more()}><a href={safeHref(moreHref)} class="font-medium text-accent-text underline underline-offset-4">{moreLabel}</a></p>}
|
|
113
114
|
</Section>
|
|
114
115
|
)}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
// A search field that sends the visitor to the site's search page (`/search?q=…`, a plain GET form, no JavaScript
|
|
3
|
+
// here: the page itself shows the results). A labelled `role="search"` landmark; the label is visible only to
|
|
4
|
+
// assistive technology unless `labelStyle` shows it.
|
|
5
|
+
import { tv } from 'tailwind-variants'
|
|
6
|
+
import { safeUrl } from '../_shared/safe-url'
|
|
7
|
+
|
|
8
|
+
const search = tv({
|
|
9
|
+
slots: {
|
|
10
|
+
form: 'flex items-center gap-2',
|
|
11
|
+
label: 'sr-only',
|
|
12
|
+
input: 'min-w-0 flex-1 rounded-md border border-line bg-surface px-3 text-ink placeholder:text-ink-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',
|
|
13
|
+
button: 'inline-flex shrink-0 cursor-pointer items-center justify-center rounded-md border border-line bg-surface px-3 font-medium text-ink hover:bg-current/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
size: { sm: { input: 'h-9 text-sm', button: 'h-9 text-sm' }, md: { input: 'h-11 text-base', button: 'h-11 text-base' } },
|
|
17
|
+
width: { auto: { form: 'w-full max-w-xs' }, full: { form: 'w-full' } },
|
|
18
|
+
// `text`: the button reads "Search"; `icon`: a magnifier with the same name for assistive technology.
|
|
19
|
+
button: { text: {}, icon: { button: 'px-0 aspect-square' } },
|
|
20
|
+
labelStyle: { hidden: {}, shown: { form: 'flex-wrap', label: 'not-sr-only basis-full text-sm font-medium' } },
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: { size: 'md', width: 'auto', button: 'text', labelStyle: 'hidden' },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
/** The search page the form sends to (it reads `q`). */
|
|
27
|
+
action?: string | undefined
|
|
28
|
+
/** The landmark's and field's accessible name ("Search"). */
|
|
29
|
+
label?: string | undefined
|
|
30
|
+
placeholder?: string | undefined
|
|
31
|
+
/** The button's name ("Search"). */
|
|
32
|
+
submitLabel?: string | undefined
|
|
33
|
+
size?: 'sm' | 'md' | undefined
|
|
34
|
+
width?: 'auto' | 'full' | undefined
|
|
35
|
+
button?: 'text' | 'icon' | undefined
|
|
36
|
+
labelStyle?: 'hidden' | 'shown' | undefined
|
|
37
|
+
id?: string | undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { action = '/search', label = 'Search', placeholder, submitLabel = label, size, width, button, labelStyle, id = 'site-search' } = Astro.props
|
|
41
|
+
const s = search({ size, width, button, labelStyle })
|
|
42
|
+
---
|
|
43
|
+
<form role="search" method="get" action={safeUrl(action)} class={s.form()} data-cr-part="search-box">
|
|
44
|
+
<label for={`${id}-q`} class={s.label()}>{label}</label>
|
|
45
|
+
<input id={`${id}-q`} type="search" name="q" placeholder={placeholder} autocomplete="off" enterkeyhint="search" class={s.input()} />
|
|
46
|
+
<button type="submit" class={s.button()} aria-label={button === 'icon' ? submitLabel : undefined}>
|
|
47
|
+
{button === 'icon'
|
|
48
|
+
? <svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="7" /><path d="m20 20-3.5-3.5" /></svg>
|
|
49
|
+
: submitLabel}
|
|
50
|
+
</button>
|
|
51
|
+
</form>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "default",
|
|
4
|
+
"props": {
|
|
5
|
+
"label": "Search",
|
|
6
|
+
"placeholder": "Search this site",
|
|
7
|
+
"submitLabel": "Search"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "small-icon",
|
|
12
|
+
"props": {
|
|
13
|
+
"size": "sm",
|
|
14
|
+
"button": "icon",
|
|
15
|
+
"label": "Search",
|
|
16
|
+
"submitLabel": "Search",
|
|
17
|
+
"id": "header-search"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "full-shown-label",
|
|
22
|
+
"props": {
|
|
23
|
+
"width": "full",
|
|
24
|
+
"labelStyle": "shown",
|
|
25
|
+
"label": "Search the journal",
|
|
26
|
+
"placeholder": "Search this site",
|
|
27
|
+
"submitLabel": "Search",
|
|
28
|
+
"id": "journal-search"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "search-box",
|
|
3
|
+
"name": "SearchBox",
|
|
4
|
+
"description": "A search field that sends the visitor to the site's search page (`/search?q=…`): a plain GET form with a labelled role=search landmark, no JavaScript. For a header slot or inline in a page.",
|
|
5
|
+
"category": "chrome",
|
|
6
|
+
"uses": [],
|
|
7
|
+
"shared": ["safe-url.ts"],
|
|
8
|
+
"variants": {
|
|
9
|
+
"size": {
|
|
10
|
+
"options": [
|
|
11
|
+
"sm",
|
|
12
|
+
"md"
|
|
13
|
+
],
|
|
14
|
+
"default": "md",
|
|
15
|
+
"description": "Field and button height (36 and 44 px)."
|
|
16
|
+
},
|
|
17
|
+
"width": {
|
|
18
|
+
"options": [
|
|
19
|
+
"auto",
|
|
20
|
+
"full"
|
|
21
|
+
],
|
|
22
|
+
"default": "auto",
|
|
23
|
+
"description": "A compact field (up to 20 rem) or the whole column."
|
|
24
|
+
},
|
|
25
|
+
"button": {
|
|
26
|
+
"options": [
|
|
27
|
+
"text",
|
|
28
|
+
"icon"
|
|
29
|
+
],
|
|
30
|
+
"default": "text",
|
|
31
|
+
"description": "The button reads its label, or shows a magnifier named by the same label."
|
|
32
|
+
},
|
|
33
|
+
"labelStyle": {
|
|
34
|
+
"options": [
|
|
35
|
+
"hidden",
|
|
36
|
+
"shown"
|
|
37
|
+
],
|
|
38
|
+
"default": "hidden",
|
|
39
|
+
"description": "The field's label for assistive technology only, or printed above the field."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"props": {
|
|
43
|
+
"action": {
|
|
44
|
+
"type": "url",
|
|
45
|
+
"default": "/search",
|
|
46
|
+
"description": "The search page the form sends to; it reads `q`."
|
|
47
|
+
},
|
|
48
|
+
"label": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "The landmark's and field's accessible name (ui-strings search.label)."
|
|
51
|
+
},
|
|
52
|
+
"placeholder": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Hint text in the field (ui-strings search.placeholder)."
|
|
55
|
+
},
|
|
56
|
+
"submitLabel": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "The button's name (ui-strings search.submit)."
|
|
59
|
+
},
|
|
60
|
+
"id": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"default": "site-search",
|
|
63
|
+
"description": "Prefix of the field's id; set it when two search boxes share a page."
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"slots": {},
|
|
67
|
+
"js": "none",
|
|
68
|
+
"dependencies": {},
|
|
69
|
+
"sources": {
|
|
70
|
+
"gutenberg": [
|
|
71
|
+
"core/search"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"a11y": "A <form role=\"search\"> with a real <label> for the type=search field (sr-only unless shown); the button has a text or aria-label name; focus rings on both."
|
|
75
|
+
}
|