@theme-registry/refract 0.1.7 → 0.1.8

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,27 @@ 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
+ Token plates — colours, the type ramp, spacing, radii, shadows, breakpoints — render from the
191
+ format-neutral token export, so **every** adapter gets them. Live recipe plates additionally need
192
+ output a browser can load as-is, which today means CSS; an SCSS/styled-components/JSON target still
193
+ renders every token and names every recipe, and says why it can't render them live. (For a live
194
+ design-review page from an SC or SCSS theme, add a CSS target to the same config — same recipes,
195
+ same core.) The page follows the target's `emit` mode, and gains a light/dark toggle when the theme
196
+ declares modes plus frame-width buttons when it declares breakpoints. Off by default.
197
+
198
+ Its machine-facing sibling is `guide: true`, which writes an `llms.txt` + `manifest.json`
199
+ consumption guide into the same folder.
200
+
177
201
  ## DTCG round-trip
178
202
 
179
203
  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,73 @@
1
+ /**
2
+ * Human-facing preview (Node-only) — render a single `preview.html` specimen of a built theme.
3
+ *
4
+ * The audience-flipped sibling of `guide.ts`: that one writes `llms.txt` + `manifest.json` for a
5
+ * downstream *agent*; this one writes a page a person opens, forwards, or hands a designer to answer
6
+ * "what does this theme actually look like?". Both are opt-in per emit target and land in the same
7
+ * `outDir`, so they travel with any distribution form.
8
+ *
9
+ * Two plate tiers, split by CAPABILITY rather than by adapter name:
10
+ *
11
+ * - **Token plates (universal).** Rendered from the format-neutral DTCG export — the same document
12
+ * `guide` embeds — so they depend on nothing the adapter emitted. Every adapter, including a
13
+ * third-party one that has never heard of previews, gets these on day one. The walk is generic
14
+ * over `$type`, so a new subsystem's tokens show up without touching this file.
15
+ *
16
+ * - **Recipe plates (live).** Require the emitted artifacts to be browser-loadable as-is, which the
17
+ * adapter alone can answer — see {@link PreviewDescriptor}. Today that means CSS; SCSS needs
18
+ * compiling, styled-components emits JS modules, JSON has no rendered form. Those adapters say so
19
+ * in their own words and the page degrades to tokens-only rather than rendering unstyled boxes.
20
+ *
21
+ * Distribution convention deliberately INVERTS `guide`'s: the page **inlines** its stylesheets by
22
+ * default (`inline: false` opts out). `guide` is machine-facing and lives next to the code it
23
+ * documents, so relative references are right there; a preview gets moved, attached, and forwarded,
24
+ * and has to keep working. `inline: false` serves the dev loop, where the page should reflect a
25
+ * rebuilt stylesheet on refresh.
26
+ */
27
+ import type { ThemeModel } from "../core/model/model";
28
+ import type { NormalizedEmit, PreviewDescriptor, UsageDescriptor } from "../core/ThemeAdapter";
29
+ /** Opt-in preview config (`EmitTarget.preview`). `true` uses every default. */
30
+ export type PreviewConfig = {
31
+ /** Output filename (default `"preview.html"`). */
32
+ readonly file?: string;
33
+ /**
34
+ * Inline the emitted stylesheets into the page so it is a single shareable file (default `true`).
35
+ * `false` emits relative `<link>`s instead — the page then only works beside its `outDir`, but it
36
+ * reflects a rebuilt stylesheet on refresh.
37
+ */
38
+ readonly inline?: boolean;
39
+ /** Page heading + `<title>` (default `"<format> theme preview"`). */
40
+ readonly title?: string;
41
+ };
42
+ export interface PreviewOptions extends PreviewConfig {
43
+ /** The file names actually written for this target — the guard against referencing a missing artifact. */
44
+ readonly files: readonly string[];
45
+ /** Emitted file contents by name, for inlining. A name missing here falls back to a `<link>`. */
46
+ readonly contents?: Readonly<Record<string, string>>;
47
+ /** The normalized emit plan — reported in the header and used for layout semantics only. */
48
+ readonly plan: NormalizedEmit;
49
+ }
50
+ export interface PreviewOutput {
51
+ /** filename → contents, ready for the build layer to write into `outDir`. */
52
+ readonly files: Record<string, string>;
53
+ }
54
+ export interface PreviewSource {
55
+ /** The adapter's usage descriptor — supplies the format id and the real recipe identities. */
56
+ readonly usage: UsageDescriptor;
57
+ /** The adapter's preview descriptor, or `undefined` when it doesn't implement `describePreview`. */
58
+ readonly preview?: PreviewDescriptor;
59
+ /** A DTCG document (from `toDTCG`) — the token-plate source. */
60
+ readonly tokens: unknown;
61
+ /** The built model — supplies appearance modes and breakpoints for the page controls. */
62
+ readonly model: ThemeModel;
63
+ }
64
+ /** Distinct appearance modes across every property's `modes` list, in first-appearance order. */
65
+ export declare function collectModes(model: ThemeModel): string[];
66
+ /**
67
+ * Build the preview artifact. Pure — returns `filename → contents` for the build layer to write.
68
+ *
69
+ * The adapter's declared `stylesheets` are intersected with `options.files` (what was actually
70
+ * written), so a descriptor that drifts from `emit()` degrades to tokens-only instead of producing a
71
+ * page that silently references a file that isn't there.
72
+ */
73
+ export declare function buildPreview(source: PreviewSource, options: PreviewOptions): PreviewOutput;