@motion-proto/live-tokens 0.43.0 → 0.45.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 +91 -0
- package/package.json +6 -1
- package/src/editor/core/palettes/paletteDerivation.ts +1 -2
- package/src/editor/core/routing/ownedRoutes.ts +1 -0
- package/src/editor/overlay/LiveEditorOverlay.svelte +9 -4
- package/src/editor/overlay/LiveTokensRouter.svelte +29 -17
- package/src/editor/pages/ColorsPage.svelte +117 -0
- package/src/editor/pages/ColorsPage.svelte.d.ts +2 -0
- package/src/editor/ui/EditorViewSwitcher.svelte +12 -8
- package/src/editor/ui/UIPillButton.svelte +15 -0
- package/src/editor/ui/colors/ColorStory.svelte +51 -159
- package/src/editor/ui/colors/ColorWheel.svelte +25 -7
- package/src/editor/ui/colors/ColorsTab.svelte +31 -38
- package/src/editor/ui/colors/HarmonyAxesList.svelte +32 -4
- package/src/editor/ui/colors/paletteBaseColor.ts +9 -19
- package/src/editor/core/palettes/recommendText.ts +0 -216
- package/src/editor/core/palettes/solveTextContrast.ts +0 -251
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.45.0 — The Color Story shows the tokens you actually have
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The Color Story's text steps read the live tokens.** The primary, secondary
|
|
8
|
+
and tertiary row rendered the values the *Even neutral steps* pill would have
|
|
9
|
+
written, not the current ones, so the same step could report two different
|
|
10
|
+
ratios in the same frame (the Canvas header said 17.9:1 while the row said
|
|
11
|
+
14.7:1). Each step now resolves `--text-primary` / `--text-secondary` /
|
|
12
|
+
`--text-tertiary` through the same path the functional chips and tone pills
|
|
13
|
+
already used, renders in its own token, and reports its real contrast against
|
|
14
|
+
the canvas.
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
|
|
18
|
+
- **The Color Story's action pills.** *Raise all text to AA*, *Even neutral
|
|
19
|
+
steps*, and the *Anchor at pure white/black* toggle, added in 0.44.0, are
|
|
20
|
+
gone. The story is a proportional preview, and authoring text steps from it
|
|
21
|
+
put a second palette editor next to the real one. Text steps are edited in
|
|
22
|
+
the Tokens view.
|
|
23
|
+
|
|
24
|
+
- **The text-contrast solvers.** `solveTextContrast.ts` and `recommendText.ts`
|
|
25
|
+
(plus `applySolvedTextCurves` and `applySuggestedNeutralText`) backed only
|
|
26
|
+
those pills and are deleted. Both were internal to `src/editor/core/palettes/`
|
|
27
|
+
and never reachable through the package exports, so no consumer import
|
|
28
|
+
changes. `BW_GUARD_MIN_L` / `BW_GUARD_MAX_L` are unaffected: they live in
|
|
29
|
+
`paletteDerivation.ts` and still guard derived inverted text.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **The story's prose lines and its unlabeled header ratio are gone.** The three
|
|
34
|
+
sentences under Canvas ("The dominant surface. Body text sits here." and its
|
|
35
|
+
two siblings) said nothing the labelled steps don't, and the ratio beside the
|
|
36
|
+
Canvas heading was primary's, measured but never named. The stacked steps
|
|
37
|
+
carry all three ratios now.
|
|
38
|
+
|
|
39
|
+
## 0.44.0 — Every text step derives to body contrast
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **A full-page Colors route.** `/live-tokens/colors` renders the Colors editor
|
|
44
|
+
as its own page, alongside the existing editor and components routes, and is
|
|
45
|
+
auto-appended to the dev nav rail. New `@motion-proto/live-tokens/colors`
|
|
46
|
+
export for consumers mounting it themselves, and a new `colors` key on
|
|
47
|
+
`editorRoutes` to relocate it or pass `false` to disable it. Entering the
|
|
48
|
+
route flips the overlay off its Colors view so the two surfaces never stack.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **Selection reads across every Colors surface.** Picking a family used to
|
|
53
|
+
highlight only its swatch. Its harmony-axis row and chip now light up, and on
|
|
54
|
+
the wheel its rail thickens to full contrast while its axis numeral goes
|
|
55
|
+
white and the others step back. Axis chips are clickable to select.
|
|
56
|
+
|
|
57
|
+
- **The Color Story is a readout, not a control panel.** Its "Use suggested"
|
|
58
|
+
button and "Use black and white" checkbox moved out to three pills above the
|
|
59
|
+
story: *Raise all text to AA* (every family, contrast-first), *Even neutral
|
|
60
|
+
steps* (Neutral only, hierarchy-first), and an *Anchor at pure white/black*
|
|
61
|
+
toggle. The old labels named neither the scope nor the effect, which are what
|
|
62
|
+
separate the two actions; each pill now carries the full explanation on hover.
|
|
63
|
+
The AA-reach line is gone.
|
|
64
|
+
|
|
65
|
+
- **Color swatches hold their position.** Both swatch rows were sorted by
|
|
66
|
+
luminance, so editing a color's lightness reshuffled the row under the
|
|
67
|
+
pointer and stuttered mid-drag. They render in declaration order now.
|
|
68
|
+
|
|
69
|
+
- **`--text-muted` now derives to 4.5:1, not 3:1.** The neutral text ramp
|
|
70
|
+
guaranteed muted at the WCAG AA floor for *large* text, one step below
|
|
71
|
+
`--text-tertiary`, on the assumption it would only ever be set at 24px (or
|
|
72
|
+
18.66px bold). Nothing in the token's name or its position in a ramp of text
|
|
73
|
+
colors carried that condition, so consumers reached for it as a quiet body
|
|
74
|
+
color and shipped text below AA — in the first site audited, all eleven uses
|
|
75
|
+
were 13–16px, every one of them failing. `--text-muted` joins primary,
|
|
76
|
+
secondary and tertiary on the 4.5:1 body floor, which leaves `--text-disabled`
|
|
77
|
+
as the ramp's only sub-AA step, and WCAG 1.4.3 exempts disabled controls.
|
|
78
|
+
`AA_LARGE` is unchanged and still floors the graded chromatic steps.
|
|
79
|
+
|
|
80
|
+
Derived palettes get a lighter (dark scheme) or darker (light scheme) muted
|
|
81
|
+
step the next time text contrast is solved. **A theme already on disk keeps
|
|
82
|
+
its stored curve** — its `--text-muted` does not move until the palette is
|
|
83
|
+
re-solved, so a consuming site that has shipped muted on small text must
|
|
84
|
+
either re-derive or restate those colors itself.
|
|
85
|
+
|
|
86
|
+
Expect muted and tertiary to derive to nearly the same color: both target the
|
|
87
|
+
default surface, so both settle just past the same floor (on the reference
|
|
88
|
+
dark palette, tertiary `#94999c` 4.58:1 and muted `#96989a` 4.55:1). That is
|
|
89
|
+
arithmetic, not a rough edge — a step quieter than tertiary cannot also clear
|
|
90
|
+
tertiary's floor. Treat `--text-muted` as an alias of `--text-tertiary` for
|
|
91
|
+
now; reach for `--text-disabled` when a genuinely quieter, non-AA step is
|
|
92
|
+
wanted, and expect a decision on deprecating one of the two.
|
|
93
|
+
|
|
3
94
|
## 0.43.0 — The editor opens on Tokens
|
|
4
95
|
|
|
5
96
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motion-proto/live-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
|
|
6
6
|
"keywords": [
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
"svelte": "./src/editor/pages/ComponentEditorPage.svelte",
|
|
66
66
|
"default": "./src/editor/pages/ComponentEditorPage.svelte"
|
|
67
67
|
},
|
|
68
|
+
"./colors": {
|
|
69
|
+
"types": "./src/editor/pages/ColorsPage.svelte.d.ts",
|
|
70
|
+
"svelte": "./src/editor/pages/ColorsPage.svelte",
|
|
71
|
+
"default": "./src/editor/pages/ColorsPage.svelte"
|
|
72
|
+
},
|
|
68
73
|
"./docs": {
|
|
69
74
|
"types": "./src/editor/docs/Docs.svelte.d.ts",
|
|
70
75
|
"svelte": "./src/editor/docs/Docs.svelte",
|
|
@@ -104,8 +104,7 @@ export const SCALES: readonly Scale[] = [
|
|
|
104
104
|
];
|
|
105
105
|
|
|
106
106
|
/** Guard band for derived near-black/near-white text: values stay between
|
|
107
|
-
* ≈ #111 and ≈ #e8e8e8
|
|
108
|
-
* beats the guard (see `recommendText.ts`). */
|
|
107
|
+
* ≈ #111 and ≈ #e8e8e8, the range most designers prefer over pure extremes. */
|
|
109
108
|
export const BW_GUARD_MIN_L = 0.17;
|
|
110
109
|
export const BW_GUARD_MAX_L = 0.93;
|
|
111
110
|
|
|
@@ -8,4 +8,5 @@
|
|
|
8
8
|
// <LiveTokensRouter>; these are only the defaults.
|
|
9
9
|
export const DEFAULT_EDITOR_PATH = '/live-tokens/editor';
|
|
10
10
|
export const DEFAULT_COMPONENTS_PATH = '/live-tokens/components';
|
|
11
|
+
export const DEFAULT_COLORS_PATH = '/live-tokens/colors';
|
|
11
12
|
export const DEFAULT_DOCS_PATH = '/live-tokens/docs';
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { fade } from 'svelte/transition';
|
|
16
16
|
import { cubicInOut } from 'svelte/easing';
|
|
17
17
|
import { route, navigate } from '../core/routing/router';
|
|
18
|
-
import { DEFAULT_EDITOR_PATH, DEFAULT_COMPONENTS_PATH } from '../core/routing/ownedRoutes';
|
|
18
|
+
import { DEFAULT_EDITOR_PATH, DEFAULT_COMPONENTS_PATH, DEFAULT_COLORS_PATH } from '../core/routing/ownedRoutes';
|
|
19
19
|
import { editorView } from '../core/store/editorViewStore';
|
|
20
20
|
import { columnsVisible, toggleColumns } from './columnsOverlay';
|
|
21
21
|
import { storageKey } from '../core/store/editorConfig';
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
open?: boolean | undefined;
|
|
30
30
|
editorPath?: string;
|
|
31
31
|
componentsPath?: string;
|
|
32
|
+
colorsPath?: string;
|
|
32
33
|
navLinks?: NavLink[];
|
|
33
34
|
pageSources?: Record<string, string>;
|
|
34
35
|
hidePageSourceOn?: string[];
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
open = $bindable(undefined),
|
|
40
41
|
editorPath = DEFAULT_EDITOR_PATH,
|
|
41
42
|
componentsPath = DEFAULT_COMPONENTS_PATH,
|
|
43
|
+
colorsPath = DEFAULT_COLORS_PATH,
|
|
42
44
|
navLinks = [],
|
|
43
45
|
pageSources = {},
|
|
44
46
|
hidePageSourceOn = [],
|
|
@@ -65,9 +67,9 @@
|
|
|
65
67
|
overlayOpen.set(!!open);
|
|
66
68
|
});
|
|
67
69
|
|
|
68
|
-
// The components
|
|
69
|
-
//
|
|
70
|
-
// tokens so the two surfaces don't stack. Fires only on route
|
|
70
|
+
// The components and colors routes render the same surfaces as the overlay's
|
|
71
|
+
// components and colors views. Pair them: on entering one, flip the overlay
|
|
72
|
+
// to tokens so the two surfaces don't stack. Fires only on route
|
|
71
73
|
// change, not on every editorView change — otherwise cross-window storage
|
|
72
74
|
// sync re-triggers the rule, which writes editorView, which fires another
|
|
73
75
|
// storage event, which fires the rule again. The result is heavy re-render
|
|
@@ -81,6 +83,9 @@
|
|
|
81
83
|
if (r === componentsPath) {
|
|
82
84
|
editorView.update((v) => (v === 'components' ? 'tokens' : v));
|
|
83
85
|
}
|
|
86
|
+
if (r === colorsPath) {
|
|
87
|
+
editorView.update((v) => (v === 'colors' ? 'tokens' : v));
|
|
88
|
+
}
|
|
84
89
|
});
|
|
85
90
|
|
|
86
91
|
// Editor route has its own chrome — hide overlay there.
|
|
@@ -30,20 +30,22 @@
|
|
|
30
30
|
/**
|
|
31
31
|
* Override the package's dev-only routes, which default to the reserved
|
|
32
32
|
* `/live-tokens/*` namespace (`/live-tokens/editor`, `/live-tokens/components`,
|
|
33
|
-
* `/live-tokens/docs`) so they never collide with
|
|
34
|
-
* string to relocate a route; pass `false` to disable
|
|
35
|
-
* dispatch and, for `components`/`docs`, no
|
|
33
|
+
* `/live-tokens/colors`, `/live-tokens/docs`) so they never collide with
|
|
34
|
+
* consumer pages. Pass a string to relocate a route; pass `false` to disable
|
|
35
|
+
* it entirely (no dispatch and, for `components`/`colors`/`docs`, no
|
|
36
|
+
* auto-injected nav-rail entry).
|
|
36
37
|
*/
|
|
37
38
|
export interface EditorRouteOverrides {
|
|
38
39
|
editor?: string | false;
|
|
39
40
|
components?: string | false;
|
|
41
|
+
colors?: string | false;
|
|
40
42
|
docs?: string | false;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* Resolve a path to the single entry that renders it. Precedence, after the
|
|
45
|
-
* package-owned `/live-tokens/*` routes (editor, components, docs)
|
|
46
|
-
* already been matched by the component:
|
|
47
|
+
* package-owned `/live-tokens/*` routes (editor, components, colors, docs)
|
|
48
|
+
* have already been matched by the component:
|
|
47
49
|
*
|
|
48
50
|
* 1. `pages[route]` — exact static match.
|
|
49
51
|
* 2. `resolve(route)` — consumer code, where params / prefixes / gating
|
|
@@ -68,7 +70,7 @@
|
|
|
68
70
|
import LiveEditorOverlay from './LiveEditorOverlay.svelte';
|
|
69
71
|
import ColumnsOverlay from './ColumnsOverlay.svelte';
|
|
70
72
|
import { route, navigate } from '../core/routing/router';
|
|
71
|
-
import { DEFAULT_EDITOR_PATH, DEFAULT_COMPONENTS_PATH, DEFAULT_DOCS_PATH } from '../core/routing/ownedRoutes';
|
|
73
|
+
import { DEFAULT_EDITOR_PATH, DEFAULT_COMPONENTS_PATH, DEFAULT_COLORS_PATH, DEFAULT_DOCS_PATH } from '../core/routing/ownedRoutes';
|
|
72
74
|
|
|
73
75
|
interface Props {
|
|
74
76
|
pages: Record<string, RouteEntry>;
|
|
@@ -86,32 +88,38 @@
|
|
|
86
88
|
|
|
87
89
|
let editorEnabled = $derived(editorRoutes.editor !== false);
|
|
88
90
|
let componentsEnabled = $derived(editorRoutes.components !== false);
|
|
91
|
+
let colorsEnabled = $derived(editorRoutes.colors !== false);
|
|
89
92
|
let docsEnabled = $derived(editorRoutes.docs !== false);
|
|
90
93
|
let editorPath = $derived(typeof editorRoutes.editor === 'string' ? editorRoutes.editor : DEFAULT_EDITOR_PATH);
|
|
91
94
|
let componentsPath = $derived(typeof editorRoutes.components === 'string' ? editorRoutes.components : DEFAULT_COMPONENTS_PATH);
|
|
95
|
+
let colorsPath = $derived(typeof editorRoutes.colors === 'string' ? editorRoutes.colors : DEFAULT_COLORS_PATH);
|
|
92
96
|
let docsPath = $derived(typeof editorRoutes.docs === 'string' ? editorRoutes.docs : DEFAULT_DOCS_PATH);
|
|
93
97
|
|
|
94
98
|
const isDev = import.meta.env.DEV;
|
|
95
99
|
let isEditor = $derived(isDev && editorEnabled && $route === editorPath);
|
|
96
100
|
let isComponentEditor = $derived(isDev && componentsEnabled && $route === componentsPath);
|
|
101
|
+
let isColors = $derived(isDev && colorsEnabled && $route === colorsPath);
|
|
97
102
|
let isDocs = $derived(isDev && docsEnabled && $route === docsPath);
|
|
98
103
|
|
|
99
104
|
// The single entry that renders the current route. Owned routes are handled
|
|
100
|
-
// by the isEditor/isComponentEditor/isDocs branches, so they resolve
|
|
101
|
-
// here; everything else flows through resolveRoute and drives both
|
|
102
|
-
// (component + props) and page-source from this one value.
|
|
105
|
+
// by the isEditor/isComponentEditor/isColors/isDocs branches, so they resolve
|
|
106
|
+
// to null here; everything else flows through resolveRoute and drives both
|
|
107
|
+
// dispatch (component + props) and page-source from this one value.
|
|
103
108
|
let resolvedEntry = $derived(
|
|
104
|
-
isEditor || isComponentEditor || isDocs ? null : resolveRoute(pages, resolve, $route),
|
|
109
|
+
isEditor || isComponentEditor || isColors || isDocs ? null : resolveRoute(pages, resolve, $route),
|
|
105
110
|
);
|
|
106
111
|
|
|
107
112
|
// Pages with a label show up in the nav rail, in declaration order. In dev,
|
|
108
|
-
// the components-editor and docs routes are auto-appended so they're
|
|
109
|
-
// from every page. Docs ship from the package, so consumers
|
|
110
|
-
// guide in the editor while building without vendoring a copy.
|
|
113
|
+
// the colors, components-editor, and docs routes are auto-appended so they're
|
|
114
|
+
// reachable from every page. Docs ship from the package, so consumers
|
|
115
|
+
// reference the guide in the editor while building without vendoring a copy.
|
|
111
116
|
let navLinks = $derived([
|
|
112
117
|
...Object.entries(pages)
|
|
113
118
|
.filter(([, e]) => !!e.label)
|
|
114
119
|
.map(([path, e]) => ({ path, label: e.label!, icon: e.icon ?? '' })),
|
|
120
|
+
...(isDev && colorsEnabled
|
|
121
|
+
? [{ path: colorsPath, label: 'Colors', icon: 'fa-palette' }]
|
|
122
|
+
: []),
|
|
115
123
|
...(isDev && componentsEnabled
|
|
116
124
|
? [{ path: componentsPath, label: 'Components', icon: 'fa-puzzle-piece' }]
|
|
117
125
|
: []),
|
|
@@ -132,14 +140,15 @@
|
|
|
132
140
|
...(resolvedEntry?.source ? { [$route]: resolvedEntry.source } : {}),
|
|
133
141
|
});
|
|
134
142
|
|
|
135
|
-
// The package-owned components and docs routes hide the page-source
|
|
136
|
-
// (no consumer source file backs them).
|
|
143
|
+
// The package-owned components, colors, and docs routes hide the page-source
|
|
144
|
+
// button (no consumer source file backs them).
|
|
137
145
|
let hidePageSourceOn = $derived([
|
|
138
146
|
...Object.entries(pages)
|
|
139
147
|
.filter(([, e]) => e.hidePageSource)
|
|
140
148
|
.map(([path]) => path),
|
|
141
149
|
...(resolvedEntry?.hidePageSource ? [$route] : []),
|
|
142
150
|
...(componentsEnabled ? [componentsPath] : []),
|
|
151
|
+
...(colorsEnabled ? [colorsPath] : []),
|
|
143
152
|
...(docsEnabled ? [docsPath] : []),
|
|
144
153
|
]);
|
|
145
154
|
|
|
@@ -150,6 +159,7 @@
|
|
|
150
159
|
let pagePromise = $derived.by(() => {
|
|
151
160
|
if (isEditor) return import('../pages/Editor.svelte');
|
|
152
161
|
if (isComponentEditor) return import('../pages/ComponentEditorPage.svelte');
|
|
162
|
+
if (isColors) return import('../pages/ColorsPage.svelte');
|
|
153
163
|
if (isDocs) return import('../docs/Docs.svelte');
|
|
154
164
|
const entry = resolvedEntry;
|
|
155
165
|
if (!entry) return Promise.resolve({ default: null as unknown as Component<any, any, any> });
|
|
@@ -177,9 +187,10 @@
|
|
|
177
187
|
class="lt-app"
|
|
178
188
|
class:is-editor={isEditor}
|
|
179
189
|
class:is-component-editor={isComponentEditor}
|
|
190
|
+
class:is-colors={isColors}
|
|
180
191
|
onclick={handleClick}
|
|
181
192
|
>
|
|
182
|
-
<LiveEditorOverlay {navLinks} {pageSources} {hidePageSourceOn} {editorPath} {componentsPath} />
|
|
193
|
+
<LiveEditorOverlay {navLinks} {pageSources} {hidePageSourceOn} {editorPath} {componentsPath} {colorsPath} />
|
|
183
194
|
<ColumnsOverlay />
|
|
184
195
|
|
|
185
196
|
{#await pagePromise then m}
|
|
@@ -210,7 +221,8 @@
|
|
|
210
221
|
padding-right: var(--lt-overlay-scroll-pad, 0px);
|
|
211
222
|
}
|
|
212
223
|
|
|
213
|
-
.lt-app.is-editor
|
|
224
|
+
.lt-app.is-editor,
|
|
225
|
+
.lt-app.is-colors {
|
|
214
226
|
padding-bottom: 0;
|
|
215
227
|
background: black;
|
|
216
228
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import ColorsTab from '../ui/colors/ColorsTab.svelte';
|
|
4
|
+
import UICopyPopover from '../ui/UICopyPopover.svelte';
|
|
5
|
+
import { installEditorKeybindings } from '../core/store/editorKeybindings';
|
|
6
|
+
import { initializeEditorStore } from '../core/store/editorStore';
|
|
7
|
+
import { storageKey } from '../core/store/editorConfig';
|
|
8
|
+
import { DEFAULT_COLORS_PATH } from '../core/routing/ownedRoutes';
|
|
9
|
+
// Editor chrome + form controls + icon font must be JS imports (not @import
|
|
10
|
+
// inside the style block) so Vite resolves them via the module graph
|
|
11
|
+
// regardless of how the consumer compiles Svelte CSS (external ?lang.css vs
|
|
12
|
+
// injected); otherwise the @import URLs leak to the browser and 404 against
|
|
13
|
+
// the consumer's root.
|
|
14
|
+
import '../styles/ui-editor.css';
|
|
15
|
+
import '../styles/ui-form-controls.css';
|
|
16
|
+
import '@fortawesome/fontawesome-free/css/all.min.css';
|
|
17
|
+
|
|
18
|
+
// Where "Back to site" sends the user. Prefer the previous non-colors entry
|
|
19
|
+
// from session history; fall back to home.
|
|
20
|
+
const backHref = pickBackHref();
|
|
21
|
+
|
|
22
|
+
function pickBackHref(): string {
|
|
23
|
+
try {
|
|
24
|
+
const prev = sessionStorage.getItem(storageKey('prev-route'));
|
|
25
|
+
if (prev && prev !== DEFAULT_COLORS_PATH) return prev;
|
|
26
|
+
} catch {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
return '/';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onMount(() => {
|
|
33
|
+
initializeEditorStore();
|
|
34
|
+
return installEditorKeybindings();
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<!-- .editor-page scopes the --ui-* token definitions from ui-editor.css. -->
|
|
39
|
+
<div class="editor-page colors-page">
|
|
40
|
+
<div class="page-bar">
|
|
41
|
+
<div class="bar-left">
|
|
42
|
+
<a href={backHref} class="back-link">
|
|
43
|
+
<i class="fas fa-arrow-left"></i>
|
|
44
|
+
<span>Back to site</span>
|
|
45
|
+
</a>
|
|
46
|
+
<span class="page-label">Colors</span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<main class="content">
|
|
51
|
+
<ColorsTab />
|
|
52
|
+
</main>
|
|
53
|
+
<UICopyPopover />
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
.colors-page {
|
|
58
|
+
min-height: 100vh;
|
|
59
|
+
background: black;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.page-bar {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: var(--ui-space-16);
|
|
66
|
+
padding: var(--ui-space-10) var(--ui-space-16);
|
|
67
|
+
background: black;
|
|
68
|
+
border-bottom: 1px solid var(--ui-border-low);
|
|
69
|
+
min-height: 52px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.bar-left {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: var(--ui-space-16);
|
|
76
|
+
min-width: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.back-link {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: var(--ui-space-6);
|
|
83
|
+
color: var(--ui-text-tertiary);
|
|
84
|
+
text-decoration: none;
|
|
85
|
+
font-size: var(--ui-font-size-md);
|
|
86
|
+
transition: color var(--ui-transition-fast);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.back-link:hover {
|
|
90
|
+
color: var(--ui-text-primary);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.page-label {
|
|
94
|
+
font-size: var(--ui-font-size-md);
|
|
95
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
96
|
+
color: var(--ui-text-secondary);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.content {
|
|
100
|
+
max-width: 1680px;
|
|
101
|
+
margin: 0 auto;
|
|
102
|
+
padding: var(--ui-space-24) var(--ui-space-32);
|
|
103
|
+
isolation: isolate;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@media (max-width: 1280px) {
|
|
107
|
+
.content {
|
|
108
|
+
padding: var(--ui-space-20) var(--ui-space-20);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@media (max-width: 1024px) {
|
|
113
|
+
.content {
|
|
114
|
+
padding: var(--ui-space-16) var(--ui-space-12);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { editorView, type EditorView } from '../core/store/editorViewStore';
|
|
3
3
|
import { parentRoute } from '../core/routing/parentRouteStore';
|
|
4
|
-
import { DEFAULT_COMPONENTS_PATH } from '../core/routing/ownedRoutes';
|
|
4
|
+
import { DEFAULT_COMPONENTS_PATH, DEFAULT_COLORS_PATH } from '../core/routing/ownedRoutes';
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
condensed?: boolean;
|
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
|
|
10
10
|
let { condensed = false }: Props = $props();
|
|
11
11
|
|
|
12
|
-
// On the components route the host page
|
|
13
|
-
// the overlay's
|
|
14
|
-
// switch. The switcher renders inside the editor iframe, so we read the
|
|
15
|
-
// *parent* route, not this iframe's own. Compares the default
|
|
16
|
-
// editorRoutes
|
|
17
|
-
//
|
|
12
|
+
// On the components or colors route the host page already renders that
|
|
13
|
+
// surface — the overlay's matching view would just stack on top, so disable
|
|
14
|
+
// the switch. The switcher renders inside the editor iframe, so we read the
|
|
15
|
+
// *parent* route, not this iframe's own. Compares the default paths:
|
|
16
|
+
// editorRoutes relocation isn't plumbed across the iframe, so a relocated
|
|
17
|
+
// route won't disable the switch.
|
|
18
18
|
let componentsDisabled = $derived($parentRoute === DEFAULT_COMPONENTS_PATH);
|
|
19
|
+
let colorsDisabled = $derived($parentRoute === DEFAULT_COLORS_PATH);
|
|
19
20
|
|
|
20
21
|
// Editing flow order: Tokens → Colors → Components. The condensed rail cycles
|
|
21
|
-
// through these;
|
|
22
|
+
// through these; a view is skipped while it's disabled (already on that
|
|
22
23
|
// page) so the cycle never lands on a dead view.
|
|
23
24
|
const CYCLE: readonly EditorView[] = ['tokens', 'colors', 'components'];
|
|
24
25
|
const ICONS: Record<EditorView, string> = {
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
i = (i + 1) % CYCLE.length;
|
|
44
45
|
const next = CYCLE[i];
|
|
45
46
|
if (next === 'components' && componentsDisabled) continue;
|
|
47
|
+
if (next === 'colors' && colorsDisabled) continue;
|
|
46
48
|
return next;
|
|
47
49
|
}
|
|
48
50
|
return current;
|
|
@@ -81,6 +83,8 @@
|
|
|
81
83
|
class="seg-btn"
|
|
82
84
|
class:active={$editorView === 'colors'}
|
|
83
85
|
aria-selected={$editorView === 'colors'}
|
|
86
|
+
disabled={colorsDisabled}
|
|
87
|
+
title={colorsDisabled ? 'Already viewing the Colors page' : undefined}
|
|
84
88
|
onclick={() => set('colors')}
|
|
85
89
|
>
|
|
86
90
|
<span class="radio" aria-hidden="true"></span>
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
title?: string;
|
|
12
12
|
type?: 'button' | 'submit' | 'reset';
|
|
13
|
+
/** Marks the pill a toggle; true also lights it as on. */
|
|
14
|
+
ariaPressed?: boolean;
|
|
13
15
|
onclick?: (e: MouseEvent) => void;
|
|
14
16
|
children?: Snippet;
|
|
15
17
|
}
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
disabled = false,
|
|
24
26
|
title,
|
|
25
27
|
type = 'button',
|
|
28
|
+
ariaPressed,
|
|
26
29
|
onclick,
|
|
27
30
|
children,
|
|
28
31
|
}: Props = $props();
|
|
@@ -48,6 +51,7 @@
|
|
|
48
51
|
{disabled}
|
|
49
52
|
{title}
|
|
50
53
|
{onclick}
|
|
54
|
+
aria-pressed={ariaPressed}
|
|
51
55
|
>
|
|
52
56
|
{#if icon}<i class="fas {icon}" aria-hidden="true"></i>{/if}
|
|
53
57
|
{#if children}<span class="ui-pill-label">{@render children()}</span>{/if}
|
|
@@ -89,6 +93,17 @@
|
|
|
89
93
|
border-color: rgba(255, 255, 255, 0.75);
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
/* Toggle "on": inset fill so it reads as depressed, not merely hovered. */
|
|
97
|
+
.ui-pill[aria-pressed='true'] {
|
|
98
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.30) 0%, rgba(255, 255, 255, 0.16) 100%);
|
|
99
|
+
border-color: rgba(255, 255, 255, 0.9);
|
|
100
|
+
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.35);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.ui-pill[aria-pressed='true'] i {
|
|
104
|
+
color: rgba(255, 255, 255, 0.95);
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
.ui-pill:focus-visible {
|
|
93
108
|
outline: none;
|
|
94
109
|
border-color: rgba(255, 255, 255, 0.9);
|