@motion-proto/live-tokens 0.9.0 → 0.11.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/README.md +50 -29
- package/dist-plugin/index.cjs +177 -125
- package/dist-plugin/index.d.cts +3 -2
- package/dist-plugin/index.d.ts +3 -2
- package/dist-plugin/index.js +177 -125
- package/package.json +4 -1
- package/src/editor/component-editor/BadgeEditor.svelte +44 -42
- package/src/editor/component-editor/ButtonEditor.svelte +224 -0
- package/src/editor/component-editor/CardEditor.svelte +2 -0
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +1 -7
- package/src/editor/component-editor/CornerBadgeEditor.svelte +44 -34
- package/src/editor/component-editor/ImageLightboxEditor.svelte +58 -0
- package/src/editor/component-editor/InputEditor.svelte +272 -0
- package/src/editor/component-editor/NotificationEditor.svelte +44 -65
- package/src/editor/component-editor/ProgressBarEditor.svelte +71 -87
- package/src/editor/component-editor/SegmentedControlEditor.svelte +98 -37
- package/src/editor/component-editor/SideNavigationEditor.svelte +342 -0
- package/src/editor/component-editor/registry.ts +35 -2
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +3 -2
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +10 -1
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +9 -10
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +60 -36
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +38 -1
- package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -1
- package/src/editor/component-editor/scaffolding/siblings.ts +2 -2
- package/src/editor/component-editor/scaffolding/types.ts +2 -1
- package/src/editor/core/components/componentConfigService.ts +7 -6
- package/src/editor/core/manifests/manifestService.ts +5 -4
- package/src/editor/core/storage/apiBase.ts +15 -0
- package/src/editor/core/storage/files/versionedFileResourceClient.ts +1 -1
- package/src/editor/core/store/editorRenderer.ts +1 -4
- package/src/editor/core/store/editorStore.ts +5 -9
- package/src/editor/core/store/editorTypes.ts +6 -13
- package/src/editor/core/themes/migrations/2026-05-13-primary-to-brand.ts +2 -29
- package/src/editor/core/themes/migrations/2026-05-24-collapsiblesection-drop-active-state.ts +28 -0
- package/src/editor/core/themes/migrations/2026-05-24-progressbar-collapse-variants.ts +41 -0
- package/src/editor/core/themes/migrations/2026-05-24-promote-state-shared-tokens.ts +59 -0
- package/src/editor/core/themes/migrations/2026-05-24-segmentedcontrol-divider-inset.ts +29 -0
- package/src/editor/core/themes/migrations/2026-05-25-cornerbadge-flatten-variants.ts +46 -0
- package/src/editor/core/themes/migrations/2026-05-26-drop-overlay-extra-stops.ts +46 -0
- package/src/editor/core/themes/migrations/index.ts +16 -0
- package/src/editor/core/themes/slices/overlays.ts +44 -75
- package/src/editor/core/themes/themeInit.ts +3 -2
- package/src/editor/core/themes/themeService.ts +3 -2
- package/src/editor/ui/SurfacesTab.svelte +3 -7
- package/src/editor/ui/UIEasingSelector.svelte +240 -0
- package/src/editor/ui/UIPaddingSelector.svelte +2 -2
- package/src/editor/ui/UIPaletteSelector.svelte +151 -36
- package/src/editor/ui/{UIRelinkConfirmPopover.svelte → UIRelinkConfirmDialog.svelte} +107 -75
- package/src/editor/ui/UITokenSelector.svelte +15 -2
- package/src/editor/ui/sections/OverlaysSection.svelte +107 -540
- package/src/editor/ui/variantScales.ts +34 -0
- package/src/system/components/Button.svelte +34 -85
- package/src/system/components/Card.svelte +2 -1
- package/src/system/components/CollapsibleSection.svelte +1 -48
- package/src/system/components/CornerBadge.svelte +72 -138
- package/src/system/components/ImageLightbox.svelte +578 -0
- package/src/system/components/Input.svelte +387 -0
- package/src/system/components/ProgressBar.svelte +62 -258
- package/src/system/components/SegmentedControl.svelte +81 -15
- package/src/system/components/SideNavigation.svelte +777 -0
- package/src/system/components/TabBar.svelte +1 -1
- package/src/system/styles/tokens.css +48 -5
- package/src/system/styles/tokens.generated.css +33 -185
- package/src/editor/component-editor/StandardButtonsEditor.svelte +0 -190
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { resolveAliasChain } from '../core/palettes/tokenRegistry';
|
|
3
|
+
import UITokenSelector from './UITokenSelector.svelte';
|
|
4
|
+
import UIOptionList from './UIOptionList.svelte';
|
|
5
|
+
import UIOptionItem from './UIOptionItem.svelte';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
variable: string;
|
|
9
|
+
component?: string | undefined;
|
|
10
|
+
canBeLinked?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
selectionsLocked?: boolean;
|
|
13
|
+
onchange?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
variable,
|
|
18
|
+
component = undefined,
|
|
19
|
+
canBeLinked = false,
|
|
20
|
+
disabled = false,
|
|
21
|
+
selectionsLocked = false,
|
|
22
|
+
onchange,
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
/** Family list mirrors easings.net's catalog. `linear` is the special-case
|
|
26
|
+
curve with no variant; all others take in/out/in-out. Keys are the suffix
|
|
27
|
+
portion of the corresponding `--ease-*` token name. */
|
|
28
|
+
const FAMILIES = [
|
|
29
|
+
{ key: 'linear', label: 'Linear' },
|
|
30
|
+
{ key: 'sine', label: 'Sine' },
|
|
31
|
+
{ key: 'quad', label: 'Quad' },
|
|
32
|
+
{ key: 'cubic', label: 'Cubic' },
|
|
33
|
+
{ key: 'quart', label: 'Quart' },
|
|
34
|
+
{ key: 'quint', label: 'Quint' },
|
|
35
|
+
{ key: 'expo', label: 'Expo' },
|
|
36
|
+
{ key: 'circ', label: 'Circ' },
|
|
37
|
+
{ key: 'back', label: 'Back' },
|
|
38
|
+
{ key: 'elastic', label: 'Elastic' },
|
|
39
|
+
{ key: 'bounce', label: 'Bounce' },
|
|
40
|
+
] as const;
|
|
41
|
+
type FamilyKey = typeof FAMILIES[number]['key'];
|
|
42
|
+
|
|
43
|
+
const VARIANTS = [
|
|
44
|
+
{ key: 'in', label: 'In' },
|
|
45
|
+
{ key: 'out', label: 'Out' },
|
|
46
|
+
{ key: 'in-out', label: 'In-Out' },
|
|
47
|
+
] as const;
|
|
48
|
+
type VariantKey = typeof VARIANTS[number]['key'];
|
|
49
|
+
|
|
50
|
+
const FAMILY_KEYS = new Set<string>(FAMILIES.map((f) => f.key));
|
|
51
|
+
const VARIANT_KEYS = new Set<string>(VARIANTS.map((v) => v.key));
|
|
52
|
+
|
|
53
|
+
let selector: UITokenSelector;
|
|
54
|
+
let chosenFamily: FamilyKey | null = $state(null);
|
|
55
|
+
let chosenVariant: VariantKey | null = $state(null);
|
|
56
|
+
let currentValue: string = $state('');
|
|
57
|
+
|
|
58
|
+
/** Parse `--ease-linear` or `--ease-<variant>-<family>` into its parts.
|
|
59
|
+
Returns nulls when the name doesn't match the ease-token convention. */
|
|
60
|
+
function parseEaseToken(varName: string): { family: FamilyKey | null; variant: VariantKey | null } {
|
|
61
|
+
if (varName === '--ease-linear') return { family: 'linear', variant: null };
|
|
62
|
+
const m = varName.match(/^--ease-(in-out|in|out)-([a-z]+)$/);
|
|
63
|
+
if (!m) return { family: null, variant: null };
|
|
64
|
+
const variant = m[1] as VariantKey;
|
|
65
|
+
const family = m[2] as FamilyKey;
|
|
66
|
+
if (!FAMILY_KEYS.has(family) || family === 'linear') return { family: null, variant: null };
|
|
67
|
+
if (!VARIANT_KEYS.has(variant)) return { family: null, variant: null };
|
|
68
|
+
return { family, variant };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildEaseToken(family: FamilyKey, variant: VariantKey | null): string | null {
|
|
72
|
+
if (family === 'linear') return '--ease-linear';
|
|
73
|
+
if (!variant) return null;
|
|
74
|
+
return `--ease-${variant}-${family}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Pull the var() reference out of `var(--ease-...)`, returns the inner name. */
|
|
78
|
+
function parseRef(raw: string): string | null {
|
|
79
|
+
const m = raw.match(/var\((--ease-[a-z-]+)\)/);
|
|
80
|
+
return m ? m[1] : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readResolved() {
|
|
84
|
+
currentValue = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function initFromCurrent() {
|
|
88
|
+
readResolved();
|
|
89
|
+
const raw = document.documentElement.style.getPropertyValue(variable).trim();
|
|
90
|
+
if (raw) {
|
|
91
|
+
const inner = parseRef(raw);
|
|
92
|
+
if (inner) {
|
|
93
|
+
const parts = parseEaseToken(inner);
|
|
94
|
+
chosenFamily = parts.family;
|
|
95
|
+
chosenVariant = parts.variant;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const alias of resolveAliasChain(variable)) {
|
|
100
|
+
const parts = parseEaseToken(alias);
|
|
101
|
+
if (parts.family) {
|
|
102
|
+
chosenFamily = parts.family;
|
|
103
|
+
chosenVariant = parts.variant;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
chosenFamily = null;
|
|
108
|
+
chosenVariant = null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function handleReset() {
|
|
112
|
+
chosenFamily = null;
|
|
113
|
+
chosenVariant = null;
|
|
114
|
+
readResolved();
|
|
115
|
+
onchange?.();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Commit (family, variant) — writes the override if both halves are valid;
|
|
119
|
+
for non-linear families without a variant yet, defaults to `out`. Closes
|
|
120
|
+
the dropdown only when a full token name was produced. */
|
|
121
|
+
function commit(family: FamilyKey, variant: VariantKey | null, close: () => void): void {
|
|
122
|
+
let v = variant;
|
|
123
|
+
if (family !== 'linear' && !v) v = 'out';
|
|
124
|
+
const target = buildEaseToken(family, v);
|
|
125
|
+
if (!target) return;
|
|
126
|
+
if (target === variable) {
|
|
127
|
+
selector.writeOverride(null);
|
|
128
|
+
} else {
|
|
129
|
+
selector.writeOverride(target);
|
|
130
|
+
}
|
|
131
|
+
chosenFamily = family;
|
|
132
|
+
chosenVariant = family === 'linear' ? null : v;
|
|
133
|
+
readResolved();
|
|
134
|
+
close();
|
|
135
|
+
onchange?.();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function selectFamily(key: FamilyKey, close: () => void) {
|
|
139
|
+
commit(key, chosenVariant, close);
|
|
140
|
+
}
|
|
141
|
+
function selectVariant(key: VariantKey, close: () => void) {
|
|
142
|
+
if (!chosenFamily || chosenFamily === 'linear') return;
|
|
143
|
+
commit(chosenFamily, key, close);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let lastSeenVariable: string | null = null;
|
|
147
|
+
$effect(() => {
|
|
148
|
+
if (variable !== lastSeenVariable) {
|
|
149
|
+
lastSeenVariable = variable;
|
|
150
|
+
initFromCurrent();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
let familyLabel = $derived(FAMILIES.find((f) => f.key === chosenFamily)?.label ?? '');
|
|
155
|
+
let variantLabel = $derived(VARIANTS.find((v) => v.key === chosenVariant)?.label ?? '');
|
|
156
|
+
let triggerTitleText = $derived(
|
|
157
|
+
chosenFamily === 'linear'
|
|
158
|
+
? 'Linear'
|
|
159
|
+
: chosenFamily && chosenVariant
|
|
160
|
+
? `${variantLabel} ${familyLabel}`
|
|
161
|
+
: chosenFamily
|
|
162
|
+
? `${familyLabel} —`
|
|
163
|
+
: '',
|
|
164
|
+
);
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
<UITokenSelector
|
|
168
|
+
bind:this={selector}
|
|
169
|
+
{variable}
|
|
170
|
+
{component}
|
|
171
|
+
{canBeLinked}
|
|
172
|
+
{disabled}
|
|
173
|
+
{selectionsLocked}
|
|
174
|
+
dropdownMinWidth="18rem"
|
|
175
|
+
onreset={handleReset}
|
|
176
|
+
onvarChange={initFromCurrent}
|
|
177
|
+
>
|
|
178
|
+
{#snippet triggerTitle()}{triggerTitleText}{/snippet}
|
|
179
|
+
{#snippet triggerMeta()}{currentValue || '—'}{/snippet}
|
|
180
|
+
|
|
181
|
+
{#snippet children({ close })}
|
|
182
|
+
<div class="ease-grid" class:no-variants={chosenFamily === 'linear'}>
|
|
183
|
+
<div class="ease-col">
|
|
184
|
+
<span class="ease-col-label">Curve</span>
|
|
185
|
+
<UIOptionList>
|
|
186
|
+
{#each FAMILIES as fam (fam.key)}
|
|
187
|
+
<UIOptionItem
|
|
188
|
+
active={chosenFamily === fam.key}
|
|
189
|
+
onclick={() => selectFamily(fam.key, close)}
|
|
190
|
+
>
|
|
191
|
+
{#snippet label()}{fam.label}{/snippet}
|
|
192
|
+
</UIOptionItem>
|
|
193
|
+
{/each}
|
|
194
|
+
</UIOptionList>
|
|
195
|
+
</div>
|
|
196
|
+
{#if chosenFamily && chosenFamily !== 'linear'}
|
|
197
|
+
<div class="ease-col">
|
|
198
|
+
<span class="ease-col-label">Variant</span>
|
|
199
|
+
<UIOptionList>
|
|
200
|
+
{#each VARIANTS as v (v.key)}
|
|
201
|
+
<UIOptionItem
|
|
202
|
+
active={chosenVariant === v.key}
|
|
203
|
+
onclick={() => selectVariant(v.key, close)}
|
|
204
|
+
>
|
|
205
|
+
{#snippet label()}{v.label}{/snippet}
|
|
206
|
+
</UIOptionItem>
|
|
207
|
+
{/each}
|
|
208
|
+
</UIOptionList>
|
|
209
|
+
</div>
|
|
210
|
+
{/if}
|
|
211
|
+
</div>
|
|
212
|
+
{/snippet}
|
|
213
|
+
</UITokenSelector>
|
|
214
|
+
|
|
215
|
+
<style>
|
|
216
|
+
.ease-grid {
|
|
217
|
+
display: grid;
|
|
218
|
+
grid-template-columns: 1fr 1fr;
|
|
219
|
+
gap: var(--ui-space-8);
|
|
220
|
+
min-width: 18rem;
|
|
221
|
+
}
|
|
222
|
+
.ease-grid.no-variants {
|
|
223
|
+
grid-template-columns: 1fr;
|
|
224
|
+
}
|
|
225
|
+
.ease-col {
|
|
226
|
+
display: flex;
|
|
227
|
+
flex-direction: column;
|
|
228
|
+
gap: var(--ui-space-4);
|
|
229
|
+
min-width: 0;
|
|
230
|
+
}
|
|
231
|
+
.ease-col-label {
|
|
232
|
+
padding: var(--ui-space-4) var(--ui-space-8) 0;
|
|
233
|
+
font-size: var(--ui-font-size-xs);
|
|
234
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
235
|
+
color: var(--ui-text-tertiary);
|
|
236
|
+
font-family: var(--ui-font-mono);
|
|
237
|
+
text-transform: uppercase;
|
|
238
|
+
letter-spacing: 0.04em;
|
|
239
|
+
}
|
|
240
|
+
</style>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
unlinkComponentProperty,
|
|
19
19
|
relinkComponentProperty,
|
|
20
20
|
} from '../core/store/editorStore';
|
|
21
|
-
import
|
|
21
|
+
import UIRelinkConfirmDialog from './UIRelinkConfirmDialog.svelte';
|
|
22
22
|
import UILinkToggle from './UILinkToggle.svelte';
|
|
23
23
|
|
|
24
24
|
interface Props {
|
|
@@ -383,7 +383,7 @@
|
|
|
383
383
|
<div class="link-toggle-wrap">
|
|
384
384
|
<UILinkToggle linked={isLinkedParent} ontoggle={toggleLinkPaddingGroup} />
|
|
385
385
|
{#if relinkOpen && component}
|
|
386
|
-
<
|
|
386
|
+
<UIRelinkConfirmDialog
|
|
387
387
|
candidates={relinkCandidates}
|
|
388
388
|
initialVariable={variable}
|
|
389
389
|
prefixToStrip={`--${component}-`}
|
|
@@ -41,7 +41,14 @@
|
|
|
41
41
|
* rendered disabled and not clickable. Out-of-family already-set
|
|
42
42
|
* choices still surface in the trigger meta. */
|
|
43
43
|
familyFilter?: string | null;
|
|
44
|
+
/** When false, omit the "None" (transparent) option from the family list.
|
|
45
|
+
* Slots that must always paint something (e.g. overlay backdrops) opt
|
|
46
|
+
* out — picking None would just degenerate to opacity 0. */
|
|
47
|
+
showNone?: boolean;
|
|
44
48
|
onchange?: () => void;
|
|
49
|
+
/** Forwarded to UITokenSelector — when set, writes route through this
|
|
50
|
+
* callback instead of the DOM. See UITokenSelector.onwrite. */
|
|
51
|
+
onwrite?: (value: string | null) => void;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
let {
|
|
@@ -51,7 +58,9 @@
|
|
|
51
58
|
disabled = false,
|
|
52
59
|
selectionsLocked = false,
|
|
53
60
|
familyFilter = null,
|
|
61
|
+
showNone = true,
|
|
54
62
|
onchange,
|
|
63
|
+
onwrite,
|
|
55
64
|
}: Props = $props();
|
|
56
65
|
|
|
57
66
|
type Category = 'palette' | 'surface' | 'border' | 'text';
|
|
@@ -137,6 +146,9 @@
|
|
|
137
146
|
let chosenFamily = $state<string | null>(null);
|
|
138
147
|
let chosenStep = $state<string | null>(null);
|
|
139
148
|
let chosenNone = $state(false);
|
|
149
|
+
/** Picked one of the invariants. Bypasses category/family/step — these are
|
|
150
|
+
* not part of any ramp and always resolve to pure white/black. */
|
|
151
|
+
let chosenStatic = $state<'white' | 'black' | null>(null);
|
|
140
152
|
let chosenGradient = $state<string | null>(null);
|
|
141
153
|
/** Per-slot angle override on the chosen linear gradient. Null means
|
|
142
154
|
* "no override" — the slot writes `var(--gradient-N)` and inherits the
|
|
@@ -243,6 +255,14 @@
|
|
|
243
255
|
return { inner: m[1], opacity: parseInt(m[2]) };
|
|
244
256
|
}
|
|
245
257
|
|
|
258
|
+
function parseStatic(raw: string): { name: 'white' | 'black'; opacity: number } | null {
|
|
259
|
+
const direct = raw.match(/^var\(--color-(white|black)\)$/);
|
|
260
|
+
if (direct) return { name: direct[1] as 'white' | 'black', opacity: 100 };
|
|
261
|
+
const wrapped = raw.match(/^color-mix\(in srgb,\s*var\(--color-(white|black)\)\s+(\d+)%,\s*transparent\)$/);
|
|
262
|
+
if (wrapped) return { name: wrapped[1] as 'white' | 'black', opacity: parseInt(wrapped[2]) };
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
246
266
|
function buildValue(varName: string): string | null {
|
|
247
267
|
if (varName === variable && opacity >= 100) return null;
|
|
248
268
|
if (opacity >= 100) return varName;
|
|
@@ -251,6 +271,11 @@
|
|
|
251
271
|
|
|
252
272
|
function applyOpacity() {
|
|
253
273
|
opacity = Math.max(0, Math.min(100, Math.round(opacity)));
|
|
274
|
+
if (chosenStatic !== null) {
|
|
275
|
+
selector?.writeOverride(buildValue(`--color-${chosenStatic}`));
|
|
276
|
+
onchange?.();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
254
279
|
if (chosenCategory === null || chosenFamily === null || chosenStep === null) return;
|
|
255
280
|
const varName = getVarName(chosenCategory, chosenFamily, chosenStep);
|
|
256
281
|
selector?.writeOverride(buildValue(varName));
|
|
@@ -323,6 +348,7 @@
|
|
|
323
348
|
|
|
324
349
|
if (raw === 'transparent') {
|
|
325
350
|
chosenNone = true;
|
|
351
|
+
chosenStatic = null;
|
|
326
352
|
chosenCategory = null;
|
|
327
353
|
chosenFamily = null;
|
|
328
354
|
chosenStep = null;
|
|
@@ -362,6 +388,17 @@
|
|
|
362
388
|
chosenGradient = null;
|
|
363
389
|
chosenAngle = null;
|
|
364
390
|
|
|
391
|
+
const staticParsed = parseStatic(raw);
|
|
392
|
+
if (staticParsed) {
|
|
393
|
+
chosenStatic = staticParsed.name;
|
|
394
|
+
chosenCategory = null;
|
|
395
|
+
chosenFamily = null;
|
|
396
|
+
chosenStep = null;
|
|
397
|
+
opacity = staticParsed.opacity;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
chosenStatic = null;
|
|
401
|
+
|
|
365
402
|
const opacityParsed = parseOpacity(raw);
|
|
366
403
|
if (opacityParsed) {
|
|
367
404
|
const parsed = parseRef(opacityParsed.inner);
|
|
@@ -424,6 +461,7 @@
|
|
|
424
461
|
|
|
425
462
|
function selectNone(close: () => void) {
|
|
426
463
|
chosenNone = true;
|
|
464
|
+
chosenStatic = null;
|
|
427
465
|
chosenCategory = null;
|
|
428
466
|
chosenFamily = null;
|
|
429
467
|
chosenStep = null;
|
|
@@ -436,9 +474,24 @@
|
|
|
436
474
|
onchange?.();
|
|
437
475
|
}
|
|
438
476
|
|
|
477
|
+
function selectStatic(name: 'white' | 'black', close: () => void) {
|
|
478
|
+
chosenNone = false;
|
|
479
|
+
chosenStatic = name;
|
|
480
|
+
chosenCategory = null;
|
|
481
|
+
chosenFamily = null;
|
|
482
|
+
chosenStep = null;
|
|
483
|
+
chosenGradient = null;
|
|
484
|
+
chosenAngle = null;
|
|
485
|
+
selector?.writeOverride(buildValue(`--color-${name}`));
|
|
486
|
+
selectedFamily = null;
|
|
487
|
+
close();
|
|
488
|
+
onchange?.();
|
|
489
|
+
}
|
|
490
|
+
|
|
439
491
|
function selectSwatch(category: Category, step: string, close: () => void) {
|
|
440
492
|
const varName = getVarName(category, selectedFamily!, step);
|
|
441
493
|
chosenNone = false;
|
|
494
|
+
chosenStatic = null;
|
|
442
495
|
chosenGradient = null;
|
|
443
496
|
chosenAngle = null;
|
|
444
497
|
chosenCategory = category;
|
|
@@ -456,6 +509,7 @@
|
|
|
456
509
|
// token's default angle but with no local override active.
|
|
457
510
|
function selectGradient(gradientVar: string, close: () => void) {
|
|
458
511
|
chosenNone = false;
|
|
512
|
+
chosenStatic = null;
|
|
459
513
|
chosenCategory = null;
|
|
460
514
|
chosenFamily = null;
|
|
461
515
|
chosenStep = null;
|
|
@@ -493,11 +547,13 @@
|
|
|
493
547
|
|
|
494
548
|
let metaLabel = $derived(chosenNone
|
|
495
549
|
? 'none'
|
|
496
|
-
:
|
|
497
|
-
?
|
|
498
|
-
:
|
|
499
|
-
?
|
|
500
|
-
:
|
|
550
|
+
: chosenStatic
|
|
551
|
+
? `color-${chosenStatic}` + (opacity < 100 ? ` (${opacity}%)` : '')
|
|
552
|
+
: chosenGradient
|
|
553
|
+
? chosenGradient.replace(/^--/, '') + (chosenAngle !== null ? ` (${effectiveAngle}°)` : '')
|
|
554
|
+
: (chosenCategory && chosenFamily && chosenStep !== null
|
|
555
|
+
? getVarName(chosenCategory, chosenFamily, chosenStep).replace(/^--/, '') + (opacity < 100 ? ` (${opacity}%)` : '')
|
|
556
|
+
: ''));
|
|
501
557
|
|
|
502
558
|
let availableTabs = $derived(selectedFamily
|
|
503
559
|
? allCategories.filter(c => c.id !== 'text' || familiesWithText.includes(selectedFamily!))
|
|
@@ -517,6 +573,7 @@
|
|
|
517
573
|
{canBeLinked}
|
|
518
574
|
{disabled}
|
|
519
575
|
{selectionsLocked}
|
|
576
|
+
{onwrite}
|
|
520
577
|
dropdownMinWidth="14rem"
|
|
521
578
|
dropdownMaxWidth="calc(100vw - 2rem)"
|
|
522
579
|
hideDefaultHeader={!!selectedFamily}
|
|
@@ -542,13 +599,23 @@
|
|
|
542
599
|
{#snippet children({ close })}
|
|
543
600
|
|
|
544
601
|
{#if selectedFamily === null}
|
|
545
|
-
<div class="
|
|
546
|
-
<button class="
|
|
547
|
-
<div class="
|
|
548
|
-
|
|
549
|
-
</div>
|
|
550
|
-
<span class="family-label">None</span>
|
|
602
|
+
<div class="static-band">
|
|
603
|
+
<button class="static-chip" class:active={chosenStatic === 'white'} onclick={() => selectStatic('white', close)}>
|
|
604
|
+
<div class="static-swatch static-swatch--white"></div>
|
|
605
|
+
<span class="static-label">White</span>
|
|
551
606
|
</button>
|
|
607
|
+
<button class="static-chip" class:active={chosenStatic === 'black'} onclick={() => selectStatic('black', close)}>
|
|
608
|
+
<div class="static-swatch static-swatch--black"></div>
|
|
609
|
+
<span class="static-label">Black</span>
|
|
610
|
+
</button>
|
|
611
|
+
{#if showNone}
|
|
612
|
+
<button class="static-chip" class:active={chosenNone} onclick={() => selectNone(close)}>
|
|
613
|
+
<div class="static-swatch static-swatch--none"></div>
|
|
614
|
+
<span class="static-label">None</span>
|
|
615
|
+
</button>
|
|
616
|
+
{/if}
|
|
617
|
+
</div>
|
|
618
|
+
<div class="family-list">
|
|
552
619
|
{#each families as fam}
|
|
553
620
|
{@const outOfFamily = familyFilter !== null && fam.name !== familyFilter}
|
|
554
621
|
<button
|
|
@@ -850,6 +917,79 @@
|
|
|
850
917
|
background: var(--ui-hover);
|
|
851
918
|
}
|
|
852
919
|
|
|
920
|
+
/* Inline band of instant-apply atoms (White / Black / None) above the
|
|
921
|
+
family list. Same swatch+label vocabulary as `.step-item`, scaled to
|
|
922
|
+
fit three across so the eye groups them as peer one-clicks distinct
|
|
923
|
+
from the multi-step family rows below. */
|
|
924
|
+
.static-band {
|
|
925
|
+
display: flex;
|
|
926
|
+
gap: var(--ui-space-4);
|
|
927
|
+
padding: var(--ui-space-8);
|
|
928
|
+
border-bottom: 1px solid var(--ui-border-low);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.static-chip {
|
|
932
|
+
flex: 1;
|
|
933
|
+
display: flex;
|
|
934
|
+
flex-direction: column;
|
|
935
|
+
align-items: center;
|
|
936
|
+
gap: var(--ui-space-2);
|
|
937
|
+
padding: var(--ui-space-4);
|
|
938
|
+
background: none;
|
|
939
|
+
border: 1px solid transparent;
|
|
940
|
+
border-radius: var(--ui-radius-sm);
|
|
941
|
+
cursor: pointer;
|
|
942
|
+
transition: all var(--ui-transition-fast);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.static-chip:hover {
|
|
946
|
+
background: var(--ui-hover);
|
|
947
|
+
border-color: var(--ui-border);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.static-chip.active {
|
|
951
|
+
border-color: var(--ui-text-accent);
|
|
952
|
+
border-width: 2px;
|
|
953
|
+
background: var(--ui-hover-high);
|
|
954
|
+
padding: 3px;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.static-chip.active .static-label {
|
|
958
|
+
color: var(--ui-text-accent);
|
|
959
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
.static-swatch {
|
|
963
|
+
width: 2rem;
|
|
964
|
+
height: 1.5rem;
|
|
965
|
+
border-radius: var(--ui-radius-sm);
|
|
966
|
+
border: 1px solid var(--ui-border-low);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.static-swatch--white {
|
|
970
|
+
background: var(--color-white);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.static-swatch--black {
|
|
974
|
+
background: var(--color-black);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
.static-swatch--none {
|
|
978
|
+
background: repeating-linear-gradient(
|
|
979
|
+
-45deg,
|
|
980
|
+
transparent,
|
|
981
|
+
transparent 3px,
|
|
982
|
+
var(--ui-border-low) 3px,
|
|
983
|
+
var(--ui-border-low) 4px
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.static-label {
|
|
988
|
+
font-size: var(--ui-font-size-xs);
|
|
989
|
+
color: var(--ui-text-secondary);
|
|
990
|
+
font-family: var(--ui-font-mono);
|
|
991
|
+
}
|
|
992
|
+
|
|
853
993
|
.family-list {
|
|
854
994
|
display: flex;
|
|
855
995
|
flex-direction: column;
|
|
@@ -905,15 +1045,6 @@
|
|
|
905
1045
|
border-radius: 2px;
|
|
906
1046
|
}
|
|
907
1047
|
|
|
908
|
-
.none-swatch {
|
|
909
|
-
width: 2.5rem;
|
|
910
|
-
height: 0.75rem;
|
|
911
|
-
border-radius: 2px;
|
|
912
|
-
border: 1px solid var(--ui-border-low);
|
|
913
|
-
position: relative;
|
|
914
|
-
overflow: hidden;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
1048
|
.gradient-swatch {
|
|
918
1049
|
width: 2.5rem;
|
|
919
1050
|
height: 0.75rem;
|
|
@@ -931,22 +1062,6 @@
|
|
|
931
1062
|
border-top: 1px solid var(--ui-border-low);
|
|
932
1063
|
}
|
|
933
1064
|
|
|
934
|
-
.none-swatch::after {
|
|
935
|
-
content: '';
|
|
936
|
-
position: absolute;
|
|
937
|
-
top: -1px;
|
|
938
|
-
left: -1px;
|
|
939
|
-
right: -1px;
|
|
940
|
-
bottom: -1px;
|
|
941
|
-
background: repeating-linear-gradient(
|
|
942
|
-
-45deg,
|
|
943
|
-
transparent,
|
|
944
|
-
transparent 3px,
|
|
945
|
-
var(--ui-border-low) 3px,
|
|
946
|
-
var(--ui-border-low) 4px
|
|
947
|
-
);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
1065
|
.family-label {
|
|
951
1066
|
flex: 1;
|
|
952
1067
|
font-size: var(--ui-font-size-sm);
|