From 046966612fbc065af1ec236ed6131f79591cc28c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 08:21:32 +0000 Subject: [PATCH 1/2] fix(configurator): correct token defaults and add raw CSS input mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SliderRow now accepts rawDefault + onRawSet props; shows actual framework default (e.g. "default: var(--sf-space-m)") below the slider and exposes a toggle to switch to a free-text raw input that accepts any CSS value including var()/calc() expressions. Auto-switches to raw mode when the current override is a CSS function. - SpacingPanel: --sf-gap, --sf-content-gap, --sf-gutter now show their real defaults (var(--sf-space-m/s/l)) instead of hardcoded rem values. - LayoutPanel: --sf-center-gutter, --sf-imposter-margin, --sf-alternate-inner-gap, --sf-sticky-offset-mobile/desktop, --sf-center-max, --sf-content-width, --sf-breakout-width all wired with rawDefault so their CSS var relationships are visible. - BordersPanel: fix radius step defaults (m: 6→8px, l: 10→12px, xl: 14→16px, 2xl: 20→24px to match calc(Npx * var(--sf-radius-scale))); fix button token names --sf-button-* → --sf-btn-* to match the framework's optional/tokens.components.css; add rawDefault for all component shape, divider-gap, and divider-width tokens. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_011GgFN1mxJWjgGdwV6WGj2B --- badges/badge-optimal.json | 2 +- .../src/components/inputs/SliderRow.svelte | 72 ++++++++++++++++--- .../src/components/panels/BordersPanel.svelte | 48 ++++++++----- .../src/components/panels/LayoutPanel.svelte | 24 +++++++ .../src/components/panels/SpacingPanel.svelte | 9 +++ 5 files changed, 127 insertions(+), 28 deletions(-) diff --git a/badges/badge-optimal.json b/badges/badge-optimal.json index 18209ea4..38f8d927 100644 --- a/badges/badge-optimal.json +++ b/badges/badge-optimal.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "label": "optimal", - "message": "17.6 kB gzip", + "message": "37.0 kB gzip", "color": "brightgreen", "namedLogo": "css3" } diff --git a/configurator/src/components/inputs/SliderRow.svelte b/configurator/src/components/inputs/SliderRow.svelte index 363639c7..b971c5ac 100644 --- a/configurator/src/components/inputs/SliderRow.svelte +++ b/configurator/src/components/inputs/SliderRow.svelte @@ -1,7 +1,10 @@
- {label} - {#if overridden} - + {#if label} + {label} + {:else} + {/if} +
+ {#if rawDefault && onRawSet} + + {/if} + {#if overridden} + + {/if} +
- + + {#if showRaw && rawDefault} + { + const v = (e.target as HTMLInputElement).value.trim(); + if (!v) { + onReset(); + } else if (onRawSet) { + onRawSet(v); + } + }} + class="w-full bg-white/5 border border-white/10 rounded px-2 py-1.5 text-[11px] font-mono text-slate-300 placeholder:text-slate-500 focus:outline-none focus:border-indigo-500" + /> + {:else} + + {#if rawDefault && !overridden} +

default: {rawDefault}

+ {/if} + {/if} + {#if help}

{help}

{/if} diff --git a/configurator/src/components/panels/BordersPanel.svelte b/configurator/src/components/panels/BordersPanel.svelte index b4adda51..d7f2fcc8 100644 --- a/configurator/src/components/panels/BordersPanel.svelte +++ b/configurator/src/components/panels/BordersPanel.svelte @@ -15,24 +15,24 @@ const BORDER_STYLES = ["solid", "dashed", "dotted"]; const RADIUS_STEPS = ["xs", "s", "m", "l", "xl", "2xl", "full"]; - const BASE_RADII: Record = { xs: 2, s: 4, m: 6, l: 10, xl: 14, "2xl": 20, full: 9999 }; + const BASE_RADII: Record = { xs: 2, s: 4, m: 8, l: 12, xl: 16, "2xl": 24, full: 9999 }; - const RADIUS_FINE: Array<{ step: string; name: string; default: number; max: number; step_size: number }> = [ - { step: "xs", name: "--sf-radius-xs", default: 2, max: 16, step_size: 0.5 }, - { step: "s", name: "--sf-radius-s", default: 4, max: 24, step_size: 0.5 }, - { step: "m", name: "--sf-radius-m", default: 6, max: 32, step_size: 1 }, - { step: "l", name: "--sf-radius-l", default: 10, max: 48, step_size: 1 }, - { step: "xl", name: "--sf-radius-xl", default: 14, max: 64, step_size: 1 }, - { step: "2xl", name: "--sf-radius-2xl", default: 20, max: 80, step_size: 2 }, + const RADIUS_FINE: Array<{ step: string; name: string; default: number; max: number; step_size: number; rawDefault: string }> = [ + { step: "xs", name: "--sf-radius-xs", default: 2, max: 16, step_size: 0.5, rawDefault: "calc(2px * var(--sf-radius-scale))" }, + { step: "s", name: "--sf-radius-s", default: 4, max: 24, step_size: 0.5, rawDefault: "calc(4px * var(--sf-radius-scale))" }, + { step: "m", name: "--sf-radius-m", default: 8, max: 32, step_size: 1, rawDefault: "calc(8px * var(--sf-radius-scale))" }, + { step: "l", name: "--sf-radius-l", default: 12, max: 48, step_size: 1, rawDefault: "calc(12px * var(--sf-radius-scale))" }, + { step: "xl", name: "--sf-radius-xl", default: 16, max: 64, step_size: 1, rawDefault: "calc(16px * var(--sf-radius-scale))" }, + { step: "2xl", name: "--sf-radius-2xl", default: 24, max: 80, step_size: 2, rawDefault: "calc(24px * var(--sf-radius-scale))" }, ]; const COMPONENT_TOKENS = [ - { label: "Button radius", token: "--sf-button-radius", unit: "rem", min: 0, max: 2, step: 0.05, default: 0.375, help: "--sf-button-radius" }, - { label: "Button padding block", token: "--sf-button-padding-block", unit: "rem", min: 0, max: 1, step: 0.025, default: 0.375, help: "--sf-button-padding-block" }, - { label: "Button padding inline", token: "--sf-button-padding-inline", unit: "rem", min: 0, max: 2, step: 0.025, default: 0.875, help: "--sf-button-padding-inline" }, - { label: "Field radius", token: "--sf-field-radius", unit: "rem", min: 0, max: 2, step: 0.05, default: 0.375, help: "--sf-field-radius" }, - { label: "Field padding block", token: "--sf-field-padding-block", unit: "rem", min: 0, max: 1, step: 0.025, default: 0.375, help: "--sf-field-padding-block" }, - { label: "Field padding inline", token: "--sf-field-padding-inline", unit: "rem", min: 0, max: 2, step: 0.025, default: 0.75, help: "--sf-field-padding-inline" }, + { label: "Button radius", token: "--sf-btn-radius", unit: "rem", min: 0, max: 2, step: 0.05, default: 0.5, rawDefault: "var(--sf-radius-m)", help: "--sf-btn-radius" }, + { label: "Button padding block", token: "--sf-btn-padding-block", unit: "rem", min: 0, max: 1, step: 0.025, default: 0.375, rawDefault: "var(--sf-space-xs)", help: "--sf-btn-padding-block" }, + { label: "Button padding inline", token: "--sf-btn-padding-inline", unit: "rem", min: 0, max: 2, step: 0.025, default: 1, rawDefault: "var(--sf-space-m)", help: "--sf-btn-padding-inline" }, + { label: "Field radius", token: "--sf-field-radius", unit: "rem", min: 0, max: 2, step: 0.05, default: 0.5, rawDefault: "var(--sf-radius-m)", help: "--sf-field-radius" }, + { label: "Field padding block", token: "--sf-field-padding-block", unit: "rem", min: 0, max: 1, step: 0.025, default: 0.375, rawDefault: "var(--sf-space-xs)", help: "--sf-field-padding-block" }, + { label: "Field padding inline", token: "--sf-field-padding-inline", unit: "rem", min: 0, max: 2, step: 0.025, default: 0.75, rawDefault: "var(--sf-space-s)", help: "--sf-field-padding-inline" }, ]; const knobs = KNOBS_BY_DOMAIN["borders"] ?? []; @@ -58,7 +58,7 @@ let focusWidth = $derived(parseNum(overrides["--sf-focus-ring-width"], 2, "px")); let focusOffset = $derived(parseNum(overrides["--sf-focus-ring-offset"], 2, "px")); let dividerWidth = $derived(parseNum(overrides["--sf-divider-width"]?.replace("px",""), 1)); - let dividerGap = $derived(parseNum(overrides["--sf-divider-gap"]?.replace("rem",""), 1)); + let dividerGap = $derived(parseNum(overrides["--sf-divider-gap"]?.replace("rem",""), 1)); // ~1rem ≈ var(--sf-space-m) at default scale let borderColor = $derived(overrides["--sf-color-border"] ?? ""); let borderStyle = $derived(overrides["--sf-border-style"] ?? "solid"); let focusRingColor = $derived(overrides["--sf-focus-ring-color"] ?? ""); @@ -71,13 +71,15 @@ function getRadiusValue(r: typeof RADIUS_FINE[0]): number { const raw = overrides[r.name]; if (!raw) return r.default; - return parseFloat(raw); + if (/^(var|calc|clamp)\(/.test(raw.trim())) return r.default; + return parseFloat(raw) || r.default; } function getComponentVal(t: typeof COMPONENT_TOKENS[0]): number { const raw = overrides[t.token]; if (!raw) return t.default; - return parseFloat(raw); + if (/^(var|calc|clamp)\(/.test(raw.trim())) return t.default; + return parseFloat(raw) || t.default; } @@ -254,6 +256,9 @@ overridden={"--sf-divider-width" in overrides} onChange={(v) => onSet("--sf-divider-width", `${v}px`)} onReset={() => onReset("--sf-divider-width")} + rawDefault="var(--sf-border-width-1)" + currentRaw={overrides["--sf-divider-width"]} + onRawSet={(v) => onSet("--sf-divider-width", v)} /> onSet("--sf-divider-gap", `${v}rem`)} onReset={() => onReset("--sf-divider-gap")} + rawDefault="var(--sf-space-m)" + currentRaw={overrides["--sf-divider-gap"]} + onRawSet={(v) => onSet("--sf-divider-gap", v)} /> {/if} @@ -363,6 +371,9 @@ overridden={r.name in overrides} onChange={(v) => onSet(r.name, `${v}px`)} onReset={() => onReset(r.name)} + rawDefault={r.rawDefault} + currentRaw={overrides[r.name]} + onRawSet={(v) => onSet(r.name, v)} /> {/each}
@@ -419,6 +430,9 @@ overridden={t.token in overrides} onChange={(v) => onSet(t.token, `${v}${t.unit}`)} onReset={() => onReset(t.token)} + rawDefault={t.rawDefault} + currentRaw={overrides[t.token]} + onRawSet={(v) => onSet(t.token, v)} /> {/each} diff --git a/configurator/src/components/panels/LayoutPanel.svelte b/configurator/src/components/panels/LayoutPanel.svelte index b526e292..89b8f2c7 100644 --- a/configurator/src/components/panels/LayoutPanel.svelte +++ b/configurator/src/components/panels/LayoutPanel.svelte @@ -169,6 +169,9 @@ overridden={"--sf-center-max" in overrides} onChange={(v) => onSet("--sf-center-max", `${v}rem`)} onReset={() => onReset("--sf-center-max")} + rawDefault="var(--sf-container-default)" + currentRaw={overrides["--sf-center-max"]} + onRawSet={(v) => onSet("--sf-center-max", v)} /> onSet("--sf-center-gutter", `${v}rem`)} onReset={() => onReset("--sf-center-gutter")} + rawDefault="var(--sf-gutter)" + currentRaw={overrides["--sf-center-gutter"]} + onRawSet={(v) => onSet("--sf-center-gutter", v)} /> {/if} @@ -307,6 +313,9 @@ overridden={"--sf-sticky-offset-mobile" in overrides} onChange={(v) => onSet("--sf-sticky-offset-mobile", `${v}rem`)} onReset={() => onReset("--sf-sticky-offset-mobile")} + rawDefault="var(--sf-header-height-mobile)" + currentRaw={overrides["--sf-sticky-offset-mobile"]} + onRawSet={(v) => onSet("--sf-sticky-offset-mobile", v)} />
@@ -316,6 +325,9 @@ overridden={"--sf-sticky-offset-desktop" in overrides} onChange={(v) => onSet("--sf-sticky-offset-desktop", `${v}rem`)} onReset={() => onReset("--sf-sticky-offset-desktop")} + rawDefault="var(--sf-header-height-desktop)" + currentRaw={overrides["--sf-sticky-offset-desktop"]} + onRawSet={(v) => onSet("--sf-sticky-offset-desktop", v)} />
@@ -552,6 +564,9 @@ overridden={"--sf-imposter-margin" in overrides} onChange={(v) => onSet("--sf-imposter-margin", `${v}rem`)} onReset={() => onReset("--sf-imposter-margin")} + rawDefault="var(--sf-space-m)" + currentRaw={overrides["--sf-imposter-margin"]} + onRawSet={(v) => onSet("--sf-imposter-margin", v)} /> @@ -564,6 +579,9 @@ overridden={"--sf-content-width" in overrides} onChange={(v) => onSet("--sf-content-width", `${v}rem`)} onReset={() => onReset("--sf-content-width")} + rawDefault="var(--sf-container-default)" + currentRaw={overrides["--sf-content-width"]} + onRawSet={(v) => onSet("--sf-content-width", v)} /> onSet("--sf-breakout-width", `${v}rem`)} onReset={() => onReset("--sf-breakout-width")} + rawDefault="var(--sf-container-wide)" + currentRaw={overrides["--sf-breakout-width"]} + onRawSet={(v) => onSet("--sf-breakout-width", v)} /> @@ -583,6 +604,9 @@ overridden={"--sf-alternate-inner-gap" in overrides} onChange={(v) => onSet("--sf-alternate-inner-gap", `${v}rem`)} onReset={() => onReset("--sf-alternate-inner-gap")} + rawDefault="var(--sf-gap)" + currentRaw={overrides["--sf-alternate-inner-gap"]} + onRawSet={(v) => onSet("--sf-alternate-inner-gap", v)} /> diff --git a/configurator/src/components/panels/SpacingPanel.svelte b/configurator/src/components/panels/SpacingPanel.svelte index 61fb7d28..cc754e16 100644 --- a/configurator/src/components/panels/SpacingPanel.svelte +++ b/configurator/src/components/panels/SpacingPanel.svelte @@ -86,6 +86,9 @@ overridden={"--sf-gap" in overrides} onChange={(v) => onSet("--sf-gap", `${v}rem`)} onReset={() => onReset("--sf-gap")} + rawDefault="var(--sf-space-m)" + currentRaw={overrides["--sf-gap"]} + onRawSet={(v) => onSet("--sf-gap", v)} /> onSet("--sf-content-gap", `${v}rem`)} onReset={() => onReset("--sf-content-gap")} + rawDefault="var(--sf-space-s)" + currentRaw={overrides["--sf-content-gap"]} + onRawSet={(v) => onSet("--sf-content-gap", v)} /> onSet("--sf-gutter", `${v}rem`)} onReset={() => onReset("--sf-gutter")} + rawDefault="var(--sf-space-l)" + currentRaw={overrides["--sf-gutter"]} + onRawSet={(v) => onSet("--sf-gutter", v)} /> {/if} From c5a1fd0e874108d61fa1b209f7ff8f197c6f2024 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 29 Jun 2026 08:42:03 +0000 Subject: [PATCH 2/2] fix(configurator): raw input no longer clears while typing Introduced a local rawDraft state and isEditing flag so the text field keeps its value during incremental typing. External currentRaw changes sync back to rawDraft only when the field is not focused, preventing Svelte re-renders from blanking the input mid-edit. Also loosened the label prop type to optional (label?: string) to match the existing template guard. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_011GgFN1mxJWjgGdwV6WGj2B --- .../src/components/inputs/SliderRow.svelte | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/configurator/src/components/inputs/SliderRow.svelte b/configurator/src/components/inputs/SliderRow.svelte index b971c5ac..35a6a730 100644 --- a/configurator/src/components/inputs/SliderRow.svelte +++ b/configurator/src/components/inputs/SliderRow.svelte @@ -5,7 +5,7 @@ label, help, value, min, max, step, unit, overridden, onChange, onReset, rawDefault, currentRaw, onRawSet }: { - label: string; + label?: string; help?: string; value: number; min: number; @@ -28,6 +28,17 @@ ); let showRaw = $derived(!!(rawDefault && onRawSet && (userRawMode || isRawOverride))); + + // Local draft so typing is never interrupted by re-renders + let rawDraft = $state(currentRaw ?? ''); + let isEditing = $state(false); + + // Sync draft from external currentRaw changes only when user is not actively editing + $effect(() => { + if (!isEditing) { + rawDraft = currentRaw ?? ''; + } + });
@@ -61,10 +72,13 @@ {#if showRaw && rawDefault} { isEditing = true; }} + onblur={() => { isEditing = false; }} oninput={(e) => { - const v = (e.target as HTMLInputElement).value.trim(); + rawDraft = (e.target as HTMLInputElement).value; + const v = rawDraft.trim(); if (!v) { onReset(); } else if (onRawSet) {