@sarimarcus/content-sites-ui 0.17.0 → 0.19.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/CHANGELOG.md +13 -0
- package/README.md +54 -2
- package/package.json +5 -3
- package/src/composed/ArrowLinkGrid.astro +36 -0
- package/src/composed/RelatedContent.astro +135 -0
- package/src/composed/RelatedContentCard.astro +99 -0
- package/src/composed/index.ts +5 -0
- package/src/composed/related.ts +16 -0
- package/src/editorial/ArticleByline.astro +34 -0
- package/src/editorial/ArticleCard.astro +56 -0
- package/src/editorial/ArticleCategoryNav.astro +50 -0
- package/src/editorial/ArticleFeatureCard.astro +71 -0
- package/src/editorial/ArticleGrid.astro +23 -0
- package/src/editorial/ArticleKeyFacts.astro +54 -0
- package/src/editorial/ArticlesListingPage.astro +40 -0
- package/src/editorial/AuthorCard.astro +174 -0
- package/src/editorial/AuthorStrip.astro +52 -0
- package/src/editorial/InsiderTips.astro +102 -0
- package/src/editorial/NotFoundPage.astro +98 -0
- package/src/editorial/StaticProsePage.astro +48 -0
- package/src/editorial/index.ts +13 -0
- package/src/editorial/types.ts +68 -0
- package/src/layout/ListingShell.astro +32 -0
- package/src/layout/index.ts +1 -0
- package/src/search/SearchPage.astro +222 -0
- package/src/search/index.ts +2 -0
- package/src/search/searchPage.ts +591 -0
- package/src/styles/not-found.css +151 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ListingShell — the frame of a listing hub: breadcrumb band, hero, optional full-width bands, <main> with a
|
|
4
|
+
* Container, and trailing bands, all inside one Pagefind-ignore wrapper (listings are not indexed). The route keeps
|
|
5
|
+
* Layout and passes StructuredData to Layout directly, so its JSON-LD lands in <head>; everything else comes in by slot.
|
|
6
|
+
*/
|
|
7
|
+
import BreadcrumbBand from './BreadcrumbBand.astro';
|
|
8
|
+
import Container from './Container.astro';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
mainClass?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { mainClass = 'py-12 lg:py-20' } = Astro.props;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<div data-pagefind-ignore>
|
|
18
|
+
<BreadcrumbBand ignore={false}>
|
|
19
|
+
<slot name="breadcrumb" />
|
|
20
|
+
</BreadcrumbBand>
|
|
21
|
+
|
|
22
|
+
<slot name="hero" />
|
|
23
|
+
<slot name="before-main" />
|
|
24
|
+
|
|
25
|
+
<main class={mainClass}>
|
|
26
|
+
<Container>
|
|
27
|
+
<slot />
|
|
28
|
+
</Container>
|
|
29
|
+
</main>
|
|
30
|
+
|
|
31
|
+
<slot name="after-main" />
|
|
32
|
+
</div>
|
package/src/layout/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Public surface of @sarimarcus/content-sites-ui/layout.
|
|
2
2
|
export { default as Container } from './Container.astro';
|
|
3
3
|
export { default as BreadcrumbBand } from './BreadcrumbBand.astro';
|
|
4
|
+
export { default as ListingShell } from './ListingShell.astro';
|
|
4
5
|
export { default as AsyncStylesheet } from './AsyncStylesheet.astro';
|
|
5
6
|
export { default as DocumentShell } from './DocumentShell.astro';
|
|
6
7
|
export { default as ConsentAnalytics } from './ConsentAnalytics.astro';
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* SearchPage — the markup of a site search page over its Pagefind index: hero and search box, type chips, an optional
|
|
4
|
+
* audience row (Pagefind `audience` filter values), the result states and the type-icon template the result cards
|
|
5
|
+
* clone. The route keeps Layout (`noindex`), imports `@sarimarcus/content-sites-ui/search.css` and calls
|
|
6
|
+
* `initSearchPage` from `@sarimarcus/content-sites-ui/search-page` in its own <script> with the same taxonomy and copy.
|
|
7
|
+
*/
|
|
8
|
+
import Container from '../layout/Container.astro';
|
|
9
|
+
import Icon from '../primitives/Icon.astro';
|
|
10
|
+
import SearchSuggestions from './SearchSuggestions.astro';
|
|
11
|
+
import type { SearchPageStrings, SearchPageType } from './searchPage.ts';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
strings: SearchPageStrings;
|
|
15
|
+
/** Insertion order is the chip order. */
|
|
16
|
+
types: Record<string, SearchPageType>;
|
|
17
|
+
suggestions: { label: string; query: string }[];
|
|
18
|
+
fallbackLinks: { label: string; href: string }[];
|
|
19
|
+
/** Renders the audience row when set (needs the audience strings). */
|
|
20
|
+
audienceFacets?: { value: string; label: string; count: number }[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { strings, types, suggestions, fallbackLinks, audienceFacets } = Astro.props;
|
|
24
|
+
const typeOrder = Object.keys(types);
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<main class="search-page" data-pagefind-ignore>
|
|
28
|
+
<section class="search-hero" aria-labelledby="search-hero-heading">
|
|
29
|
+
<Container>
|
|
30
|
+
<p class="search-hero__eyebrow">{strings.heroEyebrow}</p>
|
|
31
|
+
<h1 id="search-hero-heading" class="search-hero__heading">
|
|
32
|
+
{strings.heroHeading}
|
|
33
|
+
</h1>
|
|
34
|
+
<p class="search-hero__subhead">{strings.heroSubhead}</p>
|
|
35
|
+
|
|
36
|
+
<form class="search-form" role="search" autocomplete="off" novalidate>
|
|
37
|
+
<span class="search-form__icon" aria-hidden="true">
|
|
38
|
+
<Icon name="search" class="w-5 h-5" />
|
|
39
|
+
</span>
|
|
40
|
+
<label class="sr-only" for="search-input">{strings.inputLabel}</label>
|
|
41
|
+
<input
|
|
42
|
+
id="search-input"
|
|
43
|
+
type="search"
|
|
44
|
+
class="search-form__input"
|
|
45
|
+
placeholder={strings.inputPlaceholder}
|
|
46
|
+
spellcheck="false"
|
|
47
|
+
autocapitalize="off"
|
|
48
|
+
autocorrect="off"
|
|
49
|
+
enterkeyhint="search"
|
|
50
|
+
aria-controls="search-results-region"
|
|
51
|
+
aria-describedby="search-announce"
|
|
52
|
+
/>
|
|
53
|
+
<div id="search-announce" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></div>
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
class="search-form__clear"
|
|
57
|
+
aria-label={strings.clearLabel}
|
|
58
|
+
data-search-clear
|
|
59
|
+
hidden
|
|
60
|
+
>
|
|
61
|
+
<Icon name="x" class="w-4 h-4" />
|
|
62
|
+
</button>
|
|
63
|
+
</form>
|
|
64
|
+
|
|
65
|
+
<div
|
|
66
|
+
class="search-filters"
|
|
67
|
+
role="group"
|
|
68
|
+
aria-label={strings.filterGroupLabel}
|
|
69
|
+
hidden
|
|
70
|
+
>
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
class="search-chip search-chip--active"
|
|
74
|
+
data-search-filter=""
|
|
75
|
+
aria-pressed="true"
|
|
76
|
+
>
|
|
77
|
+
{strings.filterAllLabel}
|
|
78
|
+
</button>
|
|
79
|
+
{
|
|
80
|
+
typeOrder.map((t) => (
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
class="search-chip"
|
|
84
|
+
data-search-filter={t}
|
|
85
|
+
aria-pressed="false"
|
|
86
|
+
>
|
|
87
|
+
<Icon name={types[t].iconName} class="search-chip__icon" />
|
|
88
|
+
<span>{types[t].pluralLabel}</span>
|
|
89
|
+
</button>
|
|
90
|
+
))
|
|
91
|
+
}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{audienceFacets && (
|
|
95
|
+
<div
|
|
96
|
+
class="search-filters search-filters--audience"
|
|
97
|
+
role="group"
|
|
98
|
+
aria-label={strings.audienceGroupLabel}
|
|
99
|
+
hidden
|
|
100
|
+
>
|
|
101
|
+
<span class="search-filters__label" aria-hidden="true">{strings.audienceRowLabel}</span>
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
class="search-chip search-chip--active"
|
|
105
|
+
data-search-audience=""
|
|
106
|
+
aria-pressed="true"
|
|
107
|
+
>
|
|
108
|
+
{strings.audienceAllLabel}
|
|
109
|
+
</button>
|
|
110
|
+
{
|
|
111
|
+
audienceFacets.map((f) => (
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
class="search-chip"
|
|
115
|
+
data-search-audience={f.value}
|
|
116
|
+
data-search-audience-total={f.count}
|
|
117
|
+
aria-pressed="false"
|
|
118
|
+
>
|
|
119
|
+
<span>{f.label}</span>
|
|
120
|
+
<span class="search-chip__count">{f.count}</span>
|
|
121
|
+
</button>
|
|
122
|
+
))
|
|
123
|
+
}
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</Container>
|
|
127
|
+
</section>
|
|
128
|
+
|
|
129
|
+
{/* Hidden icon template: result cards clone an icon node by type. */}
|
|
130
|
+
<template id="search-type-icons">
|
|
131
|
+
{
|
|
132
|
+
typeOrder.map((t) => (
|
|
133
|
+
<span data-icon-for={t}>
|
|
134
|
+
<Icon name={types[t].iconName} class="search-card__badge-icon" />
|
|
135
|
+
</span>
|
|
136
|
+
))
|
|
137
|
+
}
|
|
138
|
+
</template>
|
|
139
|
+
|
|
140
|
+
<section
|
|
141
|
+
id="search-results-region"
|
|
142
|
+
class="search-results-region"
|
|
143
|
+
aria-busy="false"
|
|
144
|
+
>
|
|
145
|
+
<Container>
|
|
146
|
+
<div class="search-state" data-search-state="empty">
|
|
147
|
+
<SearchSuggestions label={strings.emptySuggestionsLabel} suggestions={suggestions} />
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div class="search-state" data-search-state="loading" hidden>
|
|
151
|
+
<p class="search-state__loading">
|
|
152
|
+
<span class="search-state__spinner" aria-hidden="true"></span>
|
|
153
|
+
{strings.loadingLabel}
|
|
154
|
+
</p>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div class="search-state" data-search-state="results" hidden>
|
|
158
|
+
<h2 class="search-results__heading" data-search-results-heading></h2>
|
|
159
|
+
{/* Only the entity templates and articles carry an `audience` filter value,
|
|
160
|
+
so an audience-filtered search cannot return guides, /discover/ pages,
|
|
161
|
+
neighborhoods or walks — they have no audience tags to match. Rather
|
|
162
|
+
than drop them silently, say how many the filter removed. */}
|
|
163
|
+
<p class="search-filter-note" data-search-filter-note hidden></p>
|
|
164
|
+
<ul class="search-results__list" data-search-results-list></ul>
|
|
165
|
+
<div class="search-results__actions">
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
class="search-load-more"
|
|
169
|
+
data-search-load-more
|
|
170
|
+
hidden
|
|
171
|
+
>
|
|
172
|
+
{strings.loadMoreLabel}
|
|
173
|
+
</button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div class="search-state" data-search-state="zero" hidden>
|
|
178
|
+
<h2 class="search-state__heading" data-search-zero-heading></h2>
|
|
179
|
+
<p class="search-state__subhead">{strings.zeroSubhead}</p>
|
|
180
|
+
<SearchSuggestions label={strings.emptySuggestionsLabel} suggestions={suggestions} />
|
|
181
|
+
<p class="search-fallback__label">{strings.zeroFallbackLabel}</p>
|
|
182
|
+
<ul class="search-fallback__links">
|
|
183
|
+
{
|
|
184
|
+
fallbackLinks.map((l) => (
|
|
185
|
+
<li>
|
|
186
|
+
<a href={l.href} class="search-fallback__link">{l.label}</a>
|
|
187
|
+
</li>
|
|
188
|
+
))
|
|
189
|
+
}
|
|
190
|
+
</ul>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div class="search-state" data-search-state="filter-empty" hidden>
|
|
194
|
+
<h2 class="search-state__heading" data-search-filter-empty-heading>{strings.filterEmptyHeading}</h2>
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
class="search-chip search-chip--suggest"
|
|
198
|
+
data-search-clear-filter
|
|
199
|
+
>
|
|
200
|
+
{strings.filterEmptyClearLabel}
|
|
201
|
+
</button>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div class="search-state" data-search-state="error" hidden>
|
|
205
|
+
<h2 class="search-state__heading">{strings.errorHeading}</h2>
|
|
206
|
+
<p class="search-state__subhead">{strings.errorSubhead}</p>
|
|
207
|
+
<button type="button" class="search-load-more" data-search-retry>
|
|
208
|
+
{strings.errorRetryLabel}
|
|
209
|
+
</button>
|
|
210
|
+
<ul class="search-fallback__links" style="margin-top: 1.5rem">
|
|
211
|
+
{
|
|
212
|
+
fallbackLinks.map((l) => (
|
|
213
|
+
<li>
|
|
214
|
+
<a href={l.href} class="search-fallback__link">{l.label}</a>
|
|
215
|
+
</li>
|
|
216
|
+
))
|
|
217
|
+
}
|
|
218
|
+
</ul>
|
|
219
|
+
</div>
|
|
220
|
+
</Container>
|
|
221
|
+
</section>
|
|
222
|
+
</main>
|
package/src/search/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Public surface of @sarimarcus/content-sites-ui/search: search-page building blocks. The site keeps its page, its
|
|
2
2
|
// type taxonomy, its copy and its Pagefind index.
|
|
3
3
|
export { default as SearchSuggestions } from './SearchSuggestions.astro';
|
|
4
|
+
export { default as SearchPage } from './SearchPage.astro';
|
|
5
|
+
export type { SearchPageStrings, SearchPageType } from './searchPage.ts';
|
|
4
6
|
export { appendResults } from './renderResult.ts';
|
|
5
7
|
export type { PagefindResultFragment, SearchTaxonomy, SearchTypeMeta } from './renderResult.ts';
|