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
3 changes: 2 additions & 1 deletion configurator/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ export default defineConfig({
viewport: { width: 1600, height: 1000 },
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'chromium', use: { browserName: 'chromium' }, testIgnore: '**/screenshots.spec.js' },
{ name: 'firefox', use: { browserName: 'firefox' }, testMatch: CROSS_ENGINE },
{ name: 'webkit', use: { browserName: 'webkit' }, testMatch: CROSS_ENGINE },
{ name: 'screenshots', use: { browserName: 'chromium' }, testMatch: '**/screenshots.spec.js' },
Comment thread
coderabbitai[bot] marked this conversation as resolved.
],
webServer: {
command: 'npm run preview -- --port 4173 --strictPort',
Expand Down
9 changes: 9 additions & 0 deletions configurator/src/components/ControlPreview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
let { token, value = '', type = 'text', label = '' } = $props();
const active = $derived(value || overrides[token?.name] || token?.value || '');
const stageStyle = $derived(buildPreviewDeclarations(overrides, ui.previewTheme));
const LABELS = {
color: 'colour', font: 'typeface', radius: 'corner', shadow: 'elevation',
motion: 'timing', 'line-height': 'leading', wrap: 'text wrap',
opacity: 'opacity', gradient: 'gradient', border: 'border style',
media: 'object fit', scale: 'scale', spacing: 'size',
};
const caption = $derived(LABELS[type] ?? type);
</script>

<div class="ctrl-preview" style={stageStyle} title={token?.name || label}>
Expand Down Expand Up @@ -37,6 +44,7 @@
<div class="ctrl-preview__spacing"><span style:inline-size={active || '40%'}></span></div>
{/if}
</div>
<p class="ctrl-preview__cap">{caption}</p>

<style>
.ctrl-preview { min-height: 54px; border: 1px solid var(--cfg-border); border-radius: var(--cfg-radius-s); background: var(--cfg-bg-2); padding: 10px; overflow: hidden; color: var(--sf-color-text, var(--cfg-text)); }
Expand All @@ -59,5 +67,6 @@
.ctrl-preview__scale span { inline-size:30px; block-size:30px; border-radius:8px; background:var(--cfg-accent-strong); }
.ctrl-preview__spacing { min-height: 34px; display:flex; align-items:center; }
.ctrl-preview__spacing span { display:block; block-size: 14px; max-inline-size:100%; min-inline-size:12px; border-radius:999px; background: var(--cfg-accent-strong); }
.ctrl-preview__cap { margin: 2px 0 0; font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--cfg-text-faint); text-align: center; }
@keyframes cp-move { to { transform: translateX(min(120px, 35vw)); } }
</style>
3 changes: 2 additions & 1 deletion configurator/src/components/ControlSection.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script>
let { title, hint = '', modifiedCount = 0, defaultOpen = true, children } = $props();
let open = $state(window.innerWidth >= 768 ? defaultOpen : false);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</script>

<details class="control-section" open={defaultOpen}>
<details class="control-section" open={open} ontoggle={(e) => (open = e.currentTarget.open)}>
<summary>
<span class="control-section__chev">›</span>
<strong>{title}</strong>
Expand Down
11 changes: 9 additions & 2 deletions configurator/src/components/FriendlyControl.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script>
import { overrides, clearOverride, setOverride } from '../lib/store.svelte.js';
import { controlForToken } from '../lib/controlSchema.js';
import { dependentsCount } from '../lib/model.js';
import TokenEditor from './TokenEditor.svelte';
import ControlPreview from './ControlPreview.svelte';
let { token, label = '', help = '', schema = {}, showToken = false } = $props();
Expand All @@ -9,6 +10,7 @@
const desc = $derived(help || meta.description);
const modified = $derived(overrides[token.name] != null);
const activeValue = $derived(overrides[token.name] ?? token.value ?? '');
const drives = $derived(dependentsCount(token?.name ?? ''));
let rawOpen = $state(false);

function onSelect(e) {
Expand All @@ -20,7 +22,10 @@
<div class="friendly__copy">
<div class="friendly__top">
<strong>{title}</strong>
{#if modified}<span>modified</span>{/if}
{#if modified}<span class="friendly__mod">modified</span>{/if}
{#if drives > 0}
<span class="friendly__drives" class:friendly__drives--master={drives >= 10} title="{drives} token{drives === 1 ? '' : 's'} reference this via var()">drives {drives}</span>
{/if}
</div>
{#if desc}<p>{desc}</p>{/if}
{#if showToken || rawOpen}<code class="friendly__token">{token.name}</code>{/if}
Expand Down Expand Up @@ -53,7 +58,9 @@
.friendly__copy { min-width: 0; display:grid; gap:4px; }
.friendly__top { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.friendly__top strong { font-size: 13px; }
.friendly__top span { font-size: 10px; text-transform: uppercase; color: var(--cfg-accent); border: 1px solid color-mix(in oklab, var(--cfg-accent) 40%, transparent); border-radius: 999px; padding: 1px 6px; }
.friendly__mod { font-size: 10px; text-transform: uppercase; color: var(--cfg-accent); border: 1px solid color-mix(in oklab, var(--cfg-accent) 40%, transparent); border-radius: 999px; padding: 1px 6px; }
.friendly__drives { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--cfg-text-faint); border: 1px solid var(--cfg-border); border-radius: 999px; padding: 1px 6px; }
.friendly__drives--master { color: var(--cfg-accent); border-color: color-mix(in oklab, var(--cfg-accent) 40%, transparent); }
.friendly__copy p { margin: 0; color: var(--cfg-text-muted); font-size: 12px; }
.friendly__token { color: var(--cfg-text-faint); font-size: 11px; word-break: break-all; }
.friendly__editor { min-width: 0; }
Expand Down
12 changes: 10 additions & 2 deletions configurator/src/components/SmartSettings.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import { overrides, ui, patchOverrides, setOverride, dragSetOverride, endDrag } from '../lib/store.svelte.js';
import { tokenByName } from '../lib/model.js';
import { tokenByName, dependentsCount } from '../lib/model.js';
import { sectionTokenNames, smartSettingsFor } from '../lib/domainSettings.js';
import { buildPreviewDeclarations } from '../lib/preview.js';
import { getFold, setFold } from '../lib/foldState.js';
Expand All @@ -15,6 +15,10 @@
const exists = (name) => tokenByName.has(name);
const token = (name) => tokenByName.get(name);

function sectionDrives(section) {
return sectionTokenNames(section).filter(exists).reduce((acc, name) => acc + dependentsCount(name), 0);
}

function cleanPatch(section, patch) {
const names = new Set(sectionTokenNames(section).filter(exists));
if (patch == null) return Object.fromEntries([...names].map((name) => [name, null]));
Expand Down Expand Up @@ -67,6 +71,8 @@
{#if sections.length}
<div class="smart">
{#each sections as section (section.id)}
{@const totalDrives = sectionDrives(section)}
{@const tokenCount = sectionTokenNames(section).filter(exists).length}
<details
class="cfg-card smart__section"
open={getFold(`${domainId}:${section.id}`, true)}
Expand All @@ -76,6 +82,7 @@
<span class="smart__chev" aria-hidden="true">›</span>
<span class="smart__title">{section.title}</span>
{#if section.hint}<span class="smart__hint">{section.hint}</span>{/if}
{#if totalDrives > 0}<span class="smart__drives">drives {totalDrives}</span>{/if}
</summary>

<div class="smart__actions" aria-label="{section.title} actions">
Expand All @@ -86,7 +93,7 @@
{/each}
</div>
{/if}
<button type="button" class="cfg-btn cfg-btn--ghost cfg-btn--sm smart__reset" onclick={() => resetSection(section)}>Reset section</button>
<button type="button" class="cfg-btn cfg-btn--ghost cfg-btn--sm smart__reset" onclick={() => resetSection(section)}>Reset section ({tokenCount})</button>
</div>

{#if section.kind === 'sliders'}
Expand Down Expand Up @@ -173,6 +180,7 @@
.smart__chev { color: var(--cfg-text-faint); transition: transform .14s; }
.smart__title { font-size: 12.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; }
.smart__hint { color: var(--cfg-text-faint); font-size: 12px; flex: 1 1 260px; }
.smart__drives { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--cfg-accent); border: 1px solid color-mix(in oklab, var(--cfg-accent) 35%, transparent); border-radius: 999px; padding: 2px 7px; }
.smart__actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--cfg-border); flex-wrap: wrap; }
.smart__reset { margin-left: auto; }
.smart__presets { display: flex; gap: 8px; flex-wrap: wrap; }
Expand Down
1 change: 1 addition & 0 deletions configurator/src/components/editors/LayoutStudio.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,5 @@
.legend b { font-size: 11px; color: var(--cfg-text-muted); text-transform: uppercase; }
@media (max-width: 840px) { .composition, .measure-lab { grid-template-columns: 1fr; } }
@media (max-width: 640px) { .workflow, .device-strip { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .workflow { grid-template-columns: repeat(4, minmax(110px, 1fr)); overflow-x: auto; padding-bottom: 4px; } }
</style>
8 changes: 7 additions & 1 deletion configurator/src/components/editors/StudioFrame.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@
.studio__steps { display: grid; gap: 6px; margin-top: 6px; list-style: none; padding: 0; }
.studio__steps li { padding: 7px 9px; border: 1px solid var(--cfg-border); border-radius: 999px; background: var(--cfg-bg-2); color: var(--cfg-text-muted); font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .08em; }
.studio__surface { min-width: 0; }
@media (max-width: 820px) { .studio { grid-template-columns: 1fr; padding: 12px; } .studio__copy { position: static; } .studio__steps { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 820px) {
.studio { grid-template-columns: 1fr; padding: 12px; }
.studio__copy { position: static; }
.studio__copy h3 { font-size: clamp(18px, 4vw, 26px); }
.studio__copy span { display: none; }
.studio__steps { grid-template-columns: repeat(3, 1fr); }
}
</style>
1 change: 1 addition & 0 deletions configurator/src/components/editors/StudioWorkflow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
.studio-workflow li { display: flex; align-items: center; gap: 8px; padding: 9px 10px; border: 1px solid var(--cfg-border); border-radius: var(--cfg-radius-s); background: var(--cfg-bg-2); color: var(--cfg-text-muted); font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; }
.studio-workflow b { display: grid; place-items: center; inline-size: 20px; block-size: 20px; border-radius: 999px; background: var(--cfg-accent-strong); color: white; font-size: 10px; }
@media (max-width: 640px) { .studio-workflow { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .studio-workflow { grid-template-columns: repeat(var(--workflow-cols, 4), minmax(110px, 1fr)); overflow-x: auto; padding-bottom: 4px; } }
</style>
9 changes: 9 additions & 0 deletions configurator/src/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,14 @@ code, kbd { font-family: var(--cfg-mono); }
line-height: 1.4;
}

/* ─── Touch targets (WCAG 2.5.5 — 44×44px minimum) ────────────────────── */
@media (max-width: 980px) {
.cfg-btn { min-height: 44px; padding-block: 10px; }
.cfg-btn--sm { min-height: 44px; padding-block: 10px; }
.cfg-input,
.cfg-select { min-height: 44px; padding-block: 10px; }
input[type='range'] { min-height: 44px; }
}

/* No per-theme overrides needed: every chrome colour derives from --sf-* tokens
that already adapt to the framework's [data-theme] light/dark switch. */
32 changes: 32 additions & 0 deletions configurator/tests-e2e/screenshots.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Screenshot QA — captures every Studio domain at three viewport widths.
*
* Not part of the main regression suite (separate "screenshots" project in
* playwright.config.js). Run with:
*
* npx playwright test --project=screenshots --reporter=html
*
* Output: test-results/screenshots/<domain>-<width>.png (27 files)
*/
import { test } from '@playwright/test';
import { gotoClean, sideItem } from './helpers.js';

const DOMAINS = ['Colors', 'Typography', 'Spacing', 'Layout', 'Borders', 'Shadows', 'Motion', 'Effects', 'Misc'];
const WIDTHS = [1280, 768, 390];

test.describe('Studio screenshot QA', () => {
for (const domain of DOMAINS) {
for (const width of WIDTHS) {
test(`${domain} @ ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await gotoClean(page);
await sideItem(page, domain).click();
await page.waitForLoadState('networkidle');
await page.screenshot({
path: `test-results/screenshots/${domain.toLowerCase()}-${width}.png`,
fullPage: true,
});
});
}
}
});