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
48 changes: 44 additions & 4 deletions configurator/src/components/inputs/ValueField.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@
let draft = $state("");
let editing = $state(false);
let cancelling = $state(false);
// Reset a user-selected tab when another surface changes this token.
// Switching back to Inherit throws away the override. The tab sits right next
// to Value/Expression and reads like a harmless view switch, so a stray click
// used to silently wipe an edit. When there's an actual override to lose we
// arm this confirm step instead of resetting immediately.
let confirmingInherit = $state(false);
// Reset a user-selected tab (and any pending confirm) when another surface
// changes this token.
$effect(() => {
overrideValue;
manualMode = null;
confirmingInherit = false;
});
// Seed the text field from the live value whenever we're not mid-edit.
$effect(() => {
Expand All @@ -41,8 +48,22 @@
}

function setMode(m: ValueMode) {
if (m === "inherit") {
// Nothing overridden → already inheriting, so this is a no-op, not a
// destructive reset; just settle on auto-detect.
if (overrideValue === undefined) { manualMode = null; confirmingInherit = false; return; }
// Otherwise require an explicit confirmation before discarding the value.
confirmingInherit = true;
return;
}
confirmingInherit = false;
manualMode = m;
if (m === "inherit") { onReset(); manualMode = null; }
}

function confirmInherit() {
confirmingInherit = false;
manualMode = null;
onReset();
}

const TABS: { id: ValueMode; label: string }[] = [
Expand All @@ -56,18 +77,37 @@
<!-- Mode tabs -->
<div class="flex items-center gap-0.5 p-0.5 rounded-md bg-black/5 dark:bg-white/5 w-fit">
{#each TABS as t (t.id)}
{@const active = confirmingInherit ? t.id === "inherit" : mode === t.id}
<button
onclick={() => setMode(t.id)}
aria-pressed={mode === t.id}
aria-pressed={active}
class={`px-1.5 py-0.5 rounded text-[9px] font-bold transition-colors cursor-pointer ${
mode === t.id
active
? "bg-white dark:bg-slate-700 text-indigo-700 dark:text-indigo-300 shadow-sm"
: "text-slate-500 hover:text-slate-700 dark:hover:text-slate-300"
}`}
>{t.label}</button>
{/each}
</div>

<!-- Confirm step for a destructive "back to Inherit" — arms on the first
click, discards the override only on the explicit Reset here. -->
{#if confirmingInherit}
<div class="flex items-center gap-1.5 px-2 py-1.5 rounded-md bg-rose-500/10 border border-rose-500/20">
<span class="text-[9px] text-rose-700 dark:text-rose-300 leading-snug flex-1 min-w-0">
Discard this override and inherit <span class="font-mono">{token.value}</span>?
</span>
<button
onclick={() => { confirmingInherit = false; }}
class="shrink-0 px-1.5 py-0.5 rounded text-[9px] font-bold text-slate-600 dark:text-slate-300 bg-black/5 dark:bg-white/10 hover:bg-black/10 dark:hover:bg-white/15 transition-colors cursor-pointer"
>Cancel</button>
<button
onclick={confirmInherit}
class="shrink-0 px-1.5 py-0.5 rounded text-[9px] font-bold text-white bg-rose-600 hover:bg-rose-500 transition-colors cursor-pointer"
>Reset</button>
</div>
{/if}

{#if mode === "inherit"}
<div class="text-[10px] font-mono text-slate-500 dark:text-slate-500">
default: <span class="text-slate-600 dark:text-slate-400">{token.value}</span>
Expand Down
20 changes: 20 additions & 0 deletions configurator/src/components/panels/BordersPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@
currentRaw={overrides["--sf-divider-gap"]}
onRawSet={(v) => onSet("--sf-divider-gap", v)}
/>

<!-- Divider preview — a real rule between two lines so colour, width,
style and the gap around it are all visible together. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3">
<p class="text-[10px] text-slate-600 dark:text-slate-400 m-0">Content above</p>
<hr style={`border:0;border-top:var(--sf-divider-width,1px) var(--sf-divider-style,solid) var(--sf-divider-color, var(--sf-color-border,#64748b));margin-block:var(--sf-divider-gap,1rem)`} />
<p class="text-[10px] text-slate-600 dark:text-slate-400 m-0">Content below</p>
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down Expand Up @@ -348,6 +356,18 @@
onRawSet={(v) => onSet(t.token, v)}
/>
{/each}

<!-- Field-shape preview — a real input styled from the field tokens, so
radius and the block/inline padding are directly visible. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3">
<input
type="text"
disabled
placeholder="Sample field"
class="w-full text-[11px] text-slate-600 dark:text-slate-300 bg-white/70 dark:bg-white/5 outline-none placeholder:text-slate-500"
style={`border:var(--sf-border-width-1,1px) solid var(--sf-color-border,#64748b);border-radius:var(--sf-field-radius,0.5rem);padding-block:var(--sf-field-padding-block,0.375rem);padding-inline:var(--sf-field-padding-inline,0.75rem)`}
/>
</div>
</div>
{/if}
</div>
Expand Down
14 changes: 14 additions & 0 deletions configurator/src/components/panels/EffectsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,20 @@
</div>
{/each}
</div>

<!-- Text-shadow preview — white text over a photo-like gradient (the real
use case: legibility over imagery), each step applying its token. -->
<div
class="rounded-xl border border-black/8 dark:border-white/8 p-4 flex gap-4 items-center justify-center flex-wrap"
style="background:linear-gradient(135deg,var(--sf-color-primary-400,#6366f1),var(--sf-color-secondary-500,#8b5cf6) 60%,var(--sf-color-tertiary-400,#0ea5e9))"
>
{#each TEXT_SHADOW_TOKENS as t (t.token)}
<div class="flex flex-col items-center gap-1">
<span class="text-[22px] font-bold text-white leading-none" style={`text-shadow:var(${t.token})`}>Aa</span>
<span class="text-[8px] font-mono text-white/70">{t.token.replace("--sf-text-shadow-", "")}</span>
</div>
{/each}
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down
31 changes: 31 additions & 0 deletions configurator/src/components/panels/LayoutPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,18 @@
{/if}
</div>
{/each}
<!-- Cluster preview — real .sf-cluster with uneven-height items, so
both align (cross-axis) and justify (main-axis) are visible. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3">
<div class="sf-cluster" style="min-height:3rem">
{#each [1.75, 1, 2.25, 1.25] as h, i (i)}
<span
class="flex items-center justify-center text-white text-[9px] font-bold"
style={`min-height:${h}rem;min-width:1.75rem;background:var(--sf-color-primary-400,#818cf8);border-radius:var(--sf-radius-s,4px)`}
>{i + 1}</span>
{/each}
</div>
</div>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</section>

<!-- Equal columns (flowing CSS multi-column, not a grid) -->
Expand Down Expand Up @@ -492,6 +504,13 @@
onReset={() => onReset("--sf-equal-rule-color")}
/>
</div>
<!-- Equal-columns preview — real .sf-equal so the flowing columns and
the rule between them react to every control above. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3">
<div class="sf-equal text-[9px] text-slate-600 dark:text-slate-300">
<p style="margin:0">SLASHED flows this text across as many equal columns as fit the box, divided by the column rule. Lower the min column width to pack in more, narrower columns; raise it for fewer, roomier ones. The rule width, style and colour draw the divider.</p>
</div>
</div>
</section>

<!-- Cover -->
Expand Down Expand Up @@ -549,6 +568,18 @@
<button onclick={() => onReset("--sf-reel-height")} class="text-[8px] text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer shrink-0">reset</button>
{/if}
</div>
<!-- Reel preview — real .sf-reel; each item takes --sf-reel-item-width
and the row scrolls horizontally when they overflow. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3">
<div class="sf-reel">
{#each [1, 2, 3, 4, 5, 6] as n (n)}
<div
class="flex items-center justify-center text-white text-[10px] font-bold"
style={`height:2.5rem;background:var(--sf-color-primary-400,#818cf8);border-radius:var(--sf-radius-s,4px)`}
>{n}</div>
{/each}
</div>
</div>
</section>

<!-- Imposter -->
Expand Down
82 changes: 82 additions & 0 deletions configurator/src/components/panels/MiscPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@
function getSizeValue(t: typeof SIZE_TOKENS[0]): number {
return parseNum(overrides[t.token]?.replace("rem",""), t.default);
}

const ICON_STEPS = ["xs", "s", "m", "l", "xl", "2xl"];

// A deliberately landscape (2:1) sample with a centered ring and a full-bleed
// border, so object-fit differences are unmistakable: cover crops the sides,
// contain letterboxes, fill distorts the circle, none overflows at native px.
const OBJECT_FIT_IMG = `data:image/svg+xml,${encodeURIComponent(
`<svg xmlns='http://www.w3.org/2000/svg' width='120' height='60'><rect width='120' height='60' fill='#6366f1'/><circle cx='60' cy='30' r='18' fill='none' stroke='#ffffff' stroke-width='4'/><rect x='3' y='3' width='114' height='54' fill='none' stroke='#ffffff' stroke-width='3'/></svg>`,
)}`;

// Layering ladder for the preview: ordered by each rung's *current* value
// (override or default), highest first, so reordering the numbers reorders
// the visual stack instead of showing a fixed hierarchy.
let zLadder = $derived(
Z_INDEX_STEPS
.map((z) => ({ ...z, val: parseNum(overrides[z.token], z.def) }))
.sort((a, b) => b.val - a.val),
);
</script>

<div class="p-4 space-y-6">
Expand Down Expand Up @@ -96,6 +114,24 @@
/>
{/each}
</div>

<!-- Layering preview — a static stack showing the rung order (higher
token = painted on top). The numbers set precedence; this shows what
that precedence means. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3">
<div class="text-[9px] text-slate-400 dark:text-slate-600 mb-2 leading-snug">Higher rungs paint above lower ones.</div>
<div class="relative h-32">
{#each zLadder as z, i (z.token)}
<div
class="absolute flex items-center gap-2 px-2 py-1 rounded-md border border-indigo-500/30 bg-indigo-500/[0.12] shadow-sm"
style={`top:${i * 11}px; left:${i * 9}px; right:0; z-index:${zLadder.length - i}`}
>
<span class="text-[9px] font-semibold text-slate-700 dark:text-slate-300">{z.label}</span>
<span class="text-[8px] font-mono text-slate-400 dark:text-slate-600 ml-auto">{z.val}</span>
</div>
{/each}
</div>
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down Expand Up @@ -310,6 +346,19 @@
onChange={(v) => onSet("--sf-icon-box-pad", `${v}em`)}
onReset={() => onReset("--sf-icon-box-pad")}
/>

<!-- Icon-size preview — the same glyph at every step (sized in em, so the
box is pinned to 16px text to make the em scale concrete). -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-4 flex items-end justify-center gap-3 flex-wrap" style="font-size:16px">
{#each ICON_STEPS as s (s)}
<div class="flex flex-col items-center gap-1">
<svg viewBox="0 0 24 24" aria-hidden="true" class="text-indigo-500" style={`width:var(--sf-icon-${s});height:var(--sf-icon-${s})`}>
<path fill="currentColor" d="M12 2l2.9 6.3 6.9.6-5.2 4.5 1.6 6.7L12 17.3 5.8 20.6l1.6-6.7L2.2 8.9l6.9-.6z" />
</svg>
<span class="text-[8px] font-mono text-slate-400 dark:text-slate-600">{s}</span>
</div>
{/each}
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down Expand Up @@ -350,6 +399,22 @@
<button onclick={() => onReset("--sf-object-position")} class="text-[8px] text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer shrink-0">reset</button>
{/if}
</div>

<!-- Object-fit preview — a landscape sample forced into a taller box, so
the current fit visibly crops / letterboxes / stretches / overflows. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3 flex flex-col items-center gap-1.5">
<div class="w-24 h-20 rounded-lg overflow-hidden border border-black/10 dark:border-white/10 bg-black/10 dark:bg-white/5">
<img
src={OBJECT_FIT_IMG}
alt="Object-fit sample"
class="w-full h-full block"
style={`object-fit:var(--sf-object-fit, cover);object-position:var(--sf-object-position, 50% 50%)`}
/>
</div>
<div class="text-[8px] font-mono text-slate-400 dark:text-slate-600">
object-fit: {overrides["--sf-object-fit"] ?? "cover"}
</div>
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down Expand Up @@ -386,6 +451,9 @@
</div>
{/each}
</div>
<p class="text-[9px] text-slate-400 dark:text-slate-600 italic leading-snug">
No preview — safe-area insets resolve to the physical device (notch / home indicator) at runtime and are 0 on desktop.
</p>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand All @@ -408,6 +476,20 @@
<button onclick={() => onReset("--sf-field-required-marker")} class="text-[8px] text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer shrink-0">reset</button>
{/if}
</div>

<!-- Field-marker preview — a required label with the marker appended, as
.sf-field renders it after a required field's label. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3">
<label class="block text-[11px] font-semibold text-slate-700 dark:text-slate-300">
Email address<span class="text-rose-500 dark:text-rose-400 whitespace-pre">{overrides["--sf-field-required-marker"] ?? " *"}</span>
<input
type="text"
disabled
placeholder="name@example.com"
class="mt-1 w-full bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded px-2 py-1 text-[10px] font-normal text-slate-500"
/>
</label>
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down
34 changes: 34 additions & 0 deletions configurator/src/components/panels/TypographyPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,16 @@
{/each}
</div>
</div>
<!-- Numeric-figures preview — a right-aligned money column. With
tabular-nums the digits share one width so the decimals line up;
proportional-nums lets them drift. -->
<div class="bg-black/4 dark:bg-white/4 rounded-lg border border-black/8 dark:border-white/8 px-3 py-2" style={`font-variant-numeric:var(--sf-font-numeric, tabular-nums)`}>
<div class="flex flex-col items-end gap-0.5 text-[12px] text-slate-700 dark:text-slate-300">
<span>1,204.05</span>
<span>98.10</span>
<span>13,760.75</span>
</div>
</div>
<div>
<div class="text-[10px] font-semibold text-slate-600 dark:text-slate-400 mb-1.5">Optical sizing</div>
<div class="flex gap-1">
Expand Down Expand Up @@ -561,6 +571,19 @@
</div>
</div>

<!-- Leading preview — a wrapped sample at each line-height so the vertical
rhythm difference is visible directly. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3 space-y-2.5">
{#each LEADING_TOKENS as t (t.token)}
<div>
<span class="text-[8px] font-mono text-slate-400 dark:text-slate-600">{t.label}</span>
<p class="text-[11px] text-slate-700 dark:text-slate-300 m-0" style={`line-height:var(${t.token})`}>
The quick brown fox jumps over the lazy dog while the rhythm of these lines shifts with the leading.
</p>
</div>
{/each}
</div>

<SliderRow
label="Leading taper" value={taper} min={0} max={0.5} step={0.01}
help="--sf-leading-taper — how aggressively large sizes shrink their line-height."
Expand Down Expand Up @@ -609,6 +632,17 @@
</div>
{/each}
</div>

<!-- Tracking preview — the same word at each step, so the spacing
difference is obvious at a glance. -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3 space-y-1.5">
{#each TRACKING_TOKENS as t (t.token)}
<div class="flex items-baseline gap-2">
<span class="text-[8px] font-mono text-slate-400 dark:text-slate-600 w-10 shrink-0">{t.label}</span>
<span class="text-[13px] text-slate-800 dark:text-slate-200 truncate" style={`letter-spacing:var(${t.token})`}>Typography</span>
</div>
{/each}
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down