@motion-proto/live-tokens 0.68.1 → 0.70.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 (95) hide show
  1. package/.claude/skills/live-tokens-build-page/SKILL.md +16 -5
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +55 -19
  3. package/.claude/skills/live-tokens-create-component/references/token-naming.md +30 -1
  4. package/.claude/skills/live-tokens-fix-findings/SKILL.md +133 -0
  5. package/.claude/skills/live-tokens-pick-component/SKILL.md +10 -1
  6. package/CHANGELOG.md +236 -0
  7. package/README.md +13 -4
  8. package/bin/check-component.mjs +367 -63
  9. package/bin/check-page.mjs +409 -0
  10. package/bin/cli.mjs +107 -9
  11. package/bin/lib/catalogue.mjs +123 -0
  12. package/bin/lib/cssValues.mjs +50 -0
  13. package/bin/lib/findings.mjs +106 -0
  14. package/bin/lib/tokenVocabulary.mjs +240 -0
  15. package/dist-plugin/adjust/index.cjs +174 -23
  16. package/dist-plugin/adjust/index.js +68 -23
  17. package/dist-plugin/{chunk-2UX6EVVA.js → chunk-2YNERPXY.js} +1 -1
  18. package/dist-plugin/{chunk-NE6N66EE.js → chunk-GPIBU44G.js} +107 -1
  19. package/dist-plugin/{chunk-ZHPX7ZYQ.js → chunk-RFVYPNRO.js} +39 -1
  20. package/dist-plugin/generateColorsAndType/index.cjs +107 -1
  21. package/dist-plugin/generateColorsAndType/index.js +1 -1
  22. package/dist-plugin/index.cjs +146 -2
  23. package/dist-plugin/index.js +3 -3
  24. package/dist-plugin/migrateData/index.cjs +107 -1
  25. package/dist-plugin/migrateData/index.js +2 -2
  26. package/dist-plugin/tokensCssMigrations/index.cjs +39 -1
  27. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  28. package/package.json +3 -2
  29. package/src/app/site.css +4 -4
  30. package/src/editor/component-editor/ButtonEditor.svelte +71 -4
  31. package/src/editor/component-editor/CardEditor.svelte +6 -6
  32. package/src/editor/component-editor/DialogEditor.svelte +3 -3
  33. package/src/editor/component-editor/IconButtonEditor.svelte +68 -3
  34. package/src/editor/component-editor/ImageEditor.svelte +8 -8
  35. package/src/editor/component-editor/ImageLightboxEditor.svelte +12 -1
  36. package/src/editor/component-editor/MenuSelectEditor.svelte +63 -3
  37. package/src/editor/component-editor/SegmentedControlEditor.svelte +63 -3
  38. package/src/editor/component-editor/SideNavigationEditor.svelte +67 -3
  39. package/src/editor/component-editor/SliderEditor.svelte +186 -0
  40. package/src/editor/component-editor/TabBarEditor.svelte +63 -3
  41. package/src/editor/component-editor/registry.ts +10 -0
  42. package/src/editor/core/components/aliasKinds.ts +51 -28
  43. package/src/editor/core/sketch/sketchLayer.ts +16 -0
  44. package/src/editor/core/store/editorPersistence.ts +44 -1
  45. package/src/editor/core/store/editorRenderer.ts +2 -2
  46. package/src/editor/core/store/editorStore.ts +18 -18
  47. package/src/editor/core/store/editorTypes.ts +7 -6
  48. package/src/editor/core/themes/migrations/2026-09-01-gate-suffix-enabled.ts +31 -0
  49. package/src/editor/core/themes/migrations/2026-09-01-scrim-rename.ts +52 -0
  50. package/src/editor/core/themes/migrations/2026-09-01-tabbar-active-tint.ts +27 -0
  51. package/src/editor/core/themes/migrations/2026-09-01-tint-rename.ts +42 -0
  52. package/src/editor/core/themes/migrations/index.ts +16 -0
  53. package/src/editor/core/themes/slices/domainVars.ts +2 -2
  54. package/src/editor/core/themes/slices/washes.ts +107 -0
  55. package/src/editor/docs/content/editing-tokens.md +5 -3
  56. package/src/editor/docs/content.generated.ts +1 -1
  57. package/src/editor/index.ts +1 -1
  58. package/src/editor/pages/EditorShell.svelte +1 -1
  59. package/src/editor/ui/SurfacesTab.svelte +3 -3
  60. package/src/editor/ui/UITokenSelector.svelte +1 -0
  61. package/src/editor/ui/VariablesTab.svelte +2 -2
  62. package/src/editor/ui/sections/{OverlaysSection.svelte → WashesSection.svelte} +44 -43
  63. package/src/live-tokens/data/colors-and-type/autumn.json +6 -6
  64. package/src/live-tokens/data/colors-and-type/default.json +6 -6
  65. package/src/live-tokens/data/colors-and-type/halloween.json +6 -6
  66. package/src/live-tokens/data/colors-and-type/midnight-study.json +6 -6
  67. package/src/live-tokens/data/colors-and-type/ocean.json +6 -6
  68. package/src/live-tokens/data/colors-and-type/royal-velvet.json +6 -6
  69. package/src/live-tokens/data/colors-and-type/sketchy.json +6 -6
  70. package/src/live-tokens/data/colors-and-type/spring-meadow.json +6 -6
  71. package/src/live-tokens/data/colors-and-type/sunset.json +6 -6
  72. package/src/live-tokens/data/themes/autumn.json +81 -13
  73. package/src/live-tokens/data/themes/halloween.json +81 -13
  74. package/src/live-tokens/data/themes/midnight-study.json +81 -13
  75. package/src/live-tokens/data/themes/ocean.json +81 -13
  76. package/src/live-tokens/data/themes/royal-velvet.json +81 -13
  77. package/src/live-tokens/data/themes/sketchy.json +81 -13
  78. package/src/live-tokens/data/themes/spring-meadow.json +81 -13
  79. package/src/live-tokens/data/themes/sunset.json +81 -13
  80. package/src/live-tokens/data/tokens.generated.css +6 -6
  81. package/src/system/components/Button.svelte +28 -10
  82. package/src/system/components/Card.svelte +6 -6
  83. package/src/system/components/Dialog.svelte +3 -3
  84. package/src/system/components/IconButton.svelte +23 -7
  85. package/src/system/components/Image.svelte +6 -6
  86. package/src/system/components/MenuSelect.svelte +17 -1
  87. package/src/system/components/SegmentedControl.svelte +20 -2
  88. package/src/system/components/SideNavigation.svelte +22 -2
  89. package/src/system/components/Slider.svelte +348 -0
  90. package/src/system/components/TabBar.svelte +20 -2
  91. package/src/system/styles/CONVENTIONS.md +2 -2
  92. package/src/system/styles/tokens.css +12 -4
  93. package/template/package.json +3 -2
  94. package/template/src/pages/Home.svelte +1 -1
  95. package/src/editor/core/themes/slices/overlays.ts +0 -101
@@ -1,6 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupTokens, buildTypeGroupShareableContexts } from './scaffolding/buildTypeGroupTokens';
3
- import type { Token, TypeGroupConfig } from './scaffolding/types';
3
+ import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
4
 
5
5
  export const component = 'menuselect';
6
6
 
@@ -91,14 +91,55 @@
91
91
  ['--menuselect-selected-icon-size', 'selected item'],
92
92
  ['--menuselect-disabled-icon-size', 'disabled item'],
93
93
  ]);
94
+
95
+ // The hover tint is off unless a project turns it on: the gate holds a
96
+ // transparent layer, a no-op, until it holds the tint itself.
97
+ export const intrinsics: IntrinsicSpec[] = [
98
+ {
99
+ key: 'hover-tint',
100
+ variants: ['default'],
101
+ variable: () => '--menuselect-hover-tint-enabled',
102
+ values: ['var(--color-transparent)', 'var(--menuselect-hover-tint)'],
103
+ default: { default: 'var(--color-transparent)' },
104
+ },
105
+ ];
94
106
  </script>
95
107
 
96
108
  <script lang="ts">
97
109
  import MenuSelect from '../../system/components/MenuSelect.svelte';
98
110
  import VariantGroup from './scaffolding/VariantGroup.svelte';
99
111
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
100
- import { editorState } from '../core/store/editorStore';
112
+ import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
101
113
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
114
+ import Toggle from '../ui/Toggle.svelte';
115
+ import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
116
+
117
+ // One switch. Turning the tint on also stands each hover surface down to its
118
+ // own base, so hover is the tint alone rather than a swap wearing a wash.
119
+ // Written as aliases rather than gated in CSS: a component token's value is
120
+ // substituted at :root, so it can never name a per-variant private var.
121
+ // Off clears the overrides, returning each surface to its shipped default.
122
+ const HOVER_SWAPS: readonly (readonly [hover: string, base: string])[] = [
123
+ ['--menuselect-hover-surface', '--menuselect-default-surface'],
124
+ ];
125
+ const TINT_ON = 'var(--menuselect-hover-tint)';
126
+ const TINT_OFF = 'var(--color-transparent)';
127
+
128
+ let tintOn = $derived.by(() => {
129
+ const ref = $editorState.components.menuselect?.aliases['--menuselect-hover-tint-enabled'];
130
+ return (ref?.kind === 'literal' ? ref.value : TINT_OFF) === TINT_ON;
131
+ });
132
+
133
+ function setTintOn(checked: boolean) {
134
+ setComponentAlias('menuselect', '--menuselect-hover-tint-enabled', {
135
+ kind: 'literal',
136
+ value: checked ? TINT_ON : TINT_OFF,
137
+ });
138
+ for (const [hover, base] of HOVER_SWAPS) {
139
+ if (checked) setComponentAlias('menuselect', hover, { kind: 'token', name: base });
140
+ else clearComponentAlias('menuselect', hover);
141
+ }
142
+ }
102
143
 
103
144
  type Item = { value: string; label: string; icon?: string; disabled?: boolean };
104
145
  const items: Item[] = [
@@ -114,7 +155,12 @@
114
155
  let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
115
156
 
116
157
  let visibleStates = $derived(Object.fromEntries(
117
- Object.entries(states).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
158
+ Object.entries(states).map(([name, list]) => [
159
+ name,
160
+ withLinkedDisabled(list, linked.varSet).map((t) =>
161
+ tintOn && t.variable.endsWith('-hover-surface') ? { ...t, disabled: true } : t,
162
+ ),
163
+ ]),
118
164
  ) as Record<string, Token[]>);
119
165
  </script>
120
166
 
@@ -126,6 +172,20 @@
126
172
  {typeGroups}
127
173
  {component}
128
174
  >
175
+ {#snippet extraPropertyRows(stateName)}
176
+ {#if stateName === 'hover item'}
177
+ <div class="property-row" data-token-variables="--menuselect-hover-tint-enabled">
178
+ <span class="property-label">tint layer</span>
179
+ <Toggle checked={tintOn} onchange={setTintOn} />
180
+ </div>
181
+ {#if tintOn}
182
+ <div class="property-row">
183
+ <span class="property-label">tint color</span>
184
+ <UIPaletteSelector variable="--menuselect-hover-tint" {component} showNone={false} />
185
+ </div>
186
+ {/if}
187
+ {/if}
188
+ {/snippet}
129
189
  {#snippet canvasToolbarExtras()}
130
190
  <hr class="canvas-toolbar-divider" />
131
191
  <label class="show-icons-row">
@@ -1,6 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupTokens, buildTypeGroupShareableContexts } from './scaffolding/buildTypeGroupTokens';
3
- import type { Token, TypeGroupConfig } from './scaffolding/types';
3
+ import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
4
 
5
5
  export const component = 'segmentedcontrol';
6
6
 
@@ -123,14 +123,55 @@
123
123
  const linkableContexts = new Map<string, string>(
124
124
  buildTypeGroupShareableContexts(typeGroups),
125
125
  );
126
+
127
+ // The hover tint is off unless a project turns it on: the gate holds a
128
+ // transparent layer, a no-op, until it holds the tint itself.
129
+ export const intrinsics: IntrinsicSpec[] = [
130
+ {
131
+ key: 'hover-tint',
132
+ variants: ['default'],
133
+ variable: () => '--segmentedcontrol-hover-tint-enabled',
134
+ values: ['var(--color-transparent)', 'var(--segmentedcontrol-hover-tint)'],
135
+ default: { default: 'var(--color-transparent)' },
136
+ },
137
+ ];
126
138
  </script>
127
139
 
128
140
  <script lang="ts">
129
141
  import SegmentedControl from '../../system/components/SegmentedControl.svelte';
130
142
  import VariantGroup from './scaffolding/VariantGroup.svelte';
131
143
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
132
- import { editorState } from '../core/store/editorStore';
144
+ import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
133
145
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
146
+ import Toggle from '../ui/Toggle.svelte';
147
+ import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
148
+
149
+ // One switch. Turning the tint on also stands each hover surface down to its
150
+ // own base, so hover is the tint alone rather than a swap wearing a wash.
151
+ // Written as aliases rather than gated in CSS: a component token's value is
152
+ // substituted at :root, so it can never name a per-variant private var.
153
+ // Off clears the overrides, returning each surface to its shipped default.
154
+ const HOVER_SWAPS: readonly (readonly [hover: string, base: string])[] = [
155
+ ['--segmentedcontrol-option-hover-surface', '--color-transparent'],
156
+ ];
157
+ const TINT_ON = 'var(--segmentedcontrol-hover-tint)';
158
+ const TINT_OFF = 'var(--color-transparent)';
159
+
160
+ let tintOn = $derived.by(() => {
161
+ const ref = $editorState.components.segmentedcontrol?.aliases['--segmentedcontrol-hover-tint-enabled'];
162
+ return (ref?.kind === 'literal' ? ref.value : TINT_OFF) === TINT_ON;
163
+ });
164
+
165
+ function setTintOn(checked: boolean) {
166
+ setComponentAlias('segmentedcontrol', '--segmentedcontrol-hover-tint-enabled', {
167
+ kind: 'literal',
168
+ value: checked ? TINT_ON : TINT_OFF,
169
+ });
170
+ for (const [hover, base] of HOVER_SWAPS) {
171
+ if (checked) setComponentAlias('segmentedcontrol', hover, { kind: 'token', name: base });
172
+ else clearComponentAlias('segmentedcontrol', hover);
173
+ }
174
+ }
134
175
 
135
176
  type Segment = { value: string; label: string; icon?: string; disabled?: boolean };
136
177
  const segments: Segment[] = [
@@ -148,7 +189,12 @@
148
189
  let activeTypeGroups = $derived(previewSize === 'small' ? emptyTypeGroups : typeGroups);
149
190
 
150
191
  let visibleStates = $derived(Object.fromEntries(
151
- Object.entries(activeStates).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
192
+ Object.entries(activeStates).map(([name, list]) => [
193
+ name,
194
+ withLinkedDisabled(list, linked.varSet).map((t) =>
195
+ tintOn && t.variable.endsWith('-hover-surface') ? { ...t, disabled: true } : t,
196
+ ),
197
+ ]),
152
198
  ) as Record<string, Token[]>);
153
199
  </script>
154
200
 
@@ -160,6 +206,20 @@
160
206
  typeGroups={activeTypeGroups}
161
207
  {component}
162
208
  >
209
+ {#snippet extraPropertyRows(stateName)}
210
+ {#if stateName === 'hover option'}
211
+ <div class="property-row" data-token-variables="--segmentedcontrol-hover-tint-enabled">
212
+ <span class="property-label">tint layer</span>
213
+ <Toggle checked={tintOn} onchange={setTintOn} />
214
+ </div>
215
+ {#if tintOn}
216
+ <div class="property-row">
217
+ <span class="property-label">tint color</span>
218
+ <UIPaletteSelector variable="--segmentedcontrol-hover-tint" {component} showNone={false} />
219
+ </div>
220
+ {/if}
221
+ {/if}
222
+ {/snippet}
163
223
  {#snippet previewActions()}
164
224
  <label>
165
225
  <span>Size</span>
@@ -1,6 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
- import type { Token, TypeGroupConfig } from './scaffolding/types';
3
+ import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
4
 
5
5
  export const component = 'sidenavigation';
6
6
 
@@ -259,14 +259,59 @@
259
259
  ['--sidenavigation-panel-padding', 'panel'],
260
260
  ['--sidenavigation-title-label-border-width', 'title block'],
261
261
  ]);
262
+
263
+ // The hover tint is off unless a project turns it on: the gate holds a
264
+ // transparent layer, a no-op, until it holds the tint itself.
265
+ export const intrinsics: IntrinsicSpec[] = [
266
+ {
267
+ key: 'hover-tint',
268
+ variants: ['default'],
269
+ variable: () => '--sidenavigation-hover-tint-enabled',
270
+ values: ['var(--color-transparent)', 'var(--sidenavigation-hover-tint)'],
271
+ default: { default: 'var(--color-transparent)' },
272
+ },
273
+ ];
262
274
  </script>
263
275
 
264
276
  <script lang="ts">
265
277
  import SideNavigation, { type SideNavSection, type SideNavFooter } from '../../system/components/SideNavigation.svelte';
266
278
  import VariantGroup from './scaffolding/VariantGroup.svelte';
267
279
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
268
- import { editorState } from '../core/store/editorStore';
280
+ import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
269
281
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
282
+ import Toggle from '../ui/Toggle.svelte';
283
+ import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
284
+
285
+ // One switch. Turning the tint on also stands each hover surface down to its
286
+ // own base, so hover is the tint alone rather than a swap wearing a wash.
287
+ // Written as aliases rather than gated in CSS: a component token's value is
288
+ // substituted at :root, so it can never name a per-variant private var.
289
+ // Off clears the overrides, returning each surface to its shipped default.
290
+ const HOVER_SWAPS: readonly (readonly [hover: string, base: string])[] = [
291
+ ['--sidenavigation-title-hover-surface', '--sidenavigation-title-default-surface'],
292
+ ['--sidenavigation-toggle-hover-surface', '--sidenavigation-toggle-default-surface'],
293
+ ['--sidenavigation-section-hover-surface', '--sidenavigation-section-default-surface'],
294
+ ['--sidenavigation-item-hover-surface', '--sidenavigation-item-default-surface'],
295
+ ['--sidenavigation-footer-hover-surface', '--sidenavigation-footer-default-surface'],
296
+ ];
297
+ const TINT_ON = 'var(--sidenavigation-hover-tint)';
298
+ const TINT_OFF = 'var(--color-transparent)';
299
+
300
+ let tintOn = $derived.by(() => {
301
+ const ref = $editorState.components.sidenavigation?.aliases['--sidenavigation-hover-tint-enabled'];
302
+ return (ref?.kind === 'literal' ? ref.value : TINT_OFF) === TINT_ON;
303
+ });
304
+
305
+ function setTintOn(checked: boolean) {
306
+ setComponentAlias('sidenavigation', '--sidenavigation-hover-tint-enabled', {
307
+ kind: 'literal',
308
+ value: checked ? TINT_ON : TINT_OFF,
309
+ });
310
+ for (const [hover, base] of HOVER_SWAPS) {
311
+ if (checked) setComponentAlias('sidenavigation', hover, { kind: 'token', name: base });
312
+ else clearComponentAlias('sidenavigation', hover);
313
+ }
314
+ }
270
315
 
271
316
  const demoSections: SideNavSection[] = [
272
317
  {
@@ -296,7 +341,12 @@
296
341
  let previewOpen = $state(true);
297
342
 
298
343
  let visibleStates = $derived(Object.fromEntries(
299
- Object.entries(states).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
344
+ Object.entries(states).map(([name, list]) => [
345
+ name,
346
+ withLinkedDisabled(list, linked.varSet).map((t) =>
347
+ tintOn && t.variable.endsWith('-hover-surface') ? { ...t, disabled: true } : t,
348
+ ),
349
+ ]),
300
350
  ) as Record<string, Token[]>);
301
351
 
302
352
  // Map the active part/state to the demo's force-hover / force-active hooks
@@ -331,6 +381,20 @@
331
381
  {component}
332
382
  selectorLabel="Part"
333
383
  >
384
+ {#snippet extraPropertyRows(stateName)}
385
+ {#if stateName.endsWith(' / Hover')}
386
+ <div class="property-row" data-token-variables="--sidenavigation-hover-tint-enabled">
387
+ <span class="property-label">tint layer</span>
388
+ <Toggle checked={tintOn} onchange={setTintOn} />
389
+ </div>
390
+ {#if tintOn}
391
+ <div class="property-row">
392
+ <span class="property-label">tint color</span>
393
+ <UIPaletteSelector variable="--sidenavigation-hover-tint" {component} showNone={false} />
394
+ </div>
395
+ {/if}
396
+ {/if}
397
+ {/snippet}
334
398
  {#snippet children({ activeState })}
335
399
  {@const { forceHoverPart, forceActivePart } = deriveForce(activeState)}
336
400
  <div class="sn-preview-frame">
@@ -0,0 +1,186 @@
1
+ <script module lang="ts">
2
+ import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
+ import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import { sliderVariants } from '../../system/components/Slider.svelte';
5
+
6
+ export const component = 'slider';
7
+ const variants = sliderVariants;
8
+ type Variant = typeof variants[number];
9
+
10
+ // Geometry and colour are identical across the two variants by default, so
11
+ // every row is linkable: an edit to the single slider's track moves the range
12
+ // slider's track until a user unlinks them on purpose.
13
+ function variantDefaultTokens(v: Variant): Token[] {
14
+ return [
15
+ { label: 'surface', element: 'track', canBeLinked: true, groupKey: 'track-surface', variable: `--slider-${v}-track-surface` },
16
+ { label: 'border', element: 'track', canBeLinked: true, groupKey: 'track-border', variable: `--slider-${v}-track-border` },
17
+ { label: 'border width', element: 'track', canBeLinked: true, groupKey: 'track-border-width', variable: `--slider-${v}-track-border-width` },
18
+ { label: 'height', element: 'track', canBeLinked: true, groupKey: 'track-height', variable: `--slider-${v}-track-height` },
19
+ { label: 'corner radius', element: 'track', canBeLinked: true, groupKey: 'track-radius', variable: `--slider-${v}-track-radius` },
20
+ { label: 'color', element: 'fill', canBeLinked: true, groupKey: 'fill', variable: `--slider-${v}-fill` },
21
+ { label: 'surface', element: 'thumb', canBeLinked: true, groupKey: 'thumb-surface', variable: `--slider-${v}-thumb-surface` },
22
+ { label: 'border', element: 'thumb', canBeLinked: true, groupKey: 'thumb-border', variable: `--slider-${v}-thumb-border` },
23
+ { label: 'border width', element: 'thumb', canBeLinked: true, groupKey: 'thumb-border-width', variable: `--slider-${v}-thumb-border-width` },
24
+ { label: 'size', element: 'thumb', canBeLinked: true, groupKey: 'thumb-size', variable: `--slider-${v}-thumb-size` },
25
+ { label: 'corner radius', element: 'thumb', canBeLinked: true, groupKey: 'thumb-radius', variable: `--slider-${v}-thumb-radius` },
26
+ { label: 'shadow', element: 'thumb', canBeLinked: true, groupKey: 'thumb-shadow', variable: `--slider-${v}-thumb-shadow` },
27
+ ];
28
+ }
29
+
30
+ function variantHoverTokens(v: Variant): Token[] {
31
+ return [
32
+ { label: 'thumb surface', canBeLinked: true, groupKey: 'hover-thumb-surface', variable: `--slider-${v}-hover-thumb-surface` },
33
+ { label: 'thumb border', canBeLinked: true, groupKey: 'hover-thumb-border', variable: `--slider-${v}-hover-thumb-border` },
34
+ ];
35
+ }
36
+
37
+ function variantDisabledTokens(v: Variant): Token[] {
38
+ return [
39
+ { label: 'track surface', canBeLinked: true, groupKey: 'disabled-track-surface', variable: `--slider-${v}-disabled-track-surface` },
40
+ { label: 'fill color', canBeLinked: true, groupKey: 'disabled-fill', variable: `--slider-${v}-disabled-fill` },
41
+ { label: 'thumb surface', canBeLinked: true, groupKey: 'disabled-thumb-surface', variable: `--slider-${v}-disabled-thumb-surface` },
42
+ { label: 'thumb border', canBeLinked: true, groupKey: 'disabled-thumb-border', variable: `--slider-${v}-disabled-thumb-border` },
43
+ ];
44
+ }
45
+
46
+ // The label and its gap are shared by both variants, so their rows render
47
+ // once, under the single variant beside the type groups.
48
+ const labelGapToken: Token = { label: 'label gap', element: 'label', groupKey: 'label-gap', variable: '--slider-label-gap' };
49
+
50
+ function variantStates(v: Variant): Record<string, Token[]> {
51
+ return {
52
+ default: v === 'single' ? [...variantDefaultTokens(v), labelGapToken] : variantDefaultTokens(v),
53
+ hover: variantHoverTokens(v),
54
+ disabled: variantDisabledTokens(v),
55
+ };
56
+ }
57
+
58
+ // The label and value readout are shared by both variants, so they live in
59
+ // one type group outside the per-variant states.
60
+ const typeGroups: Record<string, TypeGroupConfig[]> = {
61
+ default: [
62
+ {
63
+ legend: '',
64
+ element: 'label',
65
+ colorVariable: '--slider-label',
66
+ familyVariable: '--slider-label-font-family',
67
+ sizeVariable: '--slider-label-font-size',
68
+ weightVariable: '--slider-label-font-weight',
69
+ lineHeightVariable: '--slider-label-line-height',
70
+ },
71
+ {
72
+ legend: '',
73
+ element: 'value',
74
+ colorVariable: '--slider-value',
75
+ familyVariable: '--slider-value-font-family',
76
+ sizeVariable: '--slider-value-font-size',
77
+ weightVariable: '--slider-value-font-weight',
78
+ lineHeightVariable: '--slider-value-line-height',
79
+ },
80
+ ],
81
+ };
82
+
83
+ const typeGroupTokens: Token[] = [
84
+ { label: 'font family', canBeLinked: true, groupKey: 'label-font-family', variable: '--slider-label-font-family' },
85
+ { label: 'font size', canBeLinked: true, groupKey: 'label-font-size', variable: '--slider-label-font-size' },
86
+ { label: 'font weight', canBeLinked: true, groupKey: 'label-font-weight', variable: '--slider-label-font-weight' },
87
+ { label: 'line height', canBeLinked: true, groupKey: 'label-line-height', variable: '--slider-label-line-height' },
88
+ { label: 'font family', canBeLinked: true, groupKey: 'value-font-family', variable: '--slider-value-font-family' },
89
+ { label: 'font size', canBeLinked: true, groupKey: 'value-font-size', variable: '--slider-value-font-size' },
90
+ { label: 'font weight', canBeLinked: true, groupKey: 'value-font-weight', variable: '--slider-value-font-weight' },
91
+ { label: 'line height', canBeLinked: true, groupKey: 'value-line-height', variable: '--slider-value-line-height' },
92
+ ];
93
+
94
+ export const allTokens: Token[] = [
95
+ ...variants.flatMap((v) => Object.values(variantStates(v)).flat()),
96
+ ...buildTypeGroupColorTokens(typeGroups, { component }),
97
+ ...typeGroupTokens,
98
+ ];
99
+
100
+ const linkableContexts = new Map<string, string>([
101
+ ...variants.flatMap((v) =>
102
+ Object.entries(variantStates(v)).flatMap(([state, list]) =>
103
+ list.filter((t) => t.canBeLinked).map((t) => [t.variable, `${v} ${state}`] as [string, string]),
104
+ ),
105
+ ),
106
+ ...typeGroupTokens
107
+ .filter((t) => t.canBeLinked)
108
+ .map((t) => [t.variable, t.variable.includes('-label-') ? 'label' : 'value'] as [string, string]),
109
+ ]);
110
+
111
+ const variantOptions = variants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
112
+ </script>
113
+
114
+ <script lang="ts">
115
+ import Slider from '../../system/components/Slider.svelte';
116
+ import VariantGroup from './scaffolding/VariantGroup.svelte';
117
+ import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
118
+ import { editorState } from '../core/store/editorStore';
119
+ import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
120
+ import { buildSiblings } from './scaffolding/siblings';
121
+
122
+ let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
123
+ let visibleVariantStates = $derived((v: Variant) => Object.fromEntries(
124
+ Object.entries(variantStates(v)).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
125
+ ) as Record<string, Token[]>);
126
+
127
+ let value = $state(40);
128
+ let low = $state(20);
129
+ let high = $state(70);
130
+
131
+ function previewProps(activeState: string) {
132
+ return {
133
+ disabled: activeState === 'disabled',
134
+ forceClass: activeState === 'hover' ? 'force-hover' : '',
135
+ };
136
+ }
137
+ </script>
138
+
139
+ <ComponentEditorBase
140
+ {component}
141
+ title="Slider"
142
+ description="A number chosen by position on a track. Single moves one thumb to a value; range moves two thumbs to a low and a high bound."
143
+ tokens={allTokens}
144
+ {linked}
145
+ variants={variantOptions}
146
+ >
147
+ {#each variants as v}
148
+ <VariantGroup
149
+ name={v}
150
+ title={v.charAt(0).toUpperCase() + v.slice(1)}
151
+ states={visibleVariantStates(v)}
152
+ typeGroups={v === 'single' ? typeGroups : undefined}
153
+ {component}
154
+ siblings={buildSiblings(variants, v, variantStates)}
155
+ >
156
+ {#snippet children({ activeState })}
157
+ {@const p = previewProps(activeState)}
158
+ <div class="slider-demo-stack">
159
+ {#if v === 'range'}
160
+ <Slider
161
+ variant="range"
162
+ label="Price"
163
+ {low}
164
+ {high}
165
+ disabled={p.disabled}
166
+ class={p.forceClass}
167
+ onrangechange={(l, h) => { low = l; high = h; }}
168
+ />
169
+ {:else}
170
+ <Slider label="Volume" {value} disabled={p.disabled} class={p.forceClass} onchange={(n) => (value = n)} />
171
+ {/if}
172
+ </div>
173
+ {/snippet}
174
+ </VariantGroup>
175
+ {/each}
176
+ </ComponentEditorBase>
177
+
178
+ <style>
179
+ .slider-demo-stack {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: var(--space-12);
183
+ width: 100%;
184
+ max-width: 32rem;
185
+ }
186
+ </style>
@@ -1,6 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
- import type { Token, TypeGroupConfig } from './scaffolding/types';
3
+ import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
4
 
5
5
  export const component = 'tabbar';
6
6
 
@@ -81,14 +81,55 @@
81
81
  [`--tabbar-${s}-text-line-height`, `${s} tab`] as const,
82
82
  ]),
83
83
  ]);
84
+
85
+ // The hover tint is off unless a project turns it on: the gate holds a
86
+ // transparent layer, a no-op, until it holds the tint itself.
87
+ export const intrinsics: IntrinsicSpec[] = [
88
+ {
89
+ key: 'hover-tint',
90
+ variants: ['default'],
91
+ variable: () => '--tabbar-hover-tint-enabled',
92
+ values: ['var(--color-transparent)', 'var(--tabbar-hover-tint)'],
93
+ default: { default: 'var(--color-transparent)' },
94
+ },
95
+ ];
84
96
  </script>
85
97
 
86
98
  <script lang="ts">
87
99
  import TabBar from '../../system/components/TabBar.svelte';
88
100
  import VariantGroup from './scaffolding/VariantGroup.svelte';
89
101
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
90
- import { editorState } from '../core/store/editorStore';
102
+ import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
91
103
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
104
+ import Toggle from '../ui/Toggle.svelte';
105
+ import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
106
+
107
+ // One switch. Turning the tint on also stands each hover surface down to its
108
+ // own base, so hover is the tint alone rather than a swap wearing a wash.
109
+ // Written as aliases rather than gated in CSS: a component token's value is
110
+ // substituted at :root, so it can never name a per-variant private var.
111
+ // Off clears the overrides, returning each surface to its shipped default.
112
+ const HOVER_SWAPS: readonly (readonly [hover: string, base: string])[] = [
113
+ ['--tabbar-hover-surface', '--tabbar-default-surface'],
114
+ ];
115
+ const TINT_ON = 'var(--tabbar-hover-tint)';
116
+ const TINT_OFF = 'var(--color-transparent)';
117
+
118
+ let tintOn = $derived.by(() => {
119
+ const ref = $editorState.components.tabbar?.aliases['--tabbar-hover-tint-enabled'];
120
+ return (ref?.kind === 'literal' ? ref.value : TINT_OFF) === TINT_ON;
121
+ });
122
+
123
+ function setTintOn(checked: boolean) {
124
+ setComponentAlias('tabbar', '--tabbar-hover-tint-enabled', {
125
+ kind: 'literal',
126
+ value: checked ? TINT_ON : TINT_OFF,
127
+ });
128
+ for (const [hover, base] of HOVER_SWAPS) {
129
+ if (checked) setComponentAlias('tabbar', hover, { kind: 'token', name: base });
130
+ else clearComponentAlias('tabbar', hover);
131
+ }
132
+ }
92
133
 
93
134
  let selectedDemoTab = $state('overview');
94
135
  const demoTabs = [
@@ -101,7 +142,12 @@
101
142
  let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
102
143
 
103
144
  let visibleStates = $derived(Object.fromEntries(
104
- Object.entries(states).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
145
+ Object.entries(states).map(([name, list]) => [
146
+ name,
147
+ withLinkedDisabled(list, linked.varSet).map((t) =>
148
+ tintOn && t.variable.endsWith('-hover-surface') ? { ...t, disabled: true } : t,
149
+ ),
150
+ ]),
105
151
  ) as Record<string, Token[]>);
106
152
  </script>
107
153
 
@@ -114,6 +160,20 @@
114
160
  {component}
115
161
 
116
162
  >
163
+ {#snippet extraPropertyRows(stateName)}
164
+ {#if stateName === 'hover tab'}
165
+ <div class="property-row" data-token-variables="--tabbar-hover-tint-enabled">
166
+ <span class="property-label">tint layer</span>
167
+ <Toggle checked={tintOn} onchange={setTintOn} />
168
+ </div>
169
+ {#if tintOn}
170
+ <div class="property-row">
171
+ <span class="property-label">tint color</span>
172
+ <UIPaletteSelector variable="--tabbar-hover-tint" {component} showNone={false} />
173
+ </div>
174
+ {/if}
175
+ {/if}
176
+ {/snippet}
117
177
  {#snippet children({ activeState })}
118
178
  {@const forceClass = activeState === 'hover tab' ? 'force-hover' : ''}
119
179
  <TabBar tabs={demoTabs} selectedTab={selectedDemoTab} class={forceClass} on:tabChange={(e) => (selectedDemoTab = e.detail)} />
@@ -19,6 +19,7 @@ import MenuSelectEditor, { allTokens as menuSelectTokens } from './MenuSelectEdi
19
19
  import NotificationEditor, { allTokens as notificationTokens } from './NotificationEditor.svelte';
20
20
  import PanelEditor, { allTokens as panelTokens } from './PanelEditor.svelte';
21
21
  import ProgressBarEditor, { allTokens as progressBarTokens } from './ProgressBarEditor.svelte';
22
+ import SliderEditor, { allTokens as sliderTokens } from './SliderEditor.svelte';
22
23
  import RadioButtonEditor, { allTokens as radioButtonTokens } from './RadioButtonEditor.svelte';
23
24
  import SectionDividerEditor, { allTokens as sectionDividerTokens, intrinsics as sectionDividerIntrinsics } from './SectionDividerEditor.svelte';
24
25
  import SegmentedControlEditor, { allTokens as segmentedControlTokens } from './SegmentedControlEditor.svelte';
@@ -320,6 +321,15 @@ const builtInRegistry: Readonly<Record<BuiltInComponentId, RegistryEntry>> = Obj
320
321
  schema: progressBarTokens,
321
322
  origin: 'system',
322
323
  },
324
+ slider: {
325
+ id: 'slider',
326
+ label: 'Slider',
327
+ icon: 'fas fa-sliders',
328
+ sourceFile: 'src/system/components/Slider.svelte',
329
+ editorComponent: SliderEditor,
330
+ schema: sliderTokens,
331
+ origin: 'system',
332
+ },
323
333
  });
324
334
 
325
335
  /** Mutable map of consumer-registered components, populated by `registerComponent()`. */