@the-smithy/create-hearth-site 0.28.2 → 0.30.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/package.json +2 -2
- package/template/AGENTS.md +3 -3
- package/template/README.md +1 -1
- package/template/scripts/hearth.mjs +2 -2
- package/template/src/app/hearth/blocks/unstyled/route.ts +3 -2
- package/template/src/hearth/block-library/controls.tsx +0 -9
- package/template/src/hearth/block-library/page.tsx +46 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-smithy/create-hearth-site",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Start a new client website powered by Hearth: pnpm create @the-smithy/hearth-site",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
],
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"vitest": "^5.0.3",
|
|
33
|
-
"@the-smithy/hearth-schema": "^0.
|
|
33
|
+
"@the-smithy/hearth-schema": "^0.30.0"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"test": "node --test \"test/**/*.test.js\" && vitest run test/blocks.test.ts test/overrides.test.ts",
|
package/template/AGENTS.md
CHANGED
|
@@ -87,10 +87,10 @@ Work in this order, and stop at the first step that gets the result:
|
|
|
87
87
|
don't reach it, so it falls behind Hearth. Use one only when everything above can't get there, build it from
|
|
88
88
|
`src/hearth/ui.tsx` and the theme, and say why in `SITE.md`.
|
|
89
89
|
|
|
90
|
-
To style blocks without setting up pages for them, open `/hearth/blocks` while `pnpm dev` is running:
|
|
90
|
+
To style blocks without setting up pages for them, open `/hearth/blocks` while `pnpm dev` is running: each block with
|
|
91
91
|
its options and states, in sample content, drawn with this site's theme, so a change in `src/theme` shows at once.
|
|
92
|
-
|
|
93
|
-
and each element's slot name. Neither is served in production. Global blocks and the site's custom blocks aren't in it.
|
|
92
|
+
It shows one block at a time: `/hearth/blocks?block=hero` goes to one, and `?block=all` shows the whole list. Its
|
|
93
|
+
**Unstyled** switch (`/hearth/blocks/unstyled`) shows them with no design and each element's slot name. Neither is served in production. Global blocks and the site's custom blocks aren't in it.
|
|
94
94
|
Without a browser, the unstyled page is `node_modules/@the-smithy/hearth/block-reference.html`, but it's large: for slots,
|
|
95
95
|
use `src/hearth/theme/defaults.ts`.
|
|
96
96
|
|
package/template/README.md
CHANGED
|
@@ -98,7 +98,7 @@ Keep the API key out of chat: the agent never needs to see it, only `.env.local`
|
|
|
98
98
|
| `src/theme/theme.css` | Brand tokens (colors, fonts, content width) and the site's own CSS. **Start here** |
|
|
99
99
|
| `src/theme/index.ts` | The Tailwind classes of any part of any block, page, form, header or footer, by slot |
|
|
100
100
|
| `src/hearth/theme/defaults.ts` | Every slot and its default classes: read it to find the slot to change |
|
|
101
|
-
| `/hearth/blocks` | Not a file: while `pnpm dev` runs, this address shows
|
|
101
|
+
| `/hearth/blocks` | Not a file: while `pnpm dev` runs, this address shows each block with your theme, one at a time, without setting up pages for them. `?block=hero` goes to one, and `?block=all` shows the whole list. Its **Unstyled** switch shows them with no design, and each element's slot name |
|
|
102
102
|
| `src/theme/fonts.ts` | Web fonts (next/font) |
|
|
103
103
|
| `src/theme/custom-blocks.tsx` | Components for the site's own block types (Settings → Custom blocks in Hearth) |
|
|
104
104
|
| `src/theme/overrides.tsx` | Last resort: replace a whole block's component, the logo, the footer, the event or fundraiser page, their cards or the donation form (see `AGENTS.md`) |
|
|
@@ -246,9 +246,9 @@ async function drift() {
|
|
|
246
246
|
|
|
247
247
|
// Drift's skill follows its version, so like Hearth's skills it's installed rather than committed.
|
|
248
248
|
for (const dir of SKILL_DIRS) listSkills(dir, listedSkills(dir));
|
|
249
|
-
const install = spawnSync(bin, ["install", "."], { encoding: "utf8" });
|
|
249
|
+
const install = spawnSync(bin, ["install", ".", "--hook"], { encoding: "utf8" });
|
|
250
250
|
if (install.status !== 0) fail(`Drift couldn't install its skill: ${install.stderr?.trim() || install.error?.message}`);
|
|
251
|
-
console.log(`✓ Installed Drift's skill in ${SKILL_DIRS.map((d) => `${d}/`).join(" and ")}.`);
|
|
251
|
+
console.log(`✓ Installed Drift's skill in ${SKILL_DIRS.map((d) => `${d}/`).join(" and ")}, and its Claude Code hook in .claude/settings.json.`);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
/** The files in a zip archive, by name. */
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Stays on the same block, or on the whole list: #block-hero here is ?block=hero there.
|
|
5
|
+
const SWITCH = `<span class="view-switch"><span class="view-current">Unstyled</span><a href="/hearth/blocks" onclick="var id=location.hash.slice(1);if(id&&id!=='top')this.href='/hearth/blocks?block='+id.replace('block-','')">This site's theme</a></span>`;
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
+
* Each block unstyled, with its options, states and slot names, to build against while developing. Only under
|
|
8
9
|
* `pnpm dev`: the page comes from the SDK, so it matches this site's release, and visitors never see it.
|
|
9
10
|
*/
|
|
10
11
|
export function GET() {
|
|
@@ -24,15 +24,6 @@ export function SampleGuard() {
|
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/** A link to the block library's other view, staying on the same block. */
|
|
28
|
-
export function ViewLink({ href, children }: { href: string; children: ReactNode }) {
|
|
29
|
-
return (
|
|
30
|
-
<a href={href} className="hearth-library-view" onClick={(e) => (e.currentTarget.href = href + window.location.hash)}>
|
|
31
|
-
{children}
|
|
32
|
-
</a>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
27
|
/** One sample failing (a site without Giving has no fundraisers to offer) leaves the rest of the page working. */
|
|
37
28
|
export class SampleBoundary extends Component<{ children: ReactNode }, { message?: string }> {
|
|
38
29
|
state: { message?: string } = {};
|
|
@@ -3,13 +3,17 @@ import Link from "next/link";
|
|
|
3
3
|
import { notFound } from "next/navigation";
|
|
4
4
|
import { Fragment, Suspense, type ReactNode } from "react";
|
|
5
5
|
import { Blocks } from "../blocks";
|
|
6
|
-
import { SampleBoundary, SampleGuard
|
|
6
|
+
import { SampleBoundary, SampleGuard } from "./controls";
|
|
7
7
|
import { FormStates, Ground, GROUND_NAMES } from "./pieces";
|
|
8
8
|
import { blockSamples, libraryGroups } from "./samples";
|
|
9
9
|
|
|
10
10
|
export const metadata: Metadata = { title: "Block library", robots: { index: false, follow: false } };
|
|
11
11
|
|
|
12
12
|
const TONES = ["default", "muted", "inverse", "accent"] as const;
|
|
13
|
+
const START_HERE = [
|
|
14
|
+
{ type: "grounds", label: "Grounds and buttons" },
|
|
15
|
+
{ type: "form-states", label: "Form states" },
|
|
16
|
+
];
|
|
13
17
|
|
|
14
18
|
// Its own plain styles, so that nothing in a site's theme changes the tools around the samples.
|
|
15
19
|
const CSS = `
|
|
@@ -20,6 +24,9 @@ const CSS = `
|
|
|
20
24
|
.hearth-library-switch > * { padding: 4px 12px; }
|
|
21
25
|
.hearth-library-switch > * + * { border-left: 1px solid #d8d8d4; }
|
|
22
26
|
.hearth-library-current { background: #1f4fd8; color: #fff; }
|
|
27
|
+
.hearth-library-steps { display: inline-flex; gap: 8px; }
|
|
28
|
+
.hearth-library-steps > * { padding: 4px 12px; border: 1px solid #d8d8d4; border-radius: 6px; }
|
|
29
|
+
.hearth-library-steps > span { color: #5c5c58; }
|
|
23
30
|
.hearth-library-jump { position: relative; }
|
|
24
31
|
.hearth-library-jump summary { cursor: pointer; padding: 4px 12px; border: 1px solid #d8d8d4; border-radius: 6px; list-style: none; }
|
|
25
32
|
.hearth-library-jump summary::after { content: " ▾"; }
|
|
@@ -53,18 +60,26 @@ function Sample({ id, name, label, action, children }: { id?: string; name: stri
|
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
63
|
+
* The blocks, drawn by the real components with this site's theme and overrides, so that a change in src/theme shows
|
|
64
|
+
* here at once. For `pnpm dev` only. Its samples stand in for a site's content, so nothing depends on which pages
|
|
65
|
+
* exist, and nothing here is saved. It shows one block at a time, from ?block=hero; ?block=all shows the whole list.
|
|
59
66
|
*/
|
|
60
67
|
export default async function BlockLibrary({ searchParams }: { searchParams: Promise<{ block?: string | string[] }> }) {
|
|
61
68
|
if (process.env.NODE_ENV !== "development") notFound();
|
|
62
69
|
const { block } = await searchParams;
|
|
63
70
|
const samples = blockSamples(new Date());
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
71
|
+
const blocks = libraryGroups.flatMap((g) => g.blocks);
|
|
72
|
+
const items: { type: string; label: string }[] = [...START_HERE, ...blocks];
|
|
73
|
+
const everything = block === "all";
|
|
74
|
+
const index = Math.max(
|
|
75
|
+
items.findIndex((item) => item.type === block),
|
|
76
|
+
0,
|
|
77
|
+
);
|
|
78
|
+
const current = items[index];
|
|
79
|
+
const anchor = START_HERE.includes(current) ? current.type : `block-${current.type}`;
|
|
80
|
+
const unstyled = `/hearth/blocks/unstyled#${everything ? "all" : anchor}`;
|
|
81
|
+
const only = (type: string) => (everything ? <Link href={`/hearth/blocks?block=${type}`}>Only this block</Link> : undefined);
|
|
82
|
+
const showing = everything ? blocks : blocks.filter((b) => b.type === current.type);
|
|
68
83
|
return (
|
|
69
84
|
<>
|
|
70
85
|
<style>{CSS}</style>
|
|
@@ -72,14 +87,17 @@ export default async function BlockLibrary({ searchParams }: { searchParams: Pro
|
|
|
72
87
|
<div className="hearth-library-bar">
|
|
73
88
|
<b>Block library</b>
|
|
74
89
|
<span className="hearth-library-switch">
|
|
75
|
-
<
|
|
90
|
+
<a href={unstyled}>Unstyled</a>
|
|
76
91
|
<span className="hearth-library-current">This site's theme</span>
|
|
77
92
|
</span>
|
|
78
|
-
<
|
|
79
|
-
|
|
93
|
+
<span className="hearth-library-switch">
|
|
94
|
+
{everything ? <Link href="/hearth/blocks">One block</Link> : <span className="hearth-library-current">One block</span>}
|
|
95
|
+
{everything ? <span className="hearth-library-current">All blocks</span> : <Link href={`/hearth/blocks?block=all#${anchor}`}>All blocks</Link>}
|
|
96
|
+
</span>
|
|
97
|
+
<details key={everything ? "all" : current.type} className="hearth-library-jump">
|
|
98
|
+
<summary>{everything ? "Choose a block" : current.label}</summary>
|
|
80
99
|
<nav aria-label="Blocks">
|
|
81
|
-
|
|
82
|
-
{libraryGroups.map((g) => (
|
|
100
|
+
{[{ group: "Start here", blocks: START_HERE }, ...libraryGroups].map((g) => (
|
|
83
101
|
<Fragment key={g.group}>
|
|
84
102
|
<p>{g.group}</p>
|
|
85
103
|
{g.blocks.map((b) => (
|
|
@@ -91,23 +109,29 @@ export default async function BlockLibrary({ searchParams }: { searchParams: Pro
|
|
|
91
109
|
))}
|
|
92
110
|
</nav>
|
|
93
111
|
</details>
|
|
112
|
+
{everything ? null : (
|
|
113
|
+
<span className="hearth-library-steps">
|
|
114
|
+
{index > 0 ? <Link href={`/hearth/blocks?block=${items[index - 1].type}`}>← Previous</Link> : <span>← Previous</span>}
|
|
115
|
+
{index < items.length - 1 ? <Link href={`/hearth/blocks?block=${items[index + 1].type}`}>Next →</Link> : <span>Next →</span>}
|
|
116
|
+
</span>
|
|
117
|
+
)}
|
|
94
118
|
<span className="hearth-library-note">Sample content. Links and forms here do nothing.</span>
|
|
95
119
|
</div>
|
|
96
|
-
{
|
|
97
|
-
?
|
|
98
|
-
|
|
99
|
-
<Sample key={tone} id={tone === "default" ? "grounds" : undefined} name="Grounds" label={`${GROUND_NAMES[tone]} ground`}>
|
|
120
|
+
{everything || current.type === "grounds"
|
|
121
|
+
? TONES.map((tone) => (
|
|
122
|
+
<Sample key={tone} id={tone === "default" ? "grounds" : undefined} name="Grounds" label={`${GROUND_NAMES[tone]} ground`} action={only("grounds")}>
|
|
100
123
|
<Ground tone={tone} />
|
|
101
124
|
</Sample>
|
|
102
|
-
))
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
))
|
|
126
|
+
: null}
|
|
127
|
+
{everything || current.type === "form-states" ? (
|
|
128
|
+
<Sample id="form-states" name="Form states" label="Errors, filled-in fields, options and the success message" action={only("form-states")}>
|
|
105
129
|
<FormStates />
|
|
106
130
|
</Sample>
|
|
107
|
-
)}
|
|
131
|
+
) : null}
|
|
108
132
|
{showing.map((b) =>
|
|
109
133
|
samples[b.type].map((s, i) => (
|
|
110
|
-
<Sample key={s.block.id} id={i ? undefined : `block-${b.type}`} name={b.label} label={s.label} action={only
|
|
134
|
+
<Sample key={s.block.id} id={i ? undefined : `block-${b.type}`} name={b.label} label={s.label} action={only(b.type)}>
|
|
111
135
|
<Blocks blocks={[s.block]} />
|
|
112
136
|
</Sample>
|
|
113
137
|
)),
|