@motion-proto/live-tokens 0.67.1 → 0.69.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-build-page/SKILL.md +16 -5
- package/.claude/skills/live-tokens-create-component/SKILL.md +43 -8
- package/.claude/skills/live-tokens-create-component/references/token-naming.md +30 -1
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +131 -0
- package/.claude/skills/live-tokens-pick-component/SKILL.md +2 -1
- package/CHANGELOG.md +239 -0
- package/README.md +11 -4
- package/bin/check-component.mjs +367 -63
- package/bin/check-page.mjs +409 -0
- package/bin/cli.mjs +57 -8
- package/bin/generate-theme.mjs +5 -4
- package/bin/lib/cssValues.mjs +50 -0
- package/bin/lib/findings.mjs +106 -0
- package/bin/lib/tokenVocabulary.mjs +213 -0
- package/dist-plugin/adjust/index.cjs +174 -23
- package/dist-plugin/adjust/index.d.cts +1 -1
- package/dist-plugin/adjust/index.d.ts +1 -1
- package/dist-plugin/adjust/index.js +68 -23
- package/dist-plugin/{chunk-J2JT4UEA.js → chunk-2YNERPXY.js} +26 -26
- package/dist-plugin/{chunk-NE6N66EE.js → chunk-GPIBU44G.js} +107 -1
- package/dist-plugin/{chunk-ZHPX7ZYQ.js → chunk-RFVYPNRO.js} +39 -1
- package/dist-plugin/{dataPaths-DZUzVv8H.d.ts → dataPaths-bJTCEO4H.d.cts} +1 -1
- package/dist-plugin/{dataPaths-DZUzVv8H.d.cts → dataPaths-bJTCEO4H.d.ts} +1 -1
- package/dist-plugin/fontPairing/index.d.cts +1 -1
- package/dist-plugin/fontPairing/index.d.ts +1 -1
- package/dist-plugin/generateColorsAndType/index.cjs +107 -1
- package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
- package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
- package/dist-plugin/generateColorsAndType/index.js +1 -1
- package/dist-plugin/index.cjs +173 -29
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +7 -7
- package/dist-plugin/migrateData/index.cjs +132 -26
- package/dist-plugin/migrateData/index.d.cts +1 -1
- package/dist-plugin/migrateData/index.d.ts +1 -1
- package/dist-plugin/migrateData/index.js +2 -2
- package/dist-plugin/tokensCssMigrations/index.cjs +39 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
- package/dist-plugin/tokensCssMigrations/index.js +1 -1
- package/package.json +6 -5
- package/src/app/site.css +4 -4
- package/src/editor/bootstrap.ts +6 -6
- package/src/editor/component-editor/ButtonEditor.svelte +71 -4
- package/src/editor/component-editor/CardEditor.svelte +6 -6
- package/src/editor/component-editor/DialogEditor.svelte +3 -3
- package/src/editor/component-editor/IconButtonEditor.svelte +68 -3
- package/src/editor/component-editor/ImageEditor.svelte +8 -8
- package/src/editor/component-editor/ImageLightboxEditor.svelte +13 -2
- package/src/editor/component-editor/MenuSelectEditor.svelte +63 -3
- package/src/editor/component-editor/SegmentedControlEditor.svelte +63 -3
- package/src/editor/component-editor/SideNavigationEditor.svelte +67 -3
- package/src/editor/component-editor/SliderEditor.svelte +186 -0
- package/src/editor/component-editor/TabBarEditor.svelte +63 -3
- package/src/editor/component-editor/registry.ts +10 -0
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +4 -4
- package/src/editor/core/components/aliasKinds.ts +51 -28
- package/src/editor/core/fonts/fontLoader.ts +2 -2
- package/src/editor/core/preview/{lookPreview.ts → themePreview.ts} +27 -27
- package/src/editor/core/productionPulse.ts +3 -3
- package/src/editor/core/sketch/index.ts +52 -44
- package/src/editor/core/sketch/maskField.ts +9 -9
- package/src/editor/core/sketch/sketchLayer.ts +25 -9
- package/src/editor/core/sketch/sketchRegistry.ts +34 -34
- package/src/editor/core/sketch/sketchStore.ts +98 -83
- package/src/editor/core/sketch/sketchStyleService.ts +4 -4
- package/src/editor/core/sketch/sketchStyles.ts +28 -28
- package/src/editor/core/store/editorPersistence.ts +44 -1
- package/src/editor/core/store/editorRenderer.ts +2 -2
- package/src/editor/core/store/editorStore.ts +18 -18
- package/src/editor/core/store/editorTypes.ts +7 -6
- package/src/editor/core/themes/colorsAndTypeService.ts +1 -1
- package/src/editor/core/themes/loadRows.ts +8 -8
- package/src/editor/core/themes/migrations/2026-09-01-gate-suffix-enabled.ts +31 -0
- package/src/editor/core/themes/migrations/2026-09-01-scrim-rename.ts +52 -0
- package/src/editor/core/themes/migrations/2026-09-01-tabbar-active-tint.ts +27 -0
- package/src/editor/core/themes/migrations/2026-09-01-tint-rename.ts +42 -0
- package/src/editor/core/themes/migrations/index.ts +16 -0
- package/src/editor/core/themes/slices/domainVars.ts +2 -2
- package/src/editor/core/themes/slices/washes.ts +107 -0
- package/src/editor/core/themes/themeDocumentSync.ts +2 -2
- package/src/editor/core/themes/themeInit.ts +2 -2
- package/src/editor/core/themes/themeService.ts +17 -17
- package/src/editor/core/themes/{lookSummary.ts → themeSummary.ts} +9 -9
- package/src/editor/core/themes/themeTypes.ts +10 -6
- package/src/editor/docs/content/editing-tokens.md +5 -3
- package/src/editor/docs/content/getting-started.md +1 -1
- package/src/editor/docs/content/sketch-mode.md +22 -17
- package/src/editor/docs/content/themes-workflow.md +11 -11
- package/src/editor/docs/content/where-themes-live.md +6 -6
- package/src/editor/docs/content.generated.ts +5 -5
- package/src/editor/index.ts +2 -2
- package/src/editor/pages/EditorShell.svelte +1 -1
- package/src/editor/ui/SurfacesTab.svelte +3 -3
- package/src/editor/ui/ThemePanel.svelte +52 -52
- package/src/editor/ui/UITokenSelector.svelte +1 -0
- package/src/editor/ui/VariablesTab.svelte +2 -2
- package/src/editor/ui/sections/{OverlaysSection.svelte → WashesSection.svelte} +44 -43
- package/src/editor/ui/sketch/SketchPreview.svelte +2 -2
- package/src/editor/ui/sketch/SketchTab.svelte +64 -38
- package/src/live-tokens/data/colors-and-type/autumn.json +6 -6
- package/src/live-tokens/data/colors-and-type/default.json +6 -6
- package/src/live-tokens/data/colors-and-type/halloween.json +6 -6
- package/src/live-tokens/data/colors-and-type/midnight-study.json +6 -6
- package/src/live-tokens/data/colors-and-type/ocean.json +6 -6
- package/src/live-tokens/data/colors-and-type/royal-velvet.json +6 -6
- package/src/live-tokens/data/colors-and-type/sketchy.json +6 -6
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +6 -6
- package/src/live-tokens/data/colors-and-type/sunset.json +6 -6
- package/src/live-tokens/data/sketch-styles/dry.json +5 -5
- package/src/live-tokens/data/sketch-styles/hatched.json +9 -9
- package/src/live-tokens/data/themes/autumn.json +82 -14
- package/src/live-tokens/data/themes/halloween.json +82 -14
- package/src/live-tokens/data/themes/midnight-study.json +82 -14
- package/src/live-tokens/data/themes/ocean.json +82 -14
- package/src/live-tokens/data/themes/royal-velvet.json +82 -14
- package/src/live-tokens/data/themes/sketchy.json +82 -14
- package/src/live-tokens/data/themes/spring-meadow.json +82 -14
- package/src/live-tokens/data/themes/sunset.json +82 -14
- package/src/live-tokens/data/tokens.generated.css +6 -6
- package/src/system/backdrop/backdrop.ts +1 -1
- package/src/system/components/Button.svelte +28 -10
- package/src/system/components/Card.svelte +6 -6
- package/src/system/components/Dialog.svelte +3 -3
- package/src/system/components/IconButton.svelte +23 -7
- package/src/system/components/Image.svelte +6 -6
- package/src/system/components/MenuSelect.svelte +17 -1
- package/src/system/components/SectionDivider.svelte +1 -1
- package/src/system/components/SegmentedControl.svelte +20 -2
- package/src/system/components/SideNavigation.svelte +22 -2
- package/src/system/components/Slider.svelte +348 -0
- package/src/system/components/TabBar.svelte +20 -2
- package/src/system/styles/CONVENTIONS.md +2 -2
- package/src/system/styles/tokens.css +12 -4
- package/template/package.json +3 -2
- package/template/src/pages/Home.svelte +1 -1
- package/src/editor/core/themes/slices/overlays.ts +0 -101
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Slider.svelte — a number chosen by position on a track. `single` moves one
|
|
3
|
+
thumb to a value; `range` moves two thumbs to a low and a high bound. Both
|
|
4
|
+
variants share the track, fill, and thumb tokens, linked in the editor so an
|
|
5
|
+
edit to one moves the other unless deliberately unlinked.
|
|
6
|
+
-->
|
|
7
|
+
<script module lang="ts">
|
|
8
|
+
export const sliderVariants = ['single', 'range'] as const;
|
|
9
|
+
export type SliderVariant = typeof sliderVariants[number];
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
interface Props {
|
|
14
|
+
variant?: SliderVariant;
|
|
15
|
+
/** The `single` thumb. */
|
|
16
|
+
value?: number;
|
|
17
|
+
/** The `range` thumbs. */
|
|
18
|
+
low?: number;
|
|
19
|
+
high?: number;
|
|
20
|
+
min?: number;
|
|
21
|
+
max?: number;
|
|
22
|
+
step?: number;
|
|
23
|
+
label?: string;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
/** Editor preview hook — adds `.force-hover` so hover tokens paint without a real pointer. */
|
|
26
|
+
class?: string;
|
|
27
|
+
onchange?: (value: number) => void;
|
|
28
|
+
onrangechange?: (low: number, high: number) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
variant = 'single',
|
|
33
|
+
value = 50,
|
|
34
|
+
low = 25,
|
|
35
|
+
high = 75,
|
|
36
|
+
min = 0,
|
|
37
|
+
max = 100,
|
|
38
|
+
step = 1,
|
|
39
|
+
label = '',
|
|
40
|
+
disabled = false,
|
|
41
|
+
class: className = '',
|
|
42
|
+
onchange,
|
|
43
|
+
onrangechange,
|
|
44
|
+
}: Props = $props();
|
|
45
|
+
|
|
46
|
+
const span = $derived(Math.max(max - min, Number.EPSILON));
|
|
47
|
+
const percent = (n: number) => Math.min(100, Math.max(0, ((n - min) / span) * 100));
|
|
48
|
+
|
|
49
|
+
const fillStart = $derived(variant === 'range' ? percent(low) : 0);
|
|
50
|
+
const fillEnd = $derived(variant === 'range' ? percent(high) : percent(value));
|
|
51
|
+
const readout = $derived(variant === 'range' ? `${low} – ${high}` : `${value}`);
|
|
52
|
+
|
|
53
|
+
function onSingleInput(event: Event) {
|
|
54
|
+
onchange?.(Number((event.currentTarget as HTMLInputElement).value));
|
|
55
|
+
}
|
|
56
|
+
// Each thumb is its own input, so a thumb dragged past its partner is held at
|
|
57
|
+
// the partner's value rather than crossing it.
|
|
58
|
+
function onLowInput(event: Event) {
|
|
59
|
+
const next = Math.min(Number((event.currentTarget as HTMLInputElement).value), high);
|
|
60
|
+
onrangechange?.(next, high);
|
|
61
|
+
}
|
|
62
|
+
function onHighInput(event: Event) {
|
|
63
|
+
const next = Math.max(Number((event.currentTarget as HTMLInputElement).value), low);
|
|
64
|
+
onrangechange?.(low, next);
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<div class="slider {variant} {className}" class:disabled class:has-label={!!label}>
|
|
69
|
+
{#if label}
|
|
70
|
+
<div class="slider-label">
|
|
71
|
+
<span>{label}</span>
|
|
72
|
+
<span class="slider-value">{readout}</span>
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
75
|
+
<div class="slider-track">
|
|
76
|
+
<div class="slider-fill" style:--_from={fillStart / 100} style:--_to={fillEnd / 100}></div>
|
|
77
|
+
{#if variant === 'range'}
|
|
78
|
+
<input
|
|
79
|
+
type="range"
|
|
80
|
+
class="thumb low"
|
|
81
|
+
{min}
|
|
82
|
+
{max}
|
|
83
|
+
{step}
|
|
84
|
+
value={low}
|
|
85
|
+
{disabled}
|
|
86
|
+
aria-label={label ? `${label} minimum` : 'Minimum'}
|
|
87
|
+
oninput={onLowInput}
|
|
88
|
+
/>
|
|
89
|
+
<span class="cap" style:--_at={fillStart / 100} aria-hidden="true"></span>
|
|
90
|
+
<input
|
|
91
|
+
type="range"
|
|
92
|
+
class="thumb high"
|
|
93
|
+
{min}
|
|
94
|
+
{max}
|
|
95
|
+
{step}
|
|
96
|
+
value={high}
|
|
97
|
+
{disabled}
|
|
98
|
+
aria-label={label ? `${label} maximum` : 'Maximum'}
|
|
99
|
+
oninput={onHighInput}
|
|
100
|
+
/>
|
|
101
|
+
<span class="cap" style:--_at={fillEnd / 100} aria-hidden="true"></span>
|
|
102
|
+
{:else}
|
|
103
|
+
<input
|
|
104
|
+
type="range"
|
|
105
|
+
class="thumb"
|
|
106
|
+
{min}
|
|
107
|
+
{max}
|
|
108
|
+
{step}
|
|
109
|
+
{value}
|
|
110
|
+
{disabled}
|
|
111
|
+
aria-label={label || 'Value'}
|
|
112
|
+
oninput={onSingleInput}
|
|
113
|
+
/>
|
|
114
|
+
<span class="cap" style:--_at={fillEnd / 100} aria-hidden="true"></span>
|
|
115
|
+
{/if}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<style>
|
|
120
|
+
:global(:root) {
|
|
121
|
+
/* Single: one thumb. */
|
|
122
|
+
--slider-single-track-surface: var(--surface-neutral-lowest);
|
|
123
|
+
--slider-single-track-border: var(--border-neutral-subtle);
|
|
124
|
+
--slider-single-track-border-width: var(--border-width-1);
|
|
125
|
+
--slider-single-track-height: var(--space-8);
|
|
126
|
+
--slider-single-track-radius: var(--radius-full);
|
|
127
|
+
--slider-single-fill: var(--surface-brand-high);
|
|
128
|
+
--slider-single-thumb-surface: var(--surface-neutral-highest);
|
|
129
|
+
--slider-single-thumb-border: var(--border-brand);
|
|
130
|
+
--slider-single-thumb-border-width: var(--border-width-2);
|
|
131
|
+
--slider-single-thumb-size: var(--font-size-lg);
|
|
132
|
+
--slider-single-thumb-radius: var(--radius-full);
|
|
133
|
+
--slider-single-thumb-shadow: var(--shadow-sm);
|
|
134
|
+
--slider-single-hover-thumb-surface: var(--surface-brand-high);
|
|
135
|
+
--slider-single-hover-thumb-border: var(--border-brand-strong);
|
|
136
|
+
--slider-single-disabled-track-surface: var(--surface-neutral-lower);
|
|
137
|
+
--slider-single-disabled-fill: var(--surface-neutral);
|
|
138
|
+
--slider-single-disabled-thumb-surface: var(--surface-neutral-lower);
|
|
139
|
+
--slider-single-disabled-thumb-border: var(--border-neutral);
|
|
140
|
+
|
|
141
|
+
/* Range: two thumbs bounding a fill. */
|
|
142
|
+
--slider-range-track-surface: var(--surface-neutral-lowest);
|
|
143
|
+
--slider-range-track-border: var(--border-neutral-subtle);
|
|
144
|
+
--slider-range-track-border-width: var(--border-width-1);
|
|
145
|
+
--slider-range-track-height: var(--space-8);
|
|
146
|
+
--slider-range-track-radius: var(--radius-full);
|
|
147
|
+
--slider-range-fill: var(--surface-brand-high);
|
|
148
|
+
--slider-range-thumb-surface: var(--surface-neutral-highest);
|
|
149
|
+
--slider-range-thumb-border: var(--border-brand);
|
|
150
|
+
--slider-range-thumb-border-width: var(--border-width-2);
|
|
151
|
+
--slider-range-thumb-size: var(--font-size-lg);
|
|
152
|
+
--slider-range-thumb-radius: var(--radius-full);
|
|
153
|
+
--slider-range-thumb-shadow: var(--shadow-sm);
|
|
154
|
+
--slider-range-hover-thumb-surface: var(--surface-brand-high);
|
|
155
|
+
--slider-range-hover-thumb-border: var(--border-brand-strong);
|
|
156
|
+
--slider-range-disabled-track-surface: var(--surface-neutral-lower);
|
|
157
|
+
--slider-range-disabled-fill: var(--surface-neutral);
|
|
158
|
+
--slider-range-disabled-thumb-surface: var(--surface-neutral-lower);
|
|
159
|
+
--slider-range-disabled-thumb-border: var(--border-neutral);
|
|
160
|
+
|
|
161
|
+
/* Label and value readout, shared by both variants. */
|
|
162
|
+
--slider-label-gap: var(--space-6);
|
|
163
|
+
--slider-label: var(--text-secondary);
|
|
164
|
+
--slider-label-font-family: var(--font-sans);
|
|
165
|
+
--slider-label-font-size: var(--font-size-md);
|
|
166
|
+
--slider-label-font-weight: var(--font-weight-normal);
|
|
167
|
+
--slider-label-line-height: var(--line-height-normal);
|
|
168
|
+
--slider-value: var(--text-tertiary);
|
|
169
|
+
--slider-value-font-family: var(--font-mono);
|
|
170
|
+
--slider-value-font-size: var(--font-size-md);
|
|
171
|
+
--slider-value-font-weight: var(--font-weight-normal);
|
|
172
|
+
--slider-value-line-height: var(--line-height-normal);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.slider {
|
|
176
|
+
display: grid;
|
|
177
|
+
grid-template-columns: minmax(0, 1fr);
|
|
178
|
+
row-gap: var(--slider-label-gap);
|
|
179
|
+
width: 100%;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.slider-label {
|
|
183
|
+
display: flex;
|
|
184
|
+
justify-content: space-between;
|
|
185
|
+
align-items: center;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.slider-label > span {
|
|
189
|
+
color: var(--slider-label);
|
|
190
|
+
font-family: var(--slider-label-font-family);
|
|
191
|
+
font-size: var(--slider-label-font-size);
|
|
192
|
+
font-weight: var(--slider-label-font-weight);
|
|
193
|
+
line-height: var(--slider-label-line-height);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.slider-label > .slider-value {
|
|
197
|
+
color: var(--slider-value);
|
|
198
|
+
font-family: var(--slider-value-font-family);
|
|
199
|
+
font-size: var(--slider-value-font-size);
|
|
200
|
+
font-weight: var(--slider-value-font-weight);
|
|
201
|
+
line-height: var(--slider-value-line-height);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* The inputs sit on top of the track and are invisible apart from their
|
|
205
|
+
native thumb, which is kept for the pointer and the keyboard but drawn
|
|
206
|
+
transparent; the visible thumb is the `.cap` beside each input, placed on
|
|
207
|
+
the same travel the native thumb has (it stays inside the input's box, so
|
|
208
|
+
its centre runs from half a thumb in to half a thumb from the end). */
|
|
209
|
+
.slider-track {
|
|
210
|
+
position: relative;
|
|
211
|
+
box-sizing: border-box;
|
|
212
|
+
width: 100%;
|
|
213
|
+
margin-block: calc((var(--_thumb-size) - var(--_track-height)) / 2);
|
|
214
|
+
border-style: solid;
|
|
215
|
+
transition: background var(--duration-150), border-color var(--duration-150);
|
|
216
|
+
--_origin: calc(var(--_thumb-size) / 2 - var(--_track-border-width));
|
|
217
|
+
--_travel: calc(100% + var(--_track-border-width) * 2 - var(--_thumb-size));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.slider.single .slider-track {
|
|
221
|
+
--_track-height: var(--slider-single-track-height);
|
|
222
|
+
--_track-border-width: var(--slider-single-track-border-width);
|
|
223
|
+
--_thumb-size: var(--slider-single-thumb-size);
|
|
224
|
+
--_thumb-surface: var(--slider-single-thumb-surface);
|
|
225
|
+
--_thumb-border: var(--slider-single-thumb-border);
|
|
226
|
+
--_thumb-border-width: var(--slider-single-thumb-border-width);
|
|
227
|
+
--_thumb-radius: var(--slider-single-thumb-radius);
|
|
228
|
+
--_thumb-shadow: var(--slider-single-thumb-shadow);
|
|
229
|
+
height: var(--slider-single-track-height);
|
|
230
|
+
background: var(--slider-single-track-surface);
|
|
231
|
+
border-color: var(--slider-single-track-border);
|
|
232
|
+
border-width: var(--slider-single-track-border-width);
|
|
233
|
+
border-radius: var(--slider-single-track-radius);
|
|
234
|
+
}
|
|
235
|
+
.slider.range .slider-track {
|
|
236
|
+
--_track-height: var(--slider-range-track-height);
|
|
237
|
+
--_track-border-width: var(--slider-range-track-border-width);
|
|
238
|
+
--_thumb-size: var(--slider-range-thumb-size);
|
|
239
|
+
--_thumb-surface: var(--slider-range-thumb-surface);
|
|
240
|
+
--_thumb-border: var(--slider-range-thumb-border);
|
|
241
|
+
--_thumb-border-width: var(--slider-range-thumb-border-width);
|
|
242
|
+
--_thumb-radius: var(--slider-range-thumb-radius);
|
|
243
|
+
--_thumb-shadow: var(--slider-range-thumb-shadow);
|
|
244
|
+
height: var(--slider-range-track-height);
|
|
245
|
+
background: var(--slider-range-track-surface);
|
|
246
|
+
border-color: var(--slider-range-track-border);
|
|
247
|
+
border-width: var(--slider-range-track-border-width);
|
|
248
|
+
border-radius: var(--slider-range-track-radius);
|
|
249
|
+
}
|
|
250
|
+
.slider.single.disabled .slider-track { background: var(--slider-single-disabled-track-surface); }
|
|
251
|
+
.slider.range.disabled .slider-track { background: var(--slider-range-disabled-track-surface); }
|
|
252
|
+
|
|
253
|
+
.slider-fill {
|
|
254
|
+
position: absolute;
|
|
255
|
+
top: calc(-1 * var(--_track-border-width));
|
|
256
|
+
bottom: calc(-1 * var(--_track-border-width));
|
|
257
|
+
left: calc(var(--_origin) + var(--_travel) * var(--_from));
|
|
258
|
+
width: calc(var(--_travel) * (var(--_to) - var(--_from)));
|
|
259
|
+
transition: background var(--duration-150);
|
|
260
|
+
}
|
|
261
|
+
.slider.single .slider-fill {
|
|
262
|
+
left: calc(-1 * var(--_track-border-width));
|
|
263
|
+
width: calc(var(--_origin) + var(--_track-border-width) + var(--_travel) * var(--_to));
|
|
264
|
+
background: var(--slider-single-fill);
|
|
265
|
+
border-radius: var(--slider-single-track-radius);
|
|
266
|
+
}
|
|
267
|
+
.slider.range .slider-fill {
|
|
268
|
+
background: var(--slider-range-fill);
|
|
269
|
+
border-radius: var(--slider-range-track-radius);
|
|
270
|
+
}
|
|
271
|
+
.slider.single.disabled .slider-fill { background: var(--slider-single-disabled-fill); }
|
|
272
|
+
.slider.range.disabled .slider-fill { background: var(--slider-range-disabled-fill); }
|
|
273
|
+
|
|
274
|
+
.thumb {
|
|
275
|
+
position: absolute;
|
|
276
|
+
top: 50%;
|
|
277
|
+
left: calc(-1 * var(--_track-border-width));
|
|
278
|
+
width: calc(100% + var(--_track-border-width) * 2);
|
|
279
|
+
height: var(--_thumb-size);
|
|
280
|
+
margin: 0;
|
|
281
|
+
padding: 0;
|
|
282
|
+
transform: translateY(-50%);
|
|
283
|
+
appearance: none;
|
|
284
|
+
background: transparent;
|
|
285
|
+
pointer-events: none;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
}
|
|
288
|
+
.thumb:disabled { cursor: not-allowed; }
|
|
289
|
+
.thumb::-webkit-slider-thumb {
|
|
290
|
+
appearance: none;
|
|
291
|
+
width: var(--_thumb-size);
|
|
292
|
+
height: var(--_thumb-size);
|
|
293
|
+
opacity: 0;
|
|
294
|
+
pointer-events: auto;
|
|
295
|
+
}
|
|
296
|
+
.thumb::-moz-range-thumb {
|
|
297
|
+
width: var(--_thumb-size);
|
|
298
|
+
height: var(--_thumb-size);
|
|
299
|
+
border: 0;
|
|
300
|
+
opacity: 0;
|
|
301
|
+
pointer-events: auto;
|
|
302
|
+
}
|
|
303
|
+
.thumb::-webkit-slider-runnable-track,
|
|
304
|
+
.thumb::-moz-range-track {
|
|
305
|
+
background: transparent;
|
|
306
|
+
border: 0;
|
|
307
|
+
}
|
|
308
|
+
.thumb:focus-visible { outline: none; }
|
|
309
|
+
|
|
310
|
+
.cap {
|
|
311
|
+
position: absolute;
|
|
312
|
+
top: 50%;
|
|
313
|
+
left: calc(var(--_origin) + var(--_travel) * var(--_at));
|
|
314
|
+
width: var(--_thumb-size);
|
|
315
|
+
height: var(--_thumb-size);
|
|
316
|
+
box-sizing: border-box;
|
|
317
|
+
transform: translate(-50%, -50%);
|
|
318
|
+
background: var(--_thumb-surface);
|
|
319
|
+
border: var(--_thumb-border-width) solid var(--_thumb-border);
|
|
320
|
+
border-radius: var(--_thumb-radius);
|
|
321
|
+
box-shadow: var(--_thumb-shadow);
|
|
322
|
+
pointer-events: none;
|
|
323
|
+
transition: background var(--duration-150), border-color var(--duration-150);
|
|
324
|
+
}
|
|
325
|
+
.thumb:focus-visible + .cap {
|
|
326
|
+
outline: var(--_thumb-border-width) solid var(--_thumb-border);
|
|
327
|
+
outline-offset: var(--_thumb-border-width);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.slider.single .thumb:hover + .cap,
|
|
331
|
+
.slider.single.force-hover .cap {
|
|
332
|
+
--_thumb-surface: var(--slider-single-hover-thumb-surface);
|
|
333
|
+
--_thumb-border: var(--slider-single-hover-thumb-border);
|
|
334
|
+
}
|
|
335
|
+
.slider.range .thumb:hover + .cap,
|
|
336
|
+
.slider.range.force-hover .cap {
|
|
337
|
+
--_thumb-surface: var(--slider-range-hover-thumb-surface);
|
|
338
|
+
--_thumb-border: var(--slider-range-hover-thumb-border);
|
|
339
|
+
}
|
|
340
|
+
.slider.single .thumb:disabled + .cap {
|
|
341
|
+
--_thumb-surface: var(--slider-single-disabled-thumb-surface);
|
|
342
|
+
--_thumb-border: var(--slider-single-disabled-thumb-border);
|
|
343
|
+
}
|
|
344
|
+
.slider.range .thumb:disabled + .cap {
|
|
345
|
+
--_thumb-surface: var(--slider-range-disabled-thumb-surface);
|
|
346
|
+
--_thumb-border: var(--slider-range-disabled-thumb-border);
|
|
347
|
+
}
|
|
348
|
+
</style>
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
|
+
/** Wash the hovered surface with the theme's tint. `undefined` inherits the
|
|
13
|
+
global default; `true`/`false` force this instance on/off. */
|
|
14
|
+
hoverTint?: boolean | undefined;
|
|
12
15
|
tabs?: Tab[];
|
|
13
16
|
selectedTab?: string;
|
|
14
17
|
iconOnly?: boolean;
|
|
@@ -18,6 +21,7 @@
|
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
let {
|
|
24
|
+
hoverTint = undefined,
|
|
21
25
|
tabs = [],
|
|
22
26
|
selectedTab = '',
|
|
23
27
|
iconOnly = false,
|
|
@@ -25,6 +29,11 @@
|
|
|
25
29
|
ontabChange
|
|
26
30
|
}: Props = $props();
|
|
27
31
|
|
|
32
|
+
// Per-instance override of the global hover-tint intrinsic; undefined leaves :root in charge.
|
|
33
|
+
let hoverTintValue = $derived(
|
|
34
|
+
hoverTint === undefined ? undefined : hoverTint ? 'var(--tabbar-hover-tint)' : 'var(--color-transparent)',
|
|
35
|
+
);
|
|
36
|
+
|
|
28
37
|
// Dual-fire bridge — see Button.svelte for the deprecation timeline.
|
|
29
38
|
const dispatch = createEventDispatcher<{
|
|
30
39
|
tabChange: string;
|
|
@@ -38,7 +47,10 @@
|
|
|
38
47
|
}
|
|
39
48
|
</script>
|
|
40
49
|
|
|
41
|
-
<div
|
|
50
|
+
<div
|
|
51
|
+
class="tab-bar {className}"
|
|
52
|
+
style:--tabbar-hover-tint-enabled={hoverTintValue}
|
|
53
|
+
>
|
|
42
54
|
{#each tabs as tab}
|
|
43
55
|
<button
|
|
44
56
|
class="tab"
|
|
@@ -61,6 +73,11 @@
|
|
|
61
73
|
@use '../styles/padding' as *;
|
|
62
74
|
|
|
63
75
|
:global(:root) {
|
|
76
|
+
/* Hover tint: an optional wash over the hovered surface, one stop for the
|
|
77
|
+
whole component. Off by default; the gate holds the tint when enabled. */
|
|
78
|
+
--tabbar-hover-tint: var(--tint);
|
|
79
|
+
--tabbar-hover-tint-enabled: var(--color-transparent);
|
|
80
|
+
|
|
64
81
|
/* Bar */
|
|
65
82
|
--tabbar-bar-divider: var(--border-neutral-subtle);
|
|
66
83
|
--tabbar-bar-divider-thickness: var(--border-width-1);
|
|
@@ -104,7 +121,7 @@
|
|
|
104
121
|
/* Active tab */
|
|
105
122
|
--tabbar-active-text: var(--text-primary);
|
|
106
123
|
--tabbar-active-border: var(--color-brand-500);
|
|
107
|
-
--tabbar-active-surface: var(--
|
|
124
|
+
--tabbar-active-surface: var(--tint-low);
|
|
108
125
|
--tabbar-active-text-font-family: var(--font-sans);
|
|
109
126
|
--tabbar-active-text-font-size: var(--font-size-md);
|
|
110
127
|
--tabbar-active-text-font-weight: var(--font-weight-light);
|
|
@@ -197,6 +214,7 @@
|
|
|
197
214
|
|
|
198
215
|
.tab:hover:not(:disabled):not(.active),
|
|
199
216
|
.tab-bar.force-hover .tab:not(:disabled):not(.active) {
|
|
217
|
+
background-image: linear-gradient(var(--tabbar-hover-tint-enabled), var(--tabbar-hover-tint-enabled));
|
|
200
218
|
--_text-color: var(--tabbar-hover-text);
|
|
201
219
|
--_text-family: var(--tabbar-hover-text-font-family);
|
|
202
220
|
--_text-size: var(--tabbar-hover-text-font-size);
|
|
@@ -57,11 +57,11 @@ Every theme token starts with a **category prefix**. The category tells you what
|
|
|
57
57
|
| `--ring-*` | Focus rings (separate namespace from shadows) |
|
|
58
58
|
| `--border-width-*` | Stroke widths |
|
|
59
59
|
| `--transition-*`, `--opacity-*`, `--z-*` | Animation, opacity, z-index scales |
|
|
60
|
-
| `--
|
|
60
|
+
| `--scrim-*`, `--tint-*` | Scrims dim what is behind them; tints shade a surface |
|
|
61
61
|
| `--gradient-*` | Named gradients |
|
|
62
62
|
| `--columns-*`, `--page-*` | Page-level layout primitives |
|
|
63
63
|
|
|
64
|
-
**Rule: a bare one-word token belongs to *some* category.** If you find yourself writing `--
|
|
64
|
+
**Rule: a bare one-word token belongs to *some* category.** If you find yourself writing `--scrim` or `--border`, make sure the category is obvious and it's the canonical default. Otherwise pick a longer name that slots it into its family (e.g. `--border-neutral`).
|
|
65
65
|
|
|
66
66
|
### Families within a category
|
|
67
67
|
|
|
@@ -266,10 +266,18 @@
|
|
|
266
266
|
--surface-danger-higher: oklch(0.5836 0.1757 24.75);
|
|
267
267
|
--surface-danger-highest: oklch(0.6696 0.1423 24.72);
|
|
268
268
|
|
|
269
|
-
/*
|
|
270
|
-
|
|
271
|
-
--
|
|
272
|
-
--
|
|
269
|
+
/* Scrims — translucent layers that dim what sits behind them. A scrim is not
|
|
270
|
+
a tint: it darkens the content underneath, it does not shade a surface. */
|
|
271
|
+
--scrim-low: rgba(20, 3, 0, 0.38);
|
|
272
|
+
--scrim: rgba(20, 3, 0, 0.51);
|
|
273
|
+
--scrim-high: rgba(20, 3, 0, 0.64);
|
|
274
|
+
|
|
275
|
+
/* Tints — translucent washes that shade the surface they sit on, for a hover
|
|
276
|
+
or an active row. The theme aliases these to the text color, so a tint
|
|
277
|
+
lightens a dark theme and darkens a light one without picking a direction. */
|
|
278
|
+
--tint-low: rgba(255, 255, 255, 0.05);
|
|
279
|
+
--tint: rgba(255, 255, 255, 0.1);
|
|
280
|
+
--tint-high: rgba(255, 255, 255, 0.15);
|
|
273
281
|
|
|
274
282
|
/* BORDERS — 5-point scales, faint → strong. */
|
|
275
283
|
|
package/template/package.json
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite",
|
|
8
|
-
"build": "vite build",
|
|
8
|
+
"build": "npm run check:design && vite build",
|
|
9
9
|
"preview": "vite preview",
|
|
10
|
-
"check": "svelte-check --tsconfig ./tsconfig.json"
|
|
10
|
+
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
+
"check:design": "live-tokens check-page && live-tokens check-component"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
|
13
14
|
"@motion-proto/live-tokens": "__LT_VERSION__"
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Overlays slice — overlay + hover stops, each stored as `{alias, opacity}`.
|
|
3
|
-
* Emits as `color-mix(in srgb, var(<alias>) <pct>%, transparent)` so the
|
|
4
|
-
* overlay tints automatically follow the aliased source token (a brand-color
|
|
5
|
-
* shift propagates without re-editing every overlay).
|
|
6
|
-
*
|
|
7
|
-
* Defaults diverge from tokens.css by design: the editor starts from a
|
|
8
|
-
* neutral alias and tokens.css continues to win until first edit (see
|
|
9
|
-
* `overlaysEqualsDefault`).
|
|
10
|
-
*/
|
|
11
|
-
import type { EditorState, OverlayToken } from '../../store/editorTypes';
|
|
12
|
-
|
|
13
|
-
export function makeDefaultOverlayTokens(): OverlayToken[] {
|
|
14
|
-
return [
|
|
15
|
-
{ variable: '--overlay-low', label: 'Low', alias: '--surface-neutral-lowest', opacity: 0.38 },
|
|
16
|
-
{ variable: '--overlay', label: 'Base', alias: '--surface-neutral-lowest', opacity: 0.51 },
|
|
17
|
-
{ variable: '--overlay-high', label: 'High', alias: '--surface-neutral-lowest', opacity: 0.64 },
|
|
18
|
-
];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function makeDefaultHoverTokens(): OverlayToken[] {
|
|
22
|
-
return [
|
|
23
|
-
{ variable: '--hover-low', label: 'Low', alias: '--text-primary', opacity: 0.05 },
|
|
24
|
-
{ variable: '--hover', label: 'Base', alias: '--text-primary', opacity: 0.1 },
|
|
25
|
-
{ variable: '--hover-high', label: 'High', alias: '--text-primary', opacity: 0.15 },
|
|
26
|
-
];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function makeDefaultOverlaysState(): EditorState['overlays'] {
|
|
30
|
-
return {
|
|
31
|
-
tokens: makeDefaultOverlayTokens(),
|
|
32
|
-
hoverTokens: makeDefaultHoverTokens(),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const OVERLAY_VAR_NAMES = [
|
|
37
|
-
'--overlay-low', '--overlay', '--overlay-high',
|
|
38
|
-
'--hover-low', '--hover', '--hover-high',
|
|
39
|
-
] as const;
|
|
40
|
-
|
|
41
|
-
export function overlayTokenToCss(t: OverlayToken): string {
|
|
42
|
-
const pct = Math.round(t.opacity * 100);
|
|
43
|
-
if (pct >= 100) return `var(${t.alias})`;
|
|
44
|
-
return `color-mix(in srgb, var(${t.alias}) ${pct}%, transparent)`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const COLOR_MIX_RE = /^color-mix\(in srgb,\s*var\((--[a-z0-9-]+)\)\s+(\d+)%,\s*transparent\)$/i;
|
|
48
|
-
const PLAIN_VAR_RE = /^var\((--[a-z0-9-]+)\)$/i;
|
|
49
|
-
|
|
50
|
-
export function parseOverlayCss(raw: string): { alias: string; opacity: number } | null {
|
|
51
|
-
const s = raw.trim();
|
|
52
|
-
const mix = s.match(COLOR_MIX_RE);
|
|
53
|
-
if (mix) {
|
|
54
|
-
const pct = parseInt(mix[2], 10);
|
|
55
|
-
if (!Number.isFinite(pct)) return null;
|
|
56
|
-
return { alias: mix[1], opacity: Math.max(0, Math.min(100, pct)) / 100 };
|
|
57
|
-
}
|
|
58
|
-
const plain = s.match(PLAIN_VAR_RE);
|
|
59
|
-
if (plain) return { alias: plain[1], opacity: 1 };
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function overlaysToVars(o: EditorState['overlays']): Record<string, string> {
|
|
64
|
-
const out: Record<string, string> = {};
|
|
65
|
-
for (const t of o.tokens) out[t.variable] = overlayTokenToCss(t);
|
|
66
|
-
for (const t of o.hoverTokens) out[t.variable] = overlayTokenToCss(t);
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function applyOverlayVarsToState(overlays: EditorState['overlays'], vars: Record<string, string>): void {
|
|
71
|
-
const applyTo = (list: OverlayToken[]) => {
|
|
72
|
-
for (const t of list) {
|
|
73
|
-
const raw = vars[t.variable];
|
|
74
|
-
if (!raw) continue;
|
|
75
|
-
const parsed = parseOverlayCss(raw);
|
|
76
|
-
if (!parsed) continue;
|
|
77
|
-
t.alias = parsed.alias;
|
|
78
|
-
t.opacity = parsed.opacity;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
applyTo(overlays.tokens);
|
|
82
|
-
applyTo(overlays.hoverTokens);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Loader: route overlay/hover entries from a freshly-loaded theme's vars
|
|
87
|
-
* bag into `next.overlays` and remove them from the bag — but only when the
|
|
88
|
-
* value parses as the new format (color-mix / plain var). Legacy rgba values
|
|
89
|
-
* pass through to the cssVars bag so the DOM still paints them; the user's
|
|
90
|
-
* next edit in the picker promotes them to the typed slice.
|
|
91
|
-
*/
|
|
92
|
-
export function loadOverlaysFromVars(
|
|
93
|
-
next: EditorState,
|
|
94
|
-
rawVars: Record<string, string>,
|
|
95
|
-
): void {
|
|
96
|
-
applyOverlayVarsToState(next.overlays, rawVars);
|
|
97
|
-
for (const name of OVERLAY_VAR_NAMES) {
|
|
98
|
-
const raw = rawVars[name];
|
|
99
|
-
if (raw && parseOverlayCss(raw) !== null) delete rawVars[name];
|
|
100
|
-
}
|
|
101
|
-
}
|