@motion-proto/live-tokens 0.24.2 → 0.25.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.
- package/.claude/skills/live-tokens-create-component/SKILL.md +38 -24
- package/bin/check-component.mjs +52 -4
- package/dist-plugin/index.cjs +7 -0
- package/dist-plugin/index.js +7 -0
- package/package.json +1 -1
- package/src/editor/component-editor/CalloutEditor.svelte +1 -1
- package/src/editor/component-editor/CardEditor.svelte +1 -1
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +1 -1
- package/src/editor/component-editor/DialogEditor.svelte +1 -1
- package/src/editor/component-editor/PanelEditor.svelte +81 -0
- package/src/editor/component-editor/ProgressBarEditor.svelte +1 -1
- package/src/editor/component-editor/RadioButtonEditor.svelte +1 -1
- package/src/editor/component-editor/SectionDividerEditor.svelte +2 -2
- package/src/editor/component-editor/SideNavigationEditor.svelte +18 -15
- package/src/editor/component-editor/TabBarEditor.svelte +1 -1
- package/src/editor/component-editor/TableEditor.svelte +8 -17
- package/src/editor/component-editor/TooltipEditor.svelte +1 -1
- package/src/editor/component-editor/index.ts +8 -1
- package/src/editor/component-editor/registry.ts +11 -0
- package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +120 -17
- package/src/editor/component-editor/scaffolding/types.ts +4 -0
- package/src/editor/overlay/LiveEditorOverlay.svelte +79 -3
- package/src/editor/styles/ui-editor.css +8 -2
- package/src/system/assets/github-mark-white.svg +1 -0
- package/src/system/assets/npm-mark-white.svg +1 -0
- package/src/system/assets/offering.webp +0 -0
- package/src/system/components/Button.svelte +12 -12
- package/src/system/components/CodeSnippet.svelte +4 -1
- package/src/system/components/Panel.svelte +44 -0
- package/src/system/components/SectionDivider.svelte +10 -10
- package/src/system/components/SideNavigation.svelte +13 -0
- package/src/system/styles/CONVENTIONS.md +13 -6
|
@@ -16,20 +16,69 @@ export const TYPE_FONT_PROPS = [
|
|
|
16
16
|
|
|
17
17
|
export type TypeFontProp = typeof TYPE_FONT_PROPS[number];
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/** Declarative inputs that let the helpers derive a slot-scoped groupKey from a
|
|
20
|
+
variable name instead of guessing from its last dash. Pass `component` (the id,
|
|
21
|
+
so the `--<id>-` prefix is stripped) and `variants` (the variant/state segment
|
|
22
|
+
strings exactly as they appear in variable names, e.g. `['default','hover']`).
|
|
23
|
+
The derived key is everything left after removing the prefix and those segments,
|
|
24
|
+
so two parts that share a suffix (`section-text`, `item-text`) stay distinct
|
|
25
|
+
while the same slot across variants collapses to one key. */
|
|
26
|
+
export type GroupKeyDerivation = {
|
|
27
|
+
component?: string;
|
|
28
|
+
variants?: readonly string[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type BuildTypeGroupTokensOptions = GroupKeyDerivation & {
|
|
32
|
+
/** Override the derived groupKey per property. Receives the prop descriptor and the
|
|
21
33
|
type-group config it came from. Return a stable string — siblings sharing the same
|
|
22
|
-
groupKey are linked in the linked block. */
|
|
34
|
+
groupKey are linked in the linked block. Wins over structural derivation. */
|
|
23
35
|
groupKeyFor?: (prop: TypeFontProp, group: TypeGroupConfig) => string;
|
|
24
36
|
};
|
|
25
37
|
|
|
38
|
+
/** Color-token derivation: either the legacy single-arg callback form
|
|
39
|
+
`(group) => string`, or the declarative options object. */
|
|
40
|
+
export type ColorGroupKeyOption =
|
|
41
|
+
| ((group: TypeGroupConfig) => string)
|
|
42
|
+
| (GroupKeyDerivation & { groupKeyFor?: (group: TypeGroupConfig) => string });
|
|
43
|
+
|
|
44
|
+
/** Remove the first contiguous run matching each `segment` (a segment may itself
|
|
45
|
+
be multi-part, e.g. `'on-hover'`) from `segs`. */
|
|
46
|
+
function stripSegments(segs: string[], remove: readonly string[]): string[] {
|
|
47
|
+
let out = segs;
|
|
48
|
+
for (const r of remove) {
|
|
49
|
+
const rs = r.split('-');
|
|
50
|
+
for (let i = 0; i + rs.length <= out.length; i++) {
|
|
51
|
+
if (rs.every((s, k) => out[i + k] === s)) {
|
|
52
|
+
out = [...out.slice(0, i), ...out.slice(i + rs.length)];
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Slot-scoped key = the variable minus the `--<component>-` prefix and any
|
|
61
|
+
variant/state segments. Falls back to the whole variable if nothing remains. */
|
|
62
|
+
export function structuralGroupKey(
|
|
63
|
+
variable: string,
|
|
64
|
+
{ component, variants }: GroupKeyDerivation,
|
|
65
|
+
): string {
|
|
66
|
+
let body = variable.startsWith('--') ? variable.slice(2) : variable;
|
|
67
|
+
if (component && body.startsWith(component + '-')) body = body.slice(component.length + 1);
|
|
68
|
+
const segs = stripSegments(body.split('-'), variants ?? []);
|
|
69
|
+
return segs.join('-') || variable;
|
|
70
|
+
}
|
|
71
|
+
|
|
26
72
|
/** Derive the Token[] schema entries for every TypeGroupConfig in `typeGroups`. Each
|
|
27
73
|
group emits one `colorVariable` token plus up to 4 font-shape tokens (family/size/
|
|
28
74
|
weight/line-height) for whichever of those are declared on the group. Font-shape
|
|
29
75
|
tokens carry `canBeLinked: true` and a stable `groupKey` so the linked-block linkage
|
|
30
|
-
sees them; the color token is emitted plain (no groupKey, not shareable)
|
|
31
|
-
|
|
32
|
-
|
|
76
|
+
sees them; the color token is emitted plain (no groupKey, not shareable) unless a
|
|
77
|
+
derivation is supplied, in which case it too gets a slot-scoped key.
|
|
78
|
+
|
|
79
|
+
Pass `{ component, variants }` so font keys are slot-scoped
|
|
80
|
+
(`--card-default-title-font-family` → `title-font-family`) instead of the bare
|
|
81
|
+
`font-family` default, which silently merges multiple slots into one link tree.
|
|
33
82
|
|
|
34
83
|
Mirrors the `flatMap`/loop pattern in ButtonEditor and RadioButtonEditor so
|
|
35
84
|
editors don't have to hand-list 16+ near-identical Token entries. */
|
|
@@ -37,15 +86,23 @@ export function buildTypeGroupTokens(
|
|
|
37
86
|
typeGroups: Record<string, TypeGroupConfig[]>,
|
|
38
87
|
options: BuildTypeGroupTokensOptions = {},
|
|
39
88
|
): Token[] {
|
|
40
|
-
const { groupKeyFor } = options;
|
|
89
|
+
const { groupKeyFor, component, variants } = options;
|
|
90
|
+
const derive = component !== undefined || variants !== undefined;
|
|
41
91
|
const tokens: Token[] = [];
|
|
42
92
|
for (const groups of Object.values(typeGroups)) {
|
|
43
93
|
for (const group of groups) {
|
|
44
|
-
|
|
94
|
+
const colorToken: Token = { label: group.colorLabel ?? 'color', variable: group.colorVariable };
|
|
95
|
+
if (group.colorGroupKey) colorToken.groupKey = group.colorGroupKey;
|
|
96
|
+
else if (derive) colorToken.groupKey = structuralGroupKey(group.colorVariable, { component, variants });
|
|
97
|
+
tokens.push(colorToken);
|
|
45
98
|
for (const prop of TYPE_FONT_PROPS) {
|
|
46
99
|
const variable = group[prop.key];
|
|
47
100
|
if (!variable) continue;
|
|
48
|
-
const groupKey = groupKeyFor
|
|
101
|
+
const groupKey = groupKeyFor
|
|
102
|
+
? groupKeyFor(prop, group)
|
|
103
|
+
: derive
|
|
104
|
+
? structuralGroupKey(variable, { component, variants })
|
|
105
|
+
: prop.defaultGroupKey;
|
|
49
106
|
tokens.push({ label: prop.label, canBeLinked: true, groupKey, variable });
|
|
50
107
|
}
|
|
51
108
|
}
|
|
@@ -60,24 +117,70 @@ export function buildTypeGroupTokens(
|
|
|
60
117
|
cleanly into both `Object.values(typeGroups).flat()` chains and per-variant
|
|
61
118
|
`flatMap` constructions.
|
|
62
119
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
120
|
+
groupKey precedence per group: explicit `group.colorGroupKey` > the second
|
|
121
|
+
argument (`groupKeyFor` callback) > structural derivation (`{ component, variants }`).
|
|
122
|
+
With none of these the color token is emitted without a groupKey (solo, no
|
|
123
|
+
siblings) — the contract is explicit, there is no name-based inference. Pass
|
|
124
|
+
`{ component, variants }` to group a slot's color across variants; structural
|
|
125
|
+
derivation keeps several slots ending in the same word (SideNavigation's
|
|
126
|
+
section / item / footer) distinct, so `editor color/font link-group parity`
|
|
127
|
+
stays satisfied. */
|
|
68
128
|
export function buildTypeGroupColorTokens(
|
|
69
129
|
typeGroups: Record<string, TypeGroupConfig[]> | TypeGroupConfig[],
|
|
130
|
+
option?: ColorGroupKeyOption,
|
|
70
131
|
): Token[] {
|
|
71
132
|
const groups: TypeGroupConfig[] = Array.isArray(typeGroups)
|
|
72
133
|
? typeGroups
|
|
73
134
|
: Object.values(typeGroups).flat();
|
|
135
|
+
const legacyFn = typeof option === 'function' ? option : undefined;
|
|
136
|
+
const opts = typeof option === 'object' ? option : undefined;
|
|
137
|
+
const groupKeyFor = legacyFn ?? opts?.groupKeyFor;
|
|
138
|
+
const derive = opts && (opts.component !== undefined || opts.variants !== undefined);
|
|
74
139
|
return groups.map((g) => {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
140
|
+
const groupKey = g.colorGroupKey
|
|
141
|
+
? g.colorGroupKey
|
|
142
|
+
: groupKeyFor
|
|
143
|
+
? groupKeyFor(g)
|
|
144
|
+
: derive
|
|
145
|
+
? structuralGroupKey(g.colorVariable, opts)
|
|
146
|
+
: undefined;
|
|
147
|
+
const token: Token = { label: g.colorLabel ?? 'color', variable: g.colorVariable };
|
|
148
|
+
if (groupKey) token.groupKey = groupKey;
|
|
149
|
+
return token;
|
|
78
150
|
});
|
|
79
151
|
}
|
|
80
152
|
|
|
153
|
+
/** Font-only counterpart: the family/size/weight/line-height tokens for each group
|
|
154
|
+
with slot-scoped, linkable groupKeys, but no color token. Use alongside
|
|
155
|
+
`buildTypeGroupColorTokens` when a component wants its type-group colors and fonts
|
|
156
|
+
emitted separately (e.g. Table keeps per-slot colors but a custom layout). Keys
|
|
157
|
+
derive structurally from `{ component, variants }`; without a derivation they fall
|
|
158
|
+
back to the bare `font-family`/`font-size`/… defaults. */
|
|
159
|
+
export function buildTypeGroupFontTokens(
|
|
160
|
+
typeGroups: Record<string, TypeGroupConfig[]> | TypeGroupConfig[],
|
|
161
|
+
options: BuildTypeGroupTokensOptions = {},
|
|
162
|
+
): Token[] {
|
|
163
|
+
const { groupKeyFor, component, variants } = options;
|
|
164
|
+
const derive = component !== undefined || variants !== undefined;
|
|
165
|
+
const groups: TypeGroupConfig[] = Array.isArray(typeGroups)
|
|
166
|
+
? typeGroups
|
|
167
|
+
: Object.values(typeGroups).flat();
|
|
168
|
+
const tokens: Token[] = [];
|
|
169
|
+
for (const group of groups) {
|
|
170
|
+
for (const prop of TYPE_FONT_PROPS) {
|
|
171
|
+
const variable = group[prop.key];
|
|
172
|
+
if (!variable) continue;
|
|
173
|
+
const groupKey = groupKeyFor
|
|
174
|
+
? groupKeyFor(prop, group)
|
|
175
|
+
: derive
|
|
176
|
+
? structuralGroupKey(variable, { component, variants })
|
|
177
|
+
: prop.defaultGroupKey;
|
|
178
|
+
tokens.push({ label: prop.label, canBeLinked: true, groupKey, variable });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return tokens;
|
|
182
|
+
}
|
|
183
|
+
|
|
81
184
|
/** Companion helper: derive a `linkableContexts` map mapping every typography variable in
|
|
82
185
|
`typeGroups` to its state name. Use to build the linked-block context map without
|
|
83
186
|
spelling out 16+ entries; merge with non-typography entries via spread. */
|
|
@@ -84,6 +84,10 @@ export type TypeGroupConfig = {
|
|
|
84
84
|
outlineWidthLabel?: string;
|
|
85
85
|
outlineColorVariable?: string;
|
|
86
86
|
outlineColorLabel?: string;
|
|
87
|
+
/** Explicit groupKey for this group's color token. Wins over every derivation
|
|
88
|
+
(structural or `groupKeyFor`) and is never recomputed — the durable, one-line
|
|
89
|
+
fix when the derived key is wrong. */
|
|
90
|
+
colorGroupKey?: string;
|
|
87
91
|
/** See `Token.element` — when present, StateBlock groups this fieldset under
|
|
88
92
|
the matching element subsection. */
|
|
89
93
|
element?: string;
|
|
@@ -146,15 +146,61 @@
|
|
|
146
146
|
let floating = $state({ ...initial.floating });
|
|
147
147
|
|
|
148
148
|
// Collapsed-pill size; slight overshoot is fine (overflow:hidden).
|
|
149
|
-
const COLLAPSED_WIDTH =
|
|
149
|
+
const COLLAPSED_WIDTH = 232;
|
|
150
150
|
const COLLAPSED_HEIGHT = 44;
|
|
151
151
|
|
|
152
152
|
// Fade for open-only buttons (bar timing lives in CSS vars below).
|
|
153
153
|
const BTN_FADE = { duration: 130, easing: cubicInOut };
|
|
154
154
|
|
|
155
|
+
// Mirror the CSS bar timings; used for the fallback that clears the mask if
|
|
156
|
+
// no transitionend fires (reduced motion, suppressed transitions, no delta).
|
|
157
|
+
const OPEN_DUR = 240;
|
|
158
|
+
const CLOSE_DUR = 240;
|
|
159
|
+
const CLOSE_DELAY = 120; // matches --bar-close-delay: collapse waits for the fade-out
|
|
160
|
+
|
|
155
161
|
// Suppress CSS transitions during gestures + mode swaps.
|
|
156
162
|
let suppressTransition = $state(false);
|
|
157
163
|
|
|
164
|
+
// Mask the iframe during the pill↔full size change so the editor route's
|
|
165
|
+
// reflow (side-nav scrollbar, white body) never shows. On open, content fades
|
|
166
|
+
// in once the panel settles; on close, content fades out first, then the
|
|
167
|
+
// panel collapses (the shrink is delayed by --bar-close-delay to wait for it).
|
|
168
|
+
let opening = $state(false);
|
|
169
|
+
let closing = $state(false);
|
|
170
|
+
let prevOpen = open;
|
|
171
|
+
let maskTimer: ReturnType<typeof setTimeout> | undefined;
|
|
172
|
+
run(() => {
|
|
173
|
+
if (open === prevOpen) return;
|
|
174
|
+
prevOpen = open;
|
|
175
|
+
clearTimeout(maskTimer);
|
|
176
|
+
if (open) {
|
|
177
|
+
closing = false;
|
|
178
|
+
opening = true;
|
|
179
|
+
if (gesturing || suppressTransition) {
|
|
180
|
+
// .no-transition path: no transitionend will fire, so reveal next frame.
|
|
181
|
+
requestAnimationFrame(() => { opening = false; });
|
|
182
|
+
} else {
|
|
183
|
+
maskTimer = setTimeout(() => { opening = false; }, OPEN_DUR + 60);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
opening = false;
|
|
187
|
+
closing = true;
|
|
188
|
+
if (gesturing || suppressTransition) {
|
|
189
|
+
requestAnimationFrame(() => { closing = false; });
|
|
190
|
+
} else {
|
|
191
|
+
maskTimer = setTimeout(() => { closing = false; }, CLOSE_DELAY + CLOSE_DUR + 60);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
function onPanelTransitionEnd(e: TransitionEvent) {
|
|
197
|
+
if (e.target !== e.currentTarget) return;
|
|
198
|
+
if (e.propertyName !== 'width' && e.propertyName !== 'height') return;
|
|
199
|
+
clearTimeout(maskTimer);
|
|
200
|
+
opening = false;
|
|
201
|
+
closing = false;
|
|
202
|
+
}
|
|
203
|
+
|
|
158
204
|
// Scrim catches pointer events during gestures so they hit the panel, not the iframe.
|
|
159
205
|
let gesturing: 'drag' | 'resize-left' | 'resize-se' | null = $state(null);
|
|
160
206
|
|
|
@@ -268,6 +314,7 @@
|
|
|
268
314
|
});
|
|
269
315
|
onDestroy(() => {
|
|
270
316
|
window.removeEventListener('lt-overlay-toggle', handleToggleRequest);
|
|
317
|
+
clearTimeout(maskTimer);
|
|
271
318
|
});
|
|
272
319
|
|
|
273
320
|
let panelStyle = $derived(!open
|
|
@@ -286,7 +333,10 @@
|
|
|
286
333
|
class:hidden={!open}
|
|
287
334
|
class:docked={open && mode === 'docked'}
|
|
288
335
|
class:floating={open && mode === 'floating'}
|
|
336
|
+
class:opening={open && opening}
|
|
337
|
+
class:closing={!open && closing}
|
|
289
338
|
class:no-transition={!!gesturing || suppressTransition}
|
|
339
|
+
ontransitionend={onPanelTransitionEnd}
|
|
290
340
|
>
|
|
291
341
|
<div
|
|
292
342
|
class="header"
|
|
@@ -394,8 +444,8 @@
|
|
|
394
444
|
--bar-open-ease: cubic-bezier(0.65, 0, 0.35, 1);
|
|
395
445
|
--bar-open-delay: 0ms;
|
|
396
446
|
--bar-close-dur: 240ms;
|
|
397
|
-
--bar-close-ease: cubic-bezier(0
|
|
398
|
-
--bar-close-delay:
|
|
447
|
+
--bar-close-ease: cubic-bezier(0, 0, 0.2, 1); /* ease-out */
|
|
448
|
+
--bar-close-delay: 120ms; /* let the iframe fade out before the panel shrinks */
|
|
399
449
|
|
|
400
450
|
display: flex;
|
|
401
451
|
flex-direction: column;
|
|
@@ -426,6 +476,7 @@
|
|
|
426
476
|
/* Collapsed state: pinned top-right; iframe stays mounted, clipped by overflow:hidden. */
|
|
427
477
|
.lt-overlay.hidden {
|
|
428
478
|
border-radius: var(--ui-radius-lg, 6px);
|
|
479
|
+
border-color: rgba(255, 255, 255, 0.32);
|
|
429
480
|
transition:
|
|
430
481
|
width var(--bar-close-dur) var(--bar-close-ease) var(--bar-close-delay),
|
|
431
482
|
height var(--bar-close-dur) var(--bar-close-ease) var(--bar-close-delay),
|
|
@@ -616,6 +667,23 @@
|
|
|
616
667
|
height: 100%;
|
|
617
668
|
border: 0;
|
|
618
669
|
display: block;
|
|
670
|
+
transition: opacity 200ms ease-in;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/* While the panel grows pill → full, hide the iframe over the #000 frame-wrap
|
|
674
|
+
so the editor route's intermediate reflow (side-nav scrollbar, white body)
|
|
675
|
+
never shows; it fades in once the size transition settles. */
|
|
676
|
+
.lt-overlay.opening .editor-frame {
|
|
677
|
+
opacity: 0;
|
|
678
|
+
pointer-events: none;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/* On collapse, fade the iframe out quickly first (the panel shrink is held
|
|
682
|
+
back by --bar-close-delay), so the reflow during shrink stays masked. */
|
|
683
|
+
.lt-overlay.closing .editor-frame {
|
|
684
|
+
opacity: 0;
|
|
685
|
+
pointer-events: none;
|
|
686
|
+
transition: opacity 120ms ease-in;
|
|
619
687
|
}
|
|
620
688
|
|
|
621
689
|
.gesture-scrim {
|
|
@@ -654,4 +722,12 @@
|
|
|
654
722
|
transparent 55%
|
|
655
723
|
);
|
|
656
724
|
}
|
|
725
|
+
|
|
726
|
+
@media (prefers-reduced-motion: reduce) {
|
|
727
|
+
.lt-overlay,
|
|
728
|
+
.lt-overlay.hidden,
|
|
729
|
+
.editor-frame {
|
|
730
|
+
transition: none;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
657
733
|
</style>
|
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
font-family: var(--ui-font-sans);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/*
|
|
11
|
-
|
|
10
|
+
/* Pull editor chrome onto --ui-font-sans, overriding tokens.css's global
|
|
11
|
+
:where(*) theme-font baseline. The element matcher stays inside :where() so
|
|
12
|
+
this override computes to (0,1,0) — above the zero-specificity baseline but
|
|
13
|
+
below a component's single-class scoped font rule (`.sn-item.svelte-HASH` =
|
|
14
|
+
(0,2,0)). Without :where() it would tie those component rules and, winning on
|
|
15
|
+
source order, freeze every preview on --ui-font-sans (font-token edits write
|
|
16
|
+
the variable but the preview never repaints). */
|
|
17
|
+
.editor-page :where(*:not([class*="fa-"])) {
|
|
12
18
|
font-family: inherit;
|
|
13
19
|
}
|
|
14
20
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#ffffff" d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#ffffff" fill-rule="evenodd" d="M0,16V0H16V16ZM3,3V13H8V5h3v8h2V3Z"/></svg>
|
|
Binary file
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
--button-primary-surface: var(--surface-brand-high);
|
|
78
78
|
--button-primary-text: var(--text-primary);
|
|
79
79
|
--button-primary-text-font-family: var(--font-sans);
|
|
80
|
-
--button-primary-text-font-size: var(--font-size-
|
|
80
|
+
--button-primary-text-font-size: var(--font-size-lg);
|
|
81
81
|
--button-primary-text-font-weight: var(--font-weight-semibold);
|
|
82
82
|
--button-primary-text-line-height: var(--line-height-sm);
|
|
83
83
|
--button-primary-border: var(--border-brand);
|
|
@@ -90,13 +90,13 @@
|
|
|
90
90
|
--button-primary-disabled-surface: var(--color-neutral-700);
|
|
91
91
|
--button-primary-disabled-text: var(--text-tertiary);
|
|
92
92
|
--button-primary-disabled-border: var(--border-neutral-faint);
|
|
93
|
-
--button-primary-icon-size: var(--icon-size-
|
|
93
|
+
--button-primary-icon-size: var(--icon-size-md);
|
|
94
94
|
|
|
95
95
|
/* Secondary */
|
|
96
96
|
--button-secondary-surface: var(--surface-neutral-high);
|
|
97
97
|
--button-secondary-text: var(--text-primary);
|
|
98
98
|
--button-secondary-text-font-family: var(--font-sans);
|
|
99
|
-
--button-secondary-text-font-size: var(--font-size-
|
|
99
|
+
--button-secondary-text-font-size: var(--font-size-lg);
|
|
100
100
|
--button-secondary-text-font-weight: var(--font-weight-semibold);
|
|
101
101
|
--button-secondary-text-line-height: var(--line-height-sm);
|
|
102
102
|
--button-secondary-border: var(--border-neutral);
|
|
@@ -109,13 +109,13 @@
|
|
|
109
109
|
--button-secondary-disabled-surface: var(--color-neutral-700);
|
|
110
110
|
--button-secondary-disabled-text: var(--text-tertiary);
|
|
111
111
|
--button-secondary-disabled-border: var(--border-neutral-faint);
|
|
112
|
-
--button-secondary-icon-size: var(--icon-size-
|
|
112
|
+
--button-secondary-icon-size: var(--icon-size-md);
|
|
113
113
|
|
|
114
114
|
/* Outline */
|
|
115
115
|
--button-outline-surface: var(--color-transparent);
|
|
116
116
|
--button-outline-text: var(--text-primary);
|
|
117
117
|
--button-outline-text-font-family: var(--font-sans);
|
|
118
|
-
--button-outline-text-font-size: var(--font-size-
|
|
118
|
+
--button-outline-text-font-size: var(--font-size-lg);
|
|
119
119
|
--button-outline-text-font-weight: var(--font-weight-semibold);
|
|
120
120
|
--button-outline-text-line-height: var(--line-height-sm);
|
|
121
121
|
--button-outline-border: var(--border-neutral);
|
|
@@ -129,13 +129,13 @@
|
|
|
129
129
|
--button-outline-disabled-surface: var(--color-transparent);
|
|
130
130
|
--button-outline-disabled-text: var(--text-tertiary);
|
|
131
131
|
--button-outline-disabled-border: var(--border-neutral-faint);
|
|
132
|
-
--button-outline-icon-size: var(--icon-size-
|
|
132
|
+
--button-outline-icon-size: var(--icon-size-md);
|
|
133
133
|
|
|
134
134
|
/* Success */
|
|
135
135
|
--button-success-surface: var(--surface-success-low);
|
|
136
136
|
--button-success-text: var(--text-success);
|
|
137
137
|
--button-success-text-font-family: var(--font-sans);
|
|
138
|
-
--button-success-text-font-size: var(--font-size-
|
|
138
|
+
--button-success-text-font-size: var(--font-size-lg);
|
|
139
139
|
--button-success-text-font-weight: var(--font-weight-semibold);
|
|
140
140
|
--button-success-text-line-height: var(--line-height-sm);
|
|
141
141
|
--button-success-border: var(--border-success);
|
|
@@ -148,13 +148,13 @@
|
|
|
148
148
|
--button-success-disabled-surface: var(--color-neutral-700);
|
|
149
149
|
--button-success-disabled-text: var(--text-tertiary);
|
|
150
150
|
--button-success-disabled-border: var(--border-neutral-faint);
|
|
151
|
-
--button-success-icon-size: var(--icon-size-
|
|
151
|
+
--button-success-icon-size: var(--icon-size-md);
|
|
152
152
|
|
|
153
153
|
/* Danger */
|
|
154
154
|
--button-danger-surface: var(--surface-danger-low);
|
|
155
155
|
--button-danger-text: var(--text-danger);
|
|
156
156
|
--button-danger-text-font-family: var(--font-sans);
|
|
157
|
-
--button-danger-text-font-size: var(--font-size-
|
|
157
|
+
--button-danger-text-font-size: var(--font-size-lg);
|
|
158
158
|
--button-danger-text-font-weight: var(--font-weight-semibold);
|
|
159
159
|
--button-danger-text-line-height: var(--line-height-sm);
|
|
160
160
|
--button-danger-border: var(--border-danger);
|
|
@@ -167,13 +167,13 @@
|
|
|
167
167
|
--button-danger-disabled-surface: var(--color-neutral-700);
|
|
168
168
|
--button-danger-disabled-text: var(--text-tertiary);
|
|
169
169
|
--button-danger-disabled-border: var(--border-neutral-faint);
|
|
170
|
-
--button-danger-icon-size: var(--icon-size-
|
|
170
|
+
--button-danger-icon-size: var(--icon-size-md);
|
|
171
171
|
|
|
172
172
|
/* Warning */
|
|
173
173
|
--button-warning-surface: var(--surface-warning-low);
|
|
174
174
|
--button-warning-text: var(--text-warning);
|
|
175
175
|
--button-warning-text-font-family: var(--font-sans);
|
|
176
|
-
--button-warning-text-font-size: var(--font-size-
|
|
176
|
+
--button-warning-text-font-size: var(--font-size-lg);
|
|
177
177
|
--button-warning-text-font-weight: var(--font-weight-semibold);
|
|
178
178
|
--button-warning-text-line-height: var(--line-height-sm);
|
|
179
179
|
--button-warning-border: var(--border-warning);
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
--button-warning-disabled-surface: var(--color-neutral-700);
|
|
187
187
|
--button-warning-disabled-text: var(--text-tertiary);
|
|
188
188
|
--button-warning-disabled-border: var(--border-neutral-faint);
|
|
189
|
-
--button-warning-icon-size: var(--icon-size-
|
|
189
|
+
--button-warning-icon-size: var(--icon-size-md);
|
|
190
190
|
|
|
191
191
|
/* Small size — shared across all variants. The `.small` rule below reads
|
|
192
192
|
these tokens directly (no per-variant rebind needed, since small
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
--codesnippet-gap: var(--space-16);
|
|
62
62
|
|
|
63
63
|
/* Code text. */
|
|
64
|
-
--codesnippet-code-text: var(--text-brand
|
|
64
|
+
--codesnippet-code-text: var(--text-brand);
|
|
65
65
|
--codesnippet-code-font-family: var(--font-mono);
|
|
66
66
|
--codesnippet-code-font-size: var(--font-size-md);
|
|
67
67
|
--codesnippet-code-font-weight: var(--font-weight-normal);
|
|
@@ -78,6 +78,9 @@
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
.codesnippet {
|
|
81
|
+
/* Without a global border-box reset, content-box would let the snippet's own
|
|
82
|
+
padding + border overflow max-width: 100%, tripping a spurious side scroll. */
|
|
83
|
+
box-sizing: border-box;
|
|
81
84
|
display: inline-flex;
|
|
82
85
|
align-items: flex-start;
|
|
83
86
|
gap: var(--codesnippet-gap);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Forwarded to the stage so callers can inject token overrides. */
|
|
6
|
+
style?: string;
|
|
7
|
+
/** Fixes the stage height so it never reflows when its content resizes. */
|
|
8
|
+
minHeight?: string;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { style = '', minHeight, children }: Props = $props();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div class="panel" {style} style:min-height={minHeight}>
|
|
16
|
+
{@render children()}
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
:global(:root) {
|
|
21
|
+
--panel-frame-border: var(--border-neutral);
|
|
22
|
+
--panel-frame-border-width: var(--border-width-1);
|
|
23
|
+
--panel-frame-radius: var(--radius-2xl);
|
|
24
|
+
|
|
25
|
+
--panel-stage-surface: linear-gradient(0deg, color-mix(in srgb, var(--surface-neutral-low) 40%, transparent) 0.5%, color-mix(in srgb, var(--surface-neutral-lowest) 75%, transparent) 100%);
|
|
26
|
+
--panel-stage-padding: var(--space-16);
|
|
27
|
+
--panel-stage-inline-padding: var(--space-32);
|
|
28
|
+
--panel-stage-gap: var(--space-16);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.panel {
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
gap: var(--panel-stage-gap);
|
|
37
|
+
width: 100%;
|
|
38
|
+
border: var(--panel-frame-border-width) solid var(--panel-frame-border);
|
|
39
|
+
border-radius: var(--panel-frame-radius);
|
|
40
|
+
background: var(--panel-stage-surface);
|
|
41
|
+
padding: var(--panel-stage-padding) var(--panel-stage-inline-padding);
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
--sectiondivider-lg-eyebrow-font-weight: var(--font-weight-medium);
|
|
214
214
|
--sectiondivider-lg-eyebrow-font-size: var(--font-size-md);
|
|
215
215
|
--sectiondivider-lg-eyebrow-letter-spacing: var(--letter-spacing-wide);
|
|
216
|
-
--sectiondivider-lg-padding: var(--space-
|
|
216
|
+
--sectiondivider-lg-padding: var(--space-0);
|
|
217
217
|
--sectiondivider-lg-title-padding: var(--space-2);
|
|
218
218
|
--sectiondivider-lg-description-padding: var(--space-0);
|
|
219
219
|
--sectiondivider-lg-eyebrow-padding: var(--space-0);
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
--sectiondivider-md-eyebrow-font-weight: var(--font-weight-medium);
|
|
245
245
|
--sectiondivider-md-eyebrow-font-size: var(--font-size-sm);
|
|
246
246
|
--sectiondivider-md-eyebrow-letter-spacing: var(--letter-spacing-wide);
|
|
247
|
-
--sectiondivider-md-padding: var(--space-
|
|
247
|
+
--sectiondivider-md-padding: var(--space-0);
|
|
248
248
|
--sectiondivider-md-title-padding: var(--space-2);
|
|
249
249
|
--sectiondivider-md-description-padding: var(--space-0);
|
|
250
250
|
--sectiondivider-md-eyebrow-padding: var(--space-0);
|
|
@@ -262,8 +262,8 @@
|
|
|
262
262
|
|
|
263
263
|
/* Small */
|
|
264
264
|
--sectiondivider-sm-title-font-family: var(--font-display);
|
|
265
|
-
--sectiondivider-sm-title-font-weight: var(--font-weight-
|
|
266
|
-
--sectiondivider-sm-title-font-size: var(--font-size-
|
|
265
|
+
--sectiondivider-sm-title-font-weight: var(--font-weight-medium);
|
|
266
|
+
--sectiondivider-sm-title-font-size: var(--font-size-3xl);
|
|
267
267
|
--sectiondivider-sm-title-line-height: var(--line-height-md);
|
|
268
268
|
--sectiondivider-sm-title-letter-spacing: var(--letter-spacing-normal);
|
|
269
269
|
--sectiondivider-sm-title-outline-width: var(--border-width-4);
|
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
--sectiondivider-sm-eyebrow-font-weight: var(--font-weight-medium);
|
|
276
276
|
--sectiondivider-sm-eyebrow-font-size: var(--font-size-xs);
|
|
277
277
|
--sectiondivider-sm-eyebrow-letter-spacing: var(--letter-spacing-wide);
|
|
278
|
-
--sectiondivider-sm-padding: var(--space-
|
|
278
|
+
--sectiondivider-sm-padding: var(--space-0);
|
|
279
279
|
--sectiondivider-sm-title-padding: var(--space-2);
|
|
280
280
|
--sectiondivider-sm-description-padding: var(--space-0);
|
|
281
281
|
--sectiondivider-sm-eyebrow-padding: var(--space-0);
|
|
@@ -284,12 +284,12 @@
|
|
|
284
284
|
--sectiondivider-sm-shadow: var(--shadow-none);
|
|
285
285
|
--sectiondivider-sm-hairline-thickness: var(--border-width-1);
|
|
286
286
|
--sectiondivider-sm-background: var(--color-transparent);
|
|
287
|
-
--sectiondivider-sm-title: var(--text-
|
|
287
|
+
--sectiondivider-sm-title: var(--text-brand);
|
|
288
288
|
--sectiondivider-sm-description: var(--text-secondary);
|
|
289
289
|
--sectiondivider-sm-eyebrow: var(--text-tertiary);
|
|
290
290
|
--sectiondivider-sm-border: var(--color-transparent);
|
|
291
|
-
--sectiondivider-sm-title-outline-color: var(--surface-
|
|
292
|
-
--sectiondivider-sm-hairline-color: var(--border-brand-medium);
|
|
291
|
+
--sectiondivider-sm-title-outline-color: color-mix(in srgb, var(--surface-neutral-lowest) 25%, transparent);
|
|
292
|
+
--sectiondivider-sm-hairline-color: color-mix(in srgb, var(--border-brand-medium) 50%, transparent);
|
|
293
293
|
|
|
294
294
|
/* Intrinsic defaults. These keys cascade to `:root` via the editor's
|
|
295
295
|
alias bucket; un-edited variants fall back to these. The defaults
|
|
@@ -297,7 +297,7 @@
|
|
|
297
297
|
eyebrow, visible description, hidden hairline, normal-case eyebrow. */
|
|
298
298
|
--sectiondivider-lg-align: start;
|
|
299
299
|
--sectiondivider-md-align: start;
|
|
300
|
-
--sectiondivider-sm-align:
|
|
300
|
+
--sectiondivider-sm-align: center;
|
|
301
301
|
--sectiondivider-lg-eyebrow-display: block;
|
|
302
302
|
--sectiondivider-md-eyebrow-display: none;
|
|
303
303
|
--sectiondivider-sm-eyebrow-display: none;
|
|
@@ -306,7 +306,7 @@
|
|
|
306
306
|
--sectiondivider-sm-description-display: none;
|
|
307
307
|
--sectiondivider-lg-hairline: below-description;
|
|
308
308
|
--sectiondivider-md-hairline: below-label;
|
|
309
|
-
--sectiondivider-sm-hairline:
|
|
309
|
+
--sectiondivider-sm-hairline: none;
|
|
310
310
|
--sectiondivider-lg-eyebrow-text-transform: none;
|
|
311
311
|
--sectiondivider-md-eyebrow-text-transform: none;
|
|
312
312
|
--sectiondivider-sm-eyebrow-text-transform: none;
|
|
@@ -41,6 +41,13 @@
|
|
|
41
41
|
class?: string;
|
|
42
42
|
/** Toggle callback. Preferred over `on:toggle` from 0.5.0 onward. */
|
|
43
43
|
ontoggle?: () => void;
|
|
44
|
+
/** Optional content rendered at the top of the rail, above the title
|
|
45
|
+
(e.g. a logo or company name). Hidden while collapsed. No spacing is
|
|
46
|
+
imposed — the consumer controls it. */
|
|
47
|
+
lead?: import('svelte').Snippet;
|
|
48
|
+
/** Optional content rendered at the foot of the nav list, inside the panel.
|
|
49
|
+
Hidden while collapsed. No spacing is imposed — the consumer controls it. */
|
|
50
|
+
actions?: import('svelte').Snippet;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
let {
|
|
@@ -55,6 +62,8 @@
|
|
|
55
62
|
forceActivePart = null,
|
|
56
63
|
class: className = '',
|
|
57
64
|
ontoggle,
|
|
65
|
+
lead,
|
|
66
|
+
actions,
|
|
58
67
|
}: Props = $props();
|
|
59
68
|
|
|
60
69
|
// Dual-fire bridge — see Button.svelte for the deprecation timeline.
|
|
@@ -152,6 +161,8 @@
|
|
|
152
161
|
class:force-footer-hover={forceHoverPart === 'footer'}
|
|
153
162
|
class:force-footer-active={forceActivePart === 'footer'}
|
|
154
163
|
>
|
|
164
|
+
{#if open && lead}{@render lead()}{/if}
|
|
165
|
+
|
|
155
166
|
<!-- Header is always rendered so the toggle (a child here) survives the
|
|
156
167
|
collapsed state. The label is the only conditional child — when the
|
|
157
168
|
rail is closed, the header reduces to just the toggle, centred via the
|
|
@@ -220,6 +231,8 @@
|
|
|
220
231
|
<span>{footer.title}</span>
|
|
221
232
|
</a>
|
|
222
233
|
{/if}
|
|
234
|
+
|
|
235
|
+
{#if actions}{@render actions()}{/if}
|
|
223
236
|
</div>
|
|
224
237
|
{/if}
|
|
225
238
|
</aside>
|