@motion-proto/live-tokens 0.54.1 → 0.55.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 +44 -0
- package/README.md +180 -203
- package/package.json +1 -1
- package/src/editor/index.ts +3 -0
- package/src/editor/ui/PaletteEditor.svelte +30 -15
- package/src/editor/ui/palette/PaletteBase.svelte +29 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.55.0 — A focused palette arrives ready
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **A palette focused before mount opens without the reveal.** `paletteEditorOpen`
|
|
8
|
+
started false and flipped in an effect, so a deep link that set the focus
|
|
9
|
+
ahead of mount landed the visitor mid-animation, reading as a page still
|
|
10
|
+
loading. The editor now reads the pending focus when it constructs and opens
|
|
11
|
+
on the first frame; the scroll that brings it into view jumps rather than
|
|
12
|
+
gliding, since nothing expanded for the motion to match. A jump from the
|
|
13
|
+
Colors view, where the editor is already mounted, keeps its reveal.
|
|
14
|
+
|
|
15
|
+
- **"Base color must appear in palette" moved to the family band.** The toggle
|
|
16
|
+
sets `anchorToBase`, which is family-level config persisted in the theme,
|
|
17
|
+
but it sat in the base-colour panel among the controls that edit the colour's
|
|
18
|
+
*value* — and it only occupied a row of its own because it wrapped out of that
|
|
19
|
+
panel's title row. It now sits beside the family's name as
|
|
20
|
+
"Must appear in palette", the swatch alongside supplying the subject the label
|
|
21
|
+
drops; the full sentence stays as its title. The colour panel closes back up
|
|
22
|
+
to a title row and its sliders. Collapsed families are untouched: the setting
|
|
23
|
+
shows only while that family's editor is open, so a page of ten collapsed
|
|
24
|
+
palettes does not repeat it ten times.
|
|
25
|
+
|
|
26
|
+
- **The palette band holds two columns at every width.** With the setting on
|
|
27
|
+
the band's row, a band too narrow for it wrapped the buttons to the left edge,
|
|
28
|
+
under the swatch. The setting sits in the identity column instead, a third
|
|
29
|
+
line under the hex — name, value and constraint all describe the family and
|
|
30
|
+
share its left rail — which leaves the buttons a column of their own that no
|
|
31
|
+
width takes away. They wrap among themselves against the right edge before the
|
|
32
|
+
cluster ever drops a line. The swatch keeps its square: the taller column
|
|
33
|
+
would otherwise stretch the chip into a rectangle.
|
|
34
|
+
|
|
35
|
+
## 0.54.2 — Palette focus is public API
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **`openPaletteInTokens` and `openPaletteInWheel` are exported.** Both hand a
|
|
40
|
+
palette family to a view, and both were already what the palette jump buttons
|
|
41
|
+
call; only the package's own code could reach them. A consumer building a deep
|
|
42
|
+
link into the editor — "open the app with Brand's palette editor showing" —
|
|
43
|
+
now has the same entry point the UI uses. `selectedPalette` comes with them,
|
|
44
|
+
so a consumer can read which family is current. The one-shot
|
|
45
|
+
`pendingPaletteFocus` store they drive stays internal.
|
|
46
|
+
|
|
3
47
|
## 0.54.1 — Palette editing opens on a curve
|
|
4
48
|
|
|
5
49
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
# Live Tokens
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A design system for styling and building Svelte + Vite microsites. Edit tokens and components in a dev-only editor and watch the running site repaint on every input. Save the result as a theme file and carry it between projects.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install @motion-proto/live-tokens
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The editor is dev-only. Production builds get plain CSS variables and the components you used.
|
|
6
10
|
|
|
7
11
|
## What you get
|
|
8
12
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **Claude Code skill suite** — five bundled skills so you can drive the package in plain English. `build-page` composes pages from the shipped components. `pick-component` decides between confusing pairs (TabBar vs SegmentedControl, Card vs CollapsibleSection). `create-component` authors a new editable component against the project's naming, state-model, and import rules. `generate-theme` turns a mood brief ("bright and cheerful", "dark night theme") into a complete AA-checked color theme. `adjust-shape-space` turns "make the buttons pill shaped" or "space it out" into new radius, padding, gap, and border-width aliases. One command to install them all: `npx @motion-proto/live-tokens setup-claude`. See [Claude Code skills](#claude-code-skills) below.
|
|
13
|
+
- **Live token editing.** Colors, typography, spacing, radii, shadows, motion, palettes, and gradients. Every input writes a CSS variable, so the page repaints with no reload and no build step.
|
|
14
|
+
- **Live component editing.** 25 shipped Svelte components (Button, IconButton, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, Panel, InlineEditActions) declare their design-token aliases in a `:global(:root)` block. Rewire an alias from the component's editor and it updates everywhere that component is used, on your real pages.
|
|
15
|
+
- **Four dev-only routes.** `/live-tokens/editor` for tokens, `/live-tokens/colors` for palettes, `/live-tokens/components` for per-component aliases, `/live-tokens/docs` for the user guide.
|
|
16
|
+
- **Editor overlay.** Pins to the top right of every dev page and opens the editor in a side panel or floating window, so you edit on the page you are styling. Its "Page Source" button opens the current page's `.svelte` file in VS Code.
|
|
17
|
+
- **Themes.** A theme is a whole look in one file: colors and type plus a config for every component, stored by value. Loading one changes a single pointer file, and nothing your site ships changes until you Adopt. Export a theme and import it into another project to restore the look in one step.
|
|
18
|
+
- **Seven example looks.** Autumn, Halloween, Midnight Study, Ocean, Royal Velvet, Spring Meadow, and Sunset each ship as a full theme: colors, a Google Fonts pairing, and a shape personality of radius, padding, gap, and border-width aliases. They ship inside the package, so trying one needs no local files. Load Motion Proto to return to the default. Saving over a preset writes a local copy that shadows the shipped one; delete the copy and the shipped version returns.
|
|
19
|
+
- **Vite plugin.** Hosts the `/api/live-tokens/{colors-and-type,component-configs,themes}/*` routes the editor reads and writes through. The single namespace keeps these routes clear of anything your app serves under `/api`.
|
|
20
|
+
- **Claude Code skills.** Five bundled skills that drive the package from plain English. See [Claude Code skills](#claude-code-skills).
|
|
18
21
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install @motion-proto/live-tokens
|
|
23
|
-
```
|
|
22
|
+
## Install
|
|
24
23
|
|
|
25
24
|
### Vite config
|
|
26
25
|
|
|
@@ -33,7 +32,7 @@ import { themeFileApi } from '@motion-proto/live-tokens/vite-plugin';
|
|
|
33
32
|
export default defineConfig({
|
|
34
33
|
plugins: [
|
|
35
34
|
// vitePreprocess compiles the shipped components' `<style lang="scss">`
|
|
36
|
-
// blocks
|
|
35
|
+
// blocks. Install `sass` alongside it.
|
|
37
36
|
svelte({ preprocess: vitePreprocess() }),
|
|
38
37
|
themeFileApi({
|
|
39
38
|
tokensCssPath: 'src/system/styles/tokens.css',
|
|
@@ -43,43 +42,15 @@ export default defineConfig({
|
|
|
43
42
|
```
|
|
44
43
|
|
|
45
44
|
The `themeFileApi` plugin:
|
|
46
|
-
- Resolves the `default` colors and type from the installed package, so you start on the shipped defaults without a local copy.
|
|
47
|
-
- Discovers components at `src/components/*.svelte` (and `src/system/components/*.svelte` for back-compat) and seeds `src/live-tokens/data/component-configs/{comp}/default.json` from each component's `:global(:root)` block.
|
|
48
|
-
- Writes `src/live-tokens/data/themes/default.json` on dev-server start: the Default theme, derived from the shipped colors and type and those component defaults, and regenerated whenever they change. It is protected, so the editor never deletes it and an outside deletion heals on the next start.
|
|
49
|
-
- Bakes `tokens.generated.css` from the production theme at startup, so a fresh checkout builds against the look you shipped.
|
|
50
|
-
- Hosts the `/api/live-tokens/*` routes the editor uses to save and load themes + per-component configs.
|
|
51
|
-
- Auto-injects `__PROJECT_ROOT__` for the overlay's "Page Source" link and `__LIVE_TOKENS_API_BASE__` so the client uses whatever `apiBase` you configured.
|
|
52
|
-
|
|
53
|
-
A project last opened on 0.47.1 or earlier still keeps its colors and type in `themes/` and its whole looks in `manifests/`, the names 0.48 reassigned. The plugin recognises that layout, writes nothing at all, and says so. `npx live-tokens migrate` moves `themes/` to `colors-and-type/` and `manifests/` to `themes/`, then heals what is inside: it reads what the retired per-layer `_active.json` / `_production.json` pointers resolved to, records it as the production theme, and clears them. Restart the dev server afterwards.
|
|
54
|
-
|
|
55
|
-
### Where data lands — and how to move it
|
|
56
|
-
|
|
57
|
-
By default, the plugin reads and writes under one folder: `src/live-tokens/data/`. Inside that folder live three subdirectories — `colors-and-type/`, `themes/`, `component-configs/` — each owned by the plugin.
|
|
58
|
-
|
|
59
|
-
`themes/` holds the documents: one file per whole look, plus `_active.json` naming the one the editor has open and `_production.json` naming the one your site ships. `colors-and-type/` and `component-configs/{comp}/` hold each layer's `default.json` baseline, any preset you save by name, and the `_working.json` buffer for edits you have not saved into the active theme. A buffer is a delta from that open document, so ordinary theme switching leaves none.
|
|
60
|
-
|
|
61
|
-
To move them, create a `live-tokens.config.json` at your project root:
|
|
62
|
-
|
|
63
|
-
```json
|
|
64
|
-
{
|
|
65
|
-
"dataDir": "src/live-tokens/data"
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
All four keys are optional. `dataDir` is the headline knob — it relocates all three subfolders at once. The per-folder overrides exist for unusual layouts (e.g. a monorepo where colors and type are shared across packages but component-configs aren't):
|
|
70
|
-
|
|
71
|
-
```json
|
|
72
|
-
{
|
|
73
|
-
"dataDir": "src/live-tokens/data",
|
|
74
|
-
"colorsAndTypeDir": "../shared/colors-and-type",
|
|
75
|
-
"componentConfigsDir": "src/live-tokens/data/component-configs",
|
|
76
|
-
"themesDir": "src/live-tokens/data/themes"
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
45
|
|
|
80
|
-
|
|
46
|
+
- Resolves the `default` colors and type from the installed package, so you start on the shipped defaults with no local copy.
|
|
47
|
+
- Discovers components at `src/components/*.svelte` and `src/system/components/*.svelte`, and seeds `src/live-tokens/data/component-configs/{comp}/default.json` from each component's `:global(:root)` block.
|
|
48
|
+
- Writes `src/live-tokens/data/themes/default.json` at dev-server start: the Default theme, derived from the shipped colors and type plus those component defaults, and regenerated when either changes. It is protected, so the editor never deletes it and an outside deletion heals on the next start.
|
|
49
|
+
- Bakes `tokens.generated.css` from the production theme at startup, so a fresh checkout builds against the look you shipped.
|
|
50
|
+
- Hosts the `/api/live-tokens/*` routes the editor saves and loads through.
|
|
51
|
+
- Injects `__PROJECT_ROOT__` for the overlay's "Page Source" link and `__LIVE_TOKENS_API_BASE__` so the client uses whatever `apiBase` you configured.
|
|
81
52
|
|
|
82
|
-
|
|
53
|
+
A project last opened on 0.47.1 or earlier keeps its colors and type in `themes/` and its whole looks in `manifests/`, the names 0.48 reassigned. The plugin recognises that layout, writes nothing, and says so. `npx live-tokens migrate` moves `themes/` to `colors-and-type/` and `manifests/` to `themes/`, records what the retired per-layer pointers resolved to as the production theme, and clears them. Restart the dev server afterwards.
|
|
83
54
|
|
|
84
55
|
### Bootstrap in `main.ts`
|
|
85
56
|
|
|
@@ -94,28 +65,7 @@ import App from './App.svelte';
|
|
|
94
65
|
bootLiveTokens(App, '#app');
|
|
95
66
|
```
|
|
96
67
|
|
|
97
|
-
`bootLiveTokens`
|
|
98
|
-
active theme in dev, optionally registers consumer-authored components, and
|
|
99
|
-
mounts the app. FontAwesome is side-effect-imported by the bootstrap (the dev
|
|
100
|
-
overlay always needs icons). The three token-CSS imports stay with the
|
|
101
|
-
consumer because order matters and `tokens.generated.css` is project-local.
|
|
102
|
-
|
|
103
|
-
Pass `components` to register consumer-authored editable components:
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
import MyWidgetEditor, { allTokens as myWidgetTokens } from './components/MyWidgetEditor.svelte';
|
|
107
|
-
|
|
108
|
-
bootLiveTokens(App, '#app', {
|
|
109
|
-
components: [{
|
|
110
|
-
id: 'mywidget',
|
|
111
|
-
label: 'My Widget',
|
|
112
|
-
icon: 'fas fa-magic',
|
|
113
|
-
sourceFile: 'src/components/MyWidget.svelte',
|
|
114
|
-
editorComponent: MyWidgetEditor,
|
|
115
|
-
schema: myWidgetTokens,
|
|
116
|
-
}],
|
|
117
|
-
});
|
|
118
|
-
```
|
|
68
|
+
`bootLiveTokens` runs the editor's idempotent init hooks, fetches the active theme in dev, registers any consumer-authored components, and mounts the app. It side-effect-imports FontAwesome, because the dev overlay always needs icons. The three token-CSS imports stay with you: order matters, and `tokens.generated.css` is project-local.
|
|
119
69
|
|
|
120
70
|
### Mount routes with `<LiveTokensRouter>`
|
|
121
71
|
|
|
@@ -130,26 +80,9 @@ bootLiveTokens(App, '#app', {
|
|
|
130
80
|
}} />
|
|
131
81
|
```
|
|
132
82
|
|
|
133
|
-
`<LiveTokensRouter>` owns the dev overlay (`<LiveEditorOverlay>`
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
overlay needs. Each entry in `pages` is one of your routes; entries with a
|
|
137
|
-
`label` appear in the overlay's nav rail. Pass pages as `lazy: () => import('./Page.svelte')`
|
|
138
|
-
so each page's stylesheet side-effects only evaluate when that route is
|
|
139
|
-
visited; pass `component: PageComponent` instead for an eagerly-imported
|
|
140
|
-
page. The editor routes are dispatched internally, so you don't have to
|
|
141
|
-
dynamic-import the library's editor pages yourself.
|
|
142
|
-
|
|
143
|
-
For routes you can't enumerate ahead of time (a `/:id` or `/:slug`, a path
|
|
144
|
-
prefix, or a page shown only when some condition holds), add a `resolve`
|
|
145
|
-
function from the current path to a `RouteEntry`; return `null` to fall
|
|
146
|
-
through. It's plain code, so params, prefixes, and gating are a regex and an
|
|
147
|
-
`if`, and the package ships no route syntax of its own. Resolution order is
|
|
148
|
-
`pages[path]`, then `resolve(path)`, then the `pages['/']` fallback, so adding
|
|
149
|
-
`resolve` never changes how existing `pages` entries match. A resolved entry
|
|
150
|
-
can carry `props`, letting one page component serve many paths (such as the
|
|
151
|
-
matched id), and its `source` gives the dynamic route a working "Page Source"
|
|
152
|
-
button just like a static one.
|
|
83
|
+
`<LiveTokensRouter>` owns the dev overlay (`<LiveEditorOverlay>` and `<ColumnsOverlay>`), the four editor routes, in-app link-click interception, and the nav-rail and page-source plumbing the overlay needs. Each entry in `pages` is one of your routes; entries with a `label` appear in the overlay's nav rail. Pass `lazy: () => import('./Page.svelte')` so each page's stylesheet side-effects evaluate only when that route is visited, or `component: PageComponent` for an eager import. The editor routes dispatch internally, so you never import the library's editor pages yourself.
|
|
84
|
+
|
|
85
|
+
For routes you cannot enumerate ahead of time (a `/:id`, a path prefix, a page shown only under some condition), add a `resolve` function from the current path to a `RouteEntry` and return `null` to fall through. Resolution order is `pages[path]`, then `resolve(path)`, then the `pages['/']` fallback, so adding `resolve` never changes how existing entries match. A resolved entry can carry `props`, letting one component serve many paths, and its `source` gives the dynamic route a working "Page Source" button.
|
|
153
86
|
|
|
154
87
|
```svelte
|
|
155
88
|
<LiveTokensRouter
|
|
@@ -162,26 +95,9 @@ button just like a static one.
|
|
|
162
95
|
/>
|
|
163
96
|
```
|
|
164
97
|
|
|
165
|
-
|
|
166
|
-
`editorRoutes` prop: `<LiveTokensRouter pages={…} editorRoutes={{ editor: '/admin/editor', components: false }} />`.
|
|
167
|
-
Pass a string to move a route; pass `false` to remove the route entirely
|
|
168
|
-
(no dispatch and, for `components`, no auto-injected nav-rail entry).
|
|
169
|
-
|
|
170
|
-
The whole overlay surface is dev-only and tree-shakes out of production
|
|
171
|
-
builds — no `{#if import.meta.env.DEV}` guards needed.
|
|
172
|
-
|
|
173
|
-
### Lower-level API (when you need it)
|
|
98
|
+
Relocate or disable an editor route with the `editorRoutes` prop: `<LiveTokensRouter pages={…} editorRoutes={{ editor: '/admin/editor', components: false }} />`. A string moves the route; `false` removes it, along with its nav-rail entry.
|
|
174
99
|
|
|
175
|
-
|
|
176
|
-
individual init functions (`initCssVarSync`, `initRouter`,
|
|
177
|
-
`initColumnsOverlay`, `initEditorStore`, `initializeTheme`),
|
|
178
|
-
`<LiveEditorOverlay>`, `<ColumnsOverlay>`, and the editor page exports
|
|
179
|
-
(`@motion-proto/live-tokens/editor`,
|
|
180
|
-
`@motion-proto/live-tokens/component-editor-page`) all stay exported. Use
|
|
181
|
-
them directly to build a custom shell: render arbitrary markup per route, host
|
|
182
|
-
a foreign matcher, or drive the overlay yourself. You do **not** need this for
|
|
183
|
-
dynamic or gated routes; reach for `resolve` above, which keeps the overlay,
|
|
184
|
-
nav rail, and page-source intact.
|
|
100
|
+
The whole overlay surface is dev-only and tree-shakes out of production builds. No `{#if import.meta.env.DEV}` guards needed.
|
|
185
101
|
|
|
186
102
|
### Use components
|
|
187
103
|
|
|
@@ -195,16 +111,11 @@ nav rail, and page-source intact.
|
|
|
195
111
|
<Button variant="primary">Save</Button>
|
|
196
112
|
```
|
|
197
113
|
|
|
198
|
-
|
|
114
|
+
Each component carries its own design-token aliases and picks up your `tokens.css` values automatically. Import only the ones you use.
|
|
199
115
|
|
|
200
116
|
### Styles
|
|
201
117
|
|
|
202
|
-
|
|
203
|
-
**auto-loaded by the editor pages themselves**; you don't import them. The
|
|
204
|
-
only stylesheet a consumer needs is a `tokens.css` declaring the design-token
|
|
205
|
-
CSS variables on `:root`.
|
|
206
|
-
|
|
207
|
-
You can use the package's default as a starting point:
|
|
118
|
+
The editor pages load their own chrome (`ui-editor.css`, `ui-form-controls.css`) and the icon font. The only stylesheet you need is a `tokens.css` declaring the design-token CSS variables on `:root`.
|
|
208
119
|
|
|
209
120
|
```ts
|
|
210
121
|
import '@motion-proto/live-tokens/app/tokens.css';
|
|
@@ -212,47 +123,40 @@ import '@motion-proto/live-tokens/app/site.css'; // optional: themed h1/p/a st
|
|
|
212
123
|
import '@motion-proto/live-tokens/app/fonts.css'; // optional: Fraunces + Manrope @font-face
|
|
213
124
|
```
|
|
214
125
|
|
|
215
|
-
|
|
216
|
-
your project and edit. It stays hand-authored: the editor writes what you Adopt
|
|
217
|
-
into the sidecar `tokens.generated.css`, never back into `tokens.css`.
|
|
126
|
+
Or copy `node_modules/@motion-proto/live-tokens/src/system/styles/tokens.css` into your project and edit it. It stays hand-authored: what you Adopt lands in the sidecar `tokens.generated.css`, never back in `tokens.css`.
|
|
218
127
|
|
|
219
|
-
|
|
128
|
+
### Lower-level API
|
|
220
129
|
|
|
221
|
-
The
|
|
130
|
+
`bootLiveTokens` and `<LiveTokensRouter>` are wrappers. The individual init functions (`initCssVarSync`, `initRouter`, `initColumnsOverlay`, `initEditorStore`, `initializeTheme`), `<LiveEditorOverlay>`, `<ColumnsOverlay>`, and the editor page exports (`@motion-proto/live-tokens/editor`, `@motion-proto/live-tokens/component-editor-page`) are all exported. Use them to build a custom shell: arbitrary markup per route, a foreign matcher, or your own overlay wiring. Dynamic and gated routes do not need this; use `resolve` above, which keeps the overlay, nav rail, and page source intact.
|
|
222
131
|
|
|
223
|
-
|
|
224
|
-
// src/main.ts
|
|
225
|
-
import '@motion-proto/live-tokens/app/tokens.css';
|
|
226
|
-
import { mount } from 'svelte';
|
|
227
|
-
import App from './App.svelte';
|
|
132
|
+
## Where data lands, and how to move it
|
|
228
133
|
|
|
229
|
-
|
|
230
|
-
```
|
|
134
|
+
The plugin reads and writes under one folder, `src/live-tokens/data/`, which holds three subdirectories it owns: `colors-and-type/`, `themes/`, and `component-configs/`.
|
|
231
135
|
|
|
232
|
-
|
|
233
|
-
<!-- src/App.svelte -->
|
|
234
|
-
<script lang="ts">
|
|
235
|
-
import Editor from '@motion-proto/live-tokens/editor';
|
|
236
|
-
</script>
|
|
136
|
+
`themes/` holds one file per whole look, plus `_active.json` naming the one the editor has open and `_production.json` naming the one your site ships. `colors-and-type/` and `component-configs/{comp}/` hold each layer's `default.json` baseline, any preset you save by name, and the `_working.json` buffer for edits you have not saved into the active theme. A buffer is a delta from the open theme, so ordinary theme switching leaves none behind.
|
|
237
137
|
|
|
238
|
-
|
|
138
|
+
To move the data, create `live-tokens.config.json` at your project root:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"dataDir": "src/live-tokens/data"
|
|
143
|
+
}
|
|
239
144
|
```
|
|
240
145
|
|
|
241
|
-
|
|
242
|
-
// vite.config.ts
|
|
243
|
-
import { defineConfig } from 'vite';
|
|
244
|
-
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
|
146
|
+
All four keys are optional. `dataDir` relocates all three subfolders at once. The per-folder overrides cover unusual layouts, such as a monorepo where colors and type are shared across packages but component configs are not:
|
|
245
147
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"dataDir": "src/live-tokens/data",
|
|
151
|
+
"colorsAndTypeDir": "../shared/colors-and-type",
|
|
152
|
+
"componentConfigsDir": "src/live-tokens/data/component-configs",
|
|
153
|
+
"themesDir": "src/live-tokens/data/themes"
|
|
154
|
+
}
|
|
249
155
|
```
|
|
250
156
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
## Greenfield? Scaffold a new app
|
|
157
|
+
Resolution order, per folder: an explicit `themeFileApi(opts)` argument, then the matching key in `live-tokens.config.json`, then `<dataDir>/<sub>`. The dev server reads the file once at startup, so restart Vite to pick up changes.
|
|
254
158
|
|
|
255
|
-
|
|
159
|
+
## Scaffold a new app
|
|
256
160
|
|
|
257
161
|
```bash
|
|
258
162
|
npx @motion-proto/live-tokens create my-app
|
|
@@ -261,22 +165,20 @@ npm install
|
|
|
261
165
|
npm run dev
|
|
262
166
|
```
|
|
263
167
|
|
|
264
|
-
This generates a Svelte + Vite app that
|
|
265
|
-
|
|
266
|
-
(The older `npx degit motionproto/live-tokens` route cloned this whole repo as your app — the package's source, tests, and all. `create` gives you a thin consumer app instead.)
|
|
168
|
+
This generates a Svelte + Vite app that depends on the package, with `vite.config.ts`, `main.ts`, `App.svelte`, the `themeFileApi` plugin, and a placeholder `src/pages/Home.svelte` already wired. The token CSS is seeded from the version you scaffolded against. Open http://localhost:5173, replace `Home.svelte` with your content, and upgrade later with `npm update`.
|
|
267
169
|
|
|
268
170
|
## Recommended project layout
|
|
269
171
|
|
|
270
|
-
`create` scaffolds
|
|
172
|
+
`create` scaffolds this layout. Conforming to it by hand keeps upgrades non-destructive and projects consistent.
|
|
271
173
|
|
|
272
174
|
```
|
|
273
175
|
src/
|
|
274
|
-
main.ts # token CSS chain
|
|
176
|
+
main.ts # token CSS chain, then bootLiveTokens(App, '#app')
|
|
275
177
|
App.svelte # routes (e.g. <LiveTokensRouter {pages} />)
|
|
276
178
|
pages/ # your pages
|
|
277
179
|
styles/site.css # your themed page typography (yours to edit)
|
|
278
|
-
system/styles/tokens.css # vendored Layer-1 tokens
|
|
279
|
-
live-tokens/data/ # editor state
|
|
180
|
+
system/styles/tokens.css # vendored Layer-1 tokens, committed
|
|
181
|
+
live-tokens/data/ # editor state, committed
|
|
280
182
|
tokens.generated.css # editor output
|
|
281
183
|
themes/ # one file per whole look, plus the two pointers
|
|
282
184
|
colors-and-type/ component-configs/
|
|
@@ -284,17 +186,51 @@ vite.config.ts # svelte({ preprocess: vitePreprocess() }) + t
|
|
|
284
186
|
svelte.config.js # vitePreprocess()
|
|
285
187
|
```
|
|
286
188
|
|
|
287
|
-
Conventions that make
|
|
189
|
+
Conventions that make it work:
|
|
190
|
+
|
|
191
|
+
- **Vendor `tokens.css` into `src/` and commit it.** Point `themeFileApi({ tokensCssPath })` at that file, never at one inside `node_modules`, which `npm install` wipes.
|
|
192
|
+
- **Keep all editable state under `src/` and commit it**: `tokens.css`, `tokens.generated.css`, and everything in `live-tokens/data/`. This invariant is what makes upgrades safe, since `npm install` only touches `node_modules`, `package.json`, and the lockfile.
|
|
193
|
+
- **Nothing is backed up for you.** The dev server keeps no snapshots, so git is the safety net. Commit a theme you care about before editing over it.
|
|
194
|
+
- **Preprocess with `vitePreprocess()`** (bundled in `@sveltejs/vite-plugin-svelte`) and keep `sass` installed for the components' SCSS. No `svelte-preprocess`, no `legacy-peer-deps`.
|
|
195
|
+
- **Import only from the public surface**: `@motion-proto/live-tokens`, `/components/*`, `/vite-plugin`, `/app/*`.
|
|
196
|
+
|
|
197
|
+
## Minimal setup without the plugin
|
|
198
|
+
|
|
199
|
+
The least a consumer needs after `npm install @motion-proto/live-tokens`:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
// src/main.ts
|
|
203
|
+
import '@motion-proto/live-tokens/app/tokens.css';
|
|
204
|
+
import { mount } from 'svelte';
|
|
205
|
+
import App from './App.svelte';
|
|
206
|
+
|
|
207
|
+
mount(App, { target: document.getElementById('app')! });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```svelte
|
|
211
|
+
<!-- src/App.svelte -->
|
|
212
|
+
<script lang="ts">
|
|
213
|
+
import Editor from '@motion-proto/live-tokens/editor';
|
|
214
|
+
</script>
|
|
288
215
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
216
|
+
<Editor />
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
// vite.config.ts
|
|
221
|
+
import { defineConfig } from 'vite';
|
|
222
|
+
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
|
223
|
+
|
|
224
|
+
export default defineConfig({
|
|
225
|
+
plugins: [svelte({ preprocess: vitePreprocess() })],
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`vite build` works as-is: no `css: 'injected'` workaround, no `optimizeDeps` excludes. Add `themeFileApi` and `bootLiveTokens` from the Install section when you want edits persisted to disk.
|
|
294
230
|
|
|
295
231
|
## Consumer-authored components
|
|
296
232
|
|
|
297
|
-
The shipped components are first-party
|
|
233
|
+
The shipped components are first-party, but you can author your own and get the same live editing. Co-locate the runtime and editor files in `src/components/` or `src/system/components/`, then pass them to `bootLiveTokens`:
|
|
298
234
|
|
|
299
235
|
```ts
|
|
300
236
|
// src/main.ts
|
|
@@ -314,21 +250,30 @@ bootLiveTokens(App, '#app', {
|
|
|
314
250
|
});
|
|
315
251
|
```
|
|
316
252
|
|
|
317
|
-
|
|
253
|
+
`bootLiveTokens` calls `registerComponent` for each entry, gated on `import.meta.env.DEV` so registration tree-shakes out of production. Call `registerComponent` directly if you need finer control over timing.
|
|
318
254
|
|
|
319
|
-
The component appears
|
|
255
|
+
The component appears on `/live-tokens/components` under a **CUSTOM** group. Token rows, linked-block sharing, per-component config persistence, and reset-to-default behave exactly as they do for the built-in set. Import only from `@motion-proto/live-tokens` or `@motion-proto/live-tokens/component-editor`; never deep-import from `src/`.
|
|
320
256
|
|
|
321
|
-
##
|
|
257
|
+
## CLI
|
|
322
258
|
|
|
323
|
-
|
|
259
|
+
```bash
|
|
260
|
+
npx @motion-proto/live-tokens <command>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
| Command | What it does |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `create <dir> [--force]` | Scaffold a new Svelte + Vite app wired up with live-tokens. |
|
|
266
|
+
| `setup-claude [--force]` | Install the bundled Claude Code skills into `./.claude/skills/`. |
|
|
267
|
+
| `check-component <id>` | Validate a component's runtime, editor, and registration against the authoring contract. |
|
|
268
|
+
| `generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]` | Build a full theme from a 10-seed OKLCH brief, enforce AA contrast, write `themes/<slug>.json`, and open it. |
|
|
269
|
+
| `adjust <ops.json> [--dry-run]` | Move radius, padding, gap, and border-width aliases along their token scales. |
|
|
270
|
+
| `migrate [--check] [--write] [--tokens <path>]` | Reconcile the project with the installed package: additive `tokens.css` migrations, the pre-0.48 data-tree move, and a report on source references to the routes that moved in 0.35.0. |
|
|
324
271
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
| `live-tokens-generate-theme` | "make me a bright and cheerful color theme" | mood → OKLCH seed framework (chroma budget, per-role bands, gamut guardrails, holiday palettes); drives `npx live-tokens generate-theme`, which enforces AA contrast |
|
|
331
|
-
| `live-tokens-adjust-shape-space` | "make the buttons pill shaped", "space it out" | shape and space idioms (pill, sharper, softer, tighter, airier) → radius/padding/gap/border-width ops; drives `npx live-tokens adjust`, which moves aliases along the shipped token scales |
|
|
272
|
+
Once installed in a project, the same commands are available as `npx live-tokens <command>`.
|
|
273
|
+
|
|
274
|
+
## Claude Code skills
|
|
275
|
+
|
|
276
|
+
The package bundles five Claude Code skills. They encode the conventions this README cannot carry in full: which component fits a need, how a page is wired, what a valid theme looks like in OKLCH, and how shape and space move along the token scales. Each triggers from an ordinary request, so there are no slash commands to learn.
|
|
332
277
|
|
|
333
278
|
### Install
|
|
334
279
|
|
|
@@ -336,60 +281,92 @@ The package ships a suite of Claude Code skills that encode the project's conven
|
|
|
336
281
|
npx @motion-proto/live-tokens setup-claude
|
|
337
282
|
```
|
|
338
283
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
If you'd rather avoid the CLI, the equivalent one-liner:
|
|
284
|
+
This copies every bundled skill into `./.claude/skills/` in the current directory. Re-run it after upgrading the package to pick up new and changed skills, adding `--force` to overwrite. macOS and Linux only. The equivalent by hand:
|
|
342
285
|
|
|
343
286
|
```bash
|
|
344
287
|
mkdir -p .claude/skills && cp -R node_modules/@motion-proto/live-tokens/.claude/skills/. .claude/skills/
|
|
345
288
|
```
|
|
346
289
|
|
|
347
|
-
###
|
|
290
|
+
### `live-tokens-pick-component`
|
|
291
|
+
|
|
292
|
+
Ask "TabBar or SegmentedControl?", "how do I let someone pick one of four options?", or "what is the difference between a Callout and a Notification?".
|
|
293
|
+
|
|
294
|
+
The skill holds the catalogue grouped by job (action, input, selection, containers, messaging, display) and a decision table for each confusable family: `SegmentedControl` vs `TabBar` vs `RadioButton` vs `MenuSelect`, `Card` vs `CollapsibleSection` vs `Dialog`, `Callout` vs `Notification` vs `Tooltip` vs `Badge`, `Button` vs `IconButton`, and the on/off case. It answers the question and writes nothing. Read it before authoring anything new.
|
|
348
295
|
|
|
349
|
-
|
|
296
|
+
### `live-tokens-build-page`
|
|
297
|
+
|
|
298
|
+
Ask for a page, a route, or a screen: "build a pricing page", "add a /settings route", "put a hero at the top of Home".
|
|
299
|
+
|
|
300
|
+
The skill composes the page from shipped components, styles every value with `var(--token-*)` (no hex, no pixel literals, so editor changes repaint the page), places content on the column grid via `--columns-count`, `--columns-gutter`, and `--columns-max-width`, adds the route as a `lazy` import with a `source` so the overlay's "Page Source" button works, and imports `site.css` from the page rather than `main.ts` so page CSS stays out of the editor routes. It writes your page files and the route entry, and never touches the data tree.
|
|
301
|
+
|
|
302
|
+
### `live-tokens-generate-theme`
|
|
303
|
+
|
|
304
|
+
Ask for a look: "a dark, moody night theme", "a St Patrick's Day theme in green and gold", "warmer", "more contrast", "calmer".
|
|
305
|
+
|
|
306
|
+
The skill translates the brief into ten OKLCH seeds (Brand, Accent, Special, Canvas, Neutral, Alternate, Info, Success, Warning, Danger) plus a light or dark scheme, then runs `npx live-tokens generate-theme <brief.json>`. The CLI assembles the curves, enforces AA contrast on derived text tokens and auto-corrects where it can, writes `themes/<slug>.json`, opens it, and prints a contrast report. Exit 1 means the seeds themselves are unworkable, and each failure line names the seed to change.
|
|
307
|
+
|
|
308
|
+
Most of the skill is the judgment the generator cannot supply: a chroma budget scaled to how much screen area each palette covers, per-role lightness and hue bands for each scheme, gamut guardrails against impossible seeds, harmony modes, the optional canvas gradient, shadow weight for the canvas, and OKLCH anchors for named colors.
|
|
309
|
+
|
|
310
|
+
Scope: colors only. Fonts, gradients, and component aliases carry forward from the open theme, or from `--carry-from <name>`. `--dry-run` prints the report without writing; `--no-activate` writes without opening. Opening a theme never changes what your site ships; Adopt does. Regenerating replaces that theme's whole color state, including palette edits made in the editor since the last run.
|
|
311
|
+
|
|
312
|
+
### `live-tokens-adjust-shape-space`
|
|
313
|
+
|
|
314
|
+
Ask for shape or space: "make the buttons pill shaped", "sharper corners on the cards", "space it out", "tighter", "thinner borders".
|
|
315
|
+
|
|
316
|
+
The skill turns the phrase into ops (`kind` of `radius`, `padding`, `gap`, or `border-width`, with `shift: N` or `set: <token>`, optionally scoped to one component id), then runs `npx live-tokens adjust <ops.json>`. The CLI moves each matching alias along its ladder, reads the live config first so "a bit more" compounds, and prints every change and every skip.
|
|
317
|
+
|
|
318
|
+
It also knows where these edits go wrong: controls run out of room long before containers do, so a global compaction is one step and anything deeper is aimed at named containers; a pill needs more horizontal padding than a square-cornered control, not less; and content insets stop at `--space-4`, below which a relative "tighter" reports as clamped instead of writing.
|
|
319
|
+
|
|
320
|
+
Edits land in each affected component's `_working.json` buffer, which is what the running page reads, so save the open theme in the editor to keep them. `--dry-run` reports without writing, and the inverse op is the undo.
|
|
321
|
+
|
|
322
|
+
### `live-tokens-create-component`
|
|
323
|
+
|
|
324
|
+
Ask for something the catalogue lacks: "author a Rating component", "make my Chip component editable in the editor".
|
|
325
|
+
|
|
326
|
+
The skill covers the four-step recipe: the runtime `.svelte` file with its `:global(:root)` token block, the editor `.svelte` file exporting `allTokens` and its variant groups, the `registerComponent()` call, and the catalogue entry that keeps `live-tokens-pick-component` current. It carries the naming scheme, the token suffix vocabulary, the state model (component states such as selected and disabled are separate from interaction states such as hover), linked siblings, the public-imports rule, and the shipped `Toggle` as a worked example from runtime file to registration.
|
|
327
|
+
|
|
328
|
+
Verify the result:
|
|
350
329
|
|
|
351
330
|
```bash
|
|
352
331
|
npx @motion-proto/live-tokens check-component <id>
|
|
353
332
|
```
|
|
354
333
|
|
|
355
|
-
|
|
334
|
+
The validator checks the file layout, the `:global(:root)` block, the token-suffix vocabulary, the state-before-property rule, the no-raw-color-defaults rule, the public-imports rule, and the `registerComponent({ id })` call. Exit code 0 means the static contract is met. Use it after Claude generates a component, and as a pre-commit guard on hand-authored ones.
|
|
356
335
|
|
|
357
|
-
## How the editor ships changes to
|
|
336
|
+
## How the editor ships changes to production
|
|
358
337
|
|
|
359
|
-
1. Edit
|
|
338
|
+
1. Edit on `/live-tokens/editor`, `/live-tokens/colors`, or `/live-tokens/components`. Edits sit in the working buffer (`_working.json`). **Save** in the Theme panel captures the buffer into the open theme at `<dataDir>/themes/{name}.json`.
|
|
360
339
|
2. **Adopt** the theme. It becomes the production theme, and its variables are baked into `tokens.generated.css` next to your authored `tokens.css`. Nothing else writes that file, so trying a look never changes what you ship.
|
|
361
|
-
3. `npm run build` bundles both as plain CSS. No editor code, no JSON lookups, no dev surfaces
|
|
362
|
-
|
|
363
|
-
## File ownership — what the plugin writes
|
|
340
|
+
3. `npm run build` bundles both as plain CSS. No editor code, no JSON lookups, no dev surfaces reach production.
|
|
364
341
|
|
|
365
|
-
|
|
342
|
+
## File ownership: what the plugin writes
|
|
366
343
|
|
|
367
|
-
|
|
344
|
+
Knowing which files the plugin touches matters when you upgrade the package or work in a repo you do not want overwritten. For how a saved look stays safe across upgrades while `tokens.css` holds the building blocks, see [TOKENS.md](./TOKENS.md).
|
|
368
345
|
|
|
369
|
-
**
|
|
346
|
+
**On `npm install` or `npm update`: nothing outside `node_modules/`.** There are no install hooks. Upgrading never touches `src/live-tokens/data/` or any other file in `src/`.
|
|
370
347
|
|
|
371
|
-
|
|
372
|
-
- The CSS sidecars next to your `tokensCssPath` (`tokens.generated.css`, `fonts.css`).
|
|
348
|
+
**The plugin writes in two places only:**
|
|
373
349
|
|
|
374
|
-
|
|
350
|
+
- `src/live-tokens/data/`, configurable through `live-tokens.config.json`.
|
|
351
|
+
- The CSS sidecars next to your `tokensCssPath`: `tokens.generated.css` and `fonts.css`.
|
|
375
352
|
|
|
376
|
-
**At dev-server startup
|
|
353
|
+
**At dev-server startup it fills gaps and refreshes its own derived files, and overwrites no authored file:**
|
|
377
354
|
|
|
378
|
-
- `<dataDir>/themes/default.json
|
|
379
|
-
- `<dataDir>/themes/_active.json` and `_production.json
|
|
380
|
-
- `<dataDir>/component-configs/{comp}/default.json
|
|
381
|
-
-
|
|
355
|
+
- `<dataDir>/themes/default.json`, the derived Default theme, regenerated when the shipped colors and type or a component default changes.
|
|
356
|
+
- `<dataDir>/themes/_active.json` and `_production.json`, written only when missing, and healed when they name a theme that no longer resolves.
|
|
357
|
+
- `<dataDir>/component-configs/{comp}/default.json`, regenerated from the component's `:global(:root)` block only when the `.svelte` source is newer than the existing file. It is a build artifact of the source, so do not hand-edit it.
|
|
358
|
+
- `tokens.generated.css` beside your `tokensCssPath`, rebaked from the production theme so a fresh checkout builds against the look you shipped.
|
|
382
359
|
|
|
383
|
-
**
|
|
360
|
+
**Editor actions rewrite these:**
|
|
384
361
|
|
|
385
|
-
- `<dataDir>/colors-and-type/_working.json` and `<dataDir>/component-configs/{comp}/_working.json
|
|
386
|
-
- `<dataDir>/themes/{name}.json
|
|
387
|
-
- `<dataDir>/colors-and-type/{name}.json` and `<dataDir>/component-configs/{comp}/{name}.json
|
|
388
|
-
-
|
|
362
|
+
- `<dataDir>/colors-and-type/_working.json` and `<dataDir>/component-configs/{comp}/_working.json`, the buffers, written as you edit and cleared when a theme you open does not carry them.
|
|
363
|
+
- `<dataDir>/themes/{name}.json`, on every Save and Save As in the Theme panel.
|
|
364
|
+
- `<dataDir>/colors-and-type/{name}.json` and `<dataDir>/component-configs/{comp}/{name}.json`, only when you save a preset by name.
|
|
365
|
+
- `tokens.generated.css` and `fonts.css`, regenerated from the production theme when you Adopt.
|
|
389
366
|
|
|
390
|
-
The
|
|
367
|
+
The plugin never writes your authored `tokens.css`. It holds defaults you are free to hand-edit, and the editor's overrides land in `tokens.generated.css`, which the app imports immediately after it.
|
|
391
368
|
|
|
392
|
-
The one exception is
|
|
369
|
+
The one exception is `themeFileApi({ autoMigrate: true })`. With it enabled, the dev server applies pending additive token migrations (new token names only) to `tokens.css` at startup and writes the file, so it keeps up with the package as you upgrade. The change shows up in git for review. Breaking migrations that rename or remove tokens are never applied automatically; run `npx live-tokens migrate` for those during a deliberate upgrade. The option is off by default. See [TOKENS.md](./TOKENS.md).
|
|
393
370
|
|
|
394
371
|
## License
|
|
395
372
|
|
package/package.json
CHANGED
package/src/editor/index.ts
CHANGED
|
@@ -9,6 +9,9 @@ export type { BootLiveTokensOptions } from './bootstrap';
|
|
|
9
9
|
export { columnsVisible, toggleColumns, init as initColumnsOverlay } from './overlay/columnsOverlay';
|
|
10
10
|
export { configureEditor, storageKey } from './core/store/editorConfig';
|
|
11
11
|
export { openThemeSlug } from './core/store/editorConfigStore';
|
|
12
|
+
// Hand a palette family to a view. Deep links into the editor need these:
|
|
13
|
+
// the one-shot focus store they drive stays internal.
|
|
14
|
+
export { openPaletteInTokens, openPaletteInWheel, selectedPalette } from './core/store/paletteFocus';
|
|
12
15
|
export { init as initRouter, route, navigate, setScrollReset } from './core/routing/router';
|
|
13
16
|
export { init as initCssVarSync } from './core/cssVarSync';
|
|
14
17
|
export {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, onDestroy, tick } from 'svelte';
|
|
3
|
+
import { get } from 'svelte/store';
|
|
3
4
|
import { oklchToHexClamped, type Oklch } from '../core/palettes/oklch';
|
|
4
5
|
import { type CurveAnchor, lightnessCurveConfig, saturationCurveConfig, hueCurveConfig } from './curveEngine';
|
|
5
6
|
import ColorEditPanel from './ColorEditPanel.svelte';
|
|
@@ -114,7 +115,14 @@
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
let showDerived = $state(false);
|
|
117
|
-
|
|
118
|
+
// A deep link sets the focus before this mounts, so the controls are open on
|
|
119
|
+
// the first frame: UIReveal's `{#if}` never toggles and there is no reveal to
|
|
120
|
+
// sit through. Landing mid-animation reads as a page still loading.
|
|
121
|
+
// svelte-ignore state_referenced_locally
|
|
122
|
+
// The mount-time value is the whole point: a family's label is fixed for the
|
|
123
|
+
// life of the instance (PALETTE_SPECS keys the `{#each}` by it).
|
|
124
|
+
const focusedOnMount = get(pendingPaletteFocus) === label;
|
|
125
|
+
let paletteEditorOpen = $state(focusedOnMount);
|
|
118
126
|
let rootEl: HTMLElement | undefined = $state();
|
|
119
127
|
|
|
120
128
|
// Arriving from the Colors view's Edit button: open this family's controls and
|
|
@@ -123,7 +131,11 @@
|
|
|
123
131
|
if ($pendingPaletteFocus !== label) return;
|
|
124
132
|
pendingPaletteFocus.set(null);
|
|
125
133
|
paletteEditorOpen = true;
|
|
126
|
-
|
|
134
|
+
// Nothing expanded when we opened on mount, so there is no motion for a
|
|
135
|
+
// smooth scroll to match — jump, and the first frame is the final state.
|
|
136
|
+
tick().then(() =>
|
|
137
|
+
rootEl?.scrollIntoView({ behavior: focusedOnMount ? 'auto' : 'smooth', block: 'start' }),
|
|
138
|
+
);
|
|
127
139
|
});
|
|
128
140
|
|
|
129
141
|
function setLightnessCurve(a: CurveAnchor[]) { edit('lightnessCurve', a); }
|
|
@@ -536,6 +548,18 @@
|
|
|
536
548
|
onStartEdit={startBaseEdit}
|
|
537
549
|
onCopyBaseHex={copyHex}
|
|
538
550
|
>
|
|
551
|
+
{#snippet setting()}
|
|
552
|
+
<!-- The family's own swatch and name sit alongside, so the label drops
|
|
553
|
+
its subject; the title carries the full sentence. -->
|
|
554
|
+
<span class="base-anchor-toggle" title="Base color must appear in palette">
|
|
555
|
+
<Toggle
|
|
556
|
+
checked={anchorToBase}
|
|
557
|
+
onchange={(v) => setAnchorToBase(v ?? !anchorToBase)}
|
|
558
|
+
label="Must appear in palette"
|
|
559
|
+
/>
|
|
560
|
+
</span>
|
|
561
|
+
{/snippet}
|
|
562
|
+
|
|
539
563
|
{#snippet actions()}
|
|
540
564
|
<PaletteJumpButton family={label} {displayLabel} target="wheel" />
|
|
541
565
|
<UIPillButton size="compact" variant="outline" onclick={clearPaletteOverrides}>Clear Overrides</UIPillButton>
|
|
@@ -563,18 +587,7 @@
|
|
|
563
587
|
onCancel={cancelEdit}
|
|
564
588
|
onRemoveOverride={() => {}}
|
|
565
589
|
onSliderStart={() => beginSliderGesture(`edit ${label} base`)}
|
|
566
|
-
|
|
567
|
-
{#snippet actions()}
|
|
568
|
-
<div class="base-anchor-toggle">
|
|
569
|
-
<Toggle
|
|
570
|
-
checked={anchorToBase}
|
|
571
|
-
onchange={(v) => setAnchorToBase(v ?? !anchorToBase)}
|
|
572
|
-
label="Base color must appear in palette"
|
|
573
|
-
labelFirst
|
|
574
|
-
/>
|
|
575
|
-
</div>
|
|
576
|
-
{/snippet}
|
|
577
|
-
</ColorEditPanel>
|
|
590
|
+
/>
|
|
578
591
|
</div>
|
|
579
592
|
</div>
|
|
580
593
|
</UIReveal>
|
|
@@ -988,8 +1001,10 @@
|
|
|
988
1001
|
min-width: 0;
|
|
989
1002
|
}
|
|
990
1003
|
|
|
1004
|
+
/* A box of its own so the title has something to hover; placement in the
|
|
1005
|
+
band belongs to PaletteBase. */
|
|
991
1006
|
.base-anchor-toggle {
|
|
992
|
-
|
|
1007
|
+
display: flex;
|
|
993
1008
|
}
|
|
994
1009
|
|
|
995
1010
|
.curve-grid-span {
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
onStartEdit: () => void;
|
|
19
19
|
onCopyBaseHex: (key: string, hex: string, event?: MouseEvent) => void;
|
|
20
20
|
actions?: Snippet;
|
|
21
|
+
/** A family-level setting, shown beside the name while this family's
|
|
22
|
+
* editor is open. Collapsed families keep the band to identity + actions. */
|
|
23
|
+
setting?: Snippet;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
let {
|
|
@@ -29,10 +32,15 @@
|
|
|
29
32
|
copiedKey,
|
|
30
33
|
onStartEdit,
|
|
31
34
|
onCopyBaseHex,
|
|
32
|
-
actions
|
|
35
|
+
actions,
|
|
36
|
+
setting
|
|
33
37
|
}: Props = $props();
|
|
34
38
|
|
|
35
39
|
let baseHex = $derived(oklchToHexClamped(baseColor.l, baseColor.c, baseColor.h));
|
|
40
|
+
|
|
41
|
+
// `pinnedOpen || isEditingBase` is exactly when the base panel shows: editing
|
|
42
|
+
// the base implies both a panel and a colour to put in it.
|
|
43
|
+
let showSetting = $derived(!!setting && (pinnedOpen || isEditingBase));
|
|
36
44
|
</script>
|
|
37
45
|
|
|
38
46
|
<div class="editor-top">
|
|
@@ -64,6 +72,10 @@
|
|
|
64
72
|
aria-hidden="true"
|
|
65
73
|
></i>
|
|
66
74
|
</button>
|
|
75
|
+
|
|
76
|
+
{#if showSetting}
|
|
77
|
+
<div class="header-setting">{@render setting?.()}</div>
|
|
78
|
+
{/if}
|
|
67
79
|
</div>
|
|
68
80
|
</div>
|
|
69
81
|
|
|
@@ -100,12 +112,28 @@
|
|
|
100
112
|
.header-actions {
|
|
101
113
|
display: flex;
|
|
102
114
|
align-items: center;
|
|
115
|
+
flex-wrap: wrap;
|
|
116
|
+
justify-content: flex-end;
|
|
103
117
|
gap: var(--ui-space-8);
|
|
104
118
|
}
|
|
105
119
|
|
|
120
|
+
/* Third line of the identity, under the hex: name, value, constraint all
|
|
121
|
+
describe the family and share its left rail. Keeping it out of the band's
|
|
122
|
+
row is what leaves the buttons a column of their own at every width. */
|
|
123
|
+
.header-setting {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
margin-top: var(--ui-space-4);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* The setting makes the identity column taller than the swatch, and the row
|
|
130
|
+
stretches its items; left alone the chip would grow into a rectangle. It is
|
|
131
|
+
a colour sample, so it stays square and hangs from the top of the column. */
|
|
106
132
|
.header-swatch {
|
|
107
133
|
width: 4.5rem;
|
|
108
134
|
min-height: 4.5rem;
|
|
135
|
+
aspect-ratio: 1;
|
|
136
|
+
align-self: flex-start;
|
|
109
137
|
border-radius: var(--ui-radius-md);
|
|
110
138
|
border: 2px solid var(--ui-border);
|
|
111
139
|
flex-shrink: 0;
|