@theme-registry/refract 0.1.7 → 0.1.9

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 CHANGED
@@ -45,7 +45,10 @@ npm install @theme-registry/refract
45
45
  ```
46
46
 
47
47
  `styled-components` and `typescript` are **optional** peers — only needed if you use the
48
- styled-components adapter or the `.ts` build config, respectively.
48
+ styled-components adapter or the `.ts` build config, respectively. Install typescript as
49
+ **`typescript@5`**: a bare `npm i -D typescript` now resolves to 7.x, the native port, whose main
50
+ entry exports only a version string — the compiler API a `.ts` config is transpiled with sits behind
51
+ its `./unstable/*` subpaths. A `.mjs` or `.js` config never loads typescript at all.
49
52
 
50
53
  ## Scaffold a theme
51
54
 
@@ -174,6 +177,38 @@ Each target's `emit` picks the output shape (CSS adapter):
174
177
  | `subsystem` | a styles+variables pair per subsystem (`colors.css`, `colors.variables.css`, …) |
175
178
  | `components` | each component variant flattened into one self-contained file (`inline: true` bakes values; `inline: false` emits `var(--…)` + a tree-shaken `variables.css`) |
176
179
 
180
+ ### `preview` — see what you built
181
+
182
+ Set `preview: true` on any target and `refract build` also writes a `preview.html` into its
183
+ `outDir`: a rendered specimen you double-click, forward, or hand a designer. It inlines its
184
+ stylesheets by default, so the page is one self-contained file that survives being moved.
185
+
186
+ ```ts
187
+ { name: "css", adapter: createCssAdapter(), outDir: "dist/theme", preview: true }
188
+ ```
189
+
190
+ It reads as a style guide, not a token dump: a sticky section rail, colour families as contiguous
191
+ ladders with live WCAG contrast readouts on each `text` pairing, the type ramp set in its own sizes,
192
+ spacing shown as a measure *and* as an applied inset, a **state matrix** per recipe, and a
193
+ copy-on-click identifier beside every specimen. Sections appear only when the theme has tokens of
194
+ that kind.
195
+
196
+ Token plates render from the format-neutral token export, so **every** adapter gets them. Live
197
+ recipe plates additionally need output a browser can load as-is, which today means CSS; an
198
+ SCSS/styled-components/JSON target still renders every token and names every recipe, and says why it
199
+ can't render them live. (For a live design-review page from an SC or SCSS theme, add a CSS target to
200
+ the same config — same recipes, same core.)
201
+
202
+ Three things only a compiler's specimen sheet can show: **states** side by side (a CSS pseudo-class
203
+ can't be triggered from markup, so the adapter emits a parallel pinnable rule that is inlined into
204
+ the page and never added to the stylesheet you ship), an **appearance-mode diff** of the tokens that
205
+ actually carry an override, and **composition** broken into its parts — each class in a component's
206
+ identity attributed to the recipe it came from. Bare elements themed by the `globals` subsystem get
207
+ their own prose specimen, since they carry no class at all. Off by default.
208
+
209
+ Its machine-facing sibling is `guide: true`, which writes an `llms.txt` + `manifest.json`
210
+ consumption guide into the same folder.
211
+
177
212
  ## DTCG round-trip
178
213
 
179
214
  The `./dtcg` subpath reads/writes the W3C Design Token Community Group `tokens.json`
@@ -3,6 +3,7 @@ import type { RawTheme } from "../core/rawTheme";
3
3
  import type { MediaConfig } from "../core/media";
4
4
  import type { UnitsConfig } from "../core/units";
5
5
  import type { GuideConfig } from "./emitTheme";
6
+ import type { PreviewConfig } from "./preview";
6
7
  /** One emit target: an adapter + where to write it, plus which shared vendored helpers it wants. */
7
8
  export interface EmitTarget {
8
9
  readonly adapter: ThemeAdapter;
@@ -19,6 +20,14 @@ export interface EmitTarget {
19
20
  * file names or adds a `packageName` for a by-specifier import overlay. Off by default.
20
21
  */
21
22
  readonly guide?: boolean | GuideConfig;
23
+ /**
24
+ * §20 — emit a human-facing `preview.html` specimen into this target's `outDir`: token plates
25
+ * rendered from the format-neutral export (so every adapter gets them) plus live recipe plates
26
+ * when the adapter's output is browser-loadable (CSS today). The audience-flipped sibling of
27
+ * `guide`. `true` uses defaults; an object tunes the filename/title or opts out of inlining.
28
+ * Off by default.
29
+ */
30
+ readonly preview?: boolean | PreviewConfig;
22
31
  }
23
32
  /** The `theme.config` shape: the raw theme + one or more emit targets. */
24
33
  export interface ThemeConfig {
@@ -2,6 +2,7 @@ import type { ThemeAdapter, Emit } from "../core/ThemeAdapter";
2
2
  import type { RawTheme } from "../core/rawTheme";
3
3
  import type { MediaConfig } from "../core/media";
4
4
  import type { UnitsConfig } from "../core/units";
5
+ import { type PreviewConfig } from "./preview";
5
6
  /** Opt-in self-documenting output config (§C). `true` uses defaults; the object tunes names/overlay. */
6
7
  export type GuideConfig = {
7
8
  /** Published package specifier — adds a by-specifier import overlay to the guide (else relative-only). */
@@ -30,6 +31,8 @@ export interface EmitThemeOptions {
30
31
  readonly baseFontSize?: number;
31
32
  /** §C — emit a self-documenting `llms.txt` + `manifest.json` into `outDir`. Off by default. */
32
33
  readonly guide?: boolean | GuideConfig;
34
+ /** §20 — emit a human-facing `preview.html` specimen into `outDir`. Off by default. */
35
+ readonly preview?: boolean | PreviewConfig;
33
36
  }
34
37
  export interface EmitThemeResult {
35
38
  readonly outDir: string;
@@ -8,6 +8,7 @@
8
8
  export * from "./vendor";
9
9
  export * from "./paths";
10
10
  export * from "./guide";
11
+ export * from "./preview";
11
12
  export * from "./emitTheme";
12
13
  export * from "./emit";
13
14
  export * from "./config";
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Human-facing preview (Node-only) — render a single `preview.html` **style-guide specimen** of a
3
+ * built theme.
4
+ *
5
+ * The audience-flipped sibling of `guide.ts`: that one writes `llms.txt` + `manifest.json` for a
6
+ * downstream *agent*; this one writes a page a person opens, forwards, or hands a designer to answer
7
+ * "what does this theme actually look like?". Both are opt-in per emit target and land in the same
8
+ * `outDir`, so they travel with any distribution form.
9
+ *
10
+ * Two plate tiers, split by CAPABILITY rather than by adapter name:
11
+ *
12
+ * - **Token plates (universal).** Rendered from the format-neutral DTCG export — the same document
13
+ * `guide` embeds — so they depend on nothing the adapter emitted. Every adapter, including a
14
+ * third-party one that has never heard of previews, gets these on day one. The walk is generic
15
+ * over `$type` with per-group specimen overrides, so a new subsystem's tokens still appear (in a
16
+ * catch-all section) without touching this file.
17
+ *
18
+ * - **Recipe plates (live).** Require the emitted artifacts to be browser-loadable as-is, which the
19
+ * adapter alone can answer — see {@link PreviewDescriptor}. Today that means CSS; SCSS needs
20
+ * compiling, styled-components emits JS modules, JSON has no rendered form. Those adapters say so
21
+ * in their own words and the page degrades to tokens-only rather than rendering unstyled boxes.
22
+ *
23
+ * Two rules the layout is built on, both learned the hard way:
24
+ *
25
+ * 1. **The chrome is chromatically silent.** The theme owns every saturated pixel; the tool around
26
+ * it is neutral, and specimen geometry uses a dedicated mid-tone (`--rfp-spec`) rather than the
27
+ * nearest neutral to hand. Filling a swatch with the page ground makes it vanish the moment the
28
+ * ground and the stage converge — which is exactly what happens in dark mode.
29
+ * 2. **Never assume the theme is light.** The emitted stylesheet is inlined, so a theme with a
30
+ * `dark` mode restyles itself under the toggle; the chrome has to survive both, and the page
31
+ * must not hard-code a light ground under it.
32
+ *
33
+ * Distribution convention deliberately INVERTS `guide`'s: the page **inlines** its stylesheets by
34
+ * default (`inline: false` opts out). `guide` is machine-facing and lives next to the code it
35
+ * documents, so relative references are right there; a preview gets moved, attached, and forwarded,
36
+ * and has to keep working.
37
+ */
38
+ import type { ThemeModel } from "../core/model/model";
39
+ import type { NormalizedEmit, PreviewDescriptor, UsageDescriptor } from "../core/ThemeAdapter";
40
+ /** Opt-in preview config (`EmitTarget.preview`). `true` uses every default. */
41
+ export type PreviewConfig = {
42
+ /** Output filename (default `"preview.html"`). */
43
+ readonly file?: string;
44
+ /**
45
+ * Inline the emitted stylesheets into the page so it is a single shareable file (default `true`).
46
+ * `false` emits relative `<link>`s instead — the page then only works beside its `outDir`, but it
47
+ * reflects a rebuilt stylesheet on refresh.
48
+ */
49
+ readonly inline?: boolean;
50
+ /** Page heading + `<title>` (default the theme name, else `"<format> theme"`). */
51
+ readonly title?: string;
52
+ };
53
+ export interface PreviewOptions extends PreviewConfig {
54
+ /** The file names actually written for this target — the guard against referencing a missing artifact. */
55
+ readonly files: readonly string[];
56
+ /** Emitted file contents by name, for inlining + the byte counts in the masthead. */
57
+ readonly contents?: Readonly<Record<string, string>>;
58
+ /** The normalized emit plan — reported in the masthead and used for layout semantics only. */
59
+ readonly plan: NormalizedEmit;
60
+ }
61
+ export interface PreviewOutput {
62
+ /** filename → contents, ready for the build layer to write into `outDir`. */
63
+ readonly files: Record<string, string>;
64
+ }
65
+ export interface PreviewSource {
66
+ /** The adapter's usage descriptor — supplies the format id and the real recipe identities. */
67
+ readonly usage: UsageDescriptor;
68
+ /** The adapter's preview descriptor, or `undefined` when it doesn't implement `describePreview`. */
69
+ readonly preview?: PreviewDescriptor;
70
+ /** A DTCG document (from `toDTCG`) — the token-plate source. */
71
+ readonly tokens: unknown;
72
+ /** The built model — supplies appearance modes, breakpoints, and the globals element selectors. */
73
+ readonly model: ThemeModel;
74
+ }
75
+ /** Distinct appearance modes across every property's `modes` list, in first-appearance order. */
76
+ export declare function collectModes(model: ThemeModel): string[];
77
+ export declare function buildPreview(source: PreviewSource, options: PreviewOptions): PreviewOutput;