@motion-proto/live-tokens 0.9.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 (49) hide show
  1. package/README.md +50 -29
  2. package/dist-plugin/index.cjs +177 -125
  3. package/dist-plugin/index.d.cts +3 -2
  4. package/dist-plugin/index.d.ts +3 -2
  5. package/dist-plugin/index.js +177 -125
  6. package/package.json +4 -1
  7. package/src/editor/component-editor/BadgeEditor.svelte +44 -42
  8. package/src/editor/component-editor/ButtonEditor.svelte +224 -0
  9. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +1 -7
  10. package/src/editor/component-editor/CornerBadgeEditor.svelte +44 -34
  11. package/src/editor/component-editor/ImageLightboxEditor.svelte +58 -0
  12. package/src/editor/component-editor/InputEditor.svelte +272 -0
  13. package/src/editor/component-editor/NotificationEditor.svelte +44 -65
  14. package/src/editor/component-editor/ProgressBarEditor.svelte +71 -87
  15. package/src/editor/component-editor/SegmentedControlEditor.svelte +98 -37
  16. package/src/editor/component-editor/SideNavigationEditor.svelte +342 -0
  17. package/src/editor/component-editor/registry.ts +35 -2
  18. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +3 -2
  19. package/src/editor/component-editor/scaffolding/StateBlock.svelte +9 -10
  20. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +60 -36
  21. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +38 -1
  22. package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -1
  23. package/src/editor/component-editor/scaffolding/siblings.ts +2 -2
  24. package/src/editor/component-editor/scaffolding/types.ts +2 -1
  25. package/src/editor/core/components/componentConfigService.ts +7 -6
  26. package/src/editor/core/manifests/manifestService.ts +5 -4
  27. package/src/editor/core/storage/apiBase.ts +15 -0
  28. package/src/editor/core/storage/files/versionedFileResourceClient.ts +1 -1
  29. package/src/editor/core/themes/migrations/2026-05-24-collapsiblesection-drop-active-state.ts +28 -0
  30. package/src/editor/core/themes/migrations/2026-05-24-progressbar-collapse-variants.ts +41 -0
  31. package/src/editor/core/themes/migrations/2026-05-24-promote-state-shared-tokens.ts +59 -0
  32. package/src/editor/core/themes/migrations/2026-05-24-segmentedcontrol-divider-inset.ts +29 -0
  33. package/src/editor/core/themes/migrations/2026-05-25-cornerbadge-flatten-variants.ts +46 -0
  34. package/src/editor/core/themes/migrations/index.ts +10 -0
  35. package/src/editor/core/themes/themeInit.ts +3 -2
  36. package/src/editor/core/themes/themeService.ts +3 -2
  37. package/src/editor/ui/UIEasingSelector.svelte +240 -0
  38. package/src/editor/ui/variantScales.ts +34 -0
  39. package/src/system/components/Button.svelte +34 -85
  40. package/src/system/components/CollapsibleSection.svelte +1 -48
  41. package/src/system/components/CornerBadge.svelte +72 -138
  42. package/src/system/components/ImageLightbox.svelte +578 -0
  43. package/src/system/components/Input.svelte +387 -0
  44. package/src/system/components/ProgressBar.svelte +62 -258
  45. package/src/system/components/SegmentedControl.svelte +81 -15
  46. package/src/system/components/SideNavigation.svelte +777 -0
  47. package/src/system/styles/tokens.css +43 -0
  48. package/src/system/styles/tokens.generated.css +4 -183
  49. package/src/editor/component-editor/StandardButtonsEditor.svelte +0 -190
@@ -45,6 +45,11 @@ import { componentMigration_2026_05_19_sectiondividerRichGradient } from './2026
45
45
  import { componentMigration_2026_05_20_sectiondividerSlimVariants } from './2026-05-20-sectiondivider-slim-variants';
46
46
  import { componentMigration_2026_05_21_sectiondividerSpacingToPadding } from './2026-05-21-sectiondivider-spacing-to-padding';
47
47
  import { componentMigration_2026_05_22_sectiondividerIntrinsicsToCss } from './2026-05-22-sectiondivider-intrinsics-to-css';
48
+ import { componentMigration_2026_05_24_segmentedcontrolDividerInset } from './2026-05-24-segmentedcontrol-divider-inset';
49
+ import { componentMigration_2026_05_24_promoteStateSharedTokens } from './2026-05-24-promote-state-shared-tokens';
50
+ import { componentMigration_2026_05_24_progressbarCollapseVariants } from './2026-05-24-progressbar-collapse-variants';
51
+ import { componentMigration_2026_05_24_collapsiblesectionDropActiveState } from './2026-05-24-collapsiblesection-drop-active-state';
52
+ import { componentMigration_2026_05_25_cornerbadgeFlattenVariants } from './2026-05-25-cornerbadge-flatten-variants';
48
53
 
49
54
  /**
50
55
  * Registered migrations. Order in this array does not matter — the runner
@@ -64,6 +69,11 @@ export const MIGRATIONS: Migration[] = [
64
69
  componentMigration_2026_05_20_sectiondividerSlimVariants,
65
70
  componentMigration_2026_05_21_sectiondividerSpacingToPadding,
66
71
  componentMigration_2026_05_22_sectiondividerIntrinsicsToCss,
72
+ componentMigration_2026_05_24_segmentedcontrolDividerInset,
73
+ componentMigration_2026_05_24_promoteStateSharedTokens,
74
+ componentMigration_2026_05_24_progressbarCollapseVariants,
75
+ componentMigration_2026_05_24_collapsiblesectionDropActiveState,
76
+ componentMigration_2026_05_25_cornerbadgeFlattenVariants,
67
77
  ];
68
78
 
69
79
  function countFor(kind: 'theme' | 'component-config'): number {
@@ -5,6 +5,7 @@ import { applyFontSources, applyFontStacks } from '../fonts/fontLoader';
5
5
  import { loadFromFile, seedComponentsFromApi } from '../store/editorStore';
6
6
  import { getActiveComponentConfig } from '../components/componentConfigService';
7
7
  import { safeFetch } from '../storage/storage';
8
+ import { API_BASE } from '../storage/apiBase';
8
9
 
9
10
  interface ComponentSummaryDto {
10
11
  name: string;
@@ -34,7 +35,7 @@ interface ListComponentsDto {
34
35
  * `safeFetch` (instead of empty try/catch) to make the silence intentional.
35
36
  */
36
37
  export async function initializeTheme(): Promise<void> {
37
- const theme = await safeFetch<Theme>('/api/themes/active');
38
+ const theme = await safeFetch<Theme>(`${API_BASE}/themes/active`);
38
39
  if (theme) {
39
40
  migrateThemeFonts(theme);
40
41
  loadFromFile(theme);
@@ -48,7 +49,7 @@ export async function initializeTheme(): Promise<void> {
48
49
  activeFileName.set(fileName);
49
50
  }
50
51
 
51
- const list = await safeFetch<ListComponentsDto>('/api/component-configs');
52
+ const list = await safeFetch<ListComponentsDto>(`${API_BASE}/component-configs`);
52
53
  if (list && Array.isArray(list.components)) {
53
54
  const configs: Record<
54
55
  string,
@@ -5,6 +5,7 @@ import {
5
5
  versionedFileResource,
6
6
  sanitizeFileName as sanitizeFileNameImpl,
7
7
  } from '../storage/files/versionedFileResourceClient';
8
+ import { API_BASE } from '../storage/apiBase';
8
9
  import { loadFromFile as loadEditorState, toTheme, markSaved } from '../store/editorStore';
9
10
  import { activeFileName } from '../store/editorConfigStore';
10
11
  import { applyFontSources, applyFontStacks } from '../fonts/fontLoader';
@@ -12,7 +13,7 @@ import { migrateThemeFonts } from '../fonts/fontMigration';
12
13
 
13
14
  // ── API helpers ──────────────────────────────────────────────
14
15
  //
15
- // All theme CRUD goes through `versionedFileResource('/api/themes')` —
16
+ // All theme CRUD goes through `versionedFileResource(`${API_BASE}/themes`)` —
16
17
  // shared with `componentConfigService`'s per-component clients. Theme-specific
17
18
  // response shapes (ThemeMeta list payload, ProductionInfo) are layered on top
18
19
  // via the generic type parameters.
@@ -25,7 +26,7 @@ export interface ProductionInfo {
25
26
  }
26
27
 
27
28
  const themeResource = versionedFileResource<Theme, ThemeMeta, ProductionInfo>({
28
- baseUrl: '/api/themes',
29
+ baseUrl: `${API_BASE}/themes`,
29
30
  });
30
31
 
31
32
  export async function listThemes(): Promise<ThemeMeta[]> {
@@ -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
+ };
@@ -87,15 +87,9 @@
87
87
  --button-primary-hover-surface: var(--surface-brand-higher);
88
88
  --button-primary-hover-text: var(--text-primary);
89
89
  --button-primary-hover-border: var(--border-brand-strong);
90
- --button-primary-hover-border-width: var(--border-width-1);
91
- --button-primary-hover-radius: var(--radius-md);
92
- --button-primary-hover-padding: var(--space-8);
93
90
  --button-primary-disabled-surface: var(--color-neutral-700);
94
91
  --button-primary-disabled-text: var(--text-tertiary);
95
92
  --button-primary-disabled-border: var(--border-neutral-faint);
96
- --button-primary-disabled-border-width: var(--border-width-1);
97
- --button-primary-disabled-radius: var(--radius-md);
98
- --button-primary-disabled-padding: var(--space-8);
99
93
  --button-primary-icon-size: var(--icon-size-sm);
100
94
 
101
95
  /* Secondary */
@@ -112,15 +106,9 @@
112
106
  --button-secondary-hover-surface: var(--surface-neutral-higher);
113
107
  --button-secondary-hover-text: var(--text-primary);
114
108
  --button-secondary-hover-border: var(--border-neutral-strong);
115
- --button-secondary-hover-border-width: var(--border-width-1);
116
- --button-secondary-hover-radius: var(--radius-md);
117
- --button-secondary-hover-padding: var(--space-8);
118
109
  --button-secondary-disabled-surface: var(--color-neutral-700);
119
110
  --button-secondary-disabled-text: var(--text-tertiary);
120
111
  --button-secondary-disabled-border: var(--border-neutral-faint);
121
- --button-secondary-disabled-border-width: var(--border-width-1);
122
- --button-secondary-disabled-radius: var(--radius-md);
123
- --button-secondary-disabled-padding: var(--space-8);
124
112
  --button-secondary-icon-size: var(--icon-size-sm);
125
113
 
126
114
  /* Outline */
@@ -137,16 +125,10 @@
137
125
  --button-outline-hover-surface: var(--surface-neutral-lower);
138
126
  --button-outline-hover-text: var(--text-primary);
139
127
  --button-outline-hover-border: var(--border-neutral-strong);
140
- --button-outline-hover-border-width: var(--border-width-1);
141
- --button-outline-hover-radius: var(--radius-md);
142
- --button-outline-hover-padding: var(--space-8);
143
128
  --button-outline-active-surface: var(--hover);
144
129
  --button-outline-disabled-surface: var(--color-transparent);
145
130
  --button-outline-disabled-text: var(--text-tertiary);
146
131
  --button-outline-disabled-border: var(--border-neutral-faint);
147
- --button-outline-disabled-border-width: var(--border-width-1);
148
- --button-outline-disabled-radius: var(--radius-md);
149
- --button-outline-disabled-padding: var(--space-8);
150
132
  --button-outline-icon-size: var(--icon-size-sm);
151
133
 
152
134
  /* Success */
@@ -163,15 +145,9 @@
163
145
  --button-success-hover-surface: var(--surface-success-higher);
164
146
  --button-success-hover-text: var(--text-primary);
165
147
  --button-success-hover-border: var(--border-success-strong);
166
- --button-success-hover-border-width: var(--border-width-2);
167
- --button-success-hover-radius: var(--radius-md);
168
- --button-success-hover-padding: var(--space-8);
169
148
  --button-success-disabled-surface: var(--color-neutral-700);
170
149
  --button-success-disabled-text: var(--text-tertiary);
171
150
  --button-success-disabled-border: var(--border-neutral-faint);
172
- --button-success-disabled-border-width: var(--border-width-2);
173
- --button-success-disabled-radius: var(--radius-md);
174
- --button-success-disabled-padding: var(--space-8);
175
151
  --button-success-icon-size: var(--icon-size-sm);
176
152
 
177
153
  /* Danger */
@@ -188,15 +164,9 @@
188
164
  --button-danger-hover-surface: var(--surface-danger-high);
189
165
  --button-danger-hover-text: var(--text-primary);
190
166
  --button-danger-hover-border: var(--border-danger-medium);
191
- --button-danger-hover-border-width: var(--border-width-2);
192
- --button-danger-hover-radius: var(--radius-md);
193
- --button-danger-hover-padding: var(--space-8);
194
167
  --button-danger-disabled-surface: var(--color-neutral-700);
195
168
  --button-danger-disabled-text: var(--text-tertiary);
196
169
  --button-danger-disabled-border: var(--border-neutral-faint);
197
- --button-danger-disabled-border-width: var(--border-width-2);
198
- --button-danger-disabled-radius: var(--radius-md);
199
- --button-danger-disabled-padding: var(--space-8);
200
170
  --button-danger-icon-size: var(--icon-size-sm);
201
171
 
202
172
  /* Warning */
@@ -213,16 +183,20 @@
213
183
  --button-warning-hover-surface: var(--surface-warning-high);
214
184
  --button-warning-hover-text: var(--text-primary);
215
185
  --button-warning-hover-border: var(--border-warning-medium);
216
- --button-warning-hover-border-width: var(--border-width-2);
217
- --button-warning-hover-radius: var(--radius-md);
218
- --button-warning-hover-padding: var(--space-8);
219
186
  --button-warning-disabled-surface: var(--color-neutral-700);
220
187
  --button-warning-disabled-text: var(--text-tertiary);
221
188
  --button-warning-disabled-border: var(--border-neutral-faint);
222
- --button-warning-disabled-border-width: var(--border-width-2);
223
- --button-warning-disabled-radius: var(--radius-md);
224
- --button-warning-disabled-padding: var(--space-8);
225
189
  --button-warning-icon-size: var(--icon-size-sm);
190
+
191
+ /* Small size — shared across all variants. The `.small` rule below reads
192
+ these tokens directly (no per-variant rebind needed, since small
193
+ currently looks the same regardless of variant). Per-side padding
194
+ overrides power split-padding edits at small. */
195
+ --button-small-padding: var(--space-6);
196
+ --button-small-text-font-size: var(--font-size-xs);
197
+ --button-small-text-font-weight: var(--font-weight-normal);
198
+ --button-small-text-line-height: var(--line-height-sm);
199
+ --button-small-icon-size: var(--font-size-xs);
226
200
  }
227
201
 
228
202
  .button {
@@ -298,9 +272,7 @@
298
272
  &:hover:not(:disabled),
299
273
  &.force-hover:not(:disabled) {
300
274
  background: var(--button-primary-hover-surface);
301
- border: var(--button-primary-hover-border-width) solid var(--button-primary-hover-border);
302
- border-radius: var(--button-primary-hover-radius);
303
- @include themed-padding(--button-primary-hover-padding, $h: 2);
275
+ border-color: var(--button-primary-hover-border);
304
276
  color: var(--button-primary-hover-text);
305
277
  }
306
278
 
@@ -310,9 +282,7 @@
310
282
 
311
283
  &:disabled {
312
284
  background: var(--button-primary-disabled-surface);
313
- border: var(--button-primary-disabled-border-width) solid var(--button-primary-disabled-border);
314
- border-radius: var(--button-primary-disabled-radius);
315
- @include themed-padding(--button-primary-disabled-padding, $h: 2);
285
+ border-color: var(--button-primary-disabled-border);
316
286
  color: var(--button-primary-disabled-text);
317
287
  }
318
288
  }
@@ -340,17 +310,13 @@
340
310
  &:hover:not(:disabled),
341
311
  &.force-hover:not(:disabled) {
342
312
  background: var(--button-secondary-hover-surface);
343
- border: var(--button-secondary-hover-border-width) solid var(--button-secondary-hover-border);
344
- border-radius: var(--button-secondary-hover-radius);
345
- @include themed-padding(--button-secondary-hover-padding, $h: 2);
313
+ border-color: var(--button-secondary-hover-border);
346
314
  color: var(--button-secondary-hover-text);
347
315
  }
348
316
 
349
317
  &:disabled {
350
318
  background: var(--button-secondary-disabled-surface);
351
- border: var(--button-secondary-disabled-border-width) solid var(--button-secondary-disabled-border);
352
- border-radius: var(--button-secondary-disabled-radius);
353
- @include themed-padding(--button-secondary-disabled-padding, $h: 2);
319
+ border-color: var(--button-secondary-disabled-border);
354
320
  color: var(--button-secondary-disabled-text);
355
321
  }
356
322
  }
@@ -378,9 +344,7 @@
378
344
  &:hover:not(:disabled),
379
345
  &.force-hover:not(:disabled) {
380
346
  background: var(--button-outline-hover-surface);
381
- border: var(--button-outline-hover-border-width) solid var(--button-outline-hover-border);
382
- border-radius: var(--button-outline-hover-radius);
383
- @include themed-padding(--button-outline-hover-padding, $h: 2);
347
+ border-color: var(--button-outline-hover-border);
384
348
  color: var(--button-outline-hover-text);
385
349
  }
386
350
 
@@ -390,9 +354,7 @@
390
354
 
391
355
  &:disabled {
392
356
  background: var(--button-outline-disabled-surface);
393
- border: var(--button-outline-disabled-border-width) solid var(--button-outline-disabled-border);
394
- border-radius: var(--button-outline-disabled-radius);
395
- @include themed-padding(--button-outline-disabled-padding, $h: 2);
357
+ border-color: var(--button-outline-disabled-border);
396
358
  color: var(--button-outline-disabled-text);
397
359
  }
398
360
  }
@@ -420,17 +382,13 @@
420
382
  &:hover:not(:disabled),
421
383
  &.force-hover:not(:disabled) {
422
384
  background: var(--button-success-hover-surface);
423
- border: var(--button-success-hover-border-width) solid var(--button-success-hover-border);
424
- border-radius: var(--button-success-hover-radius);
425
- @include themed-padding(--button-success-hover-padding, $h: 2);
385
+ border-color: var(--button-success-hover-border);
426
386
  color: var(--button-success-hover-text);
427
387
  }
428
388
 
429
389
  &:disabled {
430
390
  background: var(--button-success-disabled-surface);
431
- border: var(--button-success-disabled-border-width) solid var(--button-success-disabled-border);
432
- border-radius: var(--button-success-disabled-radius);
433
- @include themed-padding(--button-success-disabled-padding, $h: 2);
391
+ border-color: var(--button-success-disabled-border);
434
392
  color: var(--button-success-disabled-text);
435
393
  }
436
394
  }
@@ -458,17 +416,13 @@
458
416
  &:hover:not(:disabled),
459
417
  &.force-hover:not(:disabled) {
460
418
  background: var(--button-danger-hover-surface);
461
- border: var(--button-danger-hover-border-width) solid var(--button-danger-hover-border);
462
- border-radius: var(--button-danger-hover-radius);
463
- @include themed-padding(--button-danger-hover-padding, $h: 2);
419
+ border-color: var(--button-danger-hover-border);
464
420
  color: var(--button-danger-hover-text);
465
421
  }
466
422
 
467
423
  &:disabled {
468
424
  background: var(--button-danger-disabled-surface);
469
- border: var(--button-danger-disabled-border-width) solid var(--button-danger-disabled-border);
470
- border-radius: var(--button-danger-disabled-radius);
471
- @include themed-padding(--button-danger-disabled-padding, $h: 2);
425
+ border-color: var(--button-danger-disabled-border);
472
426
  color: var(--button-danger-disabled-text);
473
427
  }
474
428
  }
@@ -496,37 +450,32 @@
496
450
  &:hover:not(:disabled),
497
451
  &.force-hover:not(:disabled) {
498
452
  background: var(--button-warning-hover-surface);
499
- border: var(--button-warning-hover-border-width) solid var(--button-warning-hover-border);
500
- border-radius: var(--button-warning-hover-radius);
501
- @include themed-padding(--button-warning-hover-padding, $h: 2);
453
+ border-color: var(--button-warning-hover-border);
502
454
  color: var(--button-warning-hover-text);
503
455
  }
504
456
 
505
457
  &:disabled {
506
458
  background: var(--button-warning-disabled-surface);
507
- border: var(--button-warning-disabled-border-width) solid var(--button-warning-disabled-border);
508
- border-radius: var(--button-warning-disabled-radius);
509
- @include themed-padding(--button-warning-disabled-padding, $h: 2);
459
+ border-color: var(--button-warning-disabled-border);
510
460
  color: var(--button-warning-disabled-text);
511
461
  }
512
462
  }
513
463
 
514
- // Small size modifier (applies to any variant).
515
- // Declared after variants so equal-specificity rules win on source order;
516
- // :hover/.force-hover/:disabled are listed so variant state-padding
517
- // doesn't clobber .small in those states.
518
- &.small,
519
- &.small:hover:not(:disabled),
520
- &.small.force-hover:not(:disabled),
521
- &.small:disabled {
522
- padding: var(--space-6) var(--space-12);
523
- font-size: var(--font-size-xs);
524
- font-weight: var(--font-weight-normal);
525
- line-height: var(--line-height-sm);
464
+ // Small size modifier (applies to any variant). Variants only set padding
465
+ // once in their base rule and never override per-state, so a single
466
+ // selector here is enough — `.small` declared after the variant block
467
+ // wins on source order. themed-padding with $h: 2 mirrors the variant
468
+ // base rule; per-side `--button-small-padding-*` tokens (registered by
469
+ // the editor) feed split edits through the mixin.
470
+ &.small {
471
+ @include themed-padding(--button-small-padding, $h: 2);
472
+ font-size: var(--button-small-text-font-size);
473
+ font-weight: var(--button-small-text-font-weight);
474
+ line-height: var(--button-small-text-line-height);
526
475
  }
527
476
 
528
477
  &.small :global(i) {
529
- font-size: var(--font-size-xs);
478
+ font-size: var(--button-small-icon-size);
530
479
  font-weight: var(--font-weight-semibold);
531
480
  }
532
481