@theme-registry/refract 0.1.0 → 0.1.1
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 +191 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# @theme-registry/refract
|
|
2
|
+
|
|
3
|
+
[](https://github.com/theme-registry/refract/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A framework-agnostic design-token toolkit. You author one **raw theme**; refract
|
|
6
|
+
compiles it into a single, format-neutral **Model** and lowers that Model to whatever
|
|
7
|
+
output you need through an **adapter** — CSS custom properties, styled-components,
|
|
8
|
+
SCSS `$variables`, or plain JSON. One source of truth, many targets.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
raw theme ──▶ Model (format-neutral) ──▶ adapter ──▶ CSS │ styled-components │ SCSS │ JSON
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> **Status: `0.x` (pinned).** Published on npm as `@theme-registry/refract`. Pin exact versions
|
|
15
|
+
> through `0.x`. **Docs, live playground & API reference:** <https://theme-registry.github.io/refract/>
|
|
16
|
+
|
|
17
|
+
## Stability
|
|
18
|
+
|
|
19
|
+
| Surface | Package | Tier |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Core + CLI (incl. DTCG interop at `/dtcg`) | `@theme-registry/refract` | **Stable** |
|
|
22
|
+
| CSS adapter | `@theme-registry/refract-css` | **Stable** |
|
|
23
|
+
| styled-components adapter | `@theme-registry/refract-styled-components` | **Stable** |
|
|
24
|
+
| MCP server (agent query surface) | `@theme-registry/refract-mcp` | **Stable** |
|
|
25
|
+
| SCSS adapter | `@theme-registry/refract-scss` | Experimental |
|
|
26
|
+
| JSON adapter | `@theme-registry/refract-json` | Experimental |
|
|
27
|
+
|
|
28
|
+
**Stable** — breaking changes are deliberate, announced events. **Experimental** — the shape may
|
|
29
|
+
still change; these adapters are reachable via the npm `experimental` dist-tag.
|
|
30
|
+
|
|
31
|
+
## Versioning
|
|
32
|
+
|
|
33
|
+
All packages share **one lockstep version** through the `0.x` line (a Changesets `fixed` group) and
|
|
34
|
+
publish together. Tiers are signalled by npm dist-tag, not by divergent versions. **Pin exact
|
|
35
|
+
versions** until `1.0`, when the group splits into independent lines. Details: **[RELEASING.md](RELEASING.md)**.
|
|
36
|
+
|
|
37
|
+
**Token paths are stable identifiers.** A token path (`colors.brand.dark`) is treated as public API —
|
|
38
|
+
it won't change or disappear within a minor/patch release, so agents, the `guide` manifest (`schema`-
|
|
39
|
+
versioned), and DTCG round-trips can bind to it. Removals/renames are breaking (major) changes.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @theme-registry/refract
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`styled-components` and `typescript` are **optional** peers — only needed if you use the
|
|
48
|
+
styled-components adapter or the `.ts` build config, respectively.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
`createTheme(raw, { adapter })` builds the theme. The `adapter` is **required** — core
|
|
53
|
+
ships no default, which is what keeps it format-neutral.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { createTheme } from "@theme-registry/refract";
|
|
57
|
+
import { createCssAdapter } from "@theme-registry/refract-css"; // adapters are separate packages
|
|
58
|
+
|
|
59
|
+
const theme = createTheme(
|
|
60
|
+
{
|
|
61
|
+
breakpoints: { sm: 576, md: 768, lg: 1024 },
|
|
62
|
+
colors: {
|
|
63
|
+
brand: { base: "#4c6ef5", text: "#ffffff" }, // → brand, brand.text, brand.light/dark/…
|
|
64
|
+
recipes: {
|
|
65
|
+
solid: { brand: { background: "brand", color: "brand.text" } },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{ adapter: createCssAdapter() },
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
theme.css; // the full stylesheet (:root vars + .classes)
|
|
73
|
+
theme.tokens["colors.brand"]; // { ref?: string, value?: … } — the flat token map
|
|
74
|
+
theme.resolveToken("colors.brand.dark"); // "#3d58c4" — derived step (follows aliases + runs derivations)
|
|
75
|
+
theme.classes; // recipe → className map
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Swap the adapter, keep the raw theme:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { createScssAdapter } from "@theme-registry/refract-scss"; // each adapter is its own package
|
|
82
|
+
const scss = createTheme(raw, { adapter: createScssAdapter() }); // scss.scss → $variables + classes
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Authoring the raw theme
|
|
86
|
+
|
|
87
|
+
The raw theme is the file you live in. It is one key per **subsystem**, each with
|
|
88
|
+
properties and an optional nested `recipes` block:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import type { RawTheme } from "@theme-registry/refract/build";
|
|
92
|
+
|
|
93
|
+
const raw = {
|
|
94
|
+
breakpoints: { sm: 576, md: 768, lg: 1024 },
|
|
95
|
+
|
|
96
|
+
colors: { /* palettes: hex | { base, text, variants, steps, responsive } + recipes */ },
|
|
97
|
+
typography: { /* fontFamily/Size/Weight/lineHeight/… (modular scale) + recipes */ },
|
|
98
|
+
effects: { /* radius/shadow/transitions/opacity/zIndex/blur/… + recipes */ },
|
|
99
|
+
layout: { /* spacing/gutters + columns/grids/stacks/container + recipes */ },
|
|
100
|
+
components: { /* composition-only: recipes that reference other subsystems' recipes */ },
|
|
101
|
+
} satisfies RawTheme;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Property values can be **literals** (`"#4c6ef5"`, `16`), **references** to other tokens
|
|
105
|
+
(`color: "brand.text"`), and carry **responsive / variant / target** overrides keyed on
|
|
106
|
+
your breakpoints. Recipes group reusable rule-sets with **states** (`hover`, `disabled`, …).
|
|
107
|
+
Full field-by-field walkthrough: **[docs/authoring.md](docs/authoring.md)**.
|
|
108
|
+
|
|
109
|
+
## Adapters — the multi-format thesis
|
|
110
|
+
|
|
111
|
+
Every adapter consumes the same Model and decides how to realize it. Four ship in-box:
|
|
112
|
+
|
|
113
|
+
Core ships **zero** adapters; each is its own installable package (`npm i @theme-registry/refract-<name>`).
|
|
114
|
+
|
|
115
|
+
| Adapter | Package | Output | Notes |
|
|
116
|
+
| --- | --- | --- | --- |
|
|
117
|
+
| **CSS** | `@theme-registry/refract-css` | `:root` custom properties + classes | the batteries-included default |
|
|
118
|
+
| **styled-components** | `@theme-registry/refract-styled-components` | `css` blocks + `createGlobalStyle` + `theme.media` | needs the `styled-components` peer |
|
|
119
|
+
| **SCSS** | `@theme-registry/refract-scss` | compile-time `$variables` + classes from Sass | `TUnit = string`, a genuinely distinct format |
|
|
120
|
+
| **JSON** | `@theme-registry/refract-json` | the full Model as address-keyed data | `TUnit = object` — proves the contract is format-generic |
|
|
121
|
+
|
|
122
|
+
Write your own with `defineAdapter(spec)` — you fill four primitives (`recipeName`,
|
|
123
|
+
`renderRecipe`, `renderVariables`, `join`) and core supplies the rest. See
|
|
124
|
+
**[docs/extending.md](docs/extending.md)** or run the `adapter-scaffold` skill.
|
|
125
|
+
|
|
126
|
+
## Build to disk (CLI)
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx refract init # scaffold a theme.config.(ts|js|mjs)
|
|
130
|
+
npx refract build # load the config → write every target's files to its outDir
|
|
131
|
+
npx refract tokens # export theme.tokens as a DTCG tokens.json (adapter-free)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`theme.config.ts` is your code — it imports the adapters (and the raw theme) it wants:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { defineConfig } from "@theme-registry/refract/build";
|
|
138
|
+
import { createCssAdapter } from "@theme-registry/refract-css";
|
|
139
|
+
import { raw } from "./theme.raw"; // a native, RawTheme-typed sibling .ts (graph-compiled)
|
|
140
|
+
|
|
141
|
+
export default defineConfig({
|
|
142
|
+
raw,
|
|
143
|
+
targets: [
|
|
144
|
+
{ name: "css", adapter: createCssAdapter(), outDir: "dist/theme" },
|
|
145
|
+
{ name: "split", adapter: createCssAdapter(), outDir: "dist/split", emit: { type: "split" } },
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### `emit` — how output is written
|
|
151
|
+
|
|
152
|
+
Each target's `emit` picks the output shape (CSS adapter):
|
|
153
|
+
|
|
154
|
+
| Mode | Result |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| `single` (default) | one `theme.css` — all `:root` vars + all rules |
|
|
157
|
+
| `split` | `styles.css` + `variables.css` (load-order contract, no `@import`) |
|
|
158
|
+
| `subsystem` | a styles+variables pair per subsystem (`colors.css`, `colors.variables.css`, …) |
|
|
159
|
+
| `components` | each component variant flattened into one self-contained file (`inline: true` bakes values; `inline: false` emits `var(--…)` + a tree-shaken `variables.css`) |
|
|
160
|
+
|
|
161
|
+
## DTCG round-trip
|
|
162
|
+
|
|
163
|
+
The `./dtcg` subpath reads/writes the W3C Design Token Community Group `tokens.json`
|
|
164
|
+
format, so a theme round-trips through Figma / Style Dictionary / other DTCG tooling.
|
|
165
|
+
It is data-interchange, **not** an output adapter — property tokens only.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { fromDTCG, toDTCG } from "@theme-registry/refract/dtcg";
|
|
169
|
+
|
|
170
|
+
const raw = fromDTCG(designTokensJson); // DTCG document → createTheme raw input
|
|
171
|
+
const doc = toDTCG(theme); // built theme's tokens → DTCG document
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Package entry points
|
|
175
|
+
|
|
176
|
+
| Subpath | Contents |
|
|
177
|
+
| --- | --- |
|
|
178
|
+
| `@theme-registry/refract` | `createTheme`, `createCssAdapter`, `createStyledComponentsAdapter`, `defineAdapter`, the Model/adapter types, subsystem descriptors |
|
|
179
|
+
| `…/css` · `…/styled-components` · `…/json` · `…/scss` | each adapter's factory + types, as its own bundle |
|
|
180
|
+
| `…/dtcg` | `fromDTCG` / `toDTCG` / `parseDTCGDocument` (pure, no runtime adapter graph) |
|
|
181
|
+
| `…/build` | `defineConfig`, `emitTheme`, the `RawTheme` authoring types, the `Emit` vocabulary (Node-only) |
|
|
182
|
+
|
|
183
|
+
## Documentation
|
|
184
|
+
|
|
185
|
+
- **[docs/authoring.md](docs/authoring.md)** — author a theme + consume its output (the user guide).
|
|
186
|
+
- **[docs/extending.md](docs/extending.md)** — write a subsystem or an adapter against the frozen contract.
|
|
187
|
+
- **[AGENTS.md](AGENTS.md)** — orientation for AI coding agents.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT © Petyo Stoyanov
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theme-registry/refract",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "The framework-agnostic, format-neutral theme compiler core — build a ThemeModel from a RawTheme, then lower it with any adapter package (CSS, SCSS, JSON, styled-components).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petyo Stoyanov",
|