diff --git a/configurator/src/App.svelte b/configurator/src/App.svelte index b30b4ba0..059f0a9b 100644 --- a/configurator/src/App.svelte +++ b/configurator/src/App.svelte @@ -5,18 +5,20 @@ * Layout (desktop ≥ 1100px): * ┌─────────────────────────────────────────────────────────────────┐ * │ Header (brand · search · basic/advanced · theme · preview) │ - * ├──────────┬───────────────────────────────────────┬──────────────┤ - * │ │ │ │ - * │ Sidebar │ Domain panel / WCAG tool │ Preview │ - * │ │ │ │ - * ├──────────┴───────────────────────────────────────┴──────────────┤ + * ├──────────┬──┬────────────────────────────────────┬──┬───────────┤ + * │ │ │ │ │ │ + * │ Sidebar │↔│ Domain panel / WCAG tool │↔│ Preview │ + * │ │ │ │ │ │ + * ├──────────┴──┴────────────────────────────────────┴──┴───────────┤ * │ Output drawer (override CSS, copy / download / import) │ * └─────────────────────────────────────────────────────────────────┘ * + * The ↔ columns are 6 px drag handles — pointer-captured resize of the + * sidebar and preview pane widths. Widths persist in localStorage. + * * Below 1100px the preview pane becomes a slide-over overlay (closed by * default, opened with the header ◨ toggle, dismissed via the scrim). - * Below 760px the sidebar collapses to a - * compact icon-only rail and the search box widens to fill the header. + * Below 760px the sidebar collapses to a compact icon-only rail. * * State lives in the shared `ui` store; this component just wires the * active domain to its panel. @@ -35,6 +37,64 @@ import Cheatsheet from './components/Cheatsheet.svelte'; import Home from './components/Home.svelte'; + // ── Pane-width resizing ─────────────────────────────────────────────────── + const WIDTHS_KEY = 'slashed-configurator/pane-widths/v1'; + const SIDEBAR_MIN = 160; + const SIDEBAR_MAX = 500; + const PREVIEW_MIN = 300; + const PREVIEW_MAX = 680; + + function clampW(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); } + + function loadWidths() { + if (typeof localStorage === 'undefined') return { sidebar: 240, preview: 440 }; + try { + const raw = localStorage.getItem(WIDTHS_KEY); + if (raw) { + const p = JSON.parse(raw); + return { + sidebar: clampW(Number(p.sidebar) || 240, SIDEBAR_MIN, SIDEBAR_MAX), + preview: clampW(Number(p.preview) || 440, PREVIEW_MIN, PREVIEW_MAX), + }; + } + } catch { /* ignore */ } + return { sidebar: 240, preview: 440 }; + } + + const _w = loadWidths(); + let sidebarWidth = $state(_w.sidebar); + let previewWidth = $state(_w.preview); + + /** Which pane is being dragged ('sidebar' | 'preview' | null). */ + let dragging = $state(/** @type {'sidebar'|'preview'|null} */ (null)); + let _dragStartX = 0; + let _dragStartW = 0; + + function startResize(pane, /** @type {PointerEvent} */ e) { + e.currentTarget.setPointerCapture(e.pointerId); + dragging = pane; + _dragStartX = e.clientX; + _dragStartW = pane === 'sidebar' ? sidebarWidth : previewWidth; + e.preventDefault(); + } + + function onResizeMove(pane, /** @type {PointerEvent} */ e) { + if (dragging !== pane) return; + const dx = e.clientX - _dragStartX; + if (pane === 'sidebar') { + sidebarWidth = clampW(_dragStartW + dx, SIDEBAR_MIN, SIDEBAR_MAX); + } else { + // Preview handle sits LEFT of the preview pane: dragging right shrinks preview. + previewWidth = clampW(_dragStartW - dx, PREVIEW_MIN, PREVIEW_MAX); + } + } + + function endResize() { + if (!dragging) return; + dragging = null; + try { localStorage.setItem(WIDTHS_KEY, JSON.stringify({ sidebar: sidebarWidth, preview: previewWidth })); } catch { /* ignore */ } + } + const home = $derived(ui.mode === 'basic' && ui.domain === 'home'); const domain = $derived(DOMAIN_BY_ID.get(ui.domain) ?? DOMAIN_BY_ID.get('colors')); const tool = $derived(domain?.tool ?? ''); @@ -141,11 +201,33 @@ -
+
+ + {#if ui.sidebarOpen} + + {/if} +
{#if home} @@ -162,6 +244,22 @@ {/if}
+ + {#if ui.previewOpen} + + {/if} + {#if ui.previewOpen} @@ -173,29 +271,60 @@