@motion-proto/live-tokens 0.95.0 → 0.97.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/CHANGELOG.md +55 -0
- package/bin/set-colors.mjs +1 -0
- package/bin/setup-claude.mjs +2 -2
- package/dist-plugin/setColors/index.cjs +7 -1
- package/dist-plugin/setColors/index.d.cts +9 -4
- package/dist-plugin/setColors/index.d.ts +9 -4
- package/dist-plugin/setColors/index.js +7 -1
- package/dist-plugin/setGeometry/index.d.cts +1 -1
- package/dist-plugin/setGeometry/index.d.ts +1 -1
- package/dist-plugin/setType/index.d.cts +1 -1
- package/dist-plugin/setType/index.d.ts +1 -1
- package/dist-plugin/{themeTypes-B8_Idrp4.d.cts → themeTypes-BaTQUyac.d.cts} +10 -1
- package/dist-plugin/{themeTypes-B8_Idrp4.d.ts → themeTypes-BaTQUyac.d.ts} +10 -1
- package/package.json +4 -2
- package/src/demo/TestingLoops.svelte +39 -16
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +0 -7
- package/src/editor/core/store/editorPersistence.ts +9 -1
- package/src/editor/core/store/editorRenderer.ts +2 -0
- package/src/editor/core/store/editorStore.ts +25 -0
- package/src/editor/core/store/editorTypes.ts +9 -0
- package/src/editor/core/themes/buildColors.ts +16 -5
- package/src/editor/core/themes/slices/customColors.ts +226 -0
- package/src/editor/core/themes/themeTypes.ts +10 -0
- package/src/editor/docs/content/editing-tokens.md +28 -0
- package/src/editor/docs/content.generated.ts +1 -1
- package/src/editor/pages/EditorShell.svelte +1 -0
- package/src/editor/ui/CustomColorReplaceStep.svelte +118 -0
- package/src/editor/ui/UIPaletteSelector.svelte +338 -15
- package/src/editor/ui/UITokenSelector.svelte +7 -12
- package/src/editor/ui/VariablesTab.svelte +3 -0
- package/src/editor/ui/anchorToTrigger.ts +63 -0
- package/src/editor/ui/sections/CustomColorsSection.svelte +254 -0
- package/src/system/components/ImageLightbox.svelte +17 -3
- package/src/testing-js/page-compliance.contract.js +31 -5
- package/src/testing-js/page-compliance.contract.js.map +1 -1
- package/src/editor/ui/keepInViewport.ts +0 -84
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.97.0 — Custom colors
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Every color picker offers custom colors.** A **Custom** row after Danger
|
|
8
|
+
lists the open theme's own colors, `--color-custom-1`, `--color-custom-2`
|
|
9
|
+
and so on, each holding one OKLCH value for light and dark. The row's
|
|
10
|
+
plus swatch starts a new color from the slot's current color and opens
|
|
11
|
+
the OKLCH editor; confirm assigns it to the slot in one undo step, and
|
|
12
|
+
cancel removes it. A slot picks an existing custom color as it picks a
|
|
13
|
+
palette step, opacity included. The pencil on a custom swatch edits it in
|
|
14
|
+
place, and the edit moves every slot that names it. The Tokens view gains a
|
|
15
|
+
**Custom Colours** section that lists, edits and removes them. Removing a
|
|
16
|
+
color that a component property, gradient stop or wash names first asks
|
|
17
|
+
for a replacement, preselects the nearest token, and rewrites each
|
|
18
|
+
reference with its opacity intact, all in one undo step.
|
|
19
|
+
- **Themes carry an optional `customColors` field.**
|
|
20
|
+
`ColorsAndType.customColors` sits beside `gradients` and holds
|
|
21
|
+
`{ variable, color }` entries. The field is additive: a theme without it
|
|
22
|
+
loads with no custom colors, `THEME_SCHEMA_VERSION` stays 5, and no
|
|
23
|
+
migration runs. Save, export and import keep the list, and Adopt writes
|
|
24
|
+
each custom color into `tokens.generated.css` with the theme's other
|
|
25
|
+
variables. `set-colors` and `save-theme` carry the field through
|
|
26
|
+
unchanged. `tokens.css` and the shipped themes declare no custom color, so
|
|
27
|
+
`check-page` reports `unknown-token` for page CSS that names
|
|
28
|
+
`var(--color-custom-1)`; reach a custom color through a component property.
|
|
29
|
+
|
|
30
|
+
## 0.96.1 — Vitest 5 peer range
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **Installs alongside Vitest 5.** The optional `vitest` peer range now reads
|
|
35
|
+
`^4.0 || ^5.0`. With `^4.0` alone, npm stopped any install that re-resolved
|
|
36
|
+
the tree with `ERESOLVE` once Vitest 5 sat anywhere in it, including as an
|
|
37
|
+
uninstalled optional peer of Vite's devtools. The `testing` contract helpers
|
|
38
|
+
pass the full suite on Vitest 5.
|
|
39
|
+
|
|
40
|
+
## 0.96.0 — Lightbox zoom readout in natural pixels
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **The `ImageLightbox` zoom readout measures the image against its natural
|
|
45
|
+
resolution.** 100% shows one source pixel per CSS pixel, the unit `maxZoom`
|
|
46
|
+
already takes. A 2880px image fitted into a 960px box opens at 33%, and
|
|
47
|
+
`maxZoom={1}` tops out at 100%. The readout used to call the fitted view
|
|
48
|
+
100% whatever the image's size. Until the natural size is known, from
|
|
49
|
+
`width`/`height` or the loaded image, the readout measures against the
|
|
50
|
+
fitted view as before. The zoom steps and caps stay the same.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **The Testing loops page keeps its diagram text crisp in sketch mode.** The
|
|
55
|
+
text lies in its own svg above the boxes and arrows, so the sketch filter
|
|
56
|
+
roughens the shapes and leaves the labels legible.
|
|
57
|
+
|
|
3
58
|
## 0.95.0 — Responsive columns and production CSS
|
|
4
59
|
|
|
5
60
|
### Changed (breaking)
|
package/bin/set-colors.mjs
CHANGED
package/bin/setup-claude.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { join } from 'node:path';
|
|
|
19
19
|
|
|
20
20
|
// The package owns this prefix in a project's skills directory. A skill without
|
|
21
21
|
// it is the project's own and is never touched, whatever the flags say.
|
|
22
|
-
const SKILL_PREFIX = 'live-tokens-';
|
|
22
|
+
export const SKILL_PREFIX = 'live-tokens-';
|
|
23
23
|
|
|
24
24
|
const SAMPLE_PROMPTS = {
|
|
25
25
|
'live-tokens-create-page': 'build a pricing page using live-tokens components',
|
|
@@ -44,7 +44,7 @@ export function runSetupClaude({ pkgRoot, cwd, force = false }) {
|
|
|
44
44
|
throw new Error(`No bundled skills found at ${srcSkills}. Is the package installed correctly?`);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const shipped = directoriesIn(srcSkills);
|
|
47
|
+
const shipped = directoriesIn(srcSkills).filter((name) => name.startsWith(SKILL_PREFIX));
|
|
48
48
|
if (shipped.length === 0) {
|
|
49
49
|
throw new Error('No bundled skills to install.');
|
|
50
50
|
}
|
|
@@ -1098,7 +1098,13 @@ function buildColors(input, carry = {}) {
|
|
|
1098
1098
|
for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
|
|
1099
1099
|
Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, baseColors.Canvas));
|
|
1100
1100
|
return {
|
|
1101
|
-
colors: {
|
|
1101
|
+
colors: {
|
|
1102
|
+
editorConfigs: configs,
|
|
1103
|
+
harmonyAxes,
|
|
1104
|
+
gradients,
|
|
1105
|
+
...carry.customColors ? { customColors: carry.customColors } : {},
|
|
1106
|
+
cssVariables
|
|
1107
|
+
},
|
|
1102
1108
|
report: {
|
|
1103
1109
|
...report,
|
|
1104
1110
|
gradients: gradients === carry.gradients ? "carried" : "recipes",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, O as Oklch,
|
|
1
|
+
import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, b as CustomColorDiskToken, O as Oklch, e as HarmonyMode } from '../themeTypes-BaTQUyac.cjs';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.cjs';
|
|
4
4
|
|
|
@@ -44,12 +44,14 @@ interface ColorsInput {
|
|
|
44
44
|
canvasGradient?: boolean;
|
|
45
45
|
}
|
|
46
46
|
/** The live colors and type this pass reads before it replaces the color
|
|
47
|
-
* state: the override bag, so every name no palette owns rides through,
|
|
48
|
-
*
|
|
49
|
-
* derived, not carried — see
|
|
47
|
+
* state: the override bag, so every name no palette owns rides through, the
|
|
48
|
+
* swatch gradients, so user-tuned ones survive, and the custom colours, which
|
|
49
|
+
* ride through untouched. Shadow opacity is derived, not carried — see
|
|
50
|
+
* `shadowVars`. */
|
|
50
51
|
interface CarryForward {
|
|
51
52
|
cssVariables?: Record<string, string>;
|
|
52
53
|
gradients?: GradientDiskToken[];
|
|
54
|
+
customColors?: CustomColorDiskToken[];
|
|
53
55
|
}
|
|
54
56
|
interface ContrastCheck {
|
|
55
57
|
textVar: string;
|
|
@@ -80,6 +82,9 @@ interface ColorsState {
|
|
|
80
82
|
editorConfigs: Record<string, PaletteConfig>;
|
|
81
83
|
harmonyAxes: HarmonyAxis[];
|
|
82
84
|
gradients: GradientDiskToken[];
|
|
85
|
+
/** Present only when the carried colours and type held the field, so a
|
|
86
|
+
* recolor adds no key to a theme that never had one. */
|
|
87
|
+
customColors?: CustomColorDiskToken[];
|
|
83
88
|
cssVariables: Record<string, string>;
|
|
84
89
|
}
|
|
85
90
|
interface BuildColorsResult {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, O as Oklch,
|
|
1
|
+
import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, b as CustomColorDiskToken, O as Oklch, e as HarmonyMode } from '../themeTypes-BaTQUyac.js';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.js';
|
|
4
4
|
|
|
@@ -44,12 +44,14 @@ interface ColorsInput {
|
|
|
44
44
|
canvasGradient?: boolean;
|
|
45
45
|
}
|
|
46
46
|
/** The live colors and type this pass reads before it replaces the color
|
|
47
|
-
* state: the override bag, so every name no palette owns rides through,
|
|
48
|
-
*
|
|
49
|
-
* derived, not carried — see
|
|
47
|
+
* state: the override bag, so every name no palette owns rides through, the
|
|
48
|
+
* swatch gradients, so user-tuned ones survive, and the custom colours, which
|
|
49
|
+
* ride through untouched. Shadow opacity is derived, not carried — see
|
|
50
|
+
* `shadowVars`. */
|
|
50
51
|
interface CarryForward {
|
|
51
52
|
cssVariables?: Record<string, string>;
|
|
52
53
|
gradients?: GradientDiskToken[];
|
|
54
|
+
customColors?: CustomColorDiskToken[];
|
|
53
55
|
}
|
|
54
56
|
interface ContrastCheck {
|
|
55
57
|
textVar: string;
|
|
@@ -80,6 +82,9 @@ interface ColorsState {
|
|
|
80
82
|
editorConfigs: Record<string, PaletteConfig>;
|
|
81
83
|
harmonyAxes: HarmonyAxis[];
|
|
82
84
|
gradients: GradientDiskToken[];
|
|
85
|
+
/** Present only when the carried colours and type held the field, so a
|
|
86
|
+
* recolor adds no key to a theme that never had one. */
|
|
87
|
+
customColors?: CustomColorDiskToken[];
|
|
83
88
|
cssVariables: Record<string, string>;
|
|
84
89
|
}
|
|
85
90
|
interface BuildColorsResult {
|
|
@@ -483,7 +483,13 @@ function buildColors(input, carry = {}) {
|
|
|
483
483
|
for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
|
|
484
484
|
Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, baseColors.Canvas));
|
|
485
485
|
return {
|
|
486
|
-
colors: {
|
|
486
|
+
colors: {
|
|
487
|
+
editorConfigs: configs,
|
|
488
|
+
harmonyAxes,
|
|
489
|
+
gradients,
|
|
490
|
+
...carry.customColors ? { customColors: carry.customColors } : {},
|
|
491
|
+
cssVariables
|
|
492
|
+
},
|
|
487
493
|
report: {
|
|
488
494
|
...report,
|
|
489
495
|
gradients: gradients === carry.gradients ? "carried" : "recipes",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-
|
|
1
|
+
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-BaTQUyac.cjs';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.cjs';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-
|
|
1
|
+
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-BaTQUyac.js';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.js';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as FontStackVariable, C as ColorsAndType, c as FontStack, F as FontSource } from '../themeTypes-BaTQUyac.cjs';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
|
|
3
3
|
|
|
4
4
|
type PairingSlot = 'display' | 'body' | 'serif' | 'mono' | 'editorial';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as FontStackVariable, C as ColorsAndType, c as FontStack, F as FontSource } from '../themeTypes-BaTQUyac.js';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
|
|
3
3
|
|
|
4
4
|
type PairingSlot = 'display' | 'body' | 'serif' | 'mono' | 'editorial';
|
|
@@ -181,6 +181,12 @@ interface GradientDiskToken {
|
|
|
181
181
|
opacity?: number;
|
|
182
182
|
}[];
|
|
183
183
|
}
|
|
184
|
+
/** On-disk shape of one `--color-custom-N` token: a theme-owned colour that
|
|
185
|
+
* holds its own OKLCH value, one value for light and dark alike. */
|
|
186
|
+
interface CustomColorDiskToken {
|
|
187
|
+
variable: string;
|
|
188
|
+
color: Oklch;
|
|
189
|
+
}
|
|
184
190
|
/**
|
|
185
191
|
* Where a live component config resolved from: the unsaved `_working`
|
|
186
192
|
* buffer, or the open theme's embedded copy. Every theme carries every
|
|
@@ -208,6 +214,9 @@ interface ColorsAndType {
|
|
|
208
214
|
/** Absent on files saved before gradients round-tripped; the loader keeps
|
|
209
215
|
* the stock defaults then, which match what those files rendered. */
|
|
210
216
|
gradients?: GradientDiskToken[];
|
|
217
|
+
/** Absent when the theme holds no custom colour; the loader reads that as
|
|
218
|
+
* an empty list. */
|
|
219
|
+
customColors?: CustomColorDiskToken[];
|
|
211
220
|
/** Four fixed harmony axes, each owning a hue with an optional bound family. */
|
|
212
221
|
harmonyAxes?: HarmonyAxis[];
|
|
213
222
|
/**
|
|
@@ -257,4 +266,4 @@ interface ComponentConfig {
|
|
|
257
266
|
schemaVersion?: number;
|
|
258
267
|
}
|
|
259
268
|
|
|
260
|
-
export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a,
|
|
269
|
+
export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a, CustomColorDiskToken as b, FontStack as c, FontStackVariable as d, HarmonyMode as e };
|
|
@@ -181,6 +181,12 @@ interface GradientDiskToken {
|
|
|
181
181
|
opacity?: number;
|
|
182
182
|
}[];
|
|
183
183
|
}
|
|
184
|
+
/** On-disk shape of one `--color-custom-N` token: a theme-owned colour that
|
|
185
|
+
* holds its own OKLCH value, one value for light and dark alike. */
|
|
186
|
+
interface CustomColorDiskToken {
|
|
187
|
+
variable: string;
|
|
188
|
+
color: Oklch;
|
|
189
|
+
}
|
|
184
190
|
/**
|
|
185
191
|
* Where a live component config resolved from: the unsaved `_working`
|
|
186
192
|
* buffer, or the open theme's embedded copy. Every theme carries every
|
|
@@ -208,6 +214,9 @@ interface ColorsAndType {
|
|
|
208
214
|
/** Absent on files saved before gradients round-tripped; the loader keeps
|
|
209
215
|
* the stock defaults then, which match what those files rendered. */
|
|
210
216
|
gradients?: GradientDiskToken[];
|
|
217
|
+
/** Absent when the theme holds no custom colour; the loader reads that as
|
|
218
|
+
* an empty list. */
|
|
219
|
+
customColors?: CustomColorDiskToken[];
|
|
211
220
|
/** Four fixed harmony axes, each owning a hue with an optional bound family. */
|
|
212
221
|
harmonyAxes?: HarmonyAxis[];
|
|
213
222
|
/**
|
|
@@ -257,4 +266,4 @@ interface ComponentConfig {
|
|
|
257
266
|
schemaVersion?: number;
|
|
258
267
|
}
|
|
259
268
|
|
|
260
|
-
export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a,
|
|
269
|
+
export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a, CustomColorDiskToken as b, FontStack as c, FontStackVariable as d, HarmonyMode as e };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motion-proto/live-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"dist-plugin",
|
|
57
57
|
"src/testing-js",
|
|
58
58
|
".claude/skills",
|
|
59
|
+
"!.claude/skills/evaluate",
|
|
59
60
|
"!.claude/skills/**/skill-alt.md",
|
|
60
61
|
"bin",
|
|
61
62
|
"template",
|
|
@@ -204,6 +205,7 @@
|
|
|
204
205
|
"check:smoke-install": "bash scripts/smoke-install.sh",
|
|
205
206
|
"check:smoke-create": "bash scripts/smoke-create.sh",
|
|
206
207
|
"eval": "bash scripts/eval.sh",
|
|
208
|
+
"eval:billing": "bash scripts/eval-billing.sh",
|
|
207
209
|
"build:eval-project": "bash scripts/build-eval-project.sh",
|
|
208
210
|
"check:smoke-component-tests": "bash scripts/smoke-component-tests.sh",
|
|
209
211
|
"check:smoke-page-tests": "bash scripts/smoke-page-tests.sh",
|
|
@@ -216,7 +218,7 @@
|
|
|
216
218
|
"sass": "^1.0",
|
|
217
219
|
"svelte": "^5",
|
|
218
220
|
"vite": "^8",
|
|
219
|
-
"vitest": "^4.0"
|
|
221
|
+
"vitest": "^4.0 || ^5.0"
|
|
220
222
|
},
|
|
221
223
|
"peerDependenciesMeta": {
|
|
222
224
|
"@playwright/test": {
|
|
@@ -121,12 +121,11 @@
|
|
|
121
121
|
<div class="figure-stage">
|
|
122
122
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex (Keyboard users need to scroll the diagram.) -->
|
|
123
123
|
<div class="figure-scroll" role="region" aria-label="Figure 1" tabindex="0">
|
|
124
|
+
<div class="dg-stack">
|
|
124
125
|
<svg class="dg" viewBox="20 12 1160 866" role="img" aria-label="The skill builds a page or component and runs the checks. The CLI checker checks the code, makes the routine repairs, tests the result, and reports the findings. The skill repairs each finding and runs the checks again until every check passes.">
|
|
125
126
|
<rect class="lane-checker" x="20" y="12" width="1160" height="866" rx="8" />
|
|
126
127
|
<path class="lane-skill" d="M28,12 H600 V878 H28 A8,8 0 0 1 20,870 V20 A8,8 0 0 1 28,12 Z" />
|
|
127
128
|
<path class="lane-shade" d="M600,12 H1172 A8,8 0 0 1 1180,20 V870 A8,8 0 0 1 1172,878 H600 Z" />
|
|
128
|
-
<text class="lane-title" x="80" y="52">Claude Skill</text>
|
|
129
|
-
<text class="lane-title" x="660" y="52">CLI checker</text>
|
|
130
129
|
<path class="flow" d="M540,148 L876,148 Q890,148 890,162 L890,225" />
|
|
131
130
|
<path class="flow" d="M890,320 L890,353" />
|
|
132
131
|
<path class="flow" d="M890,448 L890,481" />
|
|
@@ -135,33 +134,38 @@
|
|
|
135
134
|
<path class="flow" d="M890,728 L890,781" />
|
|
136
135
|
<path class="back" d="M310,640 L310,290 Q310,276 324,276 L653,276" />
|
|
137
136
|
<rect class="box-lead" x="80" y="104" width="460" height="88" rx="4" />
|
|
137
|
+
<rect class="box" x="660" y="232" width="460" height="88" rx="4" />
|
|
138
|
+
<rect class="box" x="660" y="360" width="460" height="88" rx="4" />
|
|
139
|
+
<rect class="box" x="660" y="488" width="460" height="112" rx="4" />
|
|
140
|
+
<rect class="box" x="660" y="640" width="460" height="88" rx="4" />
|
|
141
|
+
<rect class="box" x="80" y="640" width="460" height="88" rx="4" />
|
|
142
|
+
<rect class="box-lead" x="660" y="788" width="460" height="72" rx="4" />
|
|
143
|
+
</svg>
|
|
144
|
+
<svg class="dg dg-text" viewBox="20 12 1160 866" aria-hidden="true">
|
|
145
|
+
<text class="lane-title" x="80" y="52">Claude Skill</text>
|
|
146
|
+
<text class="lane-title" x="660" y="52">CLI checker</text>
|
|
138
147
|
<text class="t" x="101.3" y="138">Build a page or component</text>
|
|
139
148
|
<text class="s" x="101.3" y="166">from the catalogue and design tokens</text>
|
|
140
|
-
<rect class="box" x="660" y="232" width="460" height="88" rx="4" />
|
|
141
149
|
<text class="t" x="681.3" y="266">Check the code</text>
|
|
142
150
|
<text class="tag" x="1098.7" y="266" text-anchor="end">Static check</text>
|
|
143
151
|
<text class="s" x="681.3" y="294">design tokens, components, and props</text>
|
|
144
|
-
<rect class="box" x="660" y="360" width="460" height="88" rx="4" />
|
|
145
152
|
<text class="t" x="681.3" y="394">Make the routine repairs</text>
|
|
146
153
|
<text class="s" x="681.3" y="422">fixes with one right answer</text>
|
|
147
|
-
<rect class="box" x="660" y="488" width="460" height="112" rx="4" />
|
|
148
154
|
<text class="t" x="681.3" y="522">Test the result</text>
|
|
149
155
|
<text class="tag" x="1098.7" y="522" text-anchor="end">Vitest · Playwright</text>
|
|
150
156
|
<text class="s" x="681.3" y="550">Test component interactions, rendering, and themes.</text>
|
|
151
157
|
<text class="s" x="681.3" y="574">Check page layout, contrast, and typography.</text>
|
|
152
|
-
<rect class="box" x="660" y="640" width="460" height="88" rx="4" />
|
|
153
158
|
<text class="t" x="681.3" y="674">Report the findings</text>
|
|
154
159
|
<text class="s" x="681.3" y="702">each with guidance for its repair</text>
|
|
155
|
-
<rect class="box" x="80" y="640" width="460" height="88" rx="4" />
|
|
156
160
|
<text class="t" x="101.3" y="674">Repair each finding</text>
|
|
157
161
|
<text class="s" x="101.3" y="702">make the choice or write the code</text>
|
|
158
|
-
<rect class="box-lead" x="660" y="788" width="460" height="72" rx="4" />
|
|
159
162
|
<text class="t" x="890" y="830" text-anchor="middle">Every check passes</text>
|
|
160
163
|
<text class="lbl" x="715" y="136" text-anchor="middle">run the checks</text>
|
|
161
164
|
<text class="lbl" x="600" y="672" text-anchor="middle">findings</text>
|
|
162
165
|
<text class="lbl" x="906" y="759">none remain</text>
|
|
163
166
|
<text class="rule" x="479" y="262" text-anchor="middle">check again</text>
|
|
164
167
|
</svg>
|
|
168
|
+
</div>
|
|
165
169
|
</div>
|
|
166
170
|
</div>
|
|
167
171
|
</figure>
|
|
@@ -179,12 +183,11 @@
|
|
|
179
183
|
<div class="figure-stage">
|
|
180
184
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex (Keyboard users need to scroll the diagram.) -->
|
|
181
185
|
<div class="figure-scroll" role="region" aria-label="Figure 2" tabindex="0">
|
|
186
|
+
<div class="dg-stack">
|
|
182
187
|
<svg class="dg" viewBox="20 12 1160 916" role="img" aria-label="Read the project, plan the sections, and choose components. When no component fits, create one and check it until every check passes, and it joins the catalogue. Assemble the page and check it. Repair each finding until every check passes, then review the page.">
|
|
183
188
|
<rect class="lane-checker" x="20" y="12" width="1160" height="916" rx="8" />
|
|
184
189
|
<path class="lane-skill" d="M28,12 H600 V928 H28 A8,8 0 0 1 20,920 V20 A8,8 0 0 1 28,12 Z" />
|
|
185
190
|
<path class="lane-shade" d="M600,12 H1172 A8,8 0 0 1 1180,20 V920 A8,8 0 0 1 1172,928 H600 Z" />
|
|
186
|
-
<text class="lane-title" x="80" y="52">Page</text>
|
|
187
|
-
<text class="lane-title" x="660" y="52">New component</text>
|
|
188
191
|
<path class="flow" d="M310,192 L310,225" />
|
|
189
192
|
<path class="flow" d="M310,320 L310,353" />
|
|
190
193
|
<path class="flow" d="M310,448 L310,545" />
|
|
@@ -194,25 +197,29 @@
|
|
|
194
197
|
<path class="back" d="M540,724 L556,724 Q570,724 570,710 L570,629 Q570,620 561,620 L547,620" />
|
|
195
198
|
<path class="flow" d="M310,768 L310,813" />
|
|
196
199
|
<rect class="box-lead" x="80" y="104" width="460" height="88" rx="4" />
|
|
200
|
+
<rect class="box" x="80" y="232" width="460" height="88" rx="4" />
|
|
201
|
+
<rect class="box" x="80" y="360" width="460" height="88" rx="4" />
|
|
202
|
+
<rect class="box" x="660" y="360" width="460" height="112" rx="4" />
|
|
203
|
+
<rect class="box" x="80" y="552" width="460" height="88" rx="4" />
|
|
204
|
+
<rect class="box" x="80" y="680" width="460" height="88" rx="4" />
|
|
205
|
+
<rect class="box-lead" x="80" y="820" width="460" height="88" rx="4" />
|
|
206
|
+
</svg>
|
|
207
|
+
<svg class="dg dg-text" viewBox="20 12 1160 916" aria-hidden="true">
|
|
208
|
+
<text class="lane-title" x="80" y="52">Page</text>
|
|
209
|
+
<text class="lane-title" x="660" y="52">New component</text>
|
|
197
210
|
<text class="t" x="101.3" y="138">Read the project</text>
|
|
198
211
|
<text class="s" x="101.3" y="166">routes, grid, and the component catalogue</text>
|
|
199
|
-
<rect class="box" x="80" y="232" width="460" height="88" rx="4" />
|
|
200
212
|
<text class="t" x="101.3" y="266">Plan the sections</text>
|
|
201
213
|
<text class="s" x="101.3" y="294">one section per purpose</text>
|
|
202
|
-
<rect class="box" x="80" y="360" width="460" height="88" rx="4" />
|
|
203
214
|
<text class="t" x="101.3" y="394">Choose components</text>
|
|
204
215
|
<text class="s" x="101.3" y="422">from the catalogue first</text>
|
|
205
|
-
<rect class="box" x="660" y="360" width="460" height="112" rx="4" />
|
|
206
216
|
<text class="t" x="681.3" y="394">Create the component</text>
|
|
207
217
|
<text class="s" x="681.3" y="422">check it and repair it</text>
|
|
208
218
|
<text class="s" x="681.3" y="446">until every check passes</text>
|
|
209
|
-
<rect class="box" x="80" y="552" width="460" height="88" rx="4" />
|
|
210
219
|
<text class="t" x="101.3" y="586">Assemble the page</text>
|
|
211
220
|
<text class="s" x="101.3" y="614">components, tokens, and text styles</text>
|
|
212
|
-
<rect class="box" x="80" y="680" width="460" height="88" rx="4" />
|
|
213
221
|
<text class="t" x="101.3" y="714">Check the page</text>
|
|
214
222
|
<text class="s" x="101.3" y="742">review the code and test it in the browser</text>
|
|
215
|
-
<rect class="box-lead" x="80" y="820" width="460" height="88" rx="4" />
|
|
216
223
|
<text class="t" x="101.3" y="854">Review the page</text>
|
|
217
224
|
<text class="s" x="101.3" y="882">read it the way a visitor would</text>
|
|
218
225
|
<text class="lbl" x="326" y="499">a shipped component fits</text>
|
|
@@ -221,6 +228,7 @@
|
|
|
221
228
|
<text class="rule" x="584" y="677">repair</text>
|
|
222
229
|
<text class="lbl" x="326" y="793">every check passes</text>
|
|
223
230
|
</svg>
|
|
231
|
+
</div>
|
|
224
232
|
</div>
|
|
225
233
|
</div>
|
|
226
234
|
</figure>
|
|
@@ -832,6 +840,21 @@
|
|
|
832
840
|
color: var(--text-primary);
|
|
833
841
|
}
|
|
834
842
|
|
|
843
|
+
/* Sketch mode filters every inline svg as artwork, which smears type. The
|
|
844
|
+
text gets its own svg over the shapes, so the boxes stay drawn and the
|
|
845
|
+
labels crisp. */
|
|
846
|
+
.dg-stack {
|
|
847
|
+
display: grid;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.dg-stack > .dg {
|
|
851
|
+
grid-area: 1 / 1;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.dg-text {
|
|
855
|
+
--sketch-icon-off: none;
|
|
856
|
+
}
|
|
857
|
+
|
|
835
858
|
.dg :global(.lane-checker) {
|
|
836
859
|
fill: none;
|
|
837
860
|
stroke: var(--border-neutral-subtle);
|
|
@@ -818,13 +818,6 @@
|
|
|
818
818
|
color: var(--ui-text-tertiary);
|
|
819
819
|
}
|
|
820
820
|
|
|
821
|
-
/* Anchor dropdown to the trigger's right edge; the toolbar sits flush against
|
|
822
|
-
the editor's right gutter, so a left-anchored dropdown would clip. */
|
|
823
|
-
.canvas-toolbar :global(.ui-ts-dropdown) {
|
|
824
|
-
left: auto;
|
|
825
|
-
right: 0;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
821
|
.variant-tabs {
|
|
829
822
|
display: inline-flex;
|
|
830
823
|
flex-wrap: wrap;
|
|
@@ -19,6 +19,7 @@ import { storageKey } from './editorConfig';
|
|
|
19
19
|
import { store } from './editorCore';
|
|
20
20
|
import { quietGet, quietSet } from '../storage/storage';
|
|
21
21
|
import { isGradientSlot, makeDefaultGradients } from '../themes/slices/gradients';
|
|
22
|
+
import { isCustomColorList } from '../themes/slices/customColors';
|
|
22
23
|
import { seedShadowsFromDom } from '../themes/slices/shadows';
|
|
23
24
|
import { makeDefaultWashScale, type WashFamily } from '../themes/slices/washes';
|
|
24
25
|
import { normalizeColumnsState } from '../themes/slices/columns';
|
|
@@ -73,6 +74,13 @@ function migrateGradients(state: EditorState): EditorState {
|
|
|
73
74
|
return { ...state, gradients: { tokens: makeDefaultGradients() } };
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
// Persisted state from before custom colours gets the empty default from the
|
|
78
|
+
// shallow merge; anything else that is not a well-formed list loads as empty.
|
|
79
|
+
function migrateCustomColors(state: EditorState): EditorState {
|
|
80
|
+
if (isCustomColorList(state.customColors?.tokens)) return state;
|
|
81
|
+
return { ...state, customColors: { tokens: [] } };
|
|
82
|
+
}
|
|
83
|
+
|
|
76
84
|
// `hydrate` shallow-merges the persisted `components` bag over the default, so
|
|
77
85
|
// a slice serialized by an older build can lack fields added since (e.g.
|
|
78
86
|
// `config`, added with the two-field alias/config split). `componentsToVars`
|
|
@@ -205,7 +213,7 @@ export function hydrate(): void {
|
|
|
205
213
|
store.set(
|
|
206
214
|
normalizeHarmonyAxes(
|
|
207
215
|
normalizeBaseAnchors(
|
|
208
|
-
normalizePaletteBasis(normalizePaletteLabels(normalizeComponents(normalizeColumns(normalizeWashes(migrateGradients(merged)))))),
|
|
216
|
+
normalizePaletteBasis(normalizePaletteLabels(normalizeComponents(normalizeColumns(normalizeWashes(migrateCustomColors(migrateGradients(merged))))))),
|
|
209
217
|
),
|
|
210
218
|
),
|
|
211
219
|
);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
washesToVars,
|
|
22
22
|
shadowsToVars,
|
|
23
23
|
gradientsToVars,
|
|
24
|
+
customColorsToVars,
|
|
24
25
|
componentsToVars,
|
|
25
26
|
} from './editorStore';
|
|
26
27
|
|
|
@@ -34,6 +35,7 @@ export function deriveCssVars(state: EditorState): Record<string, string> {
|
|
|
34
35
|
if (state.gradients.tokens.length > 0) {
|
|
35
36
|
Object.assign(out, gradientsToVars(state.gradients));
|
|
36
37
|
}
|
|
38
|
+
Object.assign(out, customColorsToVars(state.customColors));
|
|
37
39
|
Object.assign(out, palettesToVars(state.palettes));
|
|
38
40
|
Object.assign(out, componentsToVars(state.components));
|
|
39
41
|
Object.assign(out, resolveFontStackValues(state.fonts.stacks, state.fonts.sources));
|
|
@@ -74,6 +74,10 @@ import {
|
|
|
74
74
|
loadGradientsFromFile,
|
|
75
75
|
makeDefaultGradients,
|
|
76
76
|
} from '../themes/slices/gradients';
|
|
77
|
+
import {
|
|
78
|
+
customColorsToVars,
|
|
79
|
+
loadCustomColorsFromFile,
|
|
80
|
+
} from '../themes/slices/customColors';
|
|
77
81
|
import {
|
|
78
82
|
componentBaseline,
|
|
79
83
|
getComponentOwnedVarNames,
|
|
@@ -102,6 +106,7 @@ function emptyState(): EditorState {
|
|
|
102
106
|
columns: defaultColumns(),
|
|
103
107
|
components: {},
|
|
104
108
|
gradients: { tokens: makeDefaultGradients() },
|
|
109
|
+
customColors: { tokens: [] },
|
|
105
110
|
harmonyAxes: defaultHarmonyAxes(),
|
|
106
111
|
cssVars: {},
|
|
107
112
|
};
|
|
@@ -179,6 +184,20 @@ export {
|
|
|
179
184
|
removeGradientToken,
|
|
180
185
|
} from '../themes/slices/gradients';
|
|
181
186
|
|
|
187
|
+
export {
|
|
188
|
+
customColorsToVars,
|
|
189
|
+
nextCustomColorSlot,
|
|
190
|
+
isCustomColorSlot,
|
|
191
|
+
addCustomColor,
|
|
192
|
+
setCustomColor,
|
|
193
|
+
removeCustomColor,
|
|
194
|
+
customColorUsages,
|
|
195
|
+
describeCustomColorUsages,
|
|
196
|
+
nearestColorToken,
|
|
197
|
+
replaceAndRemoveCustomColor,
|
|
198
|
+
} from '../themes/slices/customColors';
|
|
199
|
+
export type { CustomColorUsage } from '../themes/slices/customColors';
|
|
200
|
+
|
|
182
201
|
export {
|
|
183
202
|
componentsToVars,
|
|
184
203
|
getComponentOwnedVarNames,
|
|
@@ -404,6 +423,7 @@ export function colorsAndTypeToState(colorsAndType: ColorsAndType): EditorState
|
|
|
404
423
|
// begin with).
|
|
405
424
|
for (const load of Object.values(domainLoaders)) load(next, rawVars);
|
|
406
425
|
loadGradientsFromFile(next, colorsAndType.gradients, rawVars);
|
|
426
|
+
loadCustomColorsFromFile(next, colorsAndType.customColors, rawVars);
|
|
407
427
|
next.harmonyAxes = sanitizeHarmonyAxes(renameBackgroundHarmonyFamily(colorsAndType.harmonyAxes), next.palettes);
|
|
408
428
|
next.cssVars = rawVars;
|
|
409
429
|
return next;
|
|
@@ -438,12 +458,17 @@ function colorsAndTypeContent(state: EditorState): Omit<ColorsAndType, 'name' |
|
|
|
438
458
|
if (state.gradients.tokens.length > 0) {
|
|
439
459
|
Object.assign(cssVariables, gradientsToVars(state.gradients));
|
|
440
460
|
}
|
|
461
|
+
// An empty list writes no field, so a theme that never held a custom colour
|
|
462
|
+
// still compares equal to the buffer its load produced.
|
|
463
|
+
const hasCustomColors = state.customColors.tokens.length > 0;
|
|
464
|
+
if (hasCustomColors) Object.assign(cssVariables, customColorsToVars(state.customColors));
|
|
441
465
|
return {
|
|
442
466
|
editorConfigs: state.palettes,
|
|
443
467
|
cssVariables,
|
|
444
468
|
fontSources: state.fonts.sources,
|
|
445
469
|
fontStacks: state.fonts.stacks,
|
|
446
470
|
gradients: state.gradients.tokens,
|
|
471
|
+
...(hasCustomColors ? { customColors: state.customColors.tokens } : {}),
|
|
447
472
|
harmonyAxes: state.harmonyAxes,
|
|
448
473
|
schemaVersion: CURRENT_COLORS_AND_TYPE_SCHEMA_VERSION,
|
|
449
474
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PaletteConfig, FontSource, FontStack } from '../themes/themeTypes';
|
|
2
2
|
import type { GradientStop, GradientValue, LinearDirection } from '../themes/parsers/gradient';
|
|
3
3
|
import type { HarmonyAxis } from '../palettes/colorHarmony';
|
|
4
|
+
import type { Oklch } from '../palettes/oklch';
|
|
4
5
|
import type { ColumnViewport, ColumnViewportValues } from '../themes/slices/columns';
|
|
5
6
|
|
|
6
7
|
export interface ShadowGlobals {
|
|
@@ -80,6 +81,13 @@ export interface GradientToken {
|
|
|
80
81
|
stops: GradientTokenStop[];
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
/** A theme-owned `--color-custom-N` colour. Slots name it the way they name a
|
|
85
|
+
* palette step, so one edit moves every slot that names it. */
|
|
86
|
+
export interface CustomColorToken {
|
|
87
|
+
variable: string;
|
|
88
|
+
color: Oklch;
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
/** Structured gradient payload carried inline on a component alias.
|
|
84
92
|
* `GradientToken` minus `variable` (the alias key itself is the binding).
|
|
85
93
|
* Used when a component owns a per-instance gradient that doesn't share the
|
|
@@ -123,6 +131,7 @@ export interface EditorState {
|
|
|
123
131
|
columns: ColumnsState;
|
|
124
132
|
components: Record<string, ComponentSlice>;
|
|
125
133
|
gradients: { tokens: GradientToken[] };
|
|
134
|
+
customColors: { tokens: CustomColorToken[] };
|
|
126
135
|
/** Four fixed numbered axes (index 0 is the anchor); each owns a hue;
|
|
127
136
|
* family is the live-bound color or null. */
|
|
128
137
|
harmonyAxes: HarmonyAxis[];
|