pattern-dump 0.1.0-alpha.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.
Files changed (47) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +92 -0
  3. package/dist/dev-ui/main.css +1825 -0
  4. package/dist/dev-ui/main.js +3054 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +7 -0
  7. package/dist/pd.js +4759 -0
  8. package/dist/types/config/index.d.ts +145 -0
  9. package/dist/types/contracts/dev-protocol.d.ts +624 -0
  10. package/dist/types/contracts/index.d.ts +294 -0
  11. package/package.json +76 -0
  12. package/templates/base/README.md +77 -0
  13. package/templates/base/_gitignore +3 -0
  14. package/templates/base/pattern-dump.config.ts +36 -0
  15. package/templates/base/src/scripts/globals.d.ts +6 -0
  16. package/templates/base/src/styles/base/_tokens.scss +8 -0
  17. package/templates/base/src/styles/base/document.scss +18 -0
  18. package/templates/base/themes/dark.theme.scss +8 -0
  19. package/templates/base/themes/light.theme.scss +8 -0
  20. package/templates/base/tsconfig.json +19 -0
  21. package/templates/examples/src/patterns/atoms/button/button.hbs +1 -0
  22. package/templates/examples/src/patterns/atoms/button/button.json +1 -0
  23. package/templates/examples/src/patterns/atoms/button/button.md +21 -0
  24. package/templates/examples/src/patterns/atoms/button/button.schema.json +11 -0
  25. package/templates/examples/src/patterns/atoms/button/button~icon.hbs +5 -0
  26. package/templates/examples/src/patterns/atoms/button/button~secondary.json +1 -0
  27. package/templates/examples/src/patterns/atoms/index.md +6 -0
  28. package/templates/examples/src/patterns/index.md +3 -0
  29. package/templates/examples/src/patterns/molecules/index.md +6 -0
  30. package/templates/examples/src/patterns/molecules/search-field/input/input.hbs +4 -0
  31. package/templates/examples/src/patterns/molecules/search-field/input/input.json +1 -0
  32. package/templates/examples/src/patterns/molecules/search-field/input/input.md +8 -0
  33. package/templates/examples/src/patterns/molecules/search-field/input/input.schema.json +10 -0
  34. package/templates/examples/src/patterns/molecules/search-field/search-field.hbs +5 -0
  35. package/templates/examples/src/patterns/molecules/search-field/search-field.json +1 -0
  36. package/templates/examples/src/patterns/molecules/search-field/search-field.md +21 -0
  37. package/templates/examples/src/patterns/molecules/search-field/search-field.schema.json +11 -0
  38. package/templates/examples/src/patterns/pages/example/example.hbs +11 -0
  39. package/templates/examples/src/patterns/pages/example/example.json +8 -0
  40. package/templates/examples/src/patterns/pages/example/example.md +11 -0
  41. package/templates/examples/src/patterns/pages/example/example.schema.json +14 -0
  42. package/templates/examples/src/patterns/pages/index.md +6 -0
  43. package/templates/examples/src/scripts/search-field.ts +16 -0
  44. package/templates/examples/src/styles/atoms/button.scss +36 -0
  45. package/templates/examples/src/styles/molecules/search-field/input.scss +21 -0
  46. package/templates/examples/src/styles/molecules/search-field.scss +13 -0
  47. package/templates/examples/src/styles/pages/example.scss +28 -0
@@ -0,0 +1,294 @@
1
+ export type Visibility = "public" | "private";
2
+ export type Status = "draft" | "stable" | "deprecated";
3
+ /** Flat partial reference used in templates: `<category>-<name>`, e.g. "atoms-button". */
4
+ export type FlatPartialName = string;
5
+ export interface ComponentRef {
6
+ id: string;
7
+ category: string;
8
+ /** Informational lineage only — no longer an addressing boundary. Nesting records ownership. */
9
+ visibility?: Visibility;
10
+ path: string;
11
+ }
12
+ export interface RenderInput {
13
+ /** Component id (path under patternsDir), e.g. "atoms/button" or "molecules/field". */
14
+ component: string;
15
+ /** Name of a `<component>~<variant>.json` data set. Omitted => base (component.json). */
16
+ variant?: string;
17
+ /** Live-control overrides; highest precedence in the cascade. */
18
+ data?: Record<string, unknown>;
19
+ }
20
+ export interface RenderMeta {
21
+ componentId: string;
22
+ variant?: string;
23
+ /** Schema-alignment diagnostics (M4.9); present only when component.schema.json is provided. */
24
+ diagnostics?: import("./dev-protocol.js").Diagnostic[];
25
+ }
26
+ export interface RenderOutput {
27
+ /** The rendered component fragment (no document chrome). */
28
+ html: string;
29
+ /**
30
+ * Standalone source document the preview iframe points at:
31
+ * themeable tokens as `:root`/`[data-theme]` custom properties + compiled CSS + the fragment.
32
+ * Same isolation model for docs examples.
33
+ */
34
+ document: string;
35
+ meta?: RenderMeta;
36
+ }
37
+ /**
38
+ * {{id 'name'}} — ambient, named, deterministic, base-seeded accessibility id helper.
39
+ *
40
+ * BASE-SEEDED SCOPE:
41
+ * Every render scope has a BASE. `{{id 'name'}}` = `<base>-<name>` (deterministic derivation).
42
+ * The base is either minted automatically or supplied by a composing parent.
43
+ *
44
+ * - Minted base: when no base is supplied, the renderer derives the base from the component
45
+ * slug + composition position. The minted base for the main (top-level) component is
46
+ * `pd-<component-slug>`. For composed instances, the minting rule ensures per-instance
47
+ * distinctness (see COMPOSITION RULE below).
48
+ *
49
+ * - Supplied base: a composing parent may pass `base="<value>"` as a Handlebars partial
50
+ * hash arg — `{{> molecules-field base="email"}}` — to seed the child's entire id cluster.
51
+ * Every `{{id 'name'}}` in that subtree then yields `<supplied-base>-<name>`, making the
52
+ * cluster externally predictable (e.g. `email-input`, `email-hint`).
53
+ *
54
+ * COMPOSITION RULE (minted base, auto-distinct instances):
55
+ * When the same partial is composed multiple times within one render tree, the FIRST
56
+ * invocation with a given parent base inherits that parent base (base propagation —
57
+ * ensures sub-components share the cluster base). Each SUBSEQUENT invocation with the
58
+ * same parent base mints a new base (`pd-<partial-slug>-<N>`), disambiguating independent
59
+ * instances without hand-numbering. Template authors never write `-2`; composition
60
+ * position drives the distinction automatically.
61
+ *
62
+ * BASE PROPAGATION THROUGH COMPOSITION:
63
+ * When `field` composes `input`, both share the same base (field propagates its base to
64
+ * input). `{{id 'input'}}` in `field.hbs` and `id="{{id 'input'}}"` in `input.hbs`
65
+ * resolve to the SAME id — the cluster spans partial boundaries coherently.
66
+ *
67
+ * SCOPE RULES SUMMARY:
68
+ * 1. Same name, same scope → same id (wires label `for` ↔ input `id`).
69
+ * 2. Different name → different id (`hint` ≠ `input`).
70
+ * 3. Different instance → different base → distinct ids for the same name.
71
+ * 4. Supplied base seeds the whole cluster; every member is base + name.
72
+ * 5. Base propagates to composed children (sub-components share the cluster base).
73
+ * 6. Deterministic: same render() input → byte-identical ids and html.
74
+ * 7. Ambient: templates write `{{id 'name'}}` only; no per-element id threading by arg.
75
+ * The ONLY thing a parent may pass is the BASE for the child's scope.
76
+ *
77
+ * OPEN DECISION — all-states-in-one-document seeding:
78
+ * Two independent top-level render() calls for the same component produce the same minted
79
+ * base and therefore the same ids. When the dev environment renders ALL states/instances
80
+ * into one document (snapshot mode, multi-instance preview), id collisions occur.
81
+ * Options:
82
+ * A. Caller supplies RenderInput.seed (recommended): keeps render() pure; dev-env manages
83
+ * distinct seeds per instance. The seed is incorporated into the minted base.
84
+ * B. Renderer-managed counter: breaks render() purity (counter leaks across calls).
85
+ * Current state: `RenderInput.seed` field is defined but unused by the renderer.
86
+ * Resolve before the dev-environment renders multiple instances in one document.
87
+ */
88
+ export type IdHelperSemantics = never;
89
+ /**
90
+ * Pure render. Deterministic: same input => byte-identical output.
91
+ *
92
+ * DATA CASCADE (lowest → highest precedence), per-key deep merge:
93
+ * 1. defaults — component.json (optional; absent = empty object contributes nothing)
94
+ * 2. composed — context passed by a parent under a routing key via section descent
95
+ * ({{#routingKey}}{{> flat-name}}{{/routingKey}} → child receives routingKey subtree)
96
+ * 3. variant — <component>~<variant>.json (optional; merges only the keys it declares)
97
+ * 4. live — input.data (optional; the M3 live-controls seam)
98
+ *
99
+ * MERGE SEMANTICS (per key, applied at each level):
100
+ * - Object values: merged recursively (deep merge, not whole-object replace).
101
+ * - Scalar values: replace (string, number, boolean, null).
102
+ * - Arrays: REPLACE — `["c"]` replaces `["a","b"]`, no concatenation.
103
+ * ↳ OPEN DECISION: replace vs concat — settle before M3 controls ship.
104
+ * - null: CLEARS the inherited value (target key becomes null).
105
+ * ↳ OPEN DECISION: clear vs "inherit from defaults" — settle before M3 controls ship.
106
+ *
107
+ * PARTIAL RESOLUTION (flat naming model):
108
+ * - Templates reference partials by flat `<category>-<name>` (e.g. {{> molecules-input}}).
109
+ * - `render({ component })` and graph node ids remain full paths (e.g. "molecules/field/input").
110
+ * - The renderer builds a registry mapping FlatPartialName → full path before each render.
111
+ * - A collision (two components → same handle) quarantines the newcomer and surfaces a
112
+ * HANDLE_COLLISION diagnostic (see CollisionEntry).
113
+ * - Nesting records informational ownership (lineage); it is NOT an addressing boundary.
114
+ * The graph's `uses` edges are derived from these partial references (one source of truth).
115
+ */
116
+ export type Render = (input: RenderInput) => Promise<RenderOutput>;
117
+ export interface RendererOptions {
118
+ /** Root containing the atomic pattern folders (atoms/, molecules/, ...). */
119
+ patternsDir: string;
120
+ /** Every patterns folder, project first, when pattern packages are in use. Takes precedence over
121
+ * `patternsDir` for discovery. */
122
+ patternRoots?: PatternRoot[];
123
+ /** Theme files directory (custom-property value-sets). Optional for M0. */
124
+ themesDir?: string;
125
+ /** Default theme name applied via `data-theme` on the preview body. */
126
+ defaultTheme?: string;
127
+ /**
128
+ * Pre-loaded project-specific Handlebars helpers, keyed by name.
129
+ * Registered after the core helpers; project helpers may override core ones.
130
+ * Typical flow: core loads `PatternDumpConfig.helpers` path, imports the module, passes here.
131
+ */
132
+ helpers?: Record<string, (...args: unknown[]) => unknown>;
133
+ /**
134
+ * Project-level global data (e.g. hTWOo's `src/_data/`), merged BENEATH component data — the
135
+ * lowest tier of the render cascade: global → defaults → variant → live. Component data wins on
136
+ * every key. Loaded once by the dev-server from `PatternDumpConfig.dataDir`.
137
+ */
138
+ globalData?: Record<string, unknown>;
139
+ /**
140
+ * Project helper *registrars* — modules that self-register onto the render env, the PatternLab
141
+ * shape `module.exports = (Handlebars) => { Handlebars.registerHelper(...) }`. Invoked with the
142
+ * renderer's Handlebars env AFTER partials are registered, so helpers that read
143
+ * `Handlebars.partials` / `.compile` at CALL time (e.g. `dynamicPartial`) resolve against the real
144
+ * env — which a flat name→fn map (`helpers`) cannot express. Registered after core + `helpers`.
145
+ */
146
+ helperRegistrars?: Array<(handlebars: unknown) => void>;
147
+ /**
148
+ * M6: pre-built handle → slash-id map from the dev-server's discovery pass.
149
+ * When provided together with discoveryMap, the renderer uses these maps to register
150
+ * Handlebars partials and resolve file paths without running its own discovery walk.
151
+ * Marked for extraction to a shared discovery module in the refactor round.
152
+ */
153
+ handleMap?: HandleMap;
154
+ /**
155
+ * M6: pre-built id → DiscoveryEntry map from the dev-server's discovery pass.
156
+ * Provides the file paths (hbsPath, jsonPath, schemaPath) used by render().
157
+ * When absent, the renderer calls buildDiscoveryMap(patternsDir) from @pattern-dump/discovery —
158
+ * the real M6 file-as-component walk, never the old collectComponents folder-per-component walk.
159
+ */
160
+ discoveryMap?: DiscoveryMap;
161
+ }
162
+ /** Factory: a configured renderer closes over its source root, keeping `render()` pure. */
163
+ export type CreateRenderer = (options: RendererOptions) => Render;
164
+ /**
165
+ * Single source of truth for where each component's files live.
166
+ * Populated by buildDiscoveryMap; every file-read site uses this map — none reconstruct paths.
167
+ *
168
+ * Collapse rule: `<dir>/<dir>.hbs` → id = `<dir>` (folder-per-component collapses).
169
+ * Otherwise id = relative stem, intermediate group folders retained.
170
+ *
171
+ * For base-less variant synthesis: ownMarkup=false, hbsPath is empty string (no base file),
172
+ * variants lists the variant tokens present on disk.
173
+ */
174
+ export interface DiscoveryEntry {
175
+ /** The pattern package this component comes from (its npm name); absent for the project's own. */
176
+ source?: string;
177
+ hbsPath: string;
178
+ jsonPath?: string;
179
+ mdPath?: string;
180
+ schemaPath?: string;
181
+ scssPath?: string;
182
+ ownMarkup: boolean;
183
+ variants?: string[];
184
+ }
185
+ /** slash id → file entry. The single source of truth for file locations. */
186
+ export type DiscoveryMap = Map<string, DiscoveryEntry>;
187
+ /**
188
+ * dash handle → slash id.
189
+ * Bridge for Handlebars partial resolution: `{{> atoms-button}}` → `"atoms/button"`.
190
+ * Handles are group-less: intermediate folder segments are dropped, only category + name kept.
191
+ * Variant handles include `~`: `atoms-spinner~small` → `"atoms/loading/spinner~small"`.
192
+ */
193
+ export type HandleMap = Map<string, string>;
194
+ /** A handle collision detected at discovery time. The incumbent retains the handle. */
195
+ export interface CollisionEntry {
196
+ handle: string;
197
+ incumbentId: string;
198
+ incumbentPath: string;
199
+ quarantinedPath: string;
200
+ /** Slash id of the quarantined component (derived from quarantinedPath at discovery time). */
201
+ quarantinedId: string;
202
+ }
203
+ /** Return value of buildDiscoveryMap. */
204
+ export interface DiscoveryResult {
205
+ discoveryMap: DiscoveryMap;
206
+ handleMap: HandleMap;
207
+ collisions: CollisionEntry[];
208
+ /** Reverse index: absolute hbsPath → slash id. Populated for all entries with a real .hbs file. */
209
+ hbsPathToId: Map<string, string>;
210
+ /** quarantined slash id → CollisionEntry. Used by the detail endpoint to surface HANDLE_COLLISION. */
211
+ quarantineMap: Map<string, CollisionEntry>;
212
+ /** A component of an EARLIER source (the project before packages, then packages in order) that
213
+ * replaced a later source's component with the same handle. Not an error — it is how a project
214
+ * overrides a library component — so the shadowed one is neither quarantined nor a collision. */
215
+ overrides: OverrideEntry[];
216
+ }
217
+ export interface OverrideEntry {
218
+ handle: string;
219
+ /** The id that stays, and where it lives. */
220
+ id: string;
221
+ path: string;
222
+ /** The same-handle component that was replaced, and the package it came from. */
223
+ shadowedPath: string;
224
+ shadowedSource: string;
225
+ }
226
+ /** One folder of patterns: the project's own, or a pattern package's. */
227
+ export interface PatternRoot {
228
+ dir: string;
229
+ /** The package name; absent for the project. */
230
+ source?: string;
231
+ /** Globs relative to `dir` of templates to leave undiscovered. */
232
+ exclude?: string[];
233
+ }
234
+ /**
235
+ * A pattern package's self-description: the `patternDump` field of its package.json. Every path is
236
+ * relative to the package folder. All fields are optional, so one package can carry patterns and
237
+ * another the styles and themes they need — the second listed as a peerDependency of the first,
238
+ * which PD follows (`@n8d/htwoo-patterns` → `@n8d/htwoo-core`).
239
+ */
240
+ export interface PackageManifest {
241
+ /** Folder of Handlebars patterns (the equivalent of `patternsDir`). */
242
+ patterns?: string;
243
+ /** Global template data folder (`dataDir`). */
244
+ data?: string;
245
+ /** Handlebars helper module or folder (`helpers`). */
246
+ helpers?: string;
247
+ /** Asset folders served under /assets/<folder name>/. */
248
+ assets?: string[];
249
+ styles?: {
250
+ dir: string;
251
+ /** Compile only these entries (relative to `dir`). */
252
+ entry?: string | string[];
253
+ /** Base-origin paths of the shared document layer, as in the project config. */
254
+ documentBase?: string[];
255
+ };
256
+ themes?: {
257
+ dir: string;
258
+ default?: string;
259
+ switch?: import("./dev-protocol.js").ThemeSwitch;
260
+ };
261
+ /** Behaviour script sources (`scriptsDir`). */
262
+ scripts?: string;
263
+ /** Script ids loaded into every preview. */
264
+ globalScripts?: string[];
265
+ /** Globs (relative to `patterns`) of templates the package ships but does not want discovered. */
266
+ exclude?: string[];
267
+ }
268
+ export interface ThemeSummary {
269
+ name: string;
270
+ tokens?: Record<string, string | null>;
271
+ }
272
+ /** Brand slot for the left rail, nested under shell.brand in PatternDumpConfig. */
273
+ export interface ShellBrandConfig {
274
+ /** Wordmark text. Defaults to "Pattern Dump". */
275
+ title?: string;
276
+ /**
277
+ * Path to a logo image. Replaces the wordmark when set.
278
+ * A monochrome SVG that inherits currentColor is preferred, but note that
279
+ * <img> does NOT inherit currentColor — full currentColor shell theming is a
280
+ * design-tokens milestone concern. { light, dark } renders a <picture> with
281
+ * prefers-color-scheme sources.
282
+ */
283
+ logo?: string | {
284
+ light: string;
285
+ dark: string;
286
+ };
287
+ /** Alt text for the logo image. */
288
+ logoAlt?: string;
289
+ /** Optional href; wraps the brand slot in an <a> (History API navigation). */
290
+ href?: string;
291
+ }
292
+ /** Ordered list of CSS custom-property names used as swatch slots. */
293
+ export type SwatchSlots = string[];
294
+ export * from "./dev-protocol.js";
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "pattern-dump",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "A workbench for HTML and CSS design systems: live component previews, docs, checks and static publishing.",
5
+ "keywords": [
6
+ "design-system",
7
+ "pattern-library",
8
+ "styleguide",
9
+ "patternlab",
10
+ "handlebars",
11
+ "scss",
12
+ "component-library",
13
+ "workbench",
14
+ "living-styleguide",
15
+ "accessibility",
16
+ "static-site",
17
+ "htwoo"
18
+ ],
19
+ "author": "Stefan Bauer <stefan.bauer@n8d.at> (https://n8d.at)",
20
+ "license": "MIT",
21
+ "homepage": "https://github.com/n8design/pattern-dump#readme",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/n8design/pattern-dump.git"
25
+ },
26
+ "bugs": "https://github.com/n8design/pattern-dump/issues",
27
+ "funding": [
28
+ {
29
+ "type": "github",
30
+ "url": "https://github.com/sponsors/stfbauer"
31
+ },
32
+ {
33
+ "type": "github",
34
+ "url": "https://github.com/sponsors/n8design"
35
+ }
36
+ ],
37
+ "type": "module",
38
+ "bin": {
39
+ "pd": "./dist/pd.js"
40
+ },
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/index.d.ts",
44
+ "import": "./dist/index.js"
45
+ },
46
+ "./package.json": "./package.json"
47
+ },
48
+ "types": "./dist/index.d.ts",
49
+ "files": [
50
+ "dist",
51
+ "templates",
52
+ "README.md",
53
+ "LICENSE"
54
+ ],
55
+ "engines": {
56
+ "node": ">=20.10"
57
+ },
58
+ "dependencies": {
59
+ "@shikijs/transformers": "^4.1.0",
60
+ "autoprefixer": "^10.5.0",
61
+ "axe-core": "^4.11.4",
62
+ "chokidar": "^3.6.0",
63
+ "culori": "^4.0.2",
64
+ "esbuild": "^0.28.2",
65
+ "handlebars": "^4.7.9",
66
+ "html-validate": "^11.5.2",
67
+ "jsdom": "^27.4.0",
68
+ "markdown-it": "^14.2.0",
69
+ "node-html-parser": "^7.1.0",
70
+ "postcss": "^8.5.15",
71
+ "sass": "^1.100.0",
72
+ "shiki": "^4.1.0",
73
+ "source-map": "^0.6.1",
74
+ "ws": "^8.21.0"
75
+ }
76
+ }
@@ -0,0 +1,77 @@
1
+ # __PD_NAME__
2
+
3
+ A design system worked on with [Pattern Dump](https://www.npmjs.com/package/__PD_PACKAGE__).
4
+
5
+ ```bash
6
+ npm run dev # the workbench at http://localhost:4000
7
+ npm run audit # static defects across every component
8
+ npm run typecheck # type-check the behaviour scripts
9
+ npm run build:scripts # compile them to lib/scripts (JavaScript + type declarations)
10
+ ```
11
+
12
+ ## Layout
13
+
14
+ | Path | What lives there |
15
+ |---|---|
16
+ | `pattern-dump.config.ts` | where everything is — every path resolves against this folder |
17
+ | `src/patterns/<category>/<name>/` | one component: `<name>.hbs`, `.json` data, `.md` docs, `.schema.json` |
18
+ | `src/patterns/<category>/<name>/<name>~<variant>.json` | a variant that overrides data |
19
+ | `src/patterns/<category>/<name>/<name>~<variant>.hbs` | a variant with its own markup |
20
+ | `src/styles/` | SCSS mirroring `src/patterns/` (`atoms/button.scss` styles `atoms/button`) |
21
+ | `src/styles/base/` | shared rules with no single owner, linked beneath every preview |
22
+ | `themes/<name>.theme.scss` | a theme: custom properties under `[data-theme="<name>"]` |
23
+ | `assets/` | served at `/assets/` |
24
+ | `src/scripts/<id>.ts` | a behaviour script, loaded by the components that name it |
25
+ | `tsconfig.json` | TypeScript settings for `src/scripts` |
26
+
27
+ Include a component from another with its handle, `<category>-<name>`: `{{> atoms-button }}`.
28
+ A variant handle contains `~`, so quote it: `{{> "atoms-button~secondary" }}`.
29
+
30
+ A folder's `index.md` titles it and can order its children by name. `...` stands for every child
31
+ the list doesn't name, so a folder you add later needs no edit here:
32
+
33
+ ```md
34
+ ---
35
+ title: Atoms
36
+ order: [button, ..., badge]
37
+ ---
38
+ ```
39
+
40
+ Any component (in its `.md`) or folder (in its `index.md`) can also rank itself with a number. Without
41
+ one its rank is `0`; lower comes first, whatever the parent's list says. This puts a folder before
42
+ every rank-0 sibling (`order: 10` would put it after them):
43
+
44
+ ```md
45
+ ---
46
+ title: Projects
47
+ order: -10
48
+ ---
49
+ ```
50
+
51
+ Siblings sort by rank first, then by the parent's list, then alphabetically.
52
+ `src/patterns/index.md` orders the top-level folders.
53
+
54
+ ## Behaviour scripts
55
+
56
+ A script is a TypeScript module in `src/scripts/`. A component loads it by naming it in its `.md`
57
+ frontmatter, and every preview that shows the component — directly or included in another — loads it:
58
+
59
+ ```md
60
+ ---
61
+ title: Search field
62
+ requireScript: [search-field]
63
+ ---
64
+ ```
65
+
66
+ To load a script into every preview, list it in `pattern-dump.config.ts`: `globalScripts: ["<id>"]`.
67
+
68
+ `npm run dev` compiles scripts itself (esbuild) and reloads the preview when one changes; there is no
69
+ build step while you work. `tsconfig.json` is for your editor, `npm run typecheck` and
70
+ `npm run build:scripts`, which writes JavaScript and type declarations to `lib/scripts/`.
71
+
72
+ To let another package in your repository import the compiled scripts, export them from `package.json`:
73
+
74
+ ```json
75
+ "files": ["lib/scripts"],
76
+ "exports": { "./package.json": "./package.json", "./scripts/*": "./lib/scripts/*" }
77
+ ```
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ dist
3
+ lib
@@ -0,0 +1,36 @@
1
+ import { defineConfig } from "__PD_PACKAGE__";
2
+
3
+ // Every path below resolves against this project's root (the folder this file lives in).
4
+ export default defineConfig({
5
+ sourceDir: "./src",
6
+ patternsDir: "./src/patterns",
7
+ outDir: "./dist",
8
+
9
+ // Styles mirror the patterns tree: src/styles/atoms/button.scss belongs to atoms/button.
10
+ styles: { strategy: "parallel", dir: "./src/styles" },
11
+ // Shared, component-less rules (reset, document defaults) linked beneath every preview.
12
+ documentBase: ["base"],
13
+
14
+ themes: {
15
+ dir: "./themes",
16
+ default: "light",
17
+ switch: "attribute",
18
+ // Custom properties sampled for the swatch strip in the theme picker.
19
+ swatch: { slots: ["--color-surface", "--color-primary", "--color-text"] },
20
+ },
21
+
22
+ assets: { strategy: "central", dir: "./assets" },
23
+
24
+ // Behaviour scripts: src/scripts/<id>.ts, compiled on request. A component loads one with
25
+ // `requireScript: [<id>]` in its .md frontmatter; ids listed here load into every preview.
26
+ scriptsDir: "./src/scripts",
27
+ // globalScripts: ["<id>"],
28
+
29
+ // Pattern packages used alongside this project's own patterns — install them, then name them here.
30
+ // Each package describes its patterns, styles and themes in its package.json ("patternDump").
31
+ // packages: ["@n8d/htwoo-patterns"],
32
+
33
+ shell: {
34
+ brand: { title: __PD_BRAND_JSON__ },
35
+ },
36
+ });
@@ -0,0 +1,6 @@
1
+ // Project-wide type declarations for the behaviour scripts in this folder.
2
+ //
3
+ // A script is `src/scripts/<id>.ts`. A component loads it by naming it in its .md frontmatter,
4
+ // `requireScript: [<id>]`; `globalScripts: ["<id>"]` in pattern-dump.config.ts loads it into every
5
+ // preview. `pd dev` compiles it on request — no build step while you work.
6
+ export {};
@@ -0,0 +1,8 @@
1
+ // Sass tokens shared by every stylesheet. Emits no CSS; @use it where needed.
2
+ // Colours are NOT here: they come from the active theme (themes/*.theme.scss) as custom properties.
3
+ $space-xs: 0.25rem;
4
+ $space-sm: 0.5rem;
5
+ $space-md: 1rem;
6
+ $space-lg: 2rem;
7
+ $radius: 0.375rem;
8
+ $font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
@@ -0,0 +1,18 @@
1
+ @use "tokens";
2
+
3
+ // The document defaults every preview sits on — the base layer named by `documentBase` in the config.
4
+ // Fallbacks keep a preview readable before a theme is chosen.
5
+
6
+ *,
7
+ *::before,
8
+ *::after {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ font-family: tokens.$font-family;
15
+ line-height: 1.5;
16
+ color: var(--color-text, #1b1b1f);
17
+ background: var(--color-surface, #ffffff);
18
+ }
@@ -0,0 +1,8 @@
1
+ [data-theme="dark"] {
2
+ --color-surface: #16181d;
3
+ --color-text: #eceef2;
4
+ --color-text-muted: #b4bac4;
5
+ --color-primary: #7ab8ff;
6
+ --color-on-primary: #0b1220;
7
+ --color-border: #9aa1ad;
8
+ }
@@ -0,0 +1,8 @@
1
+ [data-theme="light"] {
2
+ --color-surface: #ffffff;
3
+ --color-text: #1b1b1f;
4
+ --color-text-muted: #4b5563;
5
+ --color-primary: #0b5cad;
6
+ --color-on-primary: #ffffff;
7
+ --color-border: #6b7280;
8
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ // Behaviour scripts in src/scripts. `pd dev` compiles them itself with esbuild — this file is for
3
+ // `npm run typecheck` (tsc --noEmit) and `npm run build:scripts` (tsc → lib/scripts).
4
+ // Self-contained on purpose: it extends nothing, so it never inherits a parent project's settings.
5
+ "compilerOptions": {
6
+ "target": "ES2022",
7
+ "module": "ES2022",
8
+ "moduleResolution": "bundler",
9
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
10
+ "strict": true,
11
+ "isolatedModules": true,
12
+ "verbatimModuleSyntax": true,
13
+ "skipLibCheck": true,
14
+ "rootDir": "src/scripts",
15
+ "outDir": "lib/scripts",
16
+ "declaration": true
17
+ },
18
+ "include": ["src/scripts"]
19
+ }
@@ -0,0 +1 @@
1
+ <button type="{{type}}" class="button button-{{variant}}">{{label}}</button>
@@ -0,0 +1 @@
1
+ { "label": "Save", "variant": "primary", "type": "button" }
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: Button
3
+ description: Triggers an action.
4
+ status: stable
5
+ ---
6
+
7
+ Triggers an action. `variant` picks the emphasis.
8
+
9
+ Two kinds of variant live next to the base component:
10
+
11
+ - **`button~secondary.json`** only overrides data — same markup, different `label` and `variant`.
12
+ - **`button~icon.hbs`** has its own markup — an icon-only button that keeps `label` as its accessible name.
13
+
14
+ ```pattern atoms/button
15
+ ```
16
+
17
+ ```pattern atoms/button~secondary
18
+ ```
19
+
20
+ ```pattern atoms/button~icon
21
+ ```
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "Button",
4
+ "type": "object",
5
+ "required": ["label", "variant", "type"],
6
+ "properties": {
7
+ "label": { "type": "string", "description": "Visible text, or the accessible name of the icon variant" },
8
+ "variant": { "type": "string", "enum": ["primary", "secondary"], "default": "primary" },
9
+ "type": { "type": "string", "enum": ["button", "submit", "reset"], "default": "button" }
10
+ }
11
+ }
@@ -0,0 +1,5 @@
1
+ <button type="{{type}}" class="button button-{{variant}} button-icon" aria-label="{{label}}">
2
+ <svg class="button-glyph" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false">
3
+ <path d="M3 8.5l3 3 7-7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
5
+ </button>
@@ -0,0 +1 @@
1
+ { "label": "Cancel", "variant": "secondary" }
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: Atoms
3
+ preview: centered
4
+ ---
5
+
6
+ The smallest building blocks. Each renders centered in the stage, hugging its content.
@@ -0,0 +1,3 @@
1
+ ---
2
+ order: [atoms, molecules, pages, ...]
3
+ ---
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: Molecules
3
+ preview: centered
4
+ ---
5
+
6
+ Small groups of atoms that work as a unit.
@@ -0,0 +1,4 @@
1
+ <label class="input">
2
+ <span class="input-label">{{inputLabel}}</span>
3
+ <input class="input-control" type="search" name="q" placeholder="{{placeholder}}">
4
+ </label>
@@ -0,0 +1 @@
1
+ { "inputLabel": "Search", "placeholder": "Components, tokens…" }
@@ -0,0 +1,8 @@
1
+ ---
2
+ title: Input
3
+ description: The search field's labelled input.
4
+ status: draft
5
+ visibility: private
6
+ ---
7
+
8
+ A private sub-component: it exists to be composed by `molecules/search-field`, not used on its own.