Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion badges/badge-optimal.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"label": "optimal",
"message": "17.6 kB gzip",
"message": "37.0 kB gzip",
"color": "brightgreen",
"namedLogo": "css3"
}
88 changes: 77 additions & 11 deletions configurator/src/components/inputs/SliderRow.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<script lang="ts">
import RangeWithNumber from './RangeWithNumber.svelte';

let { label, help, value, min, max, step, unit, overridden, onChange, onReset }: {
label: string;
let {
label, help, value, min, max, step, unit, overridden, onChange, onReset,
rawDefault, currentRaw, onRawSet
}: {
label?: string;
help?: string;
value: number;
min: number;
Expand All @@ -12,22 +15,85 @@
overridden: boolean;
onChange: (v: number) => void;
onReset: () => void;
rawDefault?: string;
currentRaw?: string;
onRawSet?: (v: string) => void;
} = $props();

let userRawMode = $state(false);

// Auto raw mode when override value is a CSS expression
let isRawOverride = $derived(
!!currentRaw && /^(var|calc|clamp|min|max|env)\(/.test(currentRaw.trim())
);

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 ?? '';
}
});
</script>

<div class="group">
<div class="flex items-center justify-between mb-1.5">
<span class="text-[11px] font-semibold text-slate-200">{label}</span>
{#if overridden}
<button
onclick={onReset}
class="text-[9px] text-slate-500 hover:text-rose-400 cursor-pointer opacity-0 group-hover:opacity-100 transition-colors"
>
reset
</button>
{#if label}
<span class="text-[11px] font-semibold text-slate-200">{label}</span>
{:else}
<span></span>
{/if}
<div class="flex items-center gap-1.5">
{#if rawDefault && onRawSet}
<button
onclick={() => { userRawMode = !userRawMode; }}
title={showRaw ? "Switch to slider" : "Enter raw CSS value"}
class={`text-[9px] font-mono cursor-pointer transition-all px-0.5 ${
showRaw
? 'text-indigo-400'
: 'opacity-0 group-hover:opacity-100 text-slate-500 hover:text-indigo-400'
}`}
>&lt;/&gt;</button>
{/if}
{#if overridden}
<button
onclick={onReset}
class="text-[9px] text-slate-500 hover:text-rose-400 cursor-pointer opacity-0 group-hover:opacity-100 transition-colors"
>reset</button>
{/if}
</div>
</div>
<RangeWithNumber {value} {min} {max} {step} {unit} {onChange} />

{#if showRaw && rawDefault}
<input
type="text"
value={rawDraft}
placeholder={rawDefault}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
onfocus={() => { isEditing = true; }}
onblur={() => { isEditing = false; }}
oninput={(e) => {
rawDraft = (e.target as HTMLInputElement).value;
const v = rawDraft.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}
<RangeWithNumber {value} {min} {max} {step} {unit} {onChange} />
{#if rawDefault && !overridden}
<p class="text-[9px] font-mono text-slate-700 mt-0.5 leading-none">default: {rawDefault}</p>
{/if}
{/if}

{#if help}
<p class="text-[10px] text-slate-600 mt-1 leading-relaxed">{help}</p>
{/if}
Expand Down
48 changes: 31 additions & 17 deletions configurator/src/components/panels/BordersPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@

const BORDER_STYLES = ["solid", "dashed", "dotted"];
const RADIUS_STEPS = ["xs", "s", "m", "l", "xl", "2xl", "full"];
const BASE_RADII: Record<string, number> = { xs: 2, s: 4, m: 6, l: 10, xl: 14, "2xl": 20, full: 9999 };
const BASE_RADII: Record<string, number> = { 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"] ?? [];
Expand All @@ -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"] ?? "");
Expand All @@ -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;
}
</script>

Expand Down Expand Up @@ -254,13 +256,19 @@
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)}
/>
<SliderRow
label="Gap" value={dividerGap} min={0} max={4} step={0.125} unit="rem"
help="--sf-divider-gap — spacing around divider lines"
overridden={"--sf-divider-gap" in overrides}
onChange={(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}
</section>
Expand Down Expand Up @@ -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}
</div>
Expand Down Expand Up @@ -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}
</div>
Expand Down
24 changes: 24 additions & 0 deletions configurator/src/components/panels/LayoutPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,19 @@
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)}
/>
<SliderRow
label="Gutter" value={centerGutter} min={0} max={4} step={0.125} unit="rem"
help="--sf-center-gutter — minimum side padding inside the center wrapper"
overridden={"--sf-center-gutter" in overrides}
onChange={(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}
</section>
Expand Down Expand Up @@ -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)}
/>
</div>
<div>
Expand All @@ -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)}
/>
</div>
</div>
Expand Down Expand Up @@ -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)}
/>
</section>

Expand All @@ -564,13 +579,19 @@
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)}
/>
<SliderRow
label="Breakout width" value={breakoutWidth} min={50} max={160} step={1} unit="rem"
help="--sf-breakout-width — wide breakout column width in .sf-content-grid"
overridden={"--sf-breakout-width" in overrides}
onChange={(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)}
/>
</section>

Expand All @@ -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)}
/>
</section>
</div>
Expand Down
9 changes: 9 additions & 0 deletions configurator/src/components/panels/SpacingPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,29 @@
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)}
/>
<SliderRow
label="Content gap" value={contentGapVal} min={0} max={2} step={0.0625} unit="rem"
help="--sf-content-gap — tighter gap within components (defaults to space-s)"
overridden={"--sf-content-gap" in overrides}
onChange={(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)}
/>
<SliderRow
label="Gutter" value={gutterVal} min={0} max={6} step={0.0625} unit="rem"
help="--sf-gutter — wide page/section side gutter (containers, .sf-section--guttered)"
overridden={"--sf-gutter" in overrides}
onChange={(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}
</section>
Expand Down
Loading