@sarimarcus/content-sites-ui 0.12.1 → 0.13.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 +4 -0
- package/package.json +2 -2
- package/src/editorial/BeforeYouGo.astro +27 -0
- package/src/editorial/EditorialGuidePage.astro +32 -0
- package/src/editorial/EventPage.astro +178 -0
- package/src/editorial/NumberedStep.astro +34 -0
- package/src/editorial/event.ts +13 -0
- package/src/editorial/index.ts +6 -2
- package/src/layout/BreadcrumbBand.astro +19 -0
- package/src/layout/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All packages in the platform release in lockstep; entries are per release version.
|
|
4
4
|
|
|
5
|
+
## 0.13.0 (2026-10-01)
|
|
6
|
+
|
|
7
|
+
- New `./editorial`: `EditorialGuidePage` (breadcrumb band, hero and body slots; owns the Pagefind markers, `pagefindType`, `breadcrumbIgnore`) and `EventPage` (back link, rendered body as `TrustedHtml`, highlights, practical information, price, official website and tags as props; `breadcrumb`, `hero`, `after-body`, `after-details` and `tail` slots), plus `BeforeYouGo` and `NumberedStep`. `./layout`: `BreadcrumbBand` (SLA-2819).
|
|
8
|
+
|
|
5
9
|
## 0.12.1 (2026-10-01)
|
|
6
10
|
|
|
7
11
|
- No changes; lockstep release with tourism (SLA-2739).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sarimarcus/content-sites-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Universal Astro UI components, layout shell and design tokens shared by the content sites.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
"@lucide/astro": "^1.23.0",
|
|
43
43
|
"astro": "^7.0.6",
|
|
44
44
|
"tailwindcss": "^4.1.18",
|
|
45
|
-
"@sarimarcus/content-sites-core": "0.
|
|
45
|
+
"@sarimarcus/content-sites-core": "0.13.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* BeforeYouGo — the boxed checklist that opens a planning guide: a heading with an info glyph and a list of
|
|
4
|
+
* `label` + `text` pairs. Labels carry their own punctuation.
|
|
5
|
+
*/
|
|
6
|
+
import Icon from '../primitives/Icon.astro';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
heading: string;
|
|
10
|
+
items: { label: string; text: string }[];
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { heading, items, class: className = 'mb-16' } = Astro.props;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<div class:list={['max-w-measure mx-auto', className]}>
|
|
18
|
+
<div class="p-6 bg-accent/10 border border-accent/20 rounded-xl">
|
|
19
|
+
<h2 class="font-semibold mb-3 flex items-center gap-2 font-display">
|
|
20
|
+
<Icon name="info" class="w-5 h-5 text-accent-strong" />
|
|
21
|
+
{heading}
|
|
22
|
+
</h2>
|
|
23
|
+
<ul class="text-sm text-muted-foreground space-y-2">
|
|
24
|
+
{items.map((item) => <li><strong>{item.label}</strong> {item.text}</li>)}
|
|
25
|
+
</ul>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* EditorialGuidePage — the shell of a long-form planning guide. The route keeps data, SEO and schema, and renders
|
|
4
|
+
* the site Layout around this template. It passes the site Breadcrumb in `breadcrumb`, its hero in `hero`, and the
|
|
5
|
+
* page body (byline, intro, sections, FAQ, sources, related blocks, each in its own Container) as default-slot
|
|
6
|
+
* children. The template owns the Pagefind markers: the hero and the body are indexed, the breadcrumb band is not.
|
|
7
|
+
*/
|
|
8
|
+
import BreadcrumbBand from '../layout/BreadcrumbBand.astro';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
/** Pagefind `type` meta on the hero. */
|
|
12
|
+
pagefindType?: string;
|
|
13
|
+
/** Keep the breadcrumb band out of the Pagefind body. */
|
|
14
|
+
breadcrumbIgnore?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { pagefindType = 'guide', breadcrumbIgnore = true } = Astro.props;
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
<BreadcrumbBand ignore={breadcrumbIgnore}>
|
|
21
|
+
<slot name="breadcrumb" />
|
|
22
|
+
</BreadcrumbBand>
|
|
23
|
+
|
|
24
|
+
<div data-pagefind-body data-pagefind-meta={`type:${pagefindType}`}>
|
|
25
|
+
<slot name="hero" />
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<main class="py-12 lg:py-20">
|
|
29
|
+
<div data-pagefind-body>
|
|
30
|
+
<slot />
|
|
31
|
+
</div>
|
|
32
|
+
</main>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* EventPage — the shell and shared body of an event page. The route keeps paths, data, SEO and schema, and renders
|
|
4
|
+
* the site Layout around this template. Slots:
|
|
5
|
+
* breadcrumb — the site Breadcrumb, in the band under the header.
|
|
6
|
+
* hero — the site hero, inside the Pagefind body with `type:<pagefindType>` meta.
|
|
7
|
+
* after-body — site blocks between the description and the shared boxes.
|
|
8
|
+
* after-details — site blocks after the tags, inside the measure column (affiliate, FAQ, onward links).
|
|
9
|
+
* tail — full-width blocks after the column (related events, related content).
|
|
10
|
+
* Every string arrives as a prop; the route renders markdown and passes it as TrustedHtml.
|
|
11
|
+
*/
|
|
12
|
+
import Container from '../layout/Container.astro';
|
|
13
|
+
import Badge from '../primitives/Badge.astro';
|
|
14
|
+
import BreadcrumbBand from '../layout/BreadcrumbBand.astro';
|
|
15
|
+
import { newTab } from '../primitives/newTab';
|
|
16
|
+
import type { TrustedHtml } from '../types';
|
|
17
|
+
import type { EventInfoRow } from './event';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
bodyHtml: TrustedHtml;
|
|
21
|
+
/** Prose modifiers after `prose`, e.g. the site's prose theme and size. */
|
|
22
|
+
proseClass?: string;
|
|
23
|
+
backLink: { href: string; label: string };
|
|
24
|
+
highlights?: { heading: string; items: string[] };
|
|
25
|
+
practicalInfo?: { heading: string; rows: EventInfoRow[] };
|
|
26
|
+
price?: { label: string; value: string };
|
|
27
|
+
officialWebsite?: { href: string; label: string };
|
|
28
|
+
tags?: { heading: string; items: string[] };
|
|
29
|
+
pagefindType?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
bodyHtml,
|
|
34
|
+
proseClass = 'prose-lg',
|
|
35
|
+
backLink,
|
|
36
|
+
highlights,
|
|
37
|
+
practicalInfo,
|
|
38
|
+
price,
|
|
39
|
+
officialWebsite,
|
|
40
|
+
tags,
|
|
41
|
+
pagefindType = 'event',
|
|
42
|
+
} = Astro.props;
|
|
43
|
+
|
|
44
|
+
const toneText = { primary: 'text-primary', secondary: 'text-secondary', accent: 'text-accent' };
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
<BreadcrumbBand>
|
|
48
|
+
<slot name="breadcrumb" />
|
|
49
|
+
</BreadcrumbBand>
|
|
50
|
+
|
|
51
|
+
<div data-pagefind-body data-pagefind-meta={`type:${pagefindType}`}>
|
|
52
|
+
<slot name="hero" />
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<main class="pt-6 pb-12 lg:pt-8 lg:pb-20">
|
|
56
|
+
<Container size="measure">
|
|
57
|
+
<a
|
|
58
|
+
href={backLink.href}
|
|
59
|
+
class="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors mb-8"
|
|
60
|
+
>
|
|
61
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
62
|
+
<path d="m12 19-7-7 7-7" /><path d="M19 12H5" />
|
|
63
|
+
</svg>
|
|
64
|
+
{backLink.label}
|
|
65
|
+
</a>
|
|
66
|
+
|
|
67
|
+
<article class={`prose ${proseClass} max-w-none`} data-pagefind-body>
|
|
68
|
+
<div set:html={bodyHtml} />
|
|
69
|
+
</article>
|
|
70
|
+
|
|
71
|
+
<slot name="after-body" />
|
|
72
|
+
|
|
73
|
+
{highlights && highlights.items.length > 0 && (
|
|
74
|
+
<div class="mt-12 p-6 bg-primary/5 rounded-xl border border-primary/20">
|
|
75
|
+
<h2
|
|
76
|
+
class="text-xl font-medium mb-6 flex items-center gap-2 font-display"
|
|
77
|
+
>
|
|
78
|
+
<svg class="w-5 h-5 shrink-0 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
79
|
+
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" />
|
|
80
|
+
</svg>
|
|
81
|
+
{highlights.heading}
|
|
82
|
+
</h2>
|
|
83
|
+
<ul class="space-y-3">
|
|
84
|
+
{highlights.items.map((highlight) => (
|
|
85
|
+
<li class="flex items-start gap-3">
|
|
86
|
+
<svg class="w-5 h-5 text-primary shrink-0 mt-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
87
|
+
<polyline points="20 6 9 17 4 12" />
|
|
88
|
+
</svg>
|
|
89
|
+
<span class="text-foreground">{highlight}</span>
|
|
90
|
+
</li>
|
|
91
|
+
))}
|
|
92
|
+
</ul>
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{practicalInfo && (
|
|
97
|
+
<div class="mt-12 p-6 bg-muted/50 rounded-xl border border-border">
|
|
98
|
+
<h2
|
|
99
|
+
class="text-xl font-medium mb-6 flex items-center gap-2 font-display"
|
|
100
|
+
>
|
|
101
|
+
<svg class="w-5 h-5 shrink-0 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
102
|
+
<circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" />
|
|
103
|
+
</svg>
|
|
104
|
+
{practicalInfo.heading}
|
|
105
|
+
</h2>
|
|
106
|
+
|
|
107
|
+
<div class="space-y-4">
|
|
108
|
+
{practicalInfo.rows.map((row) => (
|
|
109
|
+
<div class="flex items-start gap-3">
|
|
110
|
+
<div class="w-10 h-10 rounded-lg bg-background flex items-center justify-center shrink-0">
|
|
111
|
+
<svg class={`w-5 h-5 shrink-0 ${toneText[row.tone]}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
112
|
+
{row.icon === 'clock' && <Fragment><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></Fragment>}
|
|
113
|
+
{row.icon === 'sun' && <Fragment><circle cx="12" cy="12" r="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" /></Fragment>}
|
|
114
|
+
{row.icon === 'pen' && <Fragment><path d="M12 20h9" /><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" /></Fragment>}
|
|
115
|
+
{row.icon === 'bus' && <Fragment><path d="M8 6v6" /><path d="M15 6v6" /><path d="M2 12h19.6" /><path d="M18 18h3s.5-1.7.8-2.8c.1-.4.2-.8.2-1.2 0-.4-.1-.8-.2-1.2l-1.4-5C20.1 6.8 19.1 6 18 6H4a2 2 0 0 0-2 2v10h3" /><circle cx="7" cy="18" r="2" /><path d="M9 18h5" /><circle cx="16" cy="18" r="2" /></Fragment>}
|
|
116
|
+
</svg>
|
|
117
|
+
</div>
|
|
118
|
+
<div>
|
|
119
|
+
<span class="text-sm font-medium text-foreground">{row.label}</span>
|
|
120
|
+
{row.html !== undefined
|
|
121
|
+
? <div class="text-sm text-muted-foreground mt-0.5 whitespace-pre-line" set:html={row.html} />
|
|
122
|
+
: <p class="text-sm text-muted-foreground mt-0.5">{row.text}</p>}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
{price && (
|
|
131
|
+
<div class="mt-8 p-4 bg-accent/10 rounded-lg border border-accent/20">
|
|
132
|
+
<div class="flex items-center gap-3">
|
|
133
|
+
<svg class="w-6 h-6 text-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
134
|
+
<path d="M4 10h12"/><path d="M4 14h9"/><path d="M19 6a7.7 7.7 0 0 0-5.2-2A7.9 7.9 0 0 0 6 12c0 4.4 3.5 8 7.8 8 2 0 3.8-.8 5.2-2"/>
|
|
135
|
+
</svg>
|
|
136
|
+
<div>
|
|
137
|
+
<span class="text-sm font-medium">{price.label}</span>
|
|
138
|
+
<p class="text-lg text-accent font-medium">{price.value}</p>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{officialWebsite && (
|
|
145
|
+
<div class="mt-8">
|
|
146
|
+
<a
|
|
147
|
+
href={officialWebsite.href}
|
|
148
|
+
{...newTab(officialWebsite.href, '_blank')}
|
|
149
|
+
class="inline-flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary-strong transition-colors"
|
|
150
|
+
>
|
|
151
|
+
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
152
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" />
|
|
153
|
+
</svg>
|
|
154
|
+
{officialWebsite.label}
|
|
155
|
+
</a>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{tags && tags.items.length > 0 && (
|
|
160
|
+
<div class="mt-8">
|
|
161
|
+
<h3 class="text-sm font-medium text-muted-foreground mb-3">
|
|
162
|
+
{tags.heading}
|
|
163
|
+
</h3>
|
|
164
|
+
<div class="flex flex-wrap gap-2">
|
|
165
|
+
{tags.items.map((tag) => (
|
|
166
|
+
<Badge tone="custom" toneClass="bg-muted text-muted-foreground" size="lg" lines={0} class="capitalize !py-1 ![font-weight:inherit]">
|
|
167
|
+
{tag}
|
|
168
|
+
</Badge>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
<slot name="after-details" />
|
|
175
|
+
</Container>
|
|
176
|
+
|
|
177
|
+
<slot name="tail" />
|
|
178
|
+
</main>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* NumberedStep — one stage of a multi-stage plan: a round marker, a title and a subtitle, then the stage's prose in
|
|
4
|
+
* a card (default slot). `tone` colours the marker and the card; the accent tone tints the card too.
|
|
5
|
+
*/
|
|
6
|
+
interface Props {
|
|
7
|
+
marker: string;
|
|
8
|
+
title: string;
|
|
9
|
+
subtitle: string;
|
|
10
|
+
tone?: 'primary' | 'accent' | 'secondary';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { marker, title, subtitle, tone = 'primary' } = Astro.props;
|
|
14
|
+
|
|
15
|
+
const [markerBg, markerText] = {
|
|
16
|
+
primary: ['bg-primary', 'text-primary-foreground'],
|
|
17
|
+
accent: ['bg-accent', 'text-accent-foreground'],
|
|
18
|
+
secondary: ['bg-secondary', 'text-secondary-foreground'],
|
|
19
|
+
}[tone];
|
|
20
|
+
const cardClass = tone === 'accent' ? 'bg-accent/10 rounded-xl border border-accent/20' : 'bg-card rounded-xl border border-border';
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<div class="flex items-center gap-4 mb-4">
|
|
25
|
+
<div class={`w-14 h-14 rounded-full ${markerBg} flex items-center justify-center ${markerText} font-bold text-xl shrink-0`}>{marker}</div>
|
|
26
|
+
<div>
|
|
27
|
+
<h3 class="text-xl font-semibold font-display">{title}</h3>
|
|
28
|
+
<p class="text-muted-foreground text-sm">{subtitle}</p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class={`p-6 ${cardClass}`}>
|
|
32
|
+
<slot />
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TrustedHtml } from '../types';
|
|
2
|
+
|
|
3
|
+
export type EventInfoIcon = 'clock' | 'sun' | 'pen' | 'bus';
|
|
4
|
+
|
|
5
|
+
/** One row of an EventPage practical-information box. */
|
|
6
|
+
export interface EventInfoRow {
|
|
7
|
+
icon: EventInfoIcon;
|
|
8
|
+
tone: 'primary' | 'secondary' | 'accent';
|
|
9
|
+
label: string;
|
|
10
|
+
/** Plain text, or `html` for rendered markdown. */
|
|
11
|
+
text?: string;
|
|
12
|
+
html?: TrustedHtml;
|
|
13
|
+
}
|
package/src/editorial/index.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
// Public surface of @sarimarcus/content-sites-ui/editorial.
|
|
2
|
-
export {};
|
|
1
|
+
// Public surface of @sarimarcus/content-sites-ui/editorial.
|
|
2
|
+
export { default as EditorialGuidePage } from './EditorialGuidePage.astro';
|
|
3
|
+
export { default as EventPage } from './EventPage.astro';
|
|
4
|
+
export { default as BeforeYouGo } from './BeforeYouGo.astro';
|
|
5
|
+
export { default as NumberedStep } from './NumberedStep.astro';
|
|
6
|
+
export type { EventInfoRow, EventInfoIcon } from './event';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* BreadcrumbBand — the muted strip under the fixed header that holds a page's breadcrumb trail (default slot).
|
|
4
|
+
* `ignore` keeps it out of the Pagefind body.
|
|
5
|
+
*/
|
|
6
|
+
import Container from './Container.astro';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
ignore?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { ignore = true } = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<div class="pt-16 bg-muted/50 border-b border-border" data-pagefind-ignore={ignore ? '' : undefined}>
|
|
16
|
+
<Container class="py-3">
|
|
17
|
+
<slot />
|
|
18
|
+
</Container>
|
|
19
|
+
</div>
|
package/src/layout/index.ts
CHANGED