@avocadostudio-ai/blocks 0.1.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/LICENSE +201 -0
- package/dist/block-error-boundary.d.ts +18 -0
- package/dist/block-error-boundary.js +36 -0
- package/dist/blocks/_base.css +108 -0
- package/dist/blocks/_layout.css +47 -0
- package/dist/blocks/_shared.d.ts +24 -0
- package/dist/blocks/_shared.js +153 -0
- package/dist/blocks/_tokens.css +127 -0
- package/dist/blocks/banner/renderer.d.ts +2 -0
- package/dist/blocks/banner/renderer.js +21 -0
- package/dist/blocks/banner/styles.css +72 -0
- package/dist/blocks/block-image.d.ts +14 -0
- package/dist/blocks/block-image.js +10 -0
- package/dist/blocks/block-types.d.ts +2 -0
- package/dist/blocks/block-types.js +22 -0
- package/dist/blocks/card/renderer.d.ts +1 -0
- package/dist/blocks/card/renderer.js +11 -0
- package/dist/blocks/card/styles.css +94 -0
- package/dist/blocks/card-grid/renderer.d.ts +1 -0
- package/dist/blocks/card-grid/renderer.js +17 -0
- package/dist/blocks/card-grid/styles.css +38 -0
- package/dist/blocks/carousel/init.d.ts +8 -0
- package/dist/blocks/carousel/init.js +35 -0
- package/dist/blocks/carousel/renderer.d.ts +2 -0
- package/dist/blocks/carousel/renderer.js +19 -0
- package/dist/blocks/carousel/styles.css +136 -0
- package/dist/blocks/cta/renderer.d.ts +1 -0
- package/dist/blocks/cta/renderer.js +7 -0
- package/dist/blocks/cta/styles.css +111 -0
- package/dist/blocks/embed/renderer.d.ts +2 -0
- package/dist/blocks/embed/renderer.js +16 -0
- package/dist/blocks/embed/styles.css +58 -0
- package/dist/blocks/faq-accordion/renderer.d.ts +1 -0
- package/dist/blocks/faq-accordion/renderer.js +11 -0
- package/dist/blocks/faq-accordion/styles.css +68 -0
- package/dist/blocks/feature-grid/renderer.d.ts +1 -0
- package/dist/blocks/feature-grid/renderer.js +12 -0
- package/dist/blocks/feature-grid/styles.css +62 -0
- package/dist/blocks/footer/renderer.d.ts +1 -0
- package/dist/blocks/footer/renderer.js +19 -0
- package/dist/blocks/footer/styles.css +64 -0
- package/dist/blocks/gallery/renderer.d.ts +2 -0
- package/dist/blocks/gallery/renderer.js +21 -0
- package/dist/blocks/gallery/styles.css +59 -0
- package/dist/blocks/hero/renderer.d.ts +1 -0
- package/dist/blocks/hero/renderer.js +20 -0
- package/dist/blocks/hero/styles.css +137 -0
- package/dist/blocks/index.d.ts +5 -0
- package/dist/blocks/index.js +47 -0
- package/dist/blocks/quote/renderer.d.ts +2 -0
- package/dist/blocks/quote/renderer.js +9 -0
- package/dist/blocks/quote/styles.css +97 -0
- package/dist/blocks/rich-text/renderer.d.ts +1 -0
- package/dist/blocks/rich-text/renderer.js +15 -0
- package/dist/blocks/rich-text/styles.css +64 -0
- package/dist/blocks/site-header/renderer.d.ts +1 -0
- package/dist/blocks/site-header/renderer.js +64 -0
- package/dist/blocks/site-header/styles.css +345 -0
- package/dist/blocks/stats/renderer.d.ts +1 -0
- package/dist/blocks/stats/renderer.js +13 -0
- package/dist/blocks/stats/styles.css +64 -0
- package/dist/blocks/styles.css +23 -0
- package/dist/blocks/table/renderer.d.ts +2 -0
- package/dist/blocks/table/renderer.js +14 -0
- package/dist/blocks/table/styles.css +87 -0
- package/dist/blocks/tabs/init.d.ts +5 -0
- package/dist/blocks/tabs/init.js +38 -0
- package/dist/blocks/tabs/renderer.d.ts +2 -0
- package/dist/blocks/tabs/renderer.js +30 -0
- package/dist/blocks/tabs/styles.css +147 -0
- package/dist/blocks/testimonials/renderer.d.ts +1 -0
- package/dist/blocks/testimonials/renderer.js +15 -0
- package/dist/blocks/testimonials/styles.css +92 -0
- package/dist/blocks/two-column/renderer.d.ts +1 -0
- package/dist/blocks/two-column/renderer.js +41 -0
- package/dist/blocks/two-column/styles.css +83 -0
- package/dist/blocks/video/renderer.d.ts +2 -0
- package/dist/blocks/video/renderer.js +52 -0
- package/dist/blocks/video/styles.css +84 -0
- package/dist/blocks-hydrator.d.ts +5 -0
- package/dist/blocks-hydrator.js +15 -0
- package/dist/catalogue.d.ts +1 -0
- package/dist/catalogue.js +741 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/renderer.d.ts +12 -0
- package/dist/renderer.js +35 -0
- package/package.json +54 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
.hero {
|
|
2
|
+
border-radius: 0;
|
|
3
|
+
padding: 56px 20px;
|
|
4
|
+
background: var(--hero-bg);
|
|
5
|
+
position: relative;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.hero__inner {
|
|
10
|
+
position: relative;
|
|
11
|
+
gap: 24px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.hero__content h1 {
|
|
15
|
+
margin: 0 0 14px;
|
|
16
|
+
font-size: clamp(2rem, 4.2vw, 3.35rem);
|
|
17
|
+
line-height: 1.08;
|
|
18
|
+
letter-spacing: -0.02em;
|
|
19
|
+
font-weight: 800;
|
|
20
|
+
color: var(--heading);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.hero__content p {
|
|
24
|
+
margin: 0;
|
|
25
|
+
font-size: clamp(1rem, 1.65vw, 1.05rem);
|
|
26
|
+
line-height: 1.55;
|
|
27
|
+
color: var(--text-200);
|
|
28
|
+
max-width: 36ch;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.hero__media img {
|
|
32
|
+
background: var(--placeholder-img);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.hero__eyebrow {
|
|
36
|
+
display: inline-block;
|
|
37
|
+
font-size: 0.8125rem;
|
|
38
|
+
font-weight: 700;
|
|
39
|
+
letter-spacing: 0.08em;
|
|
40
|
+
text-transform: uppercase;
|
|
41
|
+
color: var(--brand);
|
|
42
|
+
margin-bottom: 8px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.hero__content--center {
|
|
46
|
+
text-align: center;
|
|
47
|
+
align-items: center;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.hero__content--center p {
|
|
51
|
+
max-width: 52ch;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.hero__content--center .hero__actions {
|
|
55
|
+
justify-content: center;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.hero__actions {
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-wrap: wrap;
|
|
61
|
+
gap: 12px;
|
|
62
|
+
margin-top: 24px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ── Full-width image variant ─────────────────────────────── */
|
|
66
|
+
.hero--full-width {
|
|
67
|
+
position: relative;
|
|
68
|
+
min-height: 420px;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.hero--full-width .hero__bg-image {
|
|
74
|
+
position: absolute;
|
|
75
|
+
inset: 0;
|
|
76
|
+
width: 100%;
|
|
77
|
+
height: 100%;
|
|
78
|
+
object-fit: cover;
|
|
79
|
+
z-index: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.hero--full-width::after {
|
|
83
|
+
content: "";
|
|
84
|
+
position: absolute;
|
|
85
|
+
inset: 0;
|
|
86
|
+
background: rgba(0, 0, 0, 0.45);
|
|
87
|
+
z-index: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.hero--full-width .hero__inner {
|
|
91
|
+
position: relative;
|
|
92
|
+
z-index: 1;
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
95
|
+
align-items: center;
|
|
96
|
+
text-align: center;
|
|
97
|
+
width: 100%;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.hero--full-width .hero__content {
|
|
101
|
+
max-width: 720px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.hero--full-width .hero__eyebrow {
|
|
105
|
+
color: rgba(255, 255, 255, 0.85);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.hero--full-width .hero__content h1,
|
|
109
|
+
.hero--full-width .hero__content h2,
|
|
110
|
+
.hero--full-width .hero__content h3 {
|
|
111
|
+
color: #fff;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.hero--full-width .hero__content p {
|
|
115
|
+
color: rgba(255, 255, 255, 0.9);
|
|
116
|
+
max-width: 52ch;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.hero--full-width .hero__actions {
|
|
120
|
+
justify-content: center;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Dark */
|
|
124
|
+
.dark .hero__content p {
|
|
125
|
+
color: var(--body);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@media (max-width: 900px) {
|
|
129
|
+
.hero__content h1 {
|
|
130
|
+
font-size: clamp(1.75rem, 8vw, 2.5rem);
|
|
131
|
+
line-height: 1.12;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.hero {
|
|
135
|
+
padding: 40px 16px;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import { type RendererBlockType } from "./block-types.ts";
|
|
3
|
+
export declare const renderers: Record<RendererBlockType, (props: Record<string, unknown>) => JSX.Element | null>;
|
|
4
|
+
export declare const rendererTypes: ("Hero" | "FeatureGrid" | "Testimonials" | "FAQAccordion" | "CTA" | "Card" | "CardGrid" | "RichText" | "Stats" | "TwoColumn" | "Footer" | "Gallery" | "Carousel" | "Tabs" | "Quote" | "SiteHeader" | "Embed" | "Banner" | "Table" | "Video")[];
|
|
5
|
+
export declare function isRendererBlockType(value: string): value is RendererBlockType;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { rendererBlockTypes } from "./block-types.js";
|
|
2
|
+
import { Hero } from "./hero/renderer.js";
|
|
3
|
+
import { FeatureGrid } from "./feature-grid/renderer.js";
|
|
4
|
+
import { Testimonials } from "./testimonials/renderer.js";
|
|
5
|
+
import { FAQAccordion } from "./faq-accordion/renderer.js";
|
|
6
|
+
import { CTA } from "./cta/renderer.js";
|
|
7
|
+
import { Card } from "./card/renderer.js";
|
|
8
|
+
import { CardGrid } from "./card-grid/renderer.js";
|
|
9
|
+
import { RichText } from "./rich-text/renderer.js";
|
|
10
|
+
import { Stats } from "./stats/renderer.js";
|
|
11
|
+
import { TwoColumn } from "./two-column/renderer.js";
|
|
12
|
+
import { Footer } from "./footer/renderer.js";
|
|
13
|
+
import { SiteHeader } from "./site-header/renderer.js";
|
|
14
|
+
import { Embed } from "./embed/renderer.js";
|
|
15
|
+
import { Banner } from "./banner/renderer.js";
|
|
16
|
+
import { Carousel } from "./carousel/renderer.js";
|
|
17
|
+
import { Gallery } from "./gallery/renderer.js";
|
|
18
|
+
import { Tabs } from "./tabs/renderer.js";
|
|
19
|
+
import { Table } from "./table/renderer.js";
|
|
20
|
+
import { Quote } from "./quote/renderer.js";
|
|
21
|
+
import { Video } from "./video/renderer.js";
|
|
22
|
+
export const renderers = {
|
|
23
|
+
Hero,
|
|
24
|
+
FeatureGrid,
|
|
25
|
+
Testimonials,
|
|
26
|
+
FAQAccordion,
|
|
27
|
+
CTA,
|
|
28
|
+
Card,
|
|
29
|
+
CardGrid,
|
|
30
|
+
RichText,
|
|
31
|
+
Stats,
|
|
32
|
+
TwoColumn,
|
|
33
|
+
Footer,
|
|
34
|
+
SiteHeader,
|
|
35
|
+
Embed,
|
|
36
|
+
Banner,
|
|
37
|
+
Carousel,
|
|
38
|
+
Gallery,
|
|
39
|
+
Tabs,
|
|
40
|
+
Table,
|
|
41
|
+
Quote,
|
|
42
|
+
Video
|
|
43
|
+
};
|
|
44
|
+
export const rendererTypes = [...rendererBlockTypes];
|
|
45
|
+
export function isRendererBlockType(value) {
|
|
46
|
+
return rendererBlockTypes.includes(value);
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { renderRichTextContent, BlockImage } from "../_shared.js";
|
|
3
|
+
export function Quote(props) {
|
|
4
|
+
const quote = String(props.quote ?? "");
|
|
5
|
+
const author = String(props.author ?? "");
|
|
6
|
+
const role = String(props.role ?? "");
|
|
7
|
+
const imageUrl = String(props.imageUrl ?? "");
|
|
8
|
+
return (_jsx("section", { className: "quote-block", children: _jsxs("div", { className: "section__inner", children: [_jsxs("blockquote", { className: "quote-block__quote", children: [_jsx("span", { className: "quote-block__mark", "aria-hidden": "true", children: "\u201C" }), _jsx("div", { className: "quote-block__text", "data-editable-target": "quote", "data-editable-target-label": "quote", "data-editable-label": "quote", children: renderRichTextContent(quote) })] }), (author.length > 0 || role.length > 0) && (_jsxs("div", { className: "quote-block__attribution", children: [imageUrl.length > 0 && (_jsx(BlockImage, { className: "quote-block__avatar", src: imageUrl, alt: author || "Author", width: 48, height: 48 })), _jsxs("div", { className: "quote-block__author-info", children: [author.length > 0 && (_jsx("span", { className: "quote-block__author", "data-editable-target": "author", "data-editable-target-label": "author", "data-editable-label": "author", children: author })), role.length > 0 && (_jsx("span", { className: "quote-block__role", "data-editable-target": "role", "data-editable-target-label": "role", "data-editable-label": "role", children: role }))] })] }))] }) }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
.quote-block {
|
|
2
|
+
padding: 40px 20px;
|
|
3
|
+
background: var(--section-bg);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.quote-block .section__inner {
|
|
7
|
+
max-width: 720px;
|
|
8
|
+
text-align: center;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.quote-block__quote {
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
position: relative;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.quote-block__mark {
|
|
18
|
+
display: block;
|
|
19
|
+
font-size: 4rem;
|
|
20
|
+
line-height: 0.8;
|
|
21
|
+
color: var(--brand);
|
|
22
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
23
|
+
user-select: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.quote-block__text {
|
|
27
|
+
font-size: 1.25rem;
|
|
28
|
+
line-height: 1.6;
|
|
29
|
+
color: var(--heading);
|
|
30
|
+
font-style: italic;
|
|
31
|
+
margin-top: 4px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.quote-block__text p {
|
|
35
|
+
margin: 0 0 8px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.quote-block__text p:last-child {
|
|
39
|
+
margin-bottom: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Attribution row */
|
|
43
|
+
.quote-block__attribution {
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
flex-direction: row;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: 12px;
|
|
48
|
+
margin-top: 20px;
|
|
49
|
+
padding-top: 16px;
|
|
50
|
+
border-top: 1px solid var(--border);
|
|
51
|
+
text-align: left;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.quote-block__avatar {
|
|
55
|
+
width: 48px;
|
|
56
|
+
height: 48px;
|
|
57
|
+
min-width: 48px;
|
|
58
|
+
min-height: 48px;
|
|
59
|
+
max-width: 48px;
|
|
60
|
+
max-height: 48px;
|
|
61
|
+
border-radius: 50%;
|
|
62
|
+
object-fit: cover;
|
|
63
|
+
display: block;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.quote-block__author-info {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
gap: 2px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.quote-block__author {
|
|
73
|
+
display: block;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
font-size: 0.95rem;
|
|
76
|
+
color: var(--heading);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.quote-block__role {
|
|
80
|
+
display: block;
|
|
81
|
+
font-size: 0.85rem;
|
|
82
|
+
color: var(--text-200);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@media (max-width: 600px) {
|
|
86
|
+
.quote-block {
|
|
87
|
+
padding: 24px 12px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.quote-block__mark {
|
|
91
|
+
font-size: 3rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.quote-block__text {
|
|
95
|
+
font-size: 1.1rem;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function RichText(props: Record<string, unknown>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { isValidElement } from "react";
|
|
3
|
+
import { resolveHeadingTag } from "@avocadostudio-ai/shared";
|
|
4
|
+
import { renderRichTextContent } from "../_shared.js";
|
|
5
|
+
export function RichText(props) {
|
|
6
|
+
const title = String(props.title ?? "");
|
|
7
|
+
// Puck's richtext transform passes React elements; plain strings come from
|
|
8
|
+
// the orchestrator/preview. Handle both.
|
|
9
|
+
const body = props.body;
|
|
10
|
+
const renderedBody = isValidElement(body)
|
|
11
|
+
? body
|
|
12
|
+
: renderRichTextContent(String(body ?? ""));
|
|
13
|
+
const HeadingTag = resolveHeadingTag("RichText", props);
|
|
14
|
+
return (_jsx("section", { className: "rich-text", children: _jsxs("div", { className: "section__inner", children: [title.length > 0 && (_jsx(HeadingTag, { "data-editable-target": "title", "data-editable-target-label": "title", "data-editable-label": "title", children: title })), _jsx("div", { className: "rich-text__body", "data-editable-target": "body", "data-editable-target-label": "body", "data-editable-label": "body", children: renderedBody })] }) }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
.rich-text {}
|
|
2
|
+
|
|
3
|
+
.rich-text__body p {
|
|
4
|
+
font-size: 1rem;
|
|
5
|
+
line-height: 1.75;
|
|
6
|
+
color: var(--body);
|
|
7
|
+
margin-bottom: 1em;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.rich-text__body p:last-child {
|
|
11
|
+
margin-bottom: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.rich-text__body ul,
|
|
15
|
+
.rich-text__body ol {
|
|
16
|
+
margin: 0 0 1em;
|
|
17
|
+
padding-left: 1.4em;
|
|
18
|
+
color: var(--body);
|
|
19
|
+
line-height: 1.75;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.rich-text__body ul {
|
|
23
|
+
list-style: disc;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.rich-text__body ol {
|
|
27
|
+
list-style: decimal;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.rich-text__body li {
|
|
31
|
+
display: list-item;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.rich-text__body li + li {
|
|
35
|
+
margin-top: 0.3em;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.rich-text__body h3 {
|
|
39
|
+
margin-top: 1.25em;
|
|
40
|
+
margin-bottom: 0.45em;
|
|
41
|
+
font-size: 1.25rem;
|
|
42
|
+
line-height: 1.3;
|
|
43
|
+
letter-spacing: -0.01em;
|
|
44
|
+
font-weight: 700;
|
|
45
|
+
color: var(--heading);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Dark */
|
|
49
|
+
.dark .rich-text__body,
|
|
50
|
+
.dark .rich-text__body li,
|
|
51
|
+
.dark .rich-text__body strong,
|
|
52
|
+
.dark .rich-text__body em {
|
|
53
|
+
color: #dbe7f7;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.dark main.editor-mode .rich-text__body p,
|
|
57
|
+
.dark main.editor-mode .rich-text__body li {
|
|
58
|
+
color: #dbe7f7;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.dark .editor-live-draft-active .rich-text__body[data-editable-target]::after {
|
|
62
|
+
background: rgba(56, 189, 248, 0.025);
|
|
63
|
+
animation: none;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SiteHeader(props: Record<string, unknown>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
4
|
+
import { BlockImage } from "../_shared.js";
|
|
5
|
+
function ChevronDown({ className }) {
|
|
6
|
+
return (_jsx("svg", { className: className, width: "14", height: "14", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
7
|
+
}
|
|
8
|
+
export function SiteHeader(props) {
|
|
9
|
+
const siteName = String(props.siteName ?? "Site");
|
|
10
|
+
const logoUrl = String(props.logoUrl ?? "/logo.svg");
|
|
11
|
+
const activePath = typeof props.activePath === "string" ? props.activePath : undefined;
|
|
12
|
+
const links = Array.isArray(props.links)
|
|
13
|
+
? props.links.filter((l) => l && typeof l.label === "string" && (typeof l.href === "string" || Array.isArray(l.children)))
|
|
14
|
+
: [];
|
|
15
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
const [expandedGroup, setExpandedGroup] = useState(null);
|
|
17
|
+
const menuRef = useRef(null);
|
|
18
|
+
const toggleRef = useRef(null);
|
|
19
|
+
const menuId = "site-mobile-menu";
|
|
20
|
+
const close = useCallback(() => { setIsOpen(false); setExpandedGroup(null); }, []);
|
|
21
|
+
const toggle = useCallback(() => setIsOpen((v) => !v), []);
|
|
22
|
+
// Close on popstate (SPA navigation)
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const handler = () => setIsOpen(false);
|
|
25
|
+
window.addEventListener("popstate", handler);
|
|
26
|
+
return () => window.removeEventListener("popstate", handler);
|
|
27
|
+
}, []);
|
|
28
|
+
// Outside click & Escape key
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!isOpen)
|
|
31
|
+
return;
|
|
32
|
+
function handlePointerDown(event) {
|
|
33
|
+
if (!menuRef.current?.contains(event.target))
|
|
34
|
+
setIsOpen(false);
|
|
35
|
+
}
|
|
36
|
+
function handleEscape(event) {
|
|
37
|
+
if (event.key === "Escape") {
|
|
38
|
+
setIsOpen(false);
|
|
39
|
+
toggleRef.current?.focus();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
window.addEventListener("pointerdown", handlePointerDown);
|
|
43
|
+
window.addEventListener("keydown", handleEscape);
|
|
44
|
+
return () => {
|
|
45
|
+
window.removeEventListener("pointerdown", handlePointerDown);
|
|
46
|
+
window.removeEventListener("keydown", handleEscape);
|
|
47
|
+
};
|
|
48
|
+
}, [isOpen]);
|
|
49
|
+
// Focus first link when menu opens
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (isOpen) {
|
|
52
|
+
const first = menuRef.current?.querySelector("a, button:not([aria-expanded])");
|
|
53
|
+
first?.focus();
|
|
54
|
+
}
|
|
55
|
+
}, [isOpen]);
|
|
56
|
+
const isActive = (href) => {
|
|
57
|
+
if (!activePath || !href)
|
|
58
|
+
return false;
|
|
59
|
+
const clean = href.split("?")[0];
|
|
60
|
+
return clean === activePath;
|
|
61
|
+
};
|
|
62
|
+
const hasActiveChild = (link) => link.children?.some((c) => isActive(c.href)) ?? false;
|
|
63
|
+
return (_jsxs(_Fragment, { children: [_jsx("header", { className: "site-top-nav", "data-block-chrome": "true", children: _jsxs("div", { className: "site-top-nav-inner", children: [_jsxs("a", { className: "site-brand", href: links[0]?.href ?? "/", "data-editable-target": "siteName", children: [logoUrl ? _jsx(BlockImage, { className: "site-logo", src: logoUrl, alt: `${siteName} logo`, width: 38, height: 38, "data-editable-target": "logoUrl" }) : null, _jsx("span", { className: "site-brand-text", children: siteName })] }), _jsx("nav", { className: "site-nav-links site-nav-links-desktop", "aria-label": "Primary", children: links.map((link, i) => link.children?.length ? (_jsxs("div", { className: "site-nav-dropdown", children: [_jsxs("button", { type: "button", className: `site-nav-dropdown-trigger${hasActiveChild(link) ? " is-active" : ""}`, "data-editable-target": `links[${i}].label`, "data-editable-target-label": `links[${i}].label`, children: [link.label, _jsx(ChevronDown, { className: "site-nav-chevron" })] }), _jsx("div", { className: "site-nav-dropdown-menu", children: link.children.map((child, j) => (_jsx("a", { href: child.href ?? "#", className: isActive(child.href) ? "is-active" : undefined, "data-editable-target": `links[${i}].children[${j}].label`, "data-editable-target-label": `links[${i}].children[${j}].label`, onClick: (e) => e.currentTarget.blur(), children: child.label }, child.href))) })] }, `group-${i}`)) : (_jsx("a", { href: link.href ?? "#", className: isActive(link.href) ? "is-active" : undefined, "data-editable-target": `links[${i}].label`, "data-editable-target-label": `links[${i}].label`, children: link.label }, link.href))) }), _jsxs("div", { ref: menuRef, className: `site-mobile-menu${isOpen ? " is-open" : ""}`, children: [_jsx("button", { ref: toggleRef, type: "button", className: "site-mobile-menu-button", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": "Toggle navigation menu", onClick: toggle, children: _jsxs("span", { className: "burger-icon", "aria-hidden": "true", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] }) }), isOpen ? (_jsx("nav", { id: menuId, className: "site-nav-links site-nav-links-mobile", "aria-label": "Mobile primary", children: links.map((link, i) => link.children?.length ? (_jsxs("div", { className: "site-nav-mobile-group", children: [_jsxs("button", { type: "button", className: `site-nav-mobile-group-trigger${hasActiveChild(link) ? " is-active" : ""}`, "aria-expanded": expandedGroup === i, onClick: () => setExpandedGroup(expandedGroup === i ? null : i), "data-editable-target": `links[${i}].label`, children: [link.label, _jsx(ChevronDown, { className: `site-nav-chevron${expandedGroup === i ? " is-expanded" : ""}` })] }), expandedGroup === i ? (_jsx("div", { className: "site-nav-mobile-group-children", children: link.children.map((child, j) => (_jsx("a", { href: child.href ?? "#", className: isActive(child.href) ? "is-active" : undefined, "data-editable-target": `links[${i}].children[${j}].label`, onClick: close, children: child.label }, `mobile-${child.href}`))) })) : null] }, `mobile-group-${i}`)) : (_jsx("a", { href: link.href ?? "#", className: isActive(link.href) ? "is-active" : undefined, "data-editable-target": `links[${i}].label`, onClick: close, children: link.label }, `mobile-${link.href}`))) })) : null] })] }) }), isOpen ? (_jsx("div", { className: "site-mobile-backdrop", onClick: close })) : null] }));
|
|
64
|
+
}
|