From ad56c7009a470ebac9d5dbe6637318e32bb7a411 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Jul 2026 09:42:58 +0000 Subject: [PATCH 1/3] feat(configurator): rebuild Components preview tab from scratch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The right-hand live preview for the Components category was rewritten. The old size-scale row read as near-identical buttons (the per-rung ladder was invisible) and its note still described the removed flatten-all globals as the primary sizing surface. New Components tab (src/lib/preview/sections.ts): - Size scale is baseline-aligned so the xs→xl min-height/padding/label ladder reads as a clear staircase, with an accurate note pointing at the per-size knobs + Label-size multiplier (not the retired flatten-all globals). - Styles shown on a brand family and a neutral (fill/soft/outline/gradient). - New Full-width section (sf-btn--block / sf-btn--block-cq). - New rich card exercising the full slot API — sf-card__media, __avatar, __title, __body, __footer — with token-driven gradient media/avatar (no external asset, re-tints live), beside the three shipped card modifiers. Verified in both light and dark themes; tsc, svelte-check, preview-coverage and the configurator unit/component suites all pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GnwqYHgHNAWPbsUJWUeY1w --- configurator/src/lib/preview/sections.ts | 85 ++++++++++++++++++------ 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/configurator/src/lib/preview/sections.ts b/configurator/src/lib/preview/sections.ts index 4e319c547..926a89bb7 100644 --- a/configurator/src/lib/preview/sections.ts +++ b/configurator/src/lib/preview/sections.ts @@ -325,48 +325,93 @@ export function layout(): string { // ── Components ───────────────────────────────────────────────────────────── const BTN_FAMILIES = ["primary", "secondary", "tertiary", "action", "base", "neutral", "success", "warning", "danger", "info"]; -const BTN_SIZES: [string, string][] = [["xs", "XS"], ["s", "Small"], ["", "Default"], ["l", "Large"], ["xl", "XL"]]; +// [modifier suffix, visible label] — "" is the default (medium) rung. +const BTN_SIZES: [string, string][] = [["xs", "XS"], ["s", "S"], ["", "M"], ["l", "L"], ["xl", "XL"]]; export function components(): string { const btn = (mods: string, label: string) => ``; - const families = well(cluster(...BTN_FAMILIES.map((f) => btn(`sf-btn--${f}`, f[0].toUpperCase() + f.slice(1))))); - const stylesRow = well(cluster( - btn("sf-btn--primary", "Fill"), - btn("sf-btn--primary sf-btn--soft", "Soft"), - btn("sf-btn--primary sf-btn--outline", "Outline"), - btn("sf-btn--primary sf-btn--gradient", "Gradient"), + // Families — every shipped colour family as a fill button. + const families = well(cluster( + ...BTN_FAMILIES.map((f) => btn(`sf-btn--${f}`, f[0].toUpperCase() + f.slice(1))), )); - const sizes = well(`
${BTN_SIZES.map( - ([s, label]) => btn(`sf-btn--primary${s ? ` sf-btn--${s}` : ""}`, label), - ).join("")}
`); + + // Styles — fill · soft · outline · gradient, on a brand family and a neutral. + const styleRow = (family: string, withGradient: boolean) => cluster( + btn(`sf-btn--${family}`, "Fill"), + btn(`sf-btn--${family} sf-btn--soft`, "Soft"), + btn(`sf-btn--${family} sf-btn--outline`, "Outline"), + ...(withGradient ? [btn(`sf-btn--${family} sf-btn--gradient`, "Gradient")] : []), + ); + const styles = well(stack("s", styleRow("primary", true), styleRow("neutral", false))); + + // Size scale — the hero. Aligned to a common baseline so the per-rung + // font-size / padding / min-height ladder reads as a clear staircase. + const sizes = well( + `
${BTN_SIZES.map( + ([s, label]) => btn(`sf-btn--primary${s ? ` sf-btn--${s}` : ""}`, label), + ).join("")}
`, + ); + + // Full width — the block modifier stretches to the container inline size. + const widths = well(stack("s", + ``, + ``, + )); + + // States — default, disabled, loading. const states = well(cluster( btn("sf-btn--primary", "Default"), ``, ``, )); + // A rich card that exercises the full slot API: media, avatar header, + // title, body and a footer action row. Media/avatar use a token-driven + // gradient (no external asset) so they re-tint live with the colour panel. + const swatch = "var(--sf-gradient-primary, var(--sf-color-primary))"; + const mediaCard = `
+
+
+
+
+
+

Media card

+ __media · __avatar · __title +
+
+
+

Composed from the full card slot API. Every dimension — padding, radius, gap, media ratio, avatar size — is token-driven and reacts live.

+ +
`; + + // The three shipped card modifiers, side by side. const card = (mods: string, title: string, body: string, btnMods: string, action: string) => `

${esc(title)}

${esc(body)}

`; - const cards = grid( - 16, - card("", "Base card", "Default surface, radius, border and text tokens.", "sf-btn--primary sf-btn--s", "Open"), - card("sf-card--bordered", "Bordered card", "The shipped bordered modifier for clearer structure.", "sf-btn--outline sf-btn--s", "Details"), - card("sf-card--elevated sf-card--interactive", "Interactive card", "Elevation and interaction states react to tokens.", "sf-btn--soft sf-btn--s", "Select"), + const cardModifiers = grid( + 15, + card("", "Base", "Default surface, radius, border and shadow.", "sf-btn--primary sf-btn--s", "Open"), + card("sf-card--bordered", "Bordered", "Keeps the border, drops the shadow.", "sf-btn--outline sf-btn--s", "Details"), + card("sf-card--elevated sf-card--interactive", "Interactive", "Elevated, with a hover/focus lift.", "sf-btn--soft sf-btn--s", "Select"), ); return page( "Components", - "Buttons and cards — every family, style, the full XS–XL size scale and states. This is where the size-scale knobs in the Components panel show their effect immediately.", - section("Button families", families), - section("Styles", stylesRow), - section("Size scale (xs · s · default · l · xl)", sizes, "Global Padding / Min-height / Label-size knobs override this whole scale."), + "Buttons and cards built from the real .sf-btn / .sf-card classes — every family and style, the full XS–XL size ladder, states, full-width and the card slot API. Edit the Components panel and each specimen reacts live.", + section("Button families", families, `${BTN_FAMILIES.length} shipped colour families`), + section("Styles", styles, "fill · soft · outline · gradient"), + section("Size scale (xs · s · default · l · xl)", sizes, "Each rung has its own font-size, padding and min-height knob (Components panel ▸ Per-size); the Label-size multiplier scales the whole ladder proportionally."), + section("Full width", widths, "sf-btn--block stretches to the container; sf-btn--block-cq does so only in a narrow query container."), section("States", states), - section("Cards", cards), + section("Card — full slot API", mediaCard), + section("Card modifiers", cardModifiers, "base · bordered · elevated + interactive"), ); } From c2ac24eb29d2f69d8596c1d2594d13c3af6f9e75 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Jul 2026 09:51:30 +0000 Subject: [PATCH 2/3] fix(configurator): demonstrate sf-btn--block-cq and give the outline block button a family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address CodeRabbit + Greptile review on PR #593 (Components preview tab): - Full-width section now shows sf-btn--block-cq in both contexts it's built for: auto width in the wide well, and stretched to 100% inside a 16rem query container (dashed) — the note promised it but no specimen existed. The stack uses align-items:flex-start so only --block (explicit 100%) and the querried block-cq actually stretch, keeping the contrast honest. - The outline block button now carries a family class (sf-btn--neutral) instead of a bare sf-btn--outline that fell back to the default family. Verified visually in light + dark; tsc, svelte-check, preview-coverage and the configurator unit/component suites pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GnwqYHgHNAWPbsUJWUeY1w --- configurator/_s.mjs | 10 ++++++++++ configurator/src/lib/preview/sections.ts | 18 +++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 configurator/_s.mjs diff --git a/configurator/_s.mjs b/configurator/_s.mjs new file mode 100644 index 000000000..eb1eb8517 --- /dev/null +++ b/configurator/_s.mjs @@ -0,0 +1,10 @@ +import { chromium } from 'playwright'; +import { pathToFileURL } from 'node:url'; +const b = await chromium.launch({executablePath:'/opt/pw-browsers/chromium-1194/chrome-linux/chrome'}); +const p = await b.newPage({viewport:{width:900,height:800}, deviceScaleFactor:2}); +await p.goto(pathToFileURL(process.env.SC+'/comp-light.html').href); +await p.waitForTimeout(300); +// clip just the Full width section for a focused check +const el = await p.$('[data-pv-section="Full width"]'); +await el.screenshot({path:process.env.SC+'/fullwidth.png'}); +await b.close(); console.log('shot'); diff --git a/configurator/src/lib/preview/sections.ts b/configurator/src/lib/preview/sections.ts index 926a89bb7..b33032783 100644 --- a/configurator/src/lib/preview/sections.ts +++ b/configurator/src/lib/preview/sections.ts @@ -353,11 +353,19 @@ export function components(): string { ).join("")}`, ); - // Full width — the block modifier stretches to the container inline size. - const widths = well(stack("s", - ``, - ``, - )); + // Full width — --block stretches unconditionally; --block-cq only inside a + // query container narrower than 20rem, so it's shown inside a 16rem container + // (dashed) where it actually fills its width. + // align-items:flex-start so buttons size to content — only --block (explicit + // 100%) and the block-cq inside the 16rem query container actually stretch. + const widths = well(`
+ + + +
+ +
+
`); // States — default, disabled, loading. const states = well(cluster( From 1fb8725a5efcebac4318860a8cf71195933993fb Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Jul 2026 09:52:11 +0000 Subject: [PATCH 3/3] chore(configurator): remove accidentally committed temp screenshot script _s.mjs was a local Playwright screenshot helper that slipped into c2ac24e when a cleanup rm targeted the wrong path. It is not part of the codebase. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GnwqYHgHNAWPbsUJWUeY1w --- configurator/_s.mjs | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 configurator/_s.mjs diff --git a/configurator/_s.mjs b/configurator/_s.mjs deleted file mode 100644 index eb1eb8517..000000000 --- a/configurator/_s.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { chromium } from 'playwright'; -import { pathToFileURL } from 'node:url'; -const b = await chromium.launch({executablePath:'/opt/pw-browsers/chromium-1194/chrome-linux/chrome'}); -const p = await b.newPage({viewport:{width:900,height:800}, deviceScaleFactor:2}); -await p.goto(pathToFileURL(process.env.SC+'/comp-light.html').href); -await p.waitForTimeout(300); -// clip just the Full width section for a focused check -const el = await p.$('[data-pv-section="Full width"]'); -await el.screenshot({path:process.env.SC+'/fullwidth.png'}); -await b.close(); console.log('shot');