@the-smithy/create-hearth-site 0.28.0 → 0.29.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-smithy/create-hearth-site",
3
- "version": "0.28.0",
3
+ "version": "0.29.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.28.0"
33
+ "@the-smithy/hearth-schema": "^0.29.0"
34
34
  },
35
35
  "scripts": {
36
36
  "test": "node --test \"test/**/*.test.js\" && vitest run test/blocks.test.ts test/overrides.test.ts",
@@ -12,6 +12,7 @@ Before grepping or reading whole files, use [Drift](https://github.com/The-Smith
12
12
  - `drift query <symbol>` or `drift find <words>`: where something is defined
13
13
  - `drift show <symbol>` or `drift sig <symbol>`: its source, or just its signature
14
14
  - `drift outline <file>`: a file's definitions and imports
15
+ - `drift docs <words>`: the section of the docs that covers something. Read it with `drift show <path:start-end>`
15
16
  - `drift refs <symbol>`, `drift importers <file>`, `drift tests --for <file>`: what uses it
16
17
  - `drift context <symbol or file>`: all of the above before changing something
17
18
  - `drift map`: orientation in a new repo
@@ -86,10 +87,10 @@ Work in this order, and stop at the first step that gets the result:
86
87
  don't reach it, so it falls behind Hearth. Use one only when everything above can't get there, build it from
87
88
  `src/hearth/ui.tsx` and the theme, and say why in `SITE.md`.
88
89
 
89
- To style blocks without setting up pages for them, open `/hearth/blocks` while `pnpm dev` is running: every block with
90
+ To style blocks without setting up pages for them, open `/hearth/blocks` while `pnpm dev` is running: each block with
90
91
  its options and states, in sample content, drawn with this site's theme, so a change in `src/theme` shows at once.
91
- `/hearth/blocks?block=hero` shows one block. Its **Unstyled** switch (`/hearth/blocks/unstyled`) shows them with no design
92
- 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.
93
94
  Without a browser, the unstyled page is `node_modules/@the-smithy/hearth/block-reference.html`, but it's large: for slots,
94
95
  use `src/hearth/theme/defaults.ts`.
95
96
 
@@ -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 every block on one page with your theme, without setting up pages for them. `?block=hero` shows one. Its **Unstyled** switch shows them with no design, and each element's slot name |
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`) |
@@ -1,10 +1,11 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- const SWITCH = `<span class="view-switch"><span class="view-current">Unstyled</span><a href="/hearth/blocks" onclick="this.href='/hearth/blocks'+location.hash">This site's theme</a></span>`;
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
- * Every block unstyled, with its options, states and slot names, to build against while developing. Only under
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 } = {};
@@ -1,14 +1,19 @@
1
1
  import type { Metadata } from "next";
2
+ import Link from "next/link";
2
3
  import { notFound } from "next/navigation";
3
4
  import { Fragment, Suspense, type ReactNode } from "react";
4
5
  import { Blocks } from "../blocks";
5
- import { SampleBoundary, SampleGuard, ViewLink } from "./controls";
6
+ import { SampleBoundary, SampleGuard } from "./controls";
6
7
  import { FormStates, Ground, GROUND_NAMES } from "./pieces";
7
8
  import { blockSamples, libraryGroups } from "./samples";
8
9
 
9
10
  export const metadata: Metadata = { title: "Block library", robots: { index: false, follow: false } };
10
11
 
11
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
+ ];
12
17
 
13
18
  // Its own plain styles, so that nothing in a site's theme changes the tools around the samples.
14
19
  const CSS = `
@@ -19,6 +24,9 @@ const CSS = `
19
24
  .hearth-library-switch > * { padding: 4px 12px; }
20
25
  .hearth-library-switch > * + * { border-left: 1px solid #d8d8d4; }
21
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; }
22
30
  .hearth-library-jump { position: relative; }
23
31
  .hearth-library-jump summary { cursor: pointer; padding: 4px 12px; border: 1px solid #d8d8d4; border-radius: 6px; list-style: none; }
24
32
  .hearth-library-jump summary::after { content: " ▾"; }
@@ -52,18 +60,26 @@ function Sample({ id, name, label, action, children }: { id?: string; name: stri
52
60
  }
53
61
 
54
62
  /**
55
- * Every block on one page, drawn by the real components with this site's theme and overrides, so that a change in
56
- * src/theme shows here at once. For `pnpm dev` only. Its samples stand in for a site's content, so nothing depends on
57
- * which pages exist, and nothing here is saved. ?block=hero shows one block's samples.
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.
58
66
  */
59
67
  export default async function BlockLibrary({ searchParams }: { searchParams: Promise<{ block?: string | string[] }> }) {
60
68
  if (process.env.NODE_ENV !== "development") notFound();
61
69
  const { block } = await searchParams;
62
70
  const samples = blockSamples(new Date());
63
- const all = libraryGroups.flatMap((g) => g.blocks);
64
- const only = all.find((b) => b.type === block);
65
- const unstyled = `/hearth/blocks/unstyled${only ? `#block-${only.type}` : ""}`;
66
- const showing = only ? [only] : all;
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);
67
83
  return (
68
84
  <>
69
85
  <style>{CSS}</style>
@@ -71,42 +87,51 @@ export default async function BlockLibrary({ searchParams }: { searchParams: Pro
71
87
  <div className="hearth-library-bar">
72
88
  <b>Block library</b>
73
89
  <span className="hearth-library-switch">
74
- <ViewLink href={unstyled}>Unstyled</ViewLink>
90
+ <a href={unstyled}>Unstyled</a>
75
91
  <span className="hearth-library-current">This site&apos;s theme</span>
76
92
  </span>
77
- <details className="hearth-library-jump">
78
- <summary>{only ? only.label : "All blocks"}</summary>
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>
79
99
  <nav aria-label="Blocks">
80
- <a href="/hearth/blocks">Every block on one page</a>
81
- {libraryGroups.map((g) => (
100
+ {[{ group: "Start here", blocks: START_HERE }, ...libraryGroups].map((g) => (
82
101
  <Fragment key={g.group}>
83
102
  <p>{g.group}</p>
84
103
  {g.blocks.map((b) => (
85
- <a key={b.type} href={`/hearth/blocks?block=${b.type}`}>
104
+ <Link key={b.type} href={`/hearth/blocks?block=${b.type}`}>
86
105
  {b.label}
87
- </a>
106
+ </Link>
88
107
  ))}
89
108
  </Fragment>
90
109
  ))}
91
110
  </nav>
92
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
+ )}
93
118
  <span className="hearth-library-note">Sample content. Links and forms here do nothing.</span>
94
119
  </div>
95
- {only
96
- ? null
97
- : TONES.map((tone) => (
98
- <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")}>
99
123
  <Ground tone={tone} />
100
124
  </Sample>
101
- ))}
102
- {only ? null : (
103
- <Sample id="form-states" name="Form states" label="Errors, filled-in fields, options and the success message">
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")}>
104
129
  <FormStates />
105
130
  </Sample>
106
- )}
131
+ ) : null}
107
132
  {showing.map((b) =>
108
133
  samples[b.type].map((s, i) => (
109
- <Sample key={s.block.id} id={i ? undefined : `block-${b.type}`} name={b.label} label={s.label} action={only ? <a href="/hearth/blocks">Show every block</a> : <a href={`/hearth/blocks?block=${b.type}`}>Only this block</a>}>
134
+ <Sample key={s.block.id} id={i ? undefined : `block-${b.type}`} name={b.label} label={s.label} action={only(b.type)}>
110
135
  <Blocks blocks={[s.block]} />
111
136
  </Sample>
112
137
  )),