@motion-proto/live-tokens 0.8.0 → 0.10.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/.claude/skills/live-tokens-add-component/SKILL.md +488 -0
- package/README.md +84 -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 +8 -2
- package/src/editor/component-editor/BadgeEditor.svelte +44 -42
- package/src/editor/component-editor/ButtonEditor.svelte +224 -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/index.ts +16 -1
- package/src/editor/component-editor/registry.ts +138 -28
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +3 -2
- package/src/editor/component-editor/scaffolding/ComponentsTab.svelte +2 -2
- 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/componentSources.ts +3 -3
- package/src/editor/component-editor/scaffolding/defaultSections.ts +15 -10
- 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/componentConfigKeys.ts +14 -3
- 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/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/index.ts +10 -0
- package/src/editor/core/themes/slices/components.ts +9 -0
- package/src/editor/core/themes/themeInit.ts +3 -2
- package/src/editor/core/themes/themeService.ts +3 -2
- package/src/editor/index.ts +10 -1
- package/src/editor/pages/ComponentEditorPage.svelte +53 -3
- package/src/editor/pages/EditorShell.svelte +53 -3
- package/src/editor/ui/UIEasingSelector.svelte +240 -0
- package/src/editor/ui/variantScales.ts +34 -0
- package/src/system/components/Button.svelte +34 -85
- package/src/system/components/CollapsibleSection.svelte +1 -48
- package/src/system/components/CornerBadge.svelte +72 -138
- package/src/system/components/Dialog.svelte +24 -4
- 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/SectionDivider.svelte +117 -43
- package/src/system/components/SegmentedControl.svelte +81 -15
- package/src/system/components/SideNavigation.svelte +777 -0
- package/src/system/styles/tokens.css +43 -0
- package/src/system/styles/tokens.generated.css +4 -183
- package/src/editor/component-editor/StandardButtonsEditor.svelte +0 -190
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import ComponentsTab from '../component-editor/scaffolding/ComponentsTab.svelte';
|
|
6
6
|
import ManifestFileManager from '../ui/ManifestFileManager.svelte';
|
|
7
7
|
import { navigate } from '../core/routing/router';
|
|
8
|
-
import {
|
|
8
|
+
import { getComponentRegistryEntries, validateRegistryAgainstServerScan } from '../component-editor/registry';
|
|
9
9
|
import { listComponents } from '../core/components/componentConfigService';
|
|
10
10
|
import { selectedComponent } from '../core/store/editorViewStore';
|
|
11
11
|
import { componentDirty } from '../core/store/editorStore';
|
|
@@ -96,7 +96,9 @@
|
|
|
96
96
|
window.removeEventListener('keydown', handleKeydown);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
const
|
|
99
|
+
const allComponentNavItems = getComponentRegistryEntries().map(({ id, label, icon, origin }) => ({ id, label, icon, origin }));
|
|
100
|
+
const systemNavItems = allComponentNavItems.filter((i) => i.origin === 'system');
|
|
101
|
+
const customNavItems = allComponentNavItems.filter((i) => i.origin === 'custom');
|
|
100
102
|
</script>
|
|
101
103
|
|
|
102
104
|
<!--
|
|
@@ -146,7 +148,7 @@
|
|
|
146
148
|
{/if}
|
|
147
149
|
</div>
|
|
148
150
|
<div class="nav-items">
|
|
149
|
-
{#each
|
|
151
|
+
{#each systemNavItems as item}
|
|
150
152
|
<button
|
|
151
153
|
class="nav-item"
|
|
152
154
|
class:active={$selectedComponent === item.id}
|
|
@@ -162,6 +164,27 @@
|
|
|
162
164
|
{/if}
|
|
163
165
|
</button>
|
|
164
166
|
{/each}
|
|
167
|
+
{#if customNavItems.length > 0}
|
|
168
|
+
<div class="nav-divider">
|
|
169
|
+
<span class="nav-divider-label">Custom</span>
|
|
170
|
+
</div>
|
|
171
|
+
{#each customNavItems as item}
|
|
172
|
+
<button
|
|
173
|
+
class="nav-item"
|
|
174
|
+
class:active={$selectedComponent === item.id}
|
|
175
|
+
class:dirty={$componentDirty[item.id]}
|
|
176
|
+
onmouseenter={(e) => showHint(item.label, e.currentTarget)}
|
|
177
|
+
onmouseleave={hideHint}
|
|
178
|
+
onclick={() => selectComponent(item.id)}
|
|
179
|
+
>
|
|
180
|
+
<i class={item.icon}></i>
|
|
181
|
+
<span class="rail-label">{item.label}</span>
|
|
182
|
+
{#if $componentDirty[item.id]}
|
|
183
|
+
<span class="dirty-dot" aria-label="Unsaved changes" title="Unsaved changes"></span>
|
|
184
|
+
{/if}
|
|
185
|
+
</button>
|
|
186
|
+
{/each}
|
|
187
|
+
{/if}
|
|
165
188
|
</div>
|
|
166
189
|
{#if drawerOpen}
|
|
167
190
|
<div class="sidebar-footer">
|
|
@@ -331,6 +354,33 @@
|
|
|
331
354
|
background: black;
|
|
332
355
|
}
|
|
333
356
|
|
|
357
|
+
/* Divider between SYSTEM and CUSTOM groups. The horizontal line uses the
|
|
358
|
+
dimmer border token (sub-element separator), with an uppercase eyebrow
|
|
359
|
+
label that fades out when the rail is collapsed so the line still reads. */
|
|
360
|
+
.nav-divider {
|
|
361
|
+
display: grid;
|
|
362
|
+
grid-template-columns: 48px 1fr;
|
|
363
|
+
align-items: center;
|
|
364
|
+
height: 28px;
|
|
365
|
+
margin-top: var(--ui-space-8);
|
|
366
|
+
border-top: 1px solid var(--ui-border-low);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.nav-divider-label {
|
|
370
|
+
grid-column: 2;
|
|
371
|
+
font-size: var(--ui-font-size-xs);
|
|
372
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
373
|
+
color: var(--ui-text-tertiary);
|
|
374
|
+
text-transform: uppercase;
|
|
375
|
+
letter-spacing: 0.04em;
|
|
376
|
+
opacity: 0;
|
|
377
|
+
transition: opacity 180ms ease;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.components-shell.rail-expanded .nav-divider-label {
|
|
381
|
+
opacity: 1;
|
|
382
|
+
}
|
|
383
|
+
|
|
334
384
|
.sidebar-footer {
|
|
335
385
|
flex-shrink: 0;
|
|
336
386
|
margin-top: auto;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { editorState } from '../core/store/editorStore';
|
|
14
14
|
import { editorView, sidebarCondensed, selectedComponent } from '../core/store/editorViewStore';
|
|
15
15
|
import { componentDirty } from '../core/store/editorStore';
|
|
16
|
-
import {
|
|
16
|
+
import { getComponentRegistryEntries, validateRegistryAgainstServerScan } from '../component-editor/registry';
|
|
17
17
|
import { listComponents } from '../core/components/componentConfigService';
|
|
18
18
|
|
|
19
19
|
const tokenNavItems = [
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
{ id: 'utility-tokens', label: 'Utility Tokens', icon: 'fas fa-sliders' }
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const allComponentNavItems = getComponentRegistryEntries().map(({ id, label, icon, origin }) => ({ id, label, icon, origin }));
|
|
33
|
+
const systemNavItems = allComponentNavItems.filter((i) => i.origin === 'system');
|
|
34
|
+
const customNavItems = allComponentNavItems.filter((i) => i.origin === 'custom');
|
|
33
35
|
|
|
34
36
|
let selectedTokenSection: string | null = $state(null);
|
|
35
37
|
let saveStatus: 'idle' | 'saving' | 'saved' | 'error' = $state('idle');
|
|
@@ -149,7 +151,7 @@
|
|
|
149
151
|
{/if}
|
|
150
152
|
{:else}
|
|
151
153
|
<div class="nav-items">
|
|
152
|
-
{#each
|
|
154
|
+
{#each systemNavItems as item}
|
|
153
155
|
<button
|
|
154
156
|
class="nav-item"
|
|
155
157
|
class:active={$selectedComponent === item.id}
|
|
@@ -165,6 +167,27 @@
|
|
|
165
167
|
{/if}
|
|
166
168
|
</button>
|
|
167
169
|
{/each}
|
|
170
|
+
{#if customNavItems.length > 0}
|
|
171
|
+
<div class="nav-divider">
|
|
172
|
+
<span class="nav-divider-label">Custom</span>
|
|
173
|
+
</div>
|
|
174
|
+
{#each customNavItems as item}
|
|
175
|
+
<button
|
|
176
|
+
class="nav-item"
|
|
177
|
+
class:active={$selectedComponent === item.id}
|
|
178
|
+
class:dirty={$componentDirty[item.id]}
|
|
179
|
+
onmouseenter={(e) => showHint(item.label, e.currentTarget)}
|
|
180
|
+
onmouseleave={hideHint}
|
|
181
|
+
onclick={() => selectComponent(item.id)}
|
|
182
|
+
>
|
|
183
|
+
<i class={item.icon}></i>
|
|
184
|
+
<span class="nav-label">{item.label}</span>
|
|
185
|
+
{#if $componentDirty[item.id]}
|
|
186
|
+
<span class="dirty-dot" aria-label="Unsaved changes" title="Unsaved changes"></span>
|
|
187
|
+
{/if}
|
|
188
|
+
</button>
|
|
189
|
+
{/each}
|
|
190
|
+
{/if}
|
|
168
191
|
</div>
|
|
169
192
|
{#if !condensed}
|
|
170
193
|
<div class="sidebar-footer">
|
|
@@ -248,6 +271,33 @@
|
|
|
248
271
|
flex-shrink: 0;
|
|
249
272
|
}
|
|
250
273
|
|
|
274
|
+
/* Divider between SYSTEM and CUSTOM groups. The horizontal line uses the
|
|
275
|
+
dimmer border token (sub-element separator), with an uppercase eyebrow
|
|
276
|
+
label that fades out when the rail is condensed so the line still reads. */
|
|
277
|
+
.nav-divider {
|
|
278
|
+
display: grid;
|
|
279
|
+
grid-template-columns: 48px 1fr;
|
|
280
|
+
align-items: center;
|
|
281
|
+
height: 28px;
|
|
282
|
+
margin-top: var(--ui-space-8);
|
|
283
|
+
border-top: 1px solid var(--ui-border-low);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.nav-divider-label {
|
|
287
|
+
grid-column: 2;
|
|
288
|
+
font-size: var(--ui-font-size-xs);
|
|
289
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
290
|
+
color: var(--ui-text-tertiary);
|
|
291
|
+
text-transform: uppercase;
|
|
292
|
+
letter-spacing: 0.04em;
|
|
293
|
+
opacity: 1;
|
|
294
|
+
transition: opacity 180ms ease;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.layout.condensed .nav-divider-label {
|
|
298
|
+
opacity: 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
251
301
|
.nav-item {
|
|
252
302
|
position: relative;
|
|
253
303
|
display: grid;
|
|
@@ -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>
|
|
@@ -106,3 +106,37 @@ export const DIVIDER_HEIGHT: VariantScaleEntry = {
|
|
|
106
106
|
{ key: 'full', label: 'Full', value: '100%' },
|
|
107
107
|
],
|
|
108
108
|
};
|
|
109
|
+
|
|
110
|
+
/** Used by `*-duration` variables (CSS transition timing). Pulls keys directly
|
|
111
|
+
* from the shared `--duration-*` scale in tokens.css — labels mirror the token
|
|
112
|
+
* slugs so the picker reflects the utility token namespace, no synthetic names. */
|
|
113
|
+
export const DURATION: VariantScaleEntry = {
|
|
114
|
+
varPrefix: '--duration-',
|
|
115
|
+
options: [
|
|
116
|
+
{ key: '75', label: '75', value: '75ms' },
|
|
117
|
+
{ key: '150', label: '150', value: '150ms' },
|
|
118
|
+
{ key: '200', label: '200', value: '200ms' },
|
|
119
|
+
{ key: '300', label: '300', value: '300ms' },
|
|
120
|
+
{ key: '500', label: '500', value: '500ms' },
|
|
121
|
+
{ key: '750', label: '750', value: '750ms' },
|
|
122
|
+
{ key: '1000', label: '1000', value: '1000ms' },
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/** Used by `*-divider-inset` variables (margin-block trimmed off a stretched
|
|
127
|
+
* divider). Labels describe the resulting divider, not the inset value: 0
|
|
128
|
+
* inset = bar-height divider ("Full"); larger insets = shorter divider.
|
|
129
|
+
* Replaces the percentage-height approach that collapsed to 0 in
|
|
130
|
+
* auto-height flex parents. */
|
|
131
|
+
export const DIVIDER_INSET: VariantScaleEntry = {
|
|
132
|
+
varPrefix: '--space-',
|
|
133
|
+
options: [
|
|
134
|
+
{ key: '0', label: 'Full', value: '0px' },
|
|
135
|
+
{ key: '2', label: 'Tall', value: '0.125rem' },
|
|
136
|
+
{ key: '4', label: 'Large', value: '0.25rem' },
|
|
137
|
+
{ key: '6', label: 'Medium', value: '0.375rem' },
|
|
138
|
+
{ key: '8', label: 'Short', value: '0.5rem' },
|
|
139
|
+
{ key: '12', label: 'XS', value: '0.75rem' },
|
|
140
|
+
{ key: '16', label: 'Tiny', value: '1rem' },
|
|
141
|
+
],
|
|
142
|
+
};
|