From e5e9f047125f33e3b7e315a31c40fd5778dc0f34 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:03:55 +0000 Subject: [PATCH 1/3] fix(configurator): tabbed Basic/Advanced UI, dark-mode preview, display scale - Resolve light-dark() per theme in the live preview + WCAG probes so dark mode actually switches (inherited light-dark() does not re-evaluate from a non-root color-scheme; mirrors the framework's [data-theme] re-declarations). Extracted to lib/lightdark.js with unit tests. - Scale generator: add the Display ramp (--sf-text-display-{s,m,l}) and a 'kinds' prop; keep the parity scale.js byte-for-byte identical. - Restructure into domain tabs (Typography, Spacing, Colors, WCAG, Layout, Borders, Shadows, Motion, More) with a global Basic/Advanced toggle, reaching parity with the WP plugin admin app. Adds lib/domains.js, TabNav, DomainPanel. - Re-sync configurator catalogue to framework v0.5.31; de-stale a test comment. Co-authored-by: Jack Granatowski --- configurator/src/App.svelte | 124 +++---------- .../src/components/DomainPanel.svelte | 157 ++++++++++++++++ configurator/src/components/Header.svelte | 50 ++--- .../src/components/ScaleGenerator.svelte | 174 +++++++++++------- configurator/src/components/Sidebar.svelte | 138 -------------- configurator/src/components/TabNav.svelte | 75 ++++++++ .../src/data/api-index.generated.json | 2 +- configurator/src/lib/domains.js | 149 +++++++++++++++ configurator/src/lib/lightdark.js | 89 +++++++++ configurator/src/lib/preview.js | 20 +- configurator/src/lib/store.svelte.js | 10 +- configurator/tests/lightdark.test.js | 57 ++++++ tests/api-index-sync.test.js | 4 +- 13 files changed, 707 insertions(+), 342 deletions(-) create mode 100644 configurator/src/components/DomainPanel.svelte delete mode 100644 configurator/src/components/Sidebar.svelte create mode 100644 configurator/src/components/TabNav.svelte create mode 100644 configurator/src/lib/domains.js create mode 100644 configurator/src/lib/lightdark.js create mode 100644 configurator/tests/lightdark.test.js diff --git a/configurator/src/App.svelte b/configurator/src/App.svelte index 85ec98e7..1ee87f20 100644 --- a/configurator/src/App.svelte +++ b/configurator/src/App.svelte @@ -1,91 +1,40 @@
+
- {#if ui.view === 'tokens'} - - -
- {#if visible.length === 0} -
-

No tokens match the current search and filters.

-
- {:else if searching} - {#each grouped as cat (cat.category)} - {#each cat.groups as g (g.name)} - - {/each} - {/each} - {:else if activeCat} -

{activeCat.category}

- {#each activeCat.groups as g (g.name)} - - {/each} - {/if} + {#if isTool} +
+ {:else} +
+ {#key ui.domain} + + {/key}
- {:else if ui.view === 'a11y'} -
- {:else if ui.view === 'scales'} -
{/if} {#if showPreview} @@ -93,57 +42,40 @@ {/if}
- {#if ui.view === 'tokens'} - - {/if} +
diff --git a/configurator/src/components/Header.svelte b/configurator/src/components/Header.svelte index cc16a2d8..8373ccfd 100644 --- a/configurator/src/components/Header.svelte +++ b/configurator/src/components/Header.svelte @@ -1,17 +1,12 @@
@@ -25,16 +20,22 @@ - +
+ + +
{#if sync.frameworkVersion} @@ -97,27 +98,26 @@ gap: 10px; flex-wrap: wrap; } - .hdr__views { + .hdr__mode { display: inline-flex; - gap: 2px; background: var(--cfg-bg); border: 1px solid var(--cfg-border-strong); border-radius: var(--cfg-radius); padding: 3px; + gap: 2px; } - .hdr__view { + .hdr__mode-btn { background: transparent; border: none; color: var(--cfg-text-muted); - padding: 6px 14px; + padding: 6px 16px; font-size: 13px; + font-weight: 600; border-radius: var(--cfg-radius-s); transition: background 0.12s ease, color 0.12s ease; } - .hdr__view:hover { - color: var(--cfg-text); - } - .hdr__view--on { + .hdr__mode-btn:hover { color: var(--cfg-text); } + .hdr__mode-btn--on { background: var(--cfg-accent-strong); color: #fff; } diff --git a/configurator/src/components/ScaleGenerator.svelte b/configurator/src/components/ScaleGenerator.svelte index 5cc68683..46065a19 100644 --- a/configurator/src/components/ScaleGenerator.svelte +++ b/configurator/src/components/ScaleGenerator.svelte @@ -1,40 +1,78 @@ -
-
-

Scale generator

-

- Build a fluid modular ramp and write each step as a clamp() override. -

-
- -
+
+ {#if kinds.length > 1}
- - -
- -
- - - - - - -
- -
- {#each computed as s (s.token)} -
- {s.token} - - {#if kind === 'space'}{:else}Ag{/if} - - {s.clamp} -
+ {#each kinds as k (k)} + {/each}
- -
- - - {#if missing > 0}{missing} step(s) not in this catalogue — skipped.{/if} -
-
-
+ {/if} + +
+ + + + + + +
+ +
+ {#each computed as s (s.token)} +
+ {s.token} + + {#if kind === 'space'}{:else}Ag{/if} + + {s.clamp} +
+ {/each} +
+ +
+ + + {#if missing > 0}{missing} step(s) not in this catalogue — skipped.{/if} +
+ diff --git a/configurator/src/components/TabNav.svelte b/configurator/src/components/TabNav.svelte new file mode 100644 index 00000000..88bb6863 --- /dev/null +++ b/configurator/src/components/TabNav.svelte @@ -0,0 +1,75 @@ + + + + + diff --git a/configurator/src/data/api-index.generated.json b/configurator/src/data/api-index.generated.json index f249752c..4e88be6e 100644 --- a/configurator/src/data/api-index.generated.json +++ b/configurator/src/data/api-index.generated.json @@ -2,7 +2,7 @@ "_sync": { "generatedBy": "configurator/scripts/sync-api.mjs", "source": "docs/api-index.json", - "frameworkVersion": "0.5.30", + "frameworkVersion": "0.5.31", "tokensHash": "17cd4051357b", "bundles": [ "essential", diff --git a/configurator/src/lib/domains.js b/configurator/src/lib/domains.js new file mode 100644 index 00000000..0285c081 --- /dev/null +++ b/configurator/src/lib/domains.js @@ -0,0 +1,149 @@ +/** + * Domain taxonomy for the tabbed configurator. + * + * Splits the full token catalogue into the user-facing domains (Typography, + * Spacing, Colors, …) the UI tabs render, and curates a small set of "basic" + * essentials per domain — the handful of tokens a typical user always needs. + * Advanced mode then exposes the FULL domain catalogue plus the generators and + * knobs. This mirrors the WordPress plugin's tab layout (ColorTab / TypographyTab + * / SpacingTab / LayoutsTab / WcagTab …) so the two tools stay at feature parity. + * + * Pure data + a classifier; no Svelte/DOM so it is trivially unit-testable. + */ + +/** + * Ordered domain → name-pattern rules. First match wins, so more specific + * patterns (e.g. text-shadow → shadows) must precede the broader ones + * (text-* → typography). Anything unmatched falls through to the `more` + * domain, so NO token is ever hidden. + * + * @type {Array<{ id:string, test:RegExp }>} + */ +const RULES = [ + // Shadows first — claim *-shadow-* before typography claims --sf-text-*. + { id: 'shadows', test: /^--sf-(.*-)?shadow|^--sf-drop-shadow|glow/ }, + // Borders, radii, strokes, dividers, outlines. + { id: 'borders', test: /^--sf-(border|radius|stroke|divider|outline)/ }, + // Typography (text, fonts, line-height, tracking, prose, headings, display). + { id: 'typography', test: /^--sf-(text|font|leading|tracking|prose|h[1-6]-|heading|display|code|optical|line-height|body|line-clamp|truncate)/ }, + // Spacing & fluid scale engine. + { id: 'spacing', test: /^--sf-(space|gap|section|fluid|flow|gutter|content-gap|component-pad)/ }, + // Colors (brand/status sources, resolved colors, links, focus, gradients…). + { id: 'colors', test: /^--sf-(color|primary|secondary|tertiary|action|neutral|base|success|warning|error|info|danger|link|focus|gradient|scrim|mask|contrast|lumlocker|current|selection|caret|surface)/ }, + // Motion & effects (animation, transition, easing, blur, opacity, scroll). + { id: 'motion', test: /^--sf-(duration|ease|transition|animation|motion|scroll|blur|opacity|backdrop|will-change)/ }, + // Layout primitives (grid/flex helpers, containers, sizes, z-index, icons…). + { id: 'layout', test: /^--sf-(grid|col|cluster|stack|reel|sidebar|switcher|cover|frame|bento|center|box|imposter|breakout|content|equal|object|aspect|ratio|safe|sticky|header|icon|touch|z|container|size|alternate|divide|field)/ }, +]; + +/** + * Classify a token into a domain id. + * @param {{name:string}} token + * @returns {string} domain id (falls back to 'more') + */ +export function domainOf(token) { + const name = token?.name || ''; + for (const rule of RULES) { + if (rule.test.test(name)) return rule.id; + } + return 'more'; +} + +/** + * Domain definitions, in tab order. `essentials` lists the curated "basic" + * token names (rendered as editor rows); names absent from the active + * catalogue are simply skipped by the UI. `generators` flags which scale + * generator ramps to surface, and `tool` marks non-token tool tabs (WCAG). + * + * @type {Array<{ + * id:string, label:string, blurb:string, + * essentials?:string[], basicGenerators?:string[], advancedGenerators?:string[], + * tool?:string + * }>} + */ +export const DOMAINS = [ + { + id: 'typography', + label: 'Typography', + blurb: 'Font families and the fluid type scale.', + essentials: [ + '--sf-font-body', + '--sf-font-heading', + '--sf-font-display', + '--sf-font-mono', + '--sf-text-base-min', + '--sf-text-base-max', + ], + advancedGenerators: ['type', 'display'], + }, + { + id: 'spacing', + label: 'Spacing', + blurb: 'The space scale that drives every gap and padding.', + essentials: ['--sf-space-scale'], + basicGenerators: ['space'], + }, + { + id: 'colors', + label: 'Colors', + blurb: 'Brand & status source colors — everything else derives from these.', + essentials: [ + '--sf-color-primary-light', + '--sf-color-secondary-light', + '--sf-color-tertiary-light', + '--sf-color-action-light', + '--sf-color-neutral-light', + '--sf-color-base-light', + ], + }, + { + id: 'wcag', + label: 'WCAG', + blurb: 'Contrast checker, matrix and accessible-palette generator.', + tool: 'wcag', + }, + { + id: 'layout', + label: 'Layout', + blurb: 'Containers, grids and structural layout primitives.', + essentials: [ + '--sf-container-narrow', + '--sf-container-default', + '--sf-container-wide', + '--sf-container-prose', + ], + }, + { + id: 'borders', + label: 'Borders', + blurb: 'Corner radius, border widths, strokes and dividers.', + essentials: [ + '--sf-radius-s', + '--sf-radius-m', + '--sf-radius-l', + '--sf-radius-full', + '--sf-border-width-1', + '--sf-color-border', + ], + }, + { + id: 'shadows', + label: 'Shadows', + blurb: 'Elevation shadow presets and shadow strength.', + essentials: ['--sf-shadow-s', '--sf-shadow-m', '--sf-shadow-l'], + }, + { + id: 'motion', + label: 'Motion', + blurb: 'Durations, easing, transitions and effects.', + essentials: ['--sf-duration-fast', '--sf-duration-normal', '--sf-duration-slow'], + }, + { + id: 'more', + label: 'More', + blurb: 'Everything else in the catalogue.', + }, +]; + +/** Quick id → definition lookup. */ +export const DOMAIN_BY_ID = new Map(DOMAINS.map((d) => [d.id, d])); diff --git a/configurator/src/lib/lightdark.js b/configurator/src/lib/lightdark.js new file mode 100644 index 00000000..8eb5357b --- /dev/null +++ b/configurator/src/lib/lightdark.js @@ -0,0 +1,89 @@ +/** + * `light-dark()` resolution helpers — pure, no Svelte / DOM / catalogue deps, + * so they are trivially unit-testable. + * + * WHY THIS EXISTS — per the framework's core/themes.css, `light-dark()` + * resolves when a custom property is *declared* (on :root), not when it is + * *inherited*. The configurator's live preview / WCAG probes declare tokens on + * a NESTED stage element and only flip `color-scheme`, which does NOT + * re-evaluate inherited `light-dark()` values — so dark mode never switched. + * The framework itself works around this by re-declaring every mode-sensitive + * token under `[data-theme="dark"]` with the explicit dark formula; these + * helpers do the equivalent by substituting each `light-dark(light, dark)` + * with the branch for the active theme before injection. + */ + +/** + * Split a string on top-level commas, honouring nested parentheses and quoted + * strings so commas inside `oklch(…)`, `var(--x, fallback)`, `calc(…)` etc. are + * not treated as argument separators. + * + * @param {string} str + * @returns {string[]} the top-level comma-separated parts (trimmed) + */ +export function splitTopLevelArgs(str) { + const parts = []; + let depth = 0; + let quote = ''; + let cur = ''; + for (let i = 0; i < str.length; i += 1) { + const ch = str[i]; + if (quote) { + if (ch === quote && str[i - 1] !== '\\') quote = ''; + cur += ch; + continue; + } + if (ch === '"' || ch === "'") { + quote = ch; + cur += ch; + continue; + } + if (ch === '(') depth += 1; + else if (ch === ')') depth -= 1; + if (ch === ',' && depth === 0) { + parts.push(cur.trim()); + cur = ''; + } else { + cur += ch; + } + } + parts.push(cur.trim()); + return parts; +} + +/** + * Resolve every `light-dark(light, dark)` occurrence in a CSS value to the + * branch for the requested theme. Handles nesting (a chosen branch may itself + * contain another `light-dark()`) and arbitrary surrounding/expression context + * (e.g. inside `oklch(from light-dark(…) …)`), matching parentheses correctly. + * Values without `light-dark()` are returned unchanged. + * + * @param {string} value a CSS value (custom-property declaration value) + * @param {'light'|'dark'} theme + * @returns {string} the value with all light-dark() calls resolved + */ +export function resolveLightDark(value, theme) { + if (typeof value !== 'string') return value; + const idx = value.toLowerCase().indexOf('light-dark('); + if (idx === -1) return value; + + const open = idx + 'light-dark('.length; + let depth = 1; + let i = open; + for (; i < value.length && depth > 0; i += 1) { + if (value[i] === '(') depth += 1; + else if (value[i] === ')') depth -= 1; + } + // `i` now points just past the matching ')'. If unbalanced, bail out safely. + if (depth !== 0) return value; + + const inner = value.slice(open, i - 1); + const before = value.slice(0, idx); + const after = value.slice(i); + const args = splitTopLevelArgs(inner); + const chosen = theme === 'dark' ? (args[1] ?? args[0] ?? '') : (args[0] ?? ''); + + // Recurse into the chosen branch (it may nest light-dark()) and into the + // remainder of the string (there may be more light-dark() calls after). + return before + resolveLightDark(chosen, theme) + resolveLightDark(after, theme); +} diff --git a/configurator/src/lib/preview.js b/configurator/src/lib/preview.js index 338e6de1..d65c2aa5 100644 --- a/configurator/src/lib/preview.js +++ b/configurator/src/lib/preview.js @@ -8,8 +8,19 @@ * colors and `light-dark()` all resolve exactly as they would under the real * framework — and it stays auto-synced, since the defaults come from the same * generated data the editor uses. + * + * DARK MODE — we resolve `light-dark()` ourselves (see ./lightdark.js) rather + * than relying on `color-scheme`: per the framework's own core/themes.css, + * inherited `light-dark()` custom properties are NOT re-evaluated when a nested + * element flips `color-scheme`, so the preview stage (which is not :root) stayed + * in light mode. Substituting each `light-dark(light, dark)` with the active + * theme's branch reproduces the framework's `[data-theme="dark"]` re-declarations + * exactly, on every engine. */ import { allTokens } from './model.js'; +import { resolveLightDark } from './lightdark.js'; + +export { resolveLightDark, splitTopLevelArgs } from './lightdark.js'; /** * @param {Record} overrides token name -> value @@ -18,8 +29,9 @@ import { allTokens } from './model.js'; */ export function buildPreviewDeclarations(overrides, theme) { const lines = []; - // color-scheme drives light-dark(); --sf-is-dark is the framework's internal - // dark flag that themes.css flips. Setting both reproduces a theme switch. + // `color-scheme` still drives native form controls / scrollbars and the few + // tokens keyed off the framework's dark flag; we ALSO resolve light-dark() + // ourselves below so inherited custom properties switch too. lines.push(`color-scheme: ${theme};`); lines.push(`--sf-is-dark: ${theme === 'dark' ? 1 : 0};`); @@ -27,11 +39,11 @@ export function buildPreviewDeclarations(overrides, theme) { if (t.value == null || t.value === '') continue; // Skip the internal dark flag — handled explicitly above. if (t.name === '--sf-is-dark') continue; - lines.push(`${t.name}: ${t.value};`); + lines.push(`${t.name}: ${resolveLightDark(t.value, theme)};`); } for (const [name, value] of Object.entries(overrides)) { if (value == null || value === '') continue; - lines.push(`${name}: ${value};`); + lines.push(`${name}: ${resolveLightDark(value, theme)};`); } return lines.join('\n'); } diff --git a/configurator/src/lib/store.svelte.js b/configurator/src/lib/store.svelte.js index 5c408275..bd2b06e3 100644 --- a/configurator/src/lib/store.svelte.js +++ b/configurator/src/lib/store.svelte.js @@ -24,13 +24,13 @@ export const overrides = $state(loadOverrides()); */ export const storage = $state({ ok: true }); -/** UI state: search, active category, tier visibility, preview theme. */ +/** UI state: active domain tab, basic/advanced mode, search, filters, preview. */ export const ui = $state({ - /** Active top-level view: 'tokens' | 'a11y' | 'scales'. */ - view: 'tokens', + /** Active domain tab id (see lib/domains.js): 'typography' | 'colors' | … */ + domain: 'typography', + /** Global complexity mode: 'basic' shows curated essentials, 'advanced' all. */ + mode: 'basic', query: '', - activeCategory: '', - showAdvanced: true, showInternal: false, onlyModified: false, previewTheme: 'light', diff --git a/configurator/tests/lightdark.test.js b/configurator/tests/lightdark.test.js new file mode 100644 index 00000000..3bf0ff64 --- /dev/null +++ b/configurator/tests/lightdark.test.js @@ -0,0 +1,57 @@ +/** + * Unit tests for the light-dark() resolver used by the live preview and the + * WCAG probes. Pure functions, no DOM — run: node --test tests/lightdark.test.js + */ +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import { resolveLightDark, splitTopLevelArgs } from '../src/lib/lightdark.js'; + +describe('splitTopLevelArgs', () => { + test('splits only top-level commas', () => { + assert.deepEqual(splitTopLevelArgs('a, b, c'), ['a', 'b', 'c']); + }); + test('ignores commas nested in parentheses', () => { + assert.deepEqual( + splitTopLevelArgs('var(--a, b), oklch(1 2 3), c'), + ['var(--a, b)', 'oklch(1 2 3)', 'c'] + ); + }); + test('ignores commas inside quotes', () => { + assert.deepEqual(splitTopLevelArgs(`'a, b', c`), [`'a, b'`, 'c']); + }); +}); + +describe('resolveLightDark', () => { + test('picks the light branch', () => { + assert.equal(resolveLightDark('light-dark(white, black)', 'light'), 'white'); + }); + test('picks the dark branch', () => { + assert.equal(resolveLightDark('light-dark(white, black)', 'dark'), 'black'); + }); + test('keeps commas inside a branch intact', () => { + const v = + 'light-dark(var(--a-light), var(--a-dark, oklch(from var(--a-light) calc(l*0.5) c h)))'; + assert.equal( + resolveLightDark(v, 'dark'), + 'var(--a-dark, oklch(from var(--a-light) calc(l*0.5) c h))' + ); + }); + test('resolves light-dark() embedded in a larger expression', () => { + assert.equal( + resolveLightDark('oklch(from light-dark(red, blue) l c h)', 'dark'), + 'oklch(from blue l c h)' + ); + }); + test('resolves nested light-dark() in the chosen branch', () => { + assert.equal( + resolveLightDark('light-dark(a, light-dark(b, c))', 'dark'), + 'c' + ); + }); + test('passes through values without light-dark()', () => { + assert.equal(resolveLightDark('clamp(1rem, 2vw, 3rem)', 'dark'), 'clamp(1rem, 2vw, 3rem)'); + }); + test('is safe on unbalanced input', () => { + assert.equal(resolveLightDark('light-dark(a, b', 'dark'), 'light-dark(a, b'); + }); +}); diff --git a/tests/api-index-sync.test.js b/tests/api-index-sync.test.js index 39f3c388..31f3463f 100644 --- a/tests/api-index-sync.test.js +++ b/tests/api-index-sync.test.js @@ -55,8 +55,8 @@ describe('api-index.json ⇄ registry.json sync', () => { }); // ── Token parity ───────────────────────────────────────────────────────── - // registry.tokens is the canonical 769-token surface; api-index token rows - // minus the legacy fallback-only channels must equal it, name-for-name. + // registry.tokens is the canonical public token surface; api-index token + // rows minus the legacy fallback-only channels must equal it, name-for-name. test('token names match registry (excluding fallback-only channels)', () => { const indexTokenNames = new Set( tokens.filter(t => !t.fallbackOnly).map(t => t.name) From e7a891f31f33ff2238a999231adcf1aec0e65fe6 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:34:54 +0000 Subject: [PATCH 2/3] refactor(configurator): address PR review feedback - Restore Modified-only + Show-internal filters (lost with the Sidebar) to the Advanced panel header. - Clear the search query on tab switch so a stale filter never empties the next domain's list. - inferControl: treat composite values (box-shadow etc.) as non-color via a top-level-space check, so shadow tokens get a text editor not a dead swatch. - Tighten the shadows domain regex (anchor glow; drop redundant drop-shadow). - Only label group categories when more than one is present in a domain. - Lift the Light/Dark preview-theme toggle into the header so it works with the preview hidden and on the WCAG tab; update copy. - Drop the dead re-export and the duplicated double scroll container. --- configurator/src/App.svelte | 10 +++++- .../src/components/DomainPanel.svelte | 34 ++++++++++++++++++- configurator/src/components/Header.svelte | 33 ++++++++++++++++++ configurator/src/components/Preview.svelte | 33 +++--------------- configurator/src/components/WcagPanel.svelte | 4 +-- configurator/src/lib/domains.js | 3 +- configurator/src/lib/model.js | 23 +++++++++++++ configurator/src/lib/preview.js | 2 -- 8 files changed, 107 insertions(+), 35 deletions(-) diff --git a/configurator/src/App.svelte b/configurator/src/App.svelte index 1ee87f20..19fa081c 100644 --- a/configurator/src/App.svelte +++ b/configurator/src/App.svelte @@ -20,6 +20,14 @@ const domain = $derived(DOMAIN_BY_ID.get(ui.domain) ?? DOMAIN_BY_ID.get('typography')); const isTool = $derived(domain?.tool === 'wcag'); + + // The search query is scoped to a domain's Advanced list; clear it when the + // active tab changes so a leftover filter never makes the next tab's list + // look empty (the search box only renders in Advanced mode). + $effect(() => { + ui.domain; + ui.query = ''; + });
@@ -62,7 +70,7 @@ .main { min-width: 0; min-height: 0; - overflow-y: auto; + overflow: hidden; display: flex; flex-direction: column; } diff --git a/configurator/src/components/DomainPanel.svelte b/configurator/src/components/DomainPanel.svelte index fbfeb2ad..9eaa86c5 100644 --- a/configurator/src/components/DomainPanel.svelte +++ b/configurator/src/components/DomainPanel.svelte @@ -46,6 +46,10 @@ ); const grouped = $derived(groupTokens(advancedVisible)); + // Only label each group's category when more than one is present in this + // domain (otherwise the repeated "Core tokens" prefix is just noise). + const categoryCount = $derived(new Set(grouped.map((c) => c.category)).size); + const basicGenerators = $derived(domain.basicGenerators ?? []); const advancedGenerators = $derived(domain.advancedGenerators ?? []); @@ -68,6 +72,19 @@ {/if}
+ {#if advanced} +
+ + +
+ {/if} + {#if essentials.length}
@@ -100,7 +117,7 @@ name={group.name} tokens={group.tokens} description={group.description} - showCategory={cat.category !== domain.label} + showCategory={categoryCount > 1} category={cat.category} /> {/each} @@ -142,6 +159,21 @@ } .panel__search:focus { outline: 2px solid var(--cfg-accent); outline-offset: -1px; } + .panel__filters { + display: flex; + gap: 18px; + margin-top: -4px; + } + .panel__check { + display: flex; + align-items: center; + gap: 7px; + font-size: 13px; + color: var(--cfg-text-muted); + cursor: pointer; + } + .panel__check input { accent-color: var(--cfg-accent-strong); } + .essentials { border: 1px solid var(--cfg-border); border-radius: var(--cfg-radius); diff --git a/configurator/src/components/Header.svelte b/configurator/src/components/Header.svelte index 8373ccfd..0eba3c9c 100644 --- a/configurator/src/components/Header.svelte +++ b/configurator/src/components/Header.svelte @@ -47,6 +47,22 @@ {/if} {sync.counts?.tokens ?? 0} tokens +
+ + +