diff --git a/configurator/src/App.svelte b/configurator/src/App.svelte index 41265ac6..33bde295 100644 --- a/configurator/src/App.svelte +++ b/configurator/src/App.svelte @@ -18,9 +18,10 @@ const DOMAIN_LABELS: Record = { home: "Home", colors: "Colors", typography: "Typography", spacing: "Spacing", - layout: "Layout", borders: "Borders", shadows: "Shadows", motion: "Motion", + layout: "Layout", borders: "Shape", shadows: "Shadows", motion: "Motion", effects: "Effects", macros: "Macros", misc: "Misc", components: "Components", - changes: "Changes", themes: "Themes", wcag: "WCAG", setup: "Install", cheatsheet: "Classes", + changes: "Changes", themes: "Presets", wcag: "Accessibility", + setup: "Install & export", cheatsheet: "Reference", }; function overridesByDomain(ov: Record): Record { @@ -48,6 +49,22 @@ let domain = $state("home"); let showPalette = $state(false); + // One-shot deep-link request from search: navigate to a domain AND focus a + // specific token's row in its All-tokens list. The nonce lets the same token + // be re-focused (a second search for it still scrolls/highlights). + let focusRequest = $state<{ token: string; nonce: number } | null>(null); + let focusNonce = 0; + + function navigateTo(domainId: string, token?: string) { + domain = domainId; + if (token) { + focusNonce += 1; + focusRequest = { token, nonce: focusNonce }; + } else { + focusRequest = null; + } + mobileView = "controls"; + } // On narrow screens the controls panel and the live preview can't both fit, so // we show one at a time and let the user fold between them (desktop shows both). let mobileView = $state<"controls" | "preview">("controls"); @@ -96,10 +113,16 @@ if (tab) untrack(() => { previewTemplate = tab; }); }); + // Each user-visible edit is its own undo step. Controls currently do not + // expose a reliable gesture boundary, so time-based coalescing could merge + // two distinct actions on the same token. + function setOverrides(updater: ((prev: Record) => Record) | Record) { const prev = overrides; const next = typeof updater === "function" ? updater(prev) : updater; if (!shallowEq(prev, next)) { + // Keep discrete edits independently undoable. Range input events are + // intentionally not time-grouped until the control provides boundaries. past = [...past.slice(-49), prev]; future = []; if (saveState === 'saved' || saveState === 'error') saveState = 'idle'; @@ -296,6 +319,7 @@ onImport={handleImport} onExport={handleExport} onSave={handleSave} + onOpenSearch={() => { showPalette = true; }} /> diff --git a/configurator/src/components/inputs/ValueField.svelte b/configurator/src/components/inputs/ValueField.svelte new file mode 100644 index 00000000..7e3d81bc --- /dev/null +++ b/configurator/src/components/inputs/ValueField.svelte @@ -0,0 +1,120 @@ + + +
+ +
+ {#each TABS as t (t.id)} + + {/each} +
+ + {#if mode === "inherit"} +
+ default: {token.value} +
+ {#if scaleOptions.length > 0} + + {/if} + {:else} + + { editing = true; }} + oninput={(e) => { draft = (e.target as HTMLInputElement).value; }} + onblur={(e) => { + editing = false; + if (cancelling) { cancelling = false; return; } + commit((e.target as HTMLInputElement).value); + }} + onkeydown={(e) => { + if (e.key === "Enter") (e.currentTarget as HTMLInputElement).blur(); + if (e.key === "Escape") { + cancelling = true; + editing = false; + draft = overrideValue ?? token.value ?? ""; + (e.currentTarget as HTMLInputElement).blur(); + } + }} + class="w-full bg-black/8 dark:bg-white/8 border border-black/10 dark:border-white/10 rounded px-1.5 py-1 text-[10px] font-mono text-slate-800 dark:text-slate-200 focus:outline-none focus:border-indigo-500" + /> + {/if} +
diff --git a/configurator/src/components/panels/AllTokensTab.svelte b/configurator/src/components/panels/AllTokensTab.svelte index 6dd11aa5..44d955c9 100644 --- a/configurator/src/components/panels/AllTokensTab.svelte +++ b/configurator/src/components/panels/AllTokensTab.svelte @@ -4,14 +4,41 @@ import { buildDependencyGraph } from '../../lib/tokenModel'; import { domainOf } from '../../lib/domains'; - let { tokens, overrides, domain, onSet, onReset }: { + let { tokens, overrides, domain, focusToken = null, focusNonce = 0, onSet, onReset }: { tokens: SlashedToken[]; overrides: Record; domain: string; + /** Deep-link target: filter to and highlight this token when it changes. */ + focusToken?: string | null; + focusNonce?: number; onSet: (name: string, value: string) => void; onReset: (name: string) => void; } = $props(); + let listEl = $state(null); + let highlightName = $state(null); + let highlightTimer: ReturnType | null = null; + let lastFocusNonce = -1; + + // React to a search deep-link: pre-fill the query with the token and briefly + // highlight + scroll to its row so the user lands exactly on the control. + $effect(() => { + const nonce = focusNonce; + if (!focusToken || nonce === lastFocusNonce) return; + lastFocusNonce = nonce; + query = focusToken.replace("--sf-", ""); + showInternal = tokens.some((t) => t.name === focusToken && t.tier === "INTERNAL") || showInternal; + onlyModified = false; + highlightName = focusToken; + if (highlightTimer) clearTimeout(highlightTimer); + highlightTimer = setTimeout(() => { highlightName = null; }, 2600); + // Scroll to the row after the filtered list renders. + requestAnimationFrame(() => { + const el = listEl?.querySelector(`[data-token="${CSS.escape(focusToken)}"]`); + el?.scrollIntoView({ block: "center", behavior: "smooth" }); + }); + }); + const TIER_ORDER: Record = { PUBLIC: 0, "PUBLIC-ADVANCED": 1, INTERNAL: 2 }; // Whole-catalogue dependency graph (built once from the full token set, not @@ -113,7 +140,7 @@
-
+
{#if filtered().length === 0}
{onlyModified ? "No modified tokens in this domain" : "No tokens found"} @@ -139,7 +166,10 @@
{/if} -
+
{#if t.tier === "PUBLIC-ADVANCED"}
{/if} diff --git a/configurator/src/components/panels/HomePanel.svelte b/configurator/src/components/panels/HomePanel.svelte index 0078d853..846a6419 100644 --- a/configurator/src/components/panels/HomePanel.svelte +++ b/configurator/src/components/panels/HomePanel.svelte @@ -1,7 +1,7 @@ - diff --git a/configurator/src/components/shell/StudioHeader.svelte b/configurator/src/components/shell/StudioHeader.svelte index daf8e72c..f76962bf 100644 --- a/configurator/src/components/shell/StudioHeader.svelte +++ b/configurator/src/components/shell/StudioHeader.svelte @@ -1,13 +1,13 @@