@motion-proto/live-tokens 0.7.1 → 0.9.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 (96) hide show
  1. package/.claude/skills/live-tokens-add-component/SKILL.md +488 -0
  2. package/README.md +34 -0
  3. package/dist-plugin/index.cjs +707 -90
  4. package/dist-plugin/index.d.cts +1 -0
  5. package/dist-plugin/index.d.ts +1 -0
  6. package/dist-plugin/index.js +707 -90
  7. package/package.json +6 -2
  8. package/src/app/site.css +1 -1
  9. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +34 -27
  10. package/src/editor/component-editor/DialogEditor.svelte +4 -4
  11. package/src/editor/component-editor/NotificationEditor.svelte +3 -1
  12. package/src/editor/component-editor/SectionDividerEditor.svelte +439 -112
  13. package/src/editor/component-editor/StandardButtonsEditor.svelte +13 -1
  14. package/src/editor/component-editor/editors.d.ts +10 -0
  15. package/src/editor/component-editor/index.ts +16 -1
  16. package/src/editor/component-editor/registry.ts +103 -26
  17. package/src/editor/component-editor/scaffolding/AngleDial.svelte +52 -13
  18. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +10 -11
  19. package/src/editor/component-editor/scaffolding/ComponentsTab.svelte +2 -2
  20. package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +0 -1
  21. package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
  22. package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +15 -2
  23. package/src/editor/component-editor/scaffolding/StateBlock.svelte +103 -15
  24. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +9 -6
  25. package/src/editor/component-editor/scaffolding/TypeEditor.svelte +13 -1
  26. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +239 -25
  27. package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -0
  28. package/src/editor/component-editor/scaffolding/componentSources.ts +3 -3
  29. package/src/editor/component-editor/scaffolding/defaultSections.ts +15 -10
  30. package/src/editor/component-editor/scaffolding/types.ts +11 -0
  31. package/src/editor/core/components/componentConfigKeys.ts +22 -3
  32. package/src/editor/core/components/componentConfigService.ts +2 -2
  33. package/src/editor/core/components/componentPersist.ts +7 -5
  34. package/src/editor/core/manifests/manifestService.ts +58 -3
  35. package/src/editor/core/palettes/familySwap.ts +99 -0
  36. package/src/editor/core/palettes/paletteDerivation.ts +69 -0
  37. package/src/editor/core/palettes/tokenRegistry.ts +4 -1
  38. package/src/editor/core/store/editorStore.ts +206 -12
  39. package/src/editor/core/store/editorTypes.ts +55 -12
  40. package/src/editor/core/store/gradientSource.ts +192 -0
  41. package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
  42. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
  43. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
  44. package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
  45. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
  46. package/src/editor/core/themes/migrations/index.ts +10 -0
  47. package/src/editor/core/themes/slices/components.ts +27 -4
  48. package/src/editor/core/themes/slices/gradients.ts +88 -13
  49. package/src/editor/core/themes/themeInit.ts +2 -2
  50. package/src/editor/core/themes/themeTypes.ts +56 -1
  51. package/src/editor/index.ts +10 -1
  52. package/src/editor/overlay/ColumnsOverlay.svelte +0 -1
  53. package/src/editor/overlay/LiveEditorOverlay.svelte +1 -4
  54. package/src/editor/pages/ComponentEditorPage.svelte +53 -3
  55. package/src/editor/pages/EditorShell.svelte +53 -3
  56. package/src/editor/styles/ui-editor.css +1 -0
  57. package/src/editor/styles/ui-form-controls.css +19 -20
  58. package/src/editor/ui/BezierCurveEditor.svelte +114 -63
  59. package/src/editor/ui/EditorViewSwitcher.svelte +0 -1
  60. package/src/editor/ui/FileLoadList.svelte +22 -5
  61. package/src/editor/ui/FontStackEditor.svelte +214 -76
  62. package/src/editor/ui/GradientEditor.svelte +435 -215
  63. package/src/editor/ui/GradientStopPicker.svelte +11 -3
  64. package/src/editor/ui/ManifestFileManager.svelte +71 -4
  65. package/src/editor/ui/PaletteEditor.svelte +52 -79
  66. package/src/editor/ui/ProjectFontsSection.svelte +328 -293
  67. package/src/editor/ui/ThemeFileManager.svelte +0 -4
  68. package/src/editor/ui/UIFontFamilySelector.svelte +0 -1
  69. package/src/editor/ui/UIFontSizeSelector.svelte +3 -0
  70. package/src/editor/ui/UIInfoPopover.svelte +0 -1
  71. package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
  72. package/src/editor/ui/UIPaletteSelector.svelte +31 -4
  73. package/src/editor/ui/UIPillButton.svelte +33 -3
  74. package/src/editor/ui/UISegmentedControl.svelte +114 -0
  75. package/src/editor/ui/UITokenSelector.svelte +4 -1
  76. package/src/editor/ui/VariablesTab.svelte +41 -35
  77. package/src/editor/ui/palette/OverridesPanel.svelte +14 -37
  78. package/src/editor/ui/palette/PaletteBase.svelte +3 -3
  79. package/src/editor/ui/sections/ColumnsSection.svelte +1 -2
  80. package/src/editor/ui/sections/GradientsSection.svelte +1 -1
  81. package/src/editor/ui/sections/OverlaysSection.svelte +1 -1
  82. package/src/editor/ui/sections/ShadowsSection.svelte +1 -1
  83. package/src/system/components/Button.svelte +2 -2
  84. package/src/system/components/Card.svelte +29 -1
  85. package/src/system/components/CollapsibleSection.svelte +25 -2
  86. package/src/system/components/Dialog.svelte +24 -4
  87. package/src/system/components/FloatingTokenTags.css +43 -24
  88. package/src/system/components/FloatingTokenTags.svelte +88 -137
  89. package/src/system/components/Notification.svelte +8 -1
  90. package/src/system/components/SectionDivider.svelte +532 -381
  91. package/src/system/styles/CONVENTIONS.md +1 -1
  92. package/src/system/styles/fonts.css +3 -16
  93. package/src/system/styles/tokens.css +356 -1199
  94. package/src/system/styles/tokens.generated.css +544 -0
  95. package/src/editor/component-editor/scaffolding/DividerEditor.svelte +0 -94
  96. package/src/editor/component-editor/scaffolding/GradientCard.svelte +0 -296
@@ -8,6 +8,7 @@
8
8
  * tab strip). This component owns the duplicated inner block so a per-state
9
9
  * control change happens in exactly one place.
10
10
  */
11
+ import type { Snippet } from 'svelte';
11
12
  import TokenLayout from './TokenLayout.svelte';
12
13
  import TypeEditor from './TypeEditor.svelte';
13
14
  import type { Token, TypeGroupConfig } from './types';
@@ -17,6 +18,12 @@
17
18
 
18
19
 
19
20
 
21
+ interface ElementToggle {
22
+ checked: boolean;
23
+ label?: string;
24
+ onchange: (checked: boolean) => void;
25
+ }
26
+
20
27
  interface Props {
21
28
  /** Tokens for this state, fed to `<TokenLayout>`. */
22
29
  tokens: Token[];
@@ -31,6 +38,21 @@
31
38
  (the two-col flex layout already partitions screen real estate when
32
39
  typeGroups are present). */
33
40
  columns?: number;
41
+ /** Per-element Show toggle. When provided for an element in element-grouped
42
+ mode, renders a checkbox next to that section's heading. The token rows
43
+ below stay visible regardless — the toggle drives preview visibility,
44
+ not editor visibility. */
45
+ elementToggles?: Record<string, ElementToggle>;
46
+ /** Explicit element ordering. Defaults to first-encounter across tokens
47
+ then typeGroups, which works when structural elements (frame, container)
48
+ come before named typography elements. Pass an explicit order when the
49
+ natural first-encounter wouldn't produce the right reading order. */
50
+ elementOrder?: string[];
51
+ /** Element-keyed snippet rendered between the section heading and the
52
+ section's typography/tokens. Lets callers inject per-element controls
53
+ (e.g. a hairline position dropdown that conceptually belongs in the
54
+ hairline section but isn't a CSS token). */
55
+ elementExtras?: Snippet<[string]>;
34
56
  onchange?: () => void;
35
57
  }
36
58
 
@@ -40,6 +62,9 @@
40
62
  component = undefined,
41
63
  linkedOrder = undefined,
42
64
  columns = 1,
65
+ elementToggles = {},
66
+ elementOrder,
67
+ elementExtras,
43
68
  onchange,
44
69
  }: Props = $props();
45
70
 
@@ -50,8 +75,13 @@
50
75
  "Frame", "Header", "Body"). Element order = first-encounter across the
51
76
  combined tokens + type-groups list. */
52
77
  let elementSections = $derived.by(() => {
53
- const order: string[] = [];
54
78
  const seen = new Set<string>();
79
+ const order: string[] = [];
80
+ if (elementOrder) {
81
+ for (const el of elementOrder) {
82
+ if (!seen.has(el)) { seen.add(el); order.push(el); }
83
+ }
84
+ }
55
85
  for (const t of tokens) {
56
86
  if (t.element && !seen.has(t.element)) {
57
87
  seen.add(t.element);
@@ -76,13 +106,27 @@
76
106
  {#if elementSections}
77
107
  <div class="state-controls element-grouped">
78
108
  {#each elementSections as section}
109
+ {@const toggle = elementToggles[section.element]}
79
110
  <section class="element-section">
80
- <h4 class="element-heading">{section.element}</h4>
111
+ <div class="element-heading-row">
112
+ <h4 class="element-heading">{section.element}</h4>
113
+ {#if toggle}
114
+ <label class="element-show-toggle">
115
+ <input
116
+ type="checkbox"
117
+ checked={toggle.checked}
118
+ onchange={(e) => toggle.onchange((e.currentTarget as HTMLInputElement).checked)}
119
+ />
120
+ <span>{toggle.label ?? `Show ${section.element}`}</span>
121
+ </label>
122
+ {/if}
123
+ </div>
124
+ {@render elementExtras?.(section.element)}
81
125
  {#if section.typeGroups.length > 0}
82
126
  <div class="state-type-groups">
83
127
  {#each section.typeGroups as tg}
84
128
  <TypeEditor
85
- legend={tg.legend ?? 'type'}
129
+ legend={tg.legend ?? ''}
86
130
  colorVariable={tg.colorVariable}
87
131
  colorLabel={tg.colorLabel ?? 'text color'}
88
132
  familyVariable={tg.familyVariable}
@@ -93,6 +137,8 @@
93
137
  weightLabel={tg.weightLabel ?? 'font weight'}
94
138
  lineHeightVariable={tg.lineHeightVariable}
95
139
  lineHeightLabel={tg.lineHeightLabel ?? 'line height'}
140
+ letterSpacingVariable={tg.letterSpacingVariable}
141
+ letterSpacingLabel={tg.letterSpacingLabel ?? 'letter spacing'}
96
142
  outlineWidthVariable={tg.outlineWidthVariable}
97
143
  outlineWidthLabel={tg.outlineWidthLabel ?? 'outline thickness'}
98
144
  outlineColorVariable={tg.outlineColorVariable}
@@ -133,6 +179,8 @@
133
179
  weightLabel={tg.weightLabel ?? 'font weight'}
134
180
  lineHeightVariable={tg.lineHeightVariable}
135
181
  lineHeightLabel={tg.lineHeightLabel ?? 'line height'}
182
+ letterSpacingVariable={tg.letterSpacingVariable}
183
+ letterSpacingLabel={tg.letterSpacingLabel ?? 'letter spacing'}
136
184
  outlineWidthVariable={tg.outlineWidthVariable}
137
185
  outlineWidthLabel={tg.outlineWidthLabel ?? 'outline thickness'}
138
186
  outlineColorVariable={tg.outlineColorVariable}
@@ -202,14 +250,19 @@
202
250
  padding-top: calc(var(--ui-font-size-xs) + var(--ui-space-4));
203
251
  }
204
252
 
205
- /* Element-grouped mode: vertical stack of subsections, each labeled by the
206
- element it targets (e.g. Frame / Header / Body). Within a section the
207
- two-col split (typography fieldsets + property grid) still applies when
208
- the section has both. */
253
+ /* Element-grouped mode: subsections fan out across available width, each
254
+ labeled by the element it targets (e.g. Frame / Header / Body). Three
255
+ columns at typical editor widths, dropping to two then one as the panel
256
+ narrows — the auto-fit + minmax does the responsive work without media
257
+ queries. `align-items: start` keeps columns of different heights aligned
258
+ to their top edge instead of stretching the shorter ones.
259
+ Within a section the two-col split (typography fieldsets + property grid)
260
+ still applies when the section has both. */
209
261
  .state-controls.element-grouped {
210
- display: flex;
211
- flex-direction: column;
212
- gap: var(--ui-space-16);
262
+ display: grid;
263
+ grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
264
+ gap: var(--ui-space-20) var(--ui-space-32);
265
+ align-items: start;
213
266
  }
214
267
 
215
268
  /* Each element section stacks typography fieldset(s) above the property
@@ -244,14 +297,49 @@
244
297
  padding: 0 var(--ui-space-4) var(--ui-space-4);
245
298
  }
246
299
 
300
+ /* TypeEditor's `.type-grid` and TokenLayout's `.token-grid` are independent
301
+ grids stacked inside the same element-section. Each sizes its label
302
+ column from its own `max-content`, so when one grid has long labels
303
+ ("letter spacing") and the other has short ones ("padding") their
304
+ dropdowns land at different x positions. Both grids honour
305
+ `--editor-label-col` for their first column track; setting it here pins
306
+ the label column to a shared minimum so short labels pad out and the
307
+ dropdowns line up across the section. The `max-content` upper bound
308
+ still lets exceptionally long labels grow.
309
+ Cascades into the inner grids; @container narrow-width overrides in
310
+ TokenLayout (which redefine `grid-template-columns` wholesale) still
311
+ win when the panel is too tight to honour the wider track. */
312
+ .element-section {
313
+ --editor-label-col: minmax(7.5rem, max-content);
314
+ }
315
+
316
+ .element-heading-row {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: var(--ui-space-12);
320
+ padding-bottom: var(--ui-space-4);
321
+ border-bottom: 1px solid var(--ui-border-low);
322
+ }
323
+
247
324
  .element-heading {
248
325
  margin: 0;
249
326
  font-size: var(--ui-font-size-sm);
250
- font-weight: var(--ui-font-weight-semibold);
251
- text-transform: uppercase;
252
- letter-spacing: 0.04em;
327
+ font-weight: var(--ui-font-weight-medium);
253
328
  color: var(--ui-text-tertiary);
254
- padding-bottom: var(--ui-space-4);
255
- border-bottom: 1px solid var(--ui-border-low);
256
329
  }
330
+
331
+ /* Show toggle next to the section heading — drives preview visibility for
332
+ the element. Property rows below stay visible so users can still tune the
333
+ hidden element's tokens. */
334
+ .element-show-toggle {
335
+ display: inline-flex;
336
+ align-items: center;
337
+ gap: var(--ui-space-6);
338
+ font-size: var(--ui-font-size-sm);
339
+ color: var(--ui-text-secondary);
340
+ cursor: pointer;
341
+ user-select: none;
342
+ }
343
+ .element-show-toggle:hover { color: var(--ui-text-primary); }
344
+ .element-show-toggle input { margin: 0; cursor: pointer; }
257
345
  </style>
@@ -92,7 +92,7 @@
92
92
  { kind: 'shadow', matches: (v) => v.endsWith('-shadow') || v.startsWith('--shadow-') },
93
93
  { kind: 'padding', matches: (v) => v.endsWith('-padding') || v.endsWith('-margin') },
94
94
  { kind: 'gap', matches: (v) => v.endsWith('-gap') },
95
- { kind: 'border-width', matches: (v) => v.endsWith('-border-width') || v.endsWith('-accent-width') || v.startsWith('--border-width-') },
95
+ { kind: 'border-width', matches: (v) => v.endsWith('-border-width') || v.endsWith('-accent-width') || v.endsWith('-hairline-thickness') || v.startsWith('--border-width-') },
96
96
  { kind: 'border', matches: (v) => v.endsWith('-border') || v.startsWith('--border-') },
97
97
  { kind: 'surface', matches: (v) => v.endsWith('-surface') || v.startsWith('--surface-') },
98
98
  ];
@@ -387,7 +387,10 @@
387
387
  --token-selector-w: 8rem;
388
388
  --columns: 1;
389
389
  display: grid;
390
- grid-template-columns: repeat(var(--columns), max-content var(--token-selector-w) 1fr);
390
+ /* Label column tracks `--editor-label-col` when an ancestor sets one
391
+ (e.g. StateBlock's `.element-section`), so this grid lines up with a
392
+ sibling `.type-grid` whose `max-content` would otherwise differ. */
393
+ grid-template-columns: repeat(var(--columns), var(--editor-label-col, max-content) var(--token-selector-w) 1fr);
391
394
  column-gap: var(--ui-space-10);
392
395
  row-gap: var(--ui-space-6);
393
396
  align-items: center;
@@ -414,13 +417,13 @@
414
417
  padding-left: var(--ui-space-20);
415
418
  }
416
419
 
417
- @container (max-width: 480px) {
420
+ @container variant-group (max-width: 480px) {
418
421
  .token-grid { --token-selector-w: 6rem; }
419
422
  }
420
423
 
421
424
  /* Narrow multi-col: shrink selector + inter-set gap further before giving
422
425
  up the second column. Targets the overlay's typical docked width range. */
423
- @container (max-width: 640px) {
426
+ @container variant-group (max-width: 640px) {
424
427
  .token-grid.multi-col {
425
428
  --token-selector-w: 6rem;
426
429
  column-gap: var(--ui-space-6);
@@ -436,7 +439,7 @@
436
439
  `1fr` so the lone column fills the panel like single-col mode, and the
437
440
  inter-set padding is suppressed so wrapped "set 2" rows don't sit
438
441
  indented. */
439
- @container (max-width: 520px) {
442
+ @container variant-group (max-width: 520px) {
440
443
  .token-grid.multi-col {
441
444
  --columns: 1;
442
445
  grid-template-columns: max-content var(--token-selector-w) 1fr;
@@ -449,7 +452,7 @@
449
452
  }
450
453
  }
451
454
 
452
- @container (max-width: 380px) {
455
+ @container variant-group (max-width: 380px) {
453
456
  .token-grid {
454
457
  grid-template-columns: max-content 1fr;
455
458
  column-gap: var(--ui-space-6);
@@ -5,6 +5,7 @@
5
5
  import UIFontSizeSelector from '../../ui/UIFontSizeSelector.svelte';
6
6
  import UIFontWeightSelector from '../../ui/UIFontWeightSelector.svelte';
7
7
  import UILineHeightSelector from '../../ui/UILineHeightSelector.svelte';
8
+ import UILetterSpacingSelector from '../../ui/UILetterSpacingSelector.svelte';
8
9
  import FieldsetWrapper from './FieldsetWrapper.svelte';
9
10
  import { BORDER_WIDTH } from '../../ui/variantScales';
10
11
 
@@ -22,6 +23,8 @@
22
23
  weightLabel?: string;
23
24
  lineHeightVariable?: string | undefined;
24
25
  lineHeightLabel?: string;
26
+ letterSpacingVariable?: string | undefined;
27
+ letterSpacingLabel?: string;
25
28
  /** Optional outline rows rendered under the typography rows so a text-with-
26
29
  stroke group keeps stroke controls visually nested with the type they
27
30
  drive (e.g. SectionDivider title outline). */
@@ -47,6 +50,8 @@
47
50
  weightLabel = 'weight',
48
51
  lineHeightVariable = undefined,
49
52
  lineHeightLabel = 'line-h',
53
+ letterSpacingVariable = undefined,
54
+ letterSpacingLabel = 'letter-sp',
50
55
  outlineWidthVariable = undefined,
51
56
  outlineWidthLabel = 'outline thickness',
52
57
  outlineColorVariable = undefined,
@@ -78,6 +83,10 @@
78
83
  <span class="row-label">{lineHeightLabel}</span>
79
84
  <UILineHeightSelector variable={lineHeightVariable} {component} canBeLinked {onchange} />
80
85
  {/if}
86
+ {#if letterSpacingVariable}
87
+ <span class="row-label">{letterSpacingLabel}</span>
88
+ <UILetterSpacingSelector variable={letterSpacingVariable} {component} canBeLinked {onchange} />
89
+ {/if}
81
90
  {#if outlineWidthVariable}
82
91
  <span class="row-label">{outlineWidthLabel}</span>
83
92
  <UIVariantSelector variable={outlineWidthVariable} {component} canBeLinked {...BORDER_WIDTH} {onchange} />
@@ -92,7 +101,10 @@
92
101
  <style>
93
102
  .type-grid {
94
103
  display: grid;
95
- grid-template-columns: max-content 8rem 1fr;
104
+ /* Label column tracks the editor-wide `--editor-label-col` when an
105
+ ancestor (e.g. StateBlock's `.element-section`) sets one, so this
106
+ grid lines up with sibling `.token-grid`s in the same section. */
107
+ grid-template-columns: var(--editor-label-col, max-content) 8rem 1fr;
96
108
  column-gap: var(--ui-space-10);
97
109
  row-gap: var(--ui-space-6);
98
110
  align-items: center;