@the-smithy/create-hearth-site 0.24.1 → 0.26.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/README.md +2 -1
- package/bin/create-hearth-site.js +1 -1
- package/package.json +4 -3
- package/template/AGENTS.md +191 -9
- package/template/CLAUDE.md +2 -124
- package/template/GEMINI.md +3 -0
- package/template/HOSTING.md +88 -0
- package/template/README.md +30 -29
- package/template/SITE.md +1 -1
- package/template/_gitignore +2 -1
- package/template/package.json +3 -2
- package/template/scripts/hearth.mjs +82 -31
- package/template/src/app/donate/thank-you/page.tsx +1 -1
- package/template/src/app/globals.css +1 -0
- package/template/src/app/layout.tsx +1 -1
- package/template/src/app/not-found.tsx +1 -1
- package/template/src/app/registered/page.tsx +1 -1
- package/template/src/hearth/base.css +66 -0
- package/template/src/hearth/blocks/collections.tsx +6 -5
- package/template/src/hearth/blocks/content.tsx +64 -28
- package/template/src/hearth/blocks/custom.tsx +9 -1
- package/template/src/hearth/blocks/events.tsx +25 -17
- package/template/src/hearth/blocks/giving.tsx +24 -20
- package/template/src/hearth/blocks/index.tsx +29 -4
- package/template/src/hearth/blocks/info.tsx +15 -13
- package/template/src/hearth/blocks/media.tsx +10 -9
- package/template/src/hearth/components/EventView.tsx +61 -29
- package/template/src/hearth/components/FundraiserView.tsx +77 -0
- package/template/src/hearth/components/NotHere.tsx +9 -0
- package/template/src/hearth/components/PageContent.tsx +23 -0
- package/template/src/hearth/components/Registration.tsx +3 -2
- package/template/src/hearth/components/Share.tsx +11 -11
- package/template/src/hearth/format.ts +5 -3
- package/template/src/hearth/forms/ContactForm.tsx +5 -3
- package/template/src/hearth/forms/DonateForm.tsx +49 -32
- package/template/src/hearth/forms/DonationForm.tsx +13 -0
- package/template/src/hearth/forms/RegistrationForm.tsx +16 -14
- package/template/src/hearth/forms/VolunteerForm.tsx +10 -8
- package/template/src/hearth/forms/fields.tsx +17 -5
- package/template/src/hearth/hearth.css +3 -47
- package/template/src/hearth/layout/MobileNav.tsx +1 -1
- package/template/src/hearth/layout/NavLink.tsx +6 -2
- package/template/src/hearth/layout/RootLayout.tsx +8 -32
- package/template/src/hearth/layout/SiteFooter.tsx +13 -6
- package/template/src/hearth/layout/SiteFrame.tsx +48 -0
- package/template/src/hearth/layout/SiteHeader.tsx +9 -4
- package/template/src/hearth/manifest.json +3 -1
- package/template/src/hearth/overrides.ts +48 -0
- package/template/src/hearth/pages/donate.tsx +24 -15
- package/template/src/hearth/pages/event.tsx +3 -2
- package/template/src/hearth/pages/events.tsx +22 -10
- package/template/src/hearth/pages/fundraiser.tsx +10 -63
- package/template/src/hearth/pages/fundraisers.tsx +21 -11
- package/template/src/hearth/pages/not-found.tsx +28 -8
- package/template/src/hearth/pages/page.tsx +9 -15
- package/template/src/hearth/pages/registered.tsx +23 -10
- package/template/src/hearth/pages/thank-you.tsx +23 -10
- package/template/src/hearth/theme/defaults.ts +125 -2
- package/template/src/hearth/ui.tsx +20 -5
- package/template/src/theme/overrides.tsx +19 -5
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { theme } from "@/theme";
|
|
4
|
+
import { hearthNext } from "../client";
|
|
5
|
+
import { PageContent } from "../components/PageContent";
|
|
4
6
|
import { Section } from "../ui";
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
/** Never in search results, whatever the page in Hearth says. */
|
|
9
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
10
|
+
const own = await hearthNext.generatePageMetadata("donate/thank-you");
|
|
11
|
+
return { ...(Object.keys(own).length ? own : { title: theme.text.thankYouTitle }), robots: { index: false, follow: false } };
|
|
12
|
+
}
|
|
7
13
|
|
|
8
|
-
/** Where the payment page sends donors back. The receipt is emailed once the payment completes. */
|
|
9
|
-
export default function
|
|
14
|
+
/** Where the payment page sends donors back. The receipt is emailed once the payment completes. A page in Hearth at `donate/thank-you` replaces these words. */
|
|
15
|
+
export default async function Page() {
|
|
16
|
+
const page = await hearthNext.getPage("donate/thank-you");
|
|
10
17
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
<div data-page="thank-you">
|
|
19
|
+
{page ? (
|
|
20
|
+
<PageContent page={page} />
|
|
21
|
+
) : (
|
|
22
|
+
<Section block="thankYou">
|
|
23
|
+
<h1 className={theme.page.title}>{theme.text.thankYouTitle}</h1>
|
|
24
|
+
<p className={theme.page.lead}>{theme.text.thankYouLead}</p>
|
|
25
|
+
<Link href="/" className={theme.page.back}>
|
|
26
|
+
{theme.text.thankYouBack}
|
|
27
|
+
</Link>
|
|
28
|
+
</Section>
|
|
29
|
+
)}
|
|
30
|
+
</div>
|
|
18
31
|
);
|
|
19
32
|
}
|
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
* data-[size=full]: data-[size=small]: data-[width=narrow]: data-[columns=4]: data-[fit=contain]:
|
|
9
9
|
* data-highlight: data-cancelled: data-full: data-flip: aria-invalid: aria-[current=page]: has-checked:
|
|
10
10
|
* in-data-[…]: the same, set on an ancestor (Tailwind's in-* variant)
|
|
11
|
+
* For CSS that picks out something in particular (site CSS in src/theme/theme.css, or in-data-[…]: in a slot):
|
|
12
|
+
* [data-block="hero"] a block's type, with [data-tone] and, on a hero, [data-size] beside it
|
|
13
|
+
* [data-page="events"] the pages the code serves (events, fundraisers, donate, thank-you, registered, not-found)
|
|
14
|
+
* [data-card="our-history"] a card in a Cards block, by its title; [data-icon="graduation-cap"] by its Icon in Hearth
|
|
11
15
|
* Slots named `tone` aren't classes: they pick the ground (default, muted, inverse or accent) a block sits on.
|
|
12
|
-
* Slots under `text` are the words the code shows (labels, buttons), for sites that word them differently
|
|
16
|
+
* Slots under `text` are the words the code shows (labels, buttons), for sites that word them differently; {name} marks a
|
|
17
|
+
* value the code fills in. A few others hold a setting, not classes: `page.themeColor`, `page.externalLinks`,
|
|
18
|
+
* `page.navMatch`, `page.dateStyle` and `map.style`.
|
|
13
19
|
*/
|
|
14
20
|
|
|
15
21
|
const card = "rounded-2xl bg-surface ring-1 ring-line on-dark:bg-white/10 on-dark:ring-0";
|
|
@@ -38,6 +44,14 @@ export const defaults = {
|
|
|
38
44
|
lead: "-mt-6 mb-10 max-w-2xl text-lg opacity-85",
|
|
39
45
|
back: more,
|
|
40
46
|
notFound: "py-12",
|
|
47
|
+
/** A setting: the browser's color around the page on phones, e.g. "#1c1c1a". Empty for none. */
|
|
48
|
+
themeColor: "",
|
|
49
|
+
/** A setting: "new-tab" opens web links off the site in a new tab (buttons, footer links, logos, registration, directions); "same-tab" doesn't. Rich text and social links stay as they are. */
|
|
50
|
+
externalLinks: "same-tab",
|
|
51
|
+
/** A setting: when a menu link counts as the current page. "prefix": on its address and the ones under it; "exact": only its own. */
|
|
52
|
+
navMatch: "prefix",
|
|
53
|
+
/** A setting: event dates, "short" (Sat, Oct 18, 2026) or "long" (Saturday, October 18, 2026). */
|
|
54
|
+
dateStyle: "short",
|
|
41
55
|
},
|
|
42
56
|
header: {
|
|
43
57
|
/** The header's ground: `tones` colors it, and inverse or accent turn on on-dark: inside it. */
|
|
@@ -52,12 +66,14 @@ export const defaults = {
|
|
|
52
66
|
nav: "hidden flex-wrap items-center justify-end gap-x-6 gap-y-1 font-medium md:flex",
|
|
53
67
|
link: "py-2 hover:text-highlight aria-[current=page]:text-highlight",
|
|
54
68
|
cta: "rounded-lg bg-accent px-4 py-2 font-semibold text-accent-ink hover:opacity-90",
|
|
69
|
+
/** What a site inside an app adds beside the menu, at every width (see SiteFrame). */
|
|
70
|
+
actions: "flex items-center gap-2",
|
|
55
71
|
/** A menu item with a dropdown (a global block in a panel): the item, its link, the button that opens it, the panel. */
|
|
56
72
|
dropdown: "relative flex items-center gap-0.5",
|
|
57
73
|
dropdownLink: "py-2 hover:text-highlight aria-[current=page]:text-highlight",
|
|
58
74
|
dropdownToggle: "inline-flex size-8 items-center justify-center rounded-md hover:text-highlight aria-expanded:rotate-180",
|
|
59
75
|
dropdownPanel:
|
|
60
|
-
"absolute right-0 top-full z-40 mt-2 max-h-[80dvh] w-[min(32rem,calc(100vw-2rem))] overflow-y-auto rounded-2xl text-ink shadow-xl ring-1 ring-line [&_h2]:text-2xl [&_section>div]:px-6 [&_section>div]:py-6",
|
|
76
|
+
"absolute right-0 top-full z-40 mt-2 max-h-[80dvh] w-[min(32rem,calc(100vw-2rem))] overflow-y-auto rounded-2xl text-ink shadow-xl ring-1 ring-line [&_h2:not(li_*)]:text-2xl [&_section>div]:px-6 [&_section>div]:py-6",
|
|
61
77
|
},
|
|
62
78
|
/** The menu on phones: a button that opens the links full screen. */
|
|
63
79
|
mobileNav: {
|
|
@@ -100,6 +116,10 @@ export const defaults = {
|
|
|
100
116
|
social: "flex flex-col gap-2",
|
|
101
117
|
socialLink: "opacity-80 hover:opacity-100",
|
|
102
118
|
copyright: "mx-auto max-w-site px-4 pb-8 text-sm opacity-60 sm:px-6",
|
|
119
|
+
/** Small print under the copyright line (Footer in Hearth's Navigation). */
|
|
120
|
+
note: "mx-auto -mt-6 max-w-site whitespace-pre-line px-4 pb-8 text-sm opacity-60 sm:px-6",
|
|
121
|
+
/** The Logo from src/theme/overrides.tsx, above the site's name. Empty, it isn't drawn, as a header logo often doesn't suit the footer: set classes here to show it. */
|
|
122
|
+
logo: "",
|
|
103
123
|
},
|
|
104
124
|
|
|
105
125
|
/* ─── Shared pieces ─── */
|
|
@@ -107,6 +127,8 @@ export const defaults = {
|
|
|
107
127
|
section: {
|
|
108
128
|
root: "",
|
|
109
129
|
inner: "mx-auto max-w-site px-4 py-16 sm:px-6 sm:py-20",
|
|
130
|
+
/** On a block with an anchor, or anything else links jump to: the space kept above it (for a sticky header, make it larger). */
|
|
131
|
+
anchored: "scroll-mt-8",
|
|
110
132
|
},
|
|
111
133
|
tones: {
|
|
112
134
|
default: "bg-bg text-ink",
|
|
@@ -132,6 +154,8 @@ export const defaults = {
|
|
|
132
154
|
/* ─── Blocks ─── */
|
|
133
155
|
hero: {
|
|
134
156
|
root: "relative overflow-hidden bg-inverse text-inverse-ink",
|
|
157
|
+
/** In place of `root` on a hero given a tone in Hearth, whose `tones` set its colors. */
|
|
158
|
+
toned: "relative overflow-hidden",
|
|
135
159
|
image: "absolute inset-0 size-full object-cover opacity-35",
|
|
136
160
|
inner: "relative mx-auto max-w-site px-4 py-16 sm:px-6 sm:py-20 data-[size=full]:py-28 sm:data-[size=full]:py-36",
|
|
137
161
|
logo: "mb-6 h-20 w-auto sm:h-24",
|
|
@@ -214,6 +238,11 @@ export const defaults = {
|
|
|
214
238
|
question: "flex min-h-14 cursor-pointer list-none items-center justify-between gap-4 py-4 text-lg font-semibold [&::-webkit-details-marker]:hidden",
|
|
215
239
|
icon: "text-highlight transition before:content-['+'] group-open:rotate-45",
|
|
216
240
|
answer: "prose pb-5 text-lg opacity-85",
|
|
241
|
+
/** Set to show every answer in Hearth: the list, each question and answer, the question, the answer. */
|
|
242
|
+
terms: "grid max-w-3xl gap-8",
|
|
243
|
+
term: "",
|
|
244
|
+
termQuestion: "text-lg font-semibold",
|
|
245
|
+
termAnswer: "prose mt-2 text-lg opacity-85",
|
|
217
246
|
},
|
|
218
247
|
quote: {
|
|
219
248
|
figure: "flex max-w-3xl flex-col gap-6",
|
|
@@ -528,5 +557,99 @@ export const defaults = {
|
|
|
528
557
|
share: "Share",
|
|
529
558
|
visitProject: "Visit {name}",
|
|
530
559
|
mapFailed: "The map couldn't load.",
|
|
560
|
+
mapOf: "Map of {name}",
|
|
561
|
+
mapHeading: "Map: {heading}",
|
|
562
|
+
mapPlaces: "Map of our locations",
|
|
563
|
+
/** Read out after Get directions, in a list of places. */
|
|
564
|
+
directionsTo: "to {name}",
|
|
565
|
+
/** Read out after a link, with `page.externalLinks` set to "new-tab". */
|
|
566
|
+
opensInNewTab: "(opens in a new tab)",
|
|
567
|
+
skipLink: "Skip to content",
|
|
568
|
+
/** Upcoming events limited to a collection, as a link to it. */
|
|
569
|
+
allCollectionEvents: "All {collection}",
|
|
570
|
+
|
|
571
|
+
/* The pages the code serves, until they're replaced by a page in Hearth at the same address. */
|
|
572
|
+
eventsTitle: "Events",
|
|
573
|
+
eventsDescription: "Upcoming events from {site}.",
|
|
574
|
+
fundraisersTitle: "Fundraisers",
|
|
575
|
+
fundraisersDescription: "Fundraisers and campaigns from {site}: see how each is doing and give.",
|
|
576
|
+
completedFundraisers: "Completed",
|
|
577
|
+
donateTitle: "Donate",
|
|
578
|
+
donateDescription: "Support {site} with a donation.",
|
|
579
|
+
donateLead: "Your gift supports {site}.",
|
|
580
|
+
thankYouTitle: "Thank you",
|
|
581
|
+
thankYouLead: "Your gift makes a difference. If you gave your email, a receipt is on its way.",
|
|
582
|
+
thankYouBack: "Back to the homepage",
|
|
583
|
+
registeredTitle: "You're registered",
|
|
584
|
+
registeredLead: "Thanks for signing up. A confirmation is on its way to your email.",
|
|
585
|
+
registeredBack: "See all events",
|
|
586
|
+
notFoundTitle: "Page not found",
|
|
587
|
+
notFoundLead: "The page you're looking for has moved or no longer exists.",
|
|
588
|
+
notFoundHome: "Go to the homepage",
|
|
589
|
+
|
|
590
|
+
/* Fundraisers. */
|
|
591
|
+
fundraiserLabel: "Fundraiser",
|
|
592
|
+
completedFundraiserLabel: "Completed fundraiser",
|
|
593
|
+
raised: "raised",
|
|
594
|
+
raisedOfGoal: "raised of {goal}",
|
|
595
|
+
progressLabel: "Progress toward the goal",
|
|
596
|
+
oneDonation: "{count} online donation",
|
|
597
|
+
donations: "{count} online donations",
|
|
598
|
+
|
|
599
|
+
/* Share (the button's dialog). */
|
|
600
|
+
shareEventHeading: "Share {title}",
|
|
601
|
+
shareEventMessage: "{title}, {date}: {url}",
|
|
602
|
+
shareFundraiserHeading: "Share this fundraiser",
|
|
603
|
+
shareFundraiserMessage: "I'm supporting {title}. Give here: {url}",
|
|
604
|
+
shareFundraiserGoalMessage: "I'm supporting {title}: {raised} raised of {goal} so far. Give here: {url}",
|
|
605
|
+
shareMessageLabel: "Your message",
|
|
606
|
+
shareCopyMessage: "Copy message",
|
|
607
|
+
shareCopyLink: "Copy link",
|
|
608
|
+
shareTheMessage: "Message",
|
|
609
|
+
shareTheLink: "Link",
|
|
610
|
+
shareCopied: "{what} copied.",
|
|
611
|
+
shareCopyFailed: "Couldn't copy automatically. Select the text and copy it.",
|
|
612
|
+
shareTo: "Or share to",
|
|
613
|
+
shareMoreApps: "More apps…",
|
|
614
|
+
shareEmail: "Email",
|
|
615
|
+
shareTextMessage: "Text message",
|
|
616
|
+
|
|
617
|
+
/* Forms. */
|
|
618
|
+
optional: "(optional)",
|
|
619
|
+
chooseOne: "Choose one…",
|
|
620
|
+
honeypot: "Leave this empty",
|
|
621
|
+
sending: "Sending…",
|
|
622
|
+
yourName: "Name",
|
|
623
|
+
yourEmail: "Email",
|
|
624
|
+
yourPhone: "Phone",
|
|
625
|
+
openingCheckout: "Opening secure checkout…",
|
|
626
|
+
/* The donation form. */
|
|
627
|
+
donateAmount: "Amount",
|
|
628
|
+
donateOther: "Other",
|
|
629
|
+
donateCustomAmount: "Amount in dollars",
|
|
630
|
+
donateRange: "{min} to {max}.",
|
|
631
|
+
donateChoice: "Where should your gift go?",
|
|
632
|
+
donateAnywhere: "Where it's needed most",
|
|
633
|
+
donateEmail: "Email for your receipt",
|
|
634
|
+
donateMessage: "Message",
|
|
635
|
+
donateAnonymous: "Don't show my name publicly",
|
|
636
|
+
donateUpdates: "Send me updates about this fundraiser",
|
|
637
|
+
donateNote: "You'll finish on a secure payment page.",
|
|
638
|
+
/* Registration. */
|
|
639
|
+
ticket: "Ticket",
|
|
640
|
+
ticketsLeft: "{count} left",
|
|
641
|
+
teamName: "Team or organization",
|
|
642
|
+
attendee: "Attendee",
|
|
643
|
+
notes: "Notes",
|
|
644
|
+
free: "Free",
|
|
645
|
+
continueToPayment: "Continue to payment",
|
|
646
|
+
registering: "Registering…",
|
|
647
|
+
/* Volunteer sign-up. */
|
|
648
|
+
shiftsMany: "Shifts you can do",
|
|
649
|
+
shiftsOne: "Choose a shift",
|
|
650
|
+
shiftFull: "Full",
|
|
651
|
+
oneSpotLeft: "{count} spot left",
|
|
652
|
+
spotsLeft: "{count} spots left",
|
|
653
|
+
signingUp: "Signing up…",
|
|
531
654
|
},
|
|
532
655
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
2
|
import { Fragment, type AnchorHTMLAttributes, type ReactNode } from "react";
|
|
3
3
|
import ReactMarkdown from "react-markdown";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
4
5
|
import { focalPosition, type ImageRef } from "@the-smithy/hearth";
|
|
5
6
|
import { theme } from "@/theme";
|
|
6
7
|
import { cx } from "./theme";
|
|
@@ -23,10 +24,13 @@ export const ground = (tone?: Tone) => ({ "data-ground": isDark(tone) ? "dark" :
|
|
|
23
24
|
/** Fills {name} placeholders in a theme text: fill(theme.text.giveTo, { fundraiser: f.title }). */
|
|
24
25
|
export const fill = (text: string, values: Record<string, string>) => text.replace(/\{(\w+)\}/g, (m, k: string) => values[k] ?? m);
|
|
25
26
|
|
|
26
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* A full-width band with the site's content width inside. `block` names the block type, for CSS that targets it; `id`
|
|
29
|
+
* is what links jump to: blocks pass their anchor from Hearth (`block.anchor`).
|
|
30
|
+
*/
|
|
27
31
|
export function Section({ tone = "default", id, block, className, innerClassName, children }: { tone?: Tone; id?: string; block?: string; className?: string; innerClassName?: string; children: ReactNode }) {
|
|
28
32
|
return (
|
|
29
|
-
<section id={id} data-block={block} {...ground(tone)} className={cx(theme.tones[tone], theme.section.root, id &&
|
|
33
|
+
<section id={id} data-block={block} {...ground(tone)} className={cx(theme.tones[tone], theme.section.root, id && theme.section.anchored, className)}>
|
|
30
34
|
<div className={cx(theme.section.inner, innerClassName)}>{children}</div>
|
|
31
35
|
</section>
|
|
32
36
|
);
|
|
@@ -75,19 +79,30 @@ export function SectionHeading({ eyebrow, heading, intro }: { eyebrow?: string;
|
|
|
75
79
|
export function Markdown({ children, className = theme.markdown.root, ...data }: { children: string; className?: string } & Record<`data-${string}`, string | undefined>) {
|
|
76
80
|
return (
|
|
77
81
|
<div className={className} {...data}>
|
|
78
|
-
<ReactMarkdown>{children}</ReactMarkdown>
|
|
82
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{children}</ReactMarkdown>
|
|
79
83
|
</div>
|
|
80
84
|
);
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
export const isExternal = (href: string) => /^(https?:|mailto:|tel:)/.test(href);
|
|
84
88
|
|
|
89
|
+
/** A plain link off the site. With `page.externalLinks` set to "new-tab", web links open in a new tab and say so to screen readers. */
|
|
90
|
+
export function ExternalLink({ href, rel, children, ...rest }: { href: string; children: ReactNode } & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">) {
|
|
91
|
+
const newTab = theme.page.externalLinks === "new-tab" && /^https?:/.test(href);
|
|
92
|
+
return (
|
|
93
|
+
<a href={href} rel={newTab ? (rel ?? "noopener") : rel} target={newTab ? "_blank" : undefined} {...rest}>
|
|
94
|
+
{children}
|
|
95
|
+
{newTab ? <span className="sr-only"> {theme.text.opensInNewTab}</span> : null}
|
|
96
|
+
</a>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
85
100
|
/** A link to a path on the site or to anywhere else. */
|
|
86
101
|
export function SmartLink({ href, className, children, ...rest }: { href: string; className?: string; children: ReactNode } & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">) {
|
|
87
102
|
return isExternal(href) ? (
|
|
88
|
-
<
|
|
103
|
+
<ExternalLink href={href} className={className} {...rest}>
|
|
89
104
|
{children}
|
|
90
|
-
</
|
|
105
|
+
</ExternalLink>
|
|
91
106
|
) : (
|
|
92
107
|
<Link href={href} className={className} {...rest}>
|
|
93
108
|
{children}
|
|
@@ -3,14 +3,28 @@ import type { ComponentType } from "react";
|
|
|
3
3
|
import type { Site } from "@the-smithy/hearth";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* The last resort, when a design can't be reached with slots and tokens: replace a whole block's component, or
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The last resort, when a design can't be reached with slots and tokens: replace a whole block's component, or the
|
|
7
|
+
* parts below. Replaced components are this site's own code: `pnpm hearth:update` won't update them, so check them
|
|
8
|
+
* against src/hearth after each update. Build them from src/hearth/ui.tsx and the theme, and say why in SITE.md.
|
|
9
9
|
*
|
|
10
10
|
* import { CountingStats } from "./CountingStats";
|
|
11
11
|
* export const blocks: Partial<BlockComponents> = { stats: CountingStats };
|
|
12
|
+
*
|
|
13
|
+
* Export any of these too, used everywhere the template draws them (types in src/hearth/overrides.ts). They can import
|
|
14
|
+
* Hearth's components, Blocks too. Words, dates and hiding things are slots: try those first.
|
|
15
|
+
*
|
|
16
|
+
* export const Footer: Overrides["Footer"] = ({ site }) => … the whole footer
|
|
17
|
+
* export const EventPage: Overrides["EventPage"] = ({ event }) => … an event's page, reusing EventRegisterSection
|
|
18
|
+
* and EventVolunteerSection from EventView
|
|
19
|
+
* export const FundraiserPage: Overrides["FundraiserPage"] = ({ fundraiser }) => …
|
|
20
|
+
* export const EventCard: Overrides["EventCard"] = async ({ event, heading }) => <li>…</li>
|
|
21
|
+
* export const FundraiserCard: Overrides["FundraiserCard"] = ({ fundraiser, heading }) => <li>…</li>
|
|
22
|
+
* export const DonateForm: Overrides["DonateForm"] = (props) => …
|
|
12
23
|
*/
|
|
13
24
|
export const blocks: Partial<BlockComponents> = {};
|
|
14
25
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The header's logo; leave null to show the logo uploaded in Hearth (Settings → Search & sharing) and the site name.
|
|
28
|
+
* Set the `footer.logo` slot to draw it in the footer too (`place` says which).
|
|
29
|
+
*/
|
|
30
|
+
export const Logo: ComponentType<{ site: Site; place: "header" | "footer" }> | null = null;
|