@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.
Files changed (61) hide show
  1. package/.claude/skills/live-tokens-add-component/SKILL.md +488 -0
  2. package/README.md +84 -29
  3. package/dist-plugin/index.cjs +177 -125
  4. package/dist-plugin/index.d.cts +3 -2
  5. package/dist-plugin/index.d.ts +3 -2
  6. package/dist-plugin/index.js +177 -125
  7. package/package.json +8 -2
  8. package/src/editor/component-editor/BadgeEditor.svelte +44 -42
  9. package/src/editor/component-editor/ButtonEditor.svelte +224 -0
  10. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +1 -7
  11. package/src/editor/component-editor/CornerBadgeEditor.svelte +44 -34
  12. package/src/editor/component-editor/ImageLightboxEditor.svelte +58 -0
  13. package/src/editor/component-editor/InputEditor.svelte +272 -0
  14. package/src/editor/component-editor/NotificationEditor.svelte +44 -65
  15. package/src/editor/component-editor/ProgressBarEditor.svelte +71 -87
  16. package/src/editor/component-editor/SegmentedControlEditor.svelte +98 -37
  17. package/src/editor/component-editor/SideNavigationEditor.svelte +342 -0
  18. package/src/editor/component-editor/index.ts +16 -1
  19. package/src/editor/component-editor/registry.ts +138 -28
  20. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +3 -2
  21. package/src/editor/component-editor/scaffolding/ComponentsTab.svelte +2 -2
  22. package/src/editor/component-editor/scaffolding/StateBlock.svelte +9 -10
  23. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +60 -36
  24. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +38 -1
  25. package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -1
  26. package/src/editor/component-editor/scaffolding/componentSources.ts +3 -3
  27. package/src/editor/component-editor/scaffolding/defaultSections.ts +15 -10
  28. package/src/editor/component-editor/scaffolding/siblings.ts +2 -2
  29. package/src/editor/component-editor/scaffolding/types.ts +2 -1
  30. package/src/editor/core/components/componentConfigKeys.ts +14 -3
  31. package/src/editor/core/components/componentConfigService.ts +7 -6
  32. package/src/editor/core/manifests/manifestService.ts +5 -4
  33. package/src/editor/core/storage/apiBase.ts +15 -0
  34. package/src/editor/core/storage/files/versionedFileResourceClient.ts +1 -1
  35. package/src/editor/core/themes/migrations/2026-05-24-collapsiblesection-drop-active-state.ts +28 -0
  36. package/src/editor/core/themes/migrations/2026-05-24-progressbar-collapse-variants.ts +41 -0
  37. package/src/editor/core/themes/migrations/2026-05-24-promote-state-shared-tokens.ts +59 -0
  38. package/src/editor/core/themes/migrations/2026-05-24-segmentedcontrol-divider-inset.ts +29 -0
  39. package/src/editor/core/themes/migrations/2026-05-25-cornerbadge-flatten-variants.ts +46 -0
  40. package/src/editor/core/themes/migrations/index.ts +10 -0
  41. package/src/editor/core/themes/slices/components.ts +9 -0
  42. package/src/editor/core/themes/themeInit.ts +3 -2
  43. package/src/editor/core/themes/themeService.ts +3 -2
  44. package/src/editor/index.ts +10 -1
  45. package/src/editor/pages/ComponentEditorPage.svelte +53 -3
  46. package/src/editor/pages/EditorShell.svelte +53 -3
  47. package/src/editor/ui/UIEasingSelector.svelte +240 -0
  48. package/src/editor/ui/variantScales.ts +34 -0
  49. package/src/system/components/Button.svelte +34 -85
  50. package/src/system/components/CollapsibleSection.svelte +1 -48
  51. package/src/system/components/CornerBadge.svelte +72 -138
  52. package/src/system/components/Dialog.svelte +24 -4
  53. package/src/system/components/ImageLightbox.svelte +578 -0
  54. package/src/system/components/Input.svelte +387 -0
  55. package/src/system/components/ProgressBar.svelte +62 -258
  56. package/src/system/components/SectionDivider.svelte +117 -43
  57. package/src/system/components/SegmentedControl.svelte +81 -15
  58. package/src/system/components/SideNavigation.svelte +777 -0
  59. package/src/system/styles/tokens.css +43 -0
  60. package/src/system/styles/tokens.generated.css +4 -183
  61. package/src/editor/component-editor/StandardButtonsEditor.svelte +0 -190
@@ -0,0 +1,387 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+
4
+ interface Props {
5
+ type?: 'text' | 'number' | 'search' | 'password';
6
+ value?: string;
7
+ placeholder?: string;
8
+ disabled?: boolean;
9
+ invalid?: boolean;
10
+ /** Field label rendered above the input. */
11
+ label?: string;
12
+ /** Helper text below the input. Hidden when `error` is set. */
13
+ hint?: string;
14
+ /** Validation error message below the input. Switches the field into the invalid visual state. */
15
+ error?: string;
16
+ /** Editor preview: force the focused visual state without real focus. */
17
+ forceFocus?: boolean;
18
+ /** Native id forwarded to the input. Auto-derived from label/placeholder when omitted. */
19
+ id?: string;
20
+ class?: string;
21
+ onchange?: (value: string) => void;
22
+ oninput?: (value: string) => void;
23
+ }
24
+
25
+ let {
26
+ type = 'text',
27
+ value = $bindable(''),
28
+ placeholder = '',
29
+ disabled = false,
30
+ invalid = false,
31
+ label = '',
32
+ hint = '',
33
+ error = '',
34
+ forceFocus = false,
35
+ id = undefined,
36
+ class: className = '',
37
+ onchange,
38
+ oninput,
39
+ }: Props = $props();
40
+
41
+ const dispatch = createEventDispatcher<{ change: string; input: string }>();
42
+
43
+ let revealed = $state(false);
44
+ let inputRef: HTMLInputElement | undefined = $state(undefined);
45
+
46
+ let effectiveType = $derived(type === 'password' && revealed ? 'text' : type);
47
+ let isInvalid = $derived(invalid || error.length > 0);
48
+
49
+ let autoId = `input-${Math.random().toString(36).slice(2, 10)}`;
50
+ let resolvedId = $derived(id ?? autoId);
51
+
52
+ function handleInput(e: Event) {
53
+ const next = (e.target as HTMLInputElement).value;
54
+ value = next;
55
+ oninput?.(next);
56
+ dispatch('input', next);
57
+ }
58
+
59
+ function handleChange(e: Event) {
60
+ const next = (e.target as HTMLInputElement).value;
61
+ onchange?.(next);
62
+ dispatch('change', next);
63
+ }
64
+
65
+ function clearSearch() {
66
+ value = '';
67
+ oninput?.('');
68
+ dispatch('input', '');
69
+ inputRef?.focus();
70
+ }
71
+ </script>
72
+
73
+ <div
74
+ class="input-field {className}"
75
+ class:disabled
76
+ class:invalid={isInvalid}
77
+ class:force-focus={forceFocus}
78
+ >
79
+ {#if label}
80
+ <label class="input-label" for={resolvedId}>{label}</label>
81
+ {/if}
82
+
83
+ <div class="input-shell">
84
+ {#if type === 'search'}
85
+ <i class="input-icon input-icon-leading fas fa-search" aria-hidden="true"></i>
86
+ {/if}
87
+
88
+ <input
89
+ bind:this={inputRef}
90
+ id={resolvedId}
91
+ class="input-control"
92
+ class:has-leading-icon={type === 'search'}
93
+ class:has-trailing-icon={type === 'password' || (type === 'search' && value.length > 0)}
94
+ type={effectiveType}
95
+ {value}
96
+ {placeholder}
97
+ {disabled}
98
+ aria-invalid={isInvalid || undefined}
99
+ aria-describedby={[hint && `${resolvedId}-hint`, error && `${resolvedId}-error`].filter(Boolean).join(' ') || undefined}
100
+ oninput={handleInput}
101
+ onchange={handleChange}
102
+ />
103
+
104
+ {#if type === 'password'}
105
+ <button
106
+ type="button"
107
+ class="input-icon-button"
108
+ aria-label={revealed ? 'Hide password' : 'Show password'}
109
+ aria-pressed={revealed}
110
+ {disabled}
111
+ onclick={() => (revealed = !revealed)}
112
+ >
113
+ <i class="input-icon fas {revealed ? 'fa-eye-slash' : 'fa-eye'}" aria-hidden="true"></i>
114
+ </button>
115
+ {:else if type === 'search' && value.length > 0}
116
+ <button
117
+ type="button"
118
+ class="input-icon-button"
119
+ aria-label="Clear search"
120
+ {disabled}
121
+ onclick={clearSearch}
122
+ >
123
+ <i class="input-icon fas fa-times" aria-hidden="true"></i>
124
+ </button>
125
+ {/if}
126
+ </div>
127
+
128
+ {#if hint}
129
+ <p id="{resolvedId}-hint" class="input-hint">{hint}</p>
130
+ {/if}
131
+ {#if error}
132
+ <p id="{resolvedId}-error" class="input-error" role="alert">
133
+ <i class="input-error-icon fas fa-exclamation-triangle" aria-hidden="true"></i>
134
+ <span>{error}</span>
135
+ </p>
136
+ {/if}
137
+ </div>
138
+
139
+ <style lang="scss">
140
+ @use '../styles/padding' as *;
141
+
142
+ :global(:root) {
143
+ /* Shape (shared across states) */
144
+ --input-radius: var(--radius-md);
145
+ --input-padding: var(--space-8);
146
+ --input-border-width: var(--border-width-1);
147
+ --input-gap: var(--space-6);
148
+
149
+ /* Default */
150
+ --input-default-surface: var(--surface-neutral-lower);
151
+ --input-default-border: var(--border-neutral);
152
+ --input-default-text: var(--text-primary);
153
+ --input-default-text-font-family: var(--font-sans);
154
+ --input-default-text-font-size: var(--font-size-sm);
155
+ --input-default-text-font-weight: var(--font-weight-normal);
156
+ --input-default-text-line-height: var(--line-height-sm);
157
+ --input-default-icon: var(--text-tertiary);
158
+ --input-default-icon-size: var(--icon-size-sm);
159
+ --input-default-placeholder: var(--text-tertiary);
160
+
161
+ /* Focused — applied as a CSS outline so the layout doesn't shift on focus */
162
+ --input-focused-surface: var(--surface-neutral-lower);
163
+ --input-focused-border: var(--border-brand);
164
+ --input-focused-border-width: var(--border-width-2);
165
+ --input-focused-text: var(--text-primary);
166
+ --input-focused-text-font-family: var(--font-sans);
167
+ --input-focused-text-font-size: var(--font-size-sm);
168
+ --input-focused-text-font-weight: var(--font-weight-normal);
169
+ --input-focused-text-line-height: var(--line-height-sm);
170
+ --input-focused-icon: var(--text-secondary);
171
+ --input-focused-icon-size: var(--icon-size-sm);
172
+
173
+ /* Disabled */
174
+ --input-disabled-surface: var(--surface-neutral);
175
+ --input-disabled-border: var(--border-neutral-faint);
176
+ --input-disabled-text: var(--text-tertiary);
177
+ --input-disabled-text-font-family: var(--font-sans);
178
+ --input-disabled-text-font-size: var(--font-size-sm);
179
+ --input-disabled-text-font-weight: var(--font-weight-normal);
180
+ --input-disabled-text-line-height: var(--line-height-sm);
181
+ --input-disabled-icon: var(--text-tertiary);
182
+ --input-disabled-icon-size: var(--icon-size-sm);
183
+
184
+ /* Label */
185
+ --input-label: var(--text-secondary);
186
+ --input-label-font-family: var(--font-sans);
187
+ --input-label-font-size: var(--font-size-sm);
188
+ --input-label-font-weight: var(--font-weight-semibold);
189
+ --input-label-line-height: var(--line-height-sm);
190
+
191
+ /* Hint */
192
+ --input-hint: var(--text-tertiary);
193
+ --input-hint-font-family: var(--font-sans);
194
+ --input-hint-font-size: var(--font-size-xs);
195
+ --input-hint-font-weight: var(--font-weight-normal);
196
+ --input-hint-line-height: var(--line-height-sm);
197
+
198
+ /* Error — outline mirrors the focused state so width changes don't reflow the field */
199
+ --input-error-border: var(--border-danger);
200
+ --input-error-border-width: var(--border-width-2);
201
+ --input-error: var(--text-danger);
202
+ --input-error-font-family: var(--font-sans);
203
+ --input-error-font-size: var(--font-size-xs);
204
+ --input-error-font-weight: var(--font-weight-medium);
205
+ --input-error-line-height: var(--line-height-sm);
206
+ }
207
+
208
+ .input-field {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: var(--input-gap);
212
+ width: 100%;
213
+ }
214
+
215
+ .input-label {
216
+ color: var(--input-label);
217
+ font-family: var(--input-label-font-family);
218
+ font-size: var(--input-label-font-size);
219
+ font-weight: var(--input-label-font-weight);
220
+ line-height: var(--input-label-line-height);
221
+ }
222
+
223
+ .input-shell {
224
+ position: relative;
225
+ display: flex;
226
+ align-items: stretch;
227
+ }
228
+
229
+ .input-control {
230
+ flex: 1 1 auto;
231
+ width: 100%;
232
+ background: var(--input-default-surface);
233
+ border: var(--input-border-width) solid var(--input-default-border);
234
+ border-radius: var(--input-radius);
235
+ color: var(--input-default-text);
236
+ font-family: var(--input-default-text-font-family);
237
+ font-size: var(--input-default-text-font-size);
238
+ font-weight: var(--input-default-text-font-weight);
239
+ line-height: var(--input-default-text-line-height);
240
+ @include themed-padding(--input-padding, $h: 1.25);
241
+ transition: outline-color var(--duration-150), background var(--duration-150), color var(--duration-150);
242
+ outline: 0 solid transparent;
243
+ outline-offset: 0;
244
+ appearance: none;
245
+ -webkit-appearance: none;
246
+
247
+ &::placeholder {
248
+ color: var(--input-default-placeholder);
249
+ opacity: 1;
250
+ }
251
+
252
+ &.has-leading-icon {
253
+ padding-left: calc(var(--input-padding) + var(--input-default-icon-size) + var(--space-12));
254
+ }
255
+
256
+ &.has-trailing-icon {
257
+ padding-right: calc(var(--input-padding) + var(--input-default-icon-size) + var(--space-12));
258
+ }
259
+
260
+ &:focus,
261
+ .input-field.force-focus & {
262
+ background: var(--input-focused-surface);
263
+ outline: var(--input-focused-border-width) solid var(--input-focused-border);
264
+ color: var(--input-focused-text);
265
+ font-family: var(--input-focused-text-font-family);
266
+ font-size: var(--input-focused-text-font-size);
267
+ font-weight: var(--input-focused-text-font-weight);
268
+ line-height: var(--input-focused-text-line-height);
269
+ }
270
+
271
+ &:disabled,
272
+ .input-field.disabled & {
273
+ background: var(--input-disabled-surface);
274
+ border-color: var(--input-disabled-border);
275
+ color: var(--input-disabled-text);
276
+ font-family: var(--input-disabled-text-font-family);
277
+ font-size: var(--input-disabled-text-font-size);
278
+ font-weight: var(--input-disabled-text-font-weight);
279
+ line-height: var(--input-disabled-text-line-height);
280
+ cursor: not-allowed;
281
+ }
282
+ }
283
+
284
+ /* Error outline overrides the focus outline so red wins when both apply. */
285
+ .input-field.invalid .input-control,
286
+ .input-field.invalid .input-control:focus,
287
+ .input-field.invalid.force-focus .input-control {
288
+ outline: var(--input-error-border-width) solid var(--input-error-border);
289
+ }
290
+
291
+ .input-icon {
292
+ color: var(--input-default-icon);
293
+ font-size: var(--input-default-icon-size);
294
+ line-height: 1;
295
+ transition: color var(--duration-150), font-size var(--duration-150);
296
+ }
297
+
298
+ .input-icon-leading {
299
+ position: absolute;
300
+ left: calc(var(--input-padding) + var(--space-2));
301
+ top: 50%;
302
+ transform: translateY(-50%);
303
+ pointer-events: none;
304
+ }
305
+
306
+ .input-icon-button {
307
+ position: absolute;
308
+ right: calc(var(--input-padding) - var(--space-2));
309
+ top: 50%;
310
+ transform: translateY(-50%);
311
+ display: inline-flex;
312
+ align-items: center;
313
+ justify-content: center;
314
+ background: transparent;
315
+ border: 0;
316
+ padding: var(--space-4);
317
+ border-radius: var(--radius-sm);
318
+ cursor: pointer;
319
+
320
+ &:hover:not(:disabled) .input-icon {
321
+ color: var(--input-focused-icon);
322
+ }
323
+
324
+ &:disabled {
325
+ cursor: not-allowed;
326
+ opacity: 0.6;
327
+ }
328
+ }
329
+
330
+ .input-shell:focus-within .input-icon,
331
+ .input-field.force-focus .input-icon {
332
+ color: var(--input-focused-icon);
333
+ font-size: var(--input-focused-icon-size);
334
+ }
335
+
336
+ .input-field.disabled .input-icon {
337
+ color: var(--input-disabled-icon);
338
+ font-size: var(--input-disabled-icon-size);
339
+ }
340
+
341
+ .input-hint {
342
+ margin: 0;
343
+ color: var(--input-hint);
344
+ font-family: var(--input-hint-font-family);
345
+ font-size: var(--input-hint-font-size);
346
+ font-weight: var(--input-hint-font-weight);
347
+ line-height: var(--input-hint-line-height);
348
+ }
349
+
350
+ .input-error {
351
+ margin: 0;
352
+ display: flex;
353
+ align-items: flex-start;
354
+ gap: var(--space-6);
355
+ color: var(--input-error);
356
+ font-family: var(--input-error-font-family);
357
+ font-size: var(--input-error-font-size);
358
+ font-weight: var(--input-error-font-weight);
359
+ line-height: var(--input-error-line-height);
360
+ }
361
+
362
+ /* Icon inherits .input-error's color so it always matches the error text. */
363
+ .input-error-icon {
364
+ flex: 0 0 auto;
365
+ font-size: 1em;
366
+ line-height: inherit;
367
+ }
368
+
369
+ /* Hide the native number spinner — themable spinners are a future enhancement. */
370
+ .input-control[type='number']::-webkit-outer-spin-button,
371
+ .input-control[type='number']::-webkit-inner-spin-button {
372
+ -webkit-appearance: none;
373
+ margin: 0;
374
+ }
375
+ .input-control[type='number'] {
376
+ -moz-appearance: textfield;
377
+ appearance: textfield;
378
+ }
379
+
380
+ /* Suppress the native search clear; we render our own affordance. */
381
+ .input-control[type='search']::-webkit-search-decoration,
382
+ .input-control[type='search']::-webkit-search-cancel-button,
383
+ .input-control[type='search']::-webkit-search-results-button,
384
+ .input-control[type='search']::-webkit-search-results-decoration {
385
+ -webkit-appearance: none;
386
+ }
387
+ </style>