@motion-proto/live-tokens 0.15.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.15.0",
3
+ "version": "0.16.2",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 6/7.",
6
6
  "keywords": [
@@ -39,16 +39,22 @@
39
39
  ];
40
40
 
41
41
  // --- Title --------------------------------------------------------------
42
+ // Title is a flex card containing the label box + toggle box. Per-state
43
+ // tokens drive the card chrome (surface, border, padding); stateless gap
44
+ // and radius live in `titleLayoutTokens` since they don't vary across
45
+ // default/hover/active.
42
46
  function titleStateTokens(s: StatefulState): Token[] {
43
47
  return [
44
48
  { label: 'surface color', groupKey: 'title-surface', variable: `--sidenavigation-title-${s}-surface` },
45
- { label: 'divider color', groupKey: 'title-border', variable: `--sidenavigation-title-${s}-border` },
46
- { label: 'divider width', canBeLinked: true, groupKey: 'title-border-width', variable: `--sidenavigation-title-${s}-border-width` },
49
+ { label: 'border color', groupKey: 'title-border', variable: `--sidenavigation-title-${s}-border` },
50
+ { label: 'border width', canBeLinked: true, groupKey: 'title-border-width', variable: `--sidenavigation-title-${s}-border-width` },
47
51
  { label: 'padding', canBeLinked: true, groupKey: 'title-padding', variable: `--sidenavigation-title-${s}-padding` },
48
- { label: 'indicator color', groupKey: 'title-accent', variable: `--sidenavigation-title-${s}-accent` },
49
- { label: 'indicator width', canBeLinked: true, groupKey: 'title-accent-width', variable: `--sidenavigation-title-${s}-accent-width` },
50
52
  ];
51
53
  }
54
+ const titleLayoutTokens: Token[] = [
55
+ { label: 'gap', canBeLinked: true, groupKey: 'title-gap', variable: '--sidenavigation-title-gap' },
56
+ { label: 'corner radius', canBeLinked: true, groupKey: 'title-radius', variable: '--sidenavigation-title-radius' },
57
+ ];
52
58
  function titleStateTypeGroups(s: StatefulState): TypeGroupConfig[] {
53
59
  return [{
54
60
  legend: 'title label',
@@ -59,6 +65,14 @@
59
65
  lineHeightVariable: `--sidenavigation-title-${s}-label-line-height`,
60
66
  }];
61
67
  }
68
+ // Title label — structural inner box (stateless, like Panel). Sits inside
69
+ // the title bar so the header reads as: outer bar → [label box] [toggle box].
70
+ const titleLabelTokens: Token[] = [
71
+ { label: 'surface color', groupKey: 'title-label-surface', variable: '--sidenavigation-title-label-surface' },
72
+ { label: 'corner radius', canBeLinked: true, groupKey: 'title-label-radius', variable: '--sidenavigation-title-label-radius' },
73
+ { label: 'padding', canBeLinked: true, groupKey: 'title-label-padding', variable: '--sidenavigation-title-label-padding' },
74
+ ];
75
+
62
76
  const titleTypographyTokens: Token[] = STATEFUL_STATES.flatMap((s) => [
63
77
  { label: 'font family', canBeLinked: true, groupKey: 'title-label-font-family', variable: `--sidenavigation-title-${s}-label-font-family` },
64
78
  { label: 'font size', canBeLinked: true, groupKey: 'title-label-font-size', variable: `--sidenavigation-title-${s}-label-font-size` },
@@ -168,6 +182,8 @@
168
182
  const states: Record<string, Token[]> = {
169
183
  'Panel': panelTokens,
170
184
  ...Object.fromEntries(STATEFUL_STATES.map((s) => [`Title / ${STATE_LABELS[s]}`, titleStateTokens(s)])),
185
+ 'Title Layout': titleLayoutTokens,
186
+ 'Title Label': titleLabelTokens,
171
187
  ...Object.fromEntries(TOGGLE_STATES.map((s) => [`Toggle / ${STATE_LABELS[s]}`, toggleStateTokens(s)])),
172
188
  ...Object.fromEntries(STATEFUL_STATES.map((s) => [`Section / ${STATE_LABELS[s]}`, sectionStateTokens(s)])),
173
189
  ...Object.fromEntries(STATEFUL_STATES.map((s) => [`Item / ${STATE_LABELS[s]}`, itemStateTokens(s)])),
@@ -196,7 +212,6 @@
196
212
  ...STATEFUL_STATES.flatMap((s): Array<[string, string]> => [
197
213
  [`--sidenavigation-title-${s}-border-width`, `title ${s}`],
198
214
  [`--sidenavigation-title-${s}-padding`, `title ${s}`],
199
- [`--sidenavigation-title-${s}-accent-width`, `title ${s}`],
200
215
  [`--sidenavigation-title-${s}-label-font-family`, `title ${s}`],
201
216
  [`--sidenavigation-title-${s}-label-font-size`, `title ${s}`],
202
217
  [`--sidenavigation-title-${s}-label-font-weight`, `title ${s}`],
@@ -37,13 +37,25 @@
37
37
 
38
38
  <section class="es-root variant-{variant} {className}">
39
39
  {#if href}
40
- <a {href} class="section-header" class:expanded>
41
- <div class="section-toggle">
40
+ <!-- Linked header: the chevron is a standalone toggle button so the
41
+ section can still be collapsed even when the label is a link. Both
42
+ live inside the same `.section-header` flex row so paint (hover,
43
+ background, indicator) continues to land on the row as a whole. -->
44
+ <div class="section-header section-header--linked" class:expanded>
45
+ <button
46
+ type="button"
47
+ class="section-toggle-button"
48
+ onclick={fireToggle}
49
+ aria-label={expanded ? 'Collapse section' : 'Expand section'}
50
+ aria-expanded={expanded}
51
+ >
42
52
  <i class="fas fa-chevron-right toggle-icon"></i>
53
+ </button>
54
+ <a {href} class="section-link">
43
55
  <span class="section-label">{label}</span>
44
- </div>
56
+ </a>
45
57
  {@render summary?.()}
46
- </a>
58
+ </div>
47
59
  {:else}
48
60
  <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions, a11y_no_static_element_interactions -->
49
61
  <div class="section-header" class:expanded onclick={fireToggle}>
@@ -170,6 +182,37 @@
170
182
  flex-shrink: 0;
171
183
  }
172
184
 
185
+ /* Linked header: chevron is a sibling button next to the label link, not a
186
+ child of an enveloping <a>. Use the tighter gap that the inner
187
+ `.section-toggle` wrapper provides in the no-href branch, so both
188
+ layouts read the same visually. */
189
+ .section-header.section-header--linked {
190
+ gap: var(--space-8);
191
+ }
192
+
193
+ .section-toggle-button {
194
+ display: inline-flex;
195
+ align-items: center;
196
+ justify-content: center;
197
+ flex-shrink: 0;
198
+ background: transparent;
199
+ border: none;
200
+ padding: 0;
201
+ margin: 0;
202
+ color: inherit;
203
+ font: inherit;
204
+ cursor: pointer;
205
+ }
206
+
207
+ .section-link {
208
+ display: inline-flex;
209
+ align-items: center;
210
+ min-width: 0;
211
+ color: inherit;
212
+ text-decoration: none;
213
+ flex: 1 1 auto;
214
+ }
215
+
173
216
  @mixin header-paint($variant, $state) {
174
217
  background: var(--collapsiblesection-#{$variant}-#{$state}-surface);
175
218
  @include themed-padding(--collapsiblesection-#{$variant}-#{$state}-padding, $h: 2);
@@ -139,28 +139,28 @@
139
139
  class:force-footer-hover={forceHoverPart === 'footer'}
140
140
  class:force-footer-active={forceActivePart === 'footer'}
141
141
  >
142
- <!-- Toggle is a single persistent element. Its `left` position is
143
- calc'd from the panel-width tokens so it transitions smoothly
144
- between right-of-title (open) and centre-of-rail (closed) using
145
- the same duration/easing tokens as the rail width. -->
146
- <button
147
- type="button"
148
- class="sn-toggle"
149
- onclick={fireToggle}
150
- aria-label={open ? 'Collapse sidebar' : 'Expand sidebar'}
151
- aria-expanded={open}
152
- >
153
- <i class="fa-solid fa-angles-right" aria-hidden="true"></i>
154
- </button>
155
-
156
- {#if open}
157
- <!-- Open layout. Title bar reserves space for the persistent toggle on
158
- the right; the menu is locked at the open-width so it can't reflow
159
- while the rail expands around it. -->
160
- <header class="sn-title" class:active={titleActive}>
142
+ <!-- Header is always rendered so the toggle (a child here) survives the
143
+ collapsed state. The label is the only conditional child when the
144
+ rail is closed, the header reduces to just the toggle, centred via the
145
+ toggle's own `left` calc. Header stays locked to open-width regardless;
146
+ the aside's overflow clips it during the close animation. -->
147
+ <header class="sn-title" class:active={titleActive}>
148
+ {#if open}
161
149
  <a href={titleHref} class="sn-title-label">{titleLabel}</a>
162
- </header>
150
+ {/if}
151
+
152
+ <button
153
+ type="button"
154
+ class="sn-toggle"
155
+ onclick={fireToggle}
156
+ aria-label={open ? 'Collapse sidebar' : 'Expand sidebar'}
157
+ aria-expanded={open}
158
+ >
159
+ <i class="fa-solid fa-angles-right" aria-hidden="true"></i>
160
+ </button>
161
+ </header>
163
162
 
163
+ {#if open}
164
164
  <div class="sn-menu">
165
165
  {#each sections as section (section.path)}
166
166
  <div class="sn-section">
@@ -228,6 +228,22 @@
228
228
  --sidenavigation-close-duration: var(--duration-150);
229
229
  --sidenavigation-close-easing: var(--ease-out-quart);
230
230
 
231
+ /* Title — layout (stateless). The header is a flex container that hosts
232
+ the label box and the toggle box side-by-side; gap and radius drive
233
+ the card-like outer shell. */
234
+ --sidenavigation-title-gap: var(--space-8);
235
+ --sidenavigation-title-radius: var(--radius-md);
236
+
237
+ /* Title label — structural inner box (stateless). Renders as a row inside
238
+ the title bar so the header reads as: outer card → [label box] [toggle
239
+ box]. align-items: stretch on the header equalises its height with the
240
+ toggle's. */
241
+ --sidenavigation-title-label-surface: var(--color-transparent);
242
+ --sidenavigation-title-label-border: var(--border-canvas-faint);
243
+ --sidenavigation-title-label-border-width: var(--border-width-1);
244
+ --sidenavigation-title-label-radius: var(--radius-md);
245
+ --sidenavigation-title-label-padding: var(--space-6);
246
+
231
247
  /* Title — default */
232
248
  --sidenavigation-title-default-surface: var(--color-transparent);
233
249
  --sidenavigation-title-default-border: var(--border-canvas-faint);
@@ -242,7 +258,7 @@
242
258
  --sidenavigation-title-default-label-line-height: var(--line-height-sm);
243
259
 
244
260
  /* Title — hover */
245
- --sidenavigation-title-hover-surface: var(--surface-canvas);
261
+ --sidenavigation-title-hover-surface: var(--color-transparent);
246
262
  --sidenavigation-title-hover-border: var(--border-canvas-faint);
247
263
  --sidenavigation-title-hover-border-width: var(--border-width-1);
248
264
  --sidenavigation-title-hover-padding: var(--space-12);
@@ -428,34 +444,27 @@
428
444
  --_border: var(--sidenavigation-title-default-border);
429
445
  --_border-width: var(--sidenavigation-title-default-border-width);
430
446
  --_padding: var(--sidenavigation-title-default-padding);
431
- --_indicator: var(--sidenavigation-title-default-accent);
432
- --_indicator-width: var(--sidenavigation-title-default-accent-width);
433
447
  --_label: var(--sidenavigation-title-default-label);
434
448
  --_label-family: var(--sidenavigation-title-default-label-font-family);
435
449
  --_label-size: var(--sidenavigation-title-default-label-font-size);
436
450
  --_label-weight: var(--sidenavigation-title-default-label-font-weight);
437
451
  --_label-line-height: var(--sidenavigation-title-default-label-line-height);
438
452
 
439
- /* Positioning context for the absolutely-anchored toggle button. */
440
- position: relative;
453
+ /* The header is a pure flex row: card-like outer with the label box and
454
+ toggle box as siblings. No absolute positioning — the label fills with
455
+ flex:1, the toggle is a fixed-size sibling. justify-content: center
456
+ takes over when the label is removed in the collapsed state and the
457
+ toggle becomes the only child. */
441
458
  flex: 0 0 auto;
442
- /* Lock to the open-width so the title doesn't reflow during the rail
443
- expansion (3rem → 16rem on open). border-box keeps the outer width
444
- equal to the token value (otherwise padding + border would push the
445
- toggle past the rail's right edge). */
446
459
  box-sizing: border-box;
447
- width: var(--sidenavigation-panel-open-width);
448
460
  display: flex;
449
- align-items: center;
450
- gap: var(--space-12);
461
+ align-items: stretch;
462
+ justify-content: center;
463
+ gap: var(--sidenavigation-title-gap);
451
464
  background: var(--_surface);
452
- border-bottom: var(--_border-width) solid var(--_border);
453
- border-left: var(--_indicator-width) solid var(--_indicator);
465
+ border-radius: var(--sidenavigation-title-radius);
454
466
  @include themed-padding(--_padding);
455
- /* Right padding clears the absolutely-positioned toggle inside this
456
- header (toggle width ~36px + 8px breathing room). */
457
- padding-right: calc(var(--space-12) + 44px);
458
- transition: background var(--duration-150), border-color var(--duration-150);
467
+ transition: background var(--duration-150);
459
468
  }
460
469
 
461
470
  .sn-title:hover:not(.active),
@@ -464,8 +473,6 @@
464
473
  --_border: var(--sidenavigation-title-hover-border);
465
474
  --_border-width: var(--sidenavigation-title-hover-border-width);
466
475
  --_padding: var(--sidenavigation-title-hover-padding);
467
- --_indicator: var(--sidenavigation-title-hover-accent);
468
- --_indicator-width: var(--sidenavigation-title-hover-accent-width);
469
476
  --_label: var(--sidenavigation-title-hover-label);
470
477
  --_label-family: var(--sidenavigation-title-hover-label-font-family);
471
478
  --_label-size: var(--sidenavigation-title-hover-label-font-size);
@@ -478,8 +485,6 @@
478
485
  --_border: var(--sidenavigation-title-active-border);
479
486
  --_border-width: var(--sidenavigation-title-active-border-width);
480
487
  --_padding: var(--sidenavigation-title-active-padding);
481
- --_indicator: var(--sidenavigation-title-active-accent);
482
- --_indicator-width: var(--sidenavigation-title-active-accent-width);
483
488
  --_label: var(--sidenavigation-title-active-label);
484
489
  --_label-family: var(--sidenavigation-title-active-label-font-family);
485
490
  --_label-size: var(--sidenavigation-title-active-label-font-size);
@@ -487,7 +492,18 @@
487
492
  --_label-line-height: var(--sidenavigation-title-active-label-line-height);
488
493
  }
489
494
 
495
+ /* In the collapsed state the header narrows with the rail. Drop horizontal
496
+ padding so the toggle (single remaining flex child) still fits in the
497
+ closed-width aside — open-state padding alone would push it out. */
498
+ .sidenavigation.collapsed .sn-title {
499
+ padding-left: 0;
500
+ padding-right: 0;
501
+ }
502
+
490
503
  .sn-title-label {
504
+ background: var(--sidenavigation-title-label-surface);
505
+ border-radius: var(--sidenavigation-title-label-radius);
506
+ @include themed-padding(--sidenavigation-title-label-padding);
491
507
  color: var(--_label);
492
508
  font-family: var(--_label-family);
493
509
  font-size: var(--_label-size);
@@ -495,6 +511,8 @@
495
511
  line-height: var(--_label-line-height);
496
512
  text-decoration: none;
497
513
  flex: 1 1 auto;
514
+ display: flex;
515
+ align-items: center;
498
516
  min-width: 0;
499
517
  white-space: nowrap;
500
518
  overflow: hidden;
@@ -510,62 +528,38 @@
510
528
  --_icon: var(--sidenavigation-toggle-default-icon);
511
529
  --_icon-size: var(--sidenavigation-toggle-default-icon-size);
512
530
 
513
- /* Persistent element anchored to the panel (not the title). Open-state
514
- `left` puts it near the right edge of the open-width title; collapsed
515
- `left` centres it in the closed-width rail. Both calc'd from the
516
- same width tokens so they stay in sync if the consumer overrides. */
517
- position: absolute;
518
- /* Toggle's outer width is derived from its constituent tokens — icon
519
- + padding (both sides) + border (both sides). Stays accurate when a
520
- consumer customizes any of those without re-hardcoding here. */
521
- --_toggle-width: calc(
522
- var(--sidenavigation-toggle-default-icon-size)
523
- + 2 * var(--sidenavigation-toggle-default-padding)
524
- + 2 * var(--sidenavigation-toggle-default-border-width)
525
- );
526
- top: var(--space-12);
527
- left: calc(var(--sidenavigation-panel-open-width) - var(--_toggle-width) - var(--space-8));
528
- z-index: 1;
529
- transition:
530
- left var(--sidenavigation-open-duration) var(--sidenavigation-open-easing),
531
- background var(--duration-150),
532
- border-color var(--duration-150),
533
- color var(--duration-150);
534
-
531
+ /* Regular flex child of .sn-title no absolute positioning. The header
532
+ row's justify-content+flex:1-on-label combo puts the toggle at the
533
+ right edge when the label is present and centres it when the label
534
+ is removed in the collapsed state. */
535
535
  display: inline-flex;
536
536
  align-items: center;
537
537
  justify-content: center;
538
- flex-shrink: 0;
538
+ flex: 0 0 auto;
539
+ box-sizing: border-box;
539
540
  background: var(--_surface);
540
541
  border: var(--_border-width) solid var(--_border);
541
542
  border-radius: var(--_radius);
542
543
  color: var(--_icon);
543
544
  @include themed-padding(--_padding);
544
545
  cursor: pointer;
546
+ transition:
547
+ background var(--duration-150),
548
+ border-color var(--duration-150),
549
+ color var(--duration-150);
545
550
  }
546
551
 
547
552
  .sn-toggle i {
548
553
  font-size: var(--_icon-size);
549
554
  line-height: 1;
550
555
  /* Icon points right by default (expand). Open state flips it to point
551
- left (collapse). Rotation tweens with the position so the affordance
552
- morphs smoothly between the two states. */
556
+ left (collapse). Rotation tweens with the rail's width transition so
557
+ the affordance morphs smoothly between the two states. */
553
558
  transition: transform var(--sidenavigation-open-duration) var(--sidenavigation-open-easing);
554
559
  }
555
560
  .sidenavigation:not(.collapsed) .sn-toggle i {
556
561
  transform: rotate(180deg);
557
562
  }
558
-
559
- /* Collapsed: centre the toggle in the rail, and swap to close-* timing
560
- tokens so the leftward slide matches the rail's shrink. */
561
- .sidenavigation.collapsed .sn-toggle {
562
- left: calc((var(--sidenavigation-panel-closed-width) - var(--_toggle-width)) / 2);
563
- transition:
564
- left var(--sidenavigation-close-duration) var(--sidenavigation-close-easing),
565
- background var(--duration-150),
566
- border-color var(--duration-150),
567
- color var(--duration-150);
568
- }
569
563
  .sidenavigation.collapsed .sn-toggle i {
570
564
  transition: transform var(--sidenavigation-close-duration) var(--sidenavigation-close-easing);
571
565
  }