From a91b5747a3089e12162d009b8fbeb903081ad544 Mon Sep 17 00:00:00 2001 From: c0dev0id Date: Sun, 28 Jun 2026 20:37:53 +0200 Subject: [PATCH] Article scope: render an actual sliding toggle, not an ASCII-labelled button Previous commit took the user's (O-) / (-O) shorthand literally and put those glyphs as the button label. They were describing the visual states of a real toggle switch, not text to display. Now: pill-shaped track with a sliding circular thumb, "default" and "overwrite" labels flanking it (the active side brightens). The button carries role="switch" + aria-checked for accessibility. Click anywhere on the track to flip and mark the score dirty. --- static/components/PaneFO.js | 28 ++++++++++++------------ static/style.css | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/static/components/PaneFO.js b/static/components/PaneFO.js index 5d04ec4..51153bf 100644 --- a/static/components/PaneFO.js +++ b/static/components/PaneFO.js @@ -155,25 +155,25 @@ export const PaneFO = { ); } else if (node.type === 'article') { const markDirty = () => props.store.markDirty(); - const SCOPES = ['defaults', 'overwrites']; - const scopeLabel = s => s === 'defaults' ? '(O-) default' : '(-O) overwrite'; children.push( h('h4', H4, `Article: ${node.name}`), h('ul', { class: 'se-article-props', style: 'list-style:none;padding:0;margin:0' }, - node.properties.map((p, idx) => - h('li', { key: idx, style: 'display:flex;gap:0.5rem;align-items:center;padding:0.25rem 0' }, [ + node.properties.map((p, idx) => { + const isOverwrite = p.scope === 'overwrites'; + const flip = () => { p.scope = isOverwrite ? 'defaults' : 'overwrites'; markDirty(); }; + return h('li', { key: idx, style: 'display:flex;gap:0.5rem;align-items:center;padding:0.25rem 0' }, [ h('span', { style: 'flex:1' }, `${p.name} = ${p.value}`), + h('span', { class: ['se-toggle-label', !isOverwrite ? 'active' : null], style: 'text-align:right' }, 'default'), h('button', { - class: ['se-scope-toggle', `scope-${p.scope}`], - style: 'font-family:monospace;min-width:8em', - onClick: () => { - const i = SCOPES.indexOf(p.scope); - p.scope = SCOPES[(i + 1) % SCOPES.length]; - markDirty(); - }, - }, scopeLabel(p.scope)), - ]) - ) + class: 'se-toggle', + role: 'switch', + 'aria-checked': String(isOverwrite), + title: isOverwrite ? 'overwrites — click to switch to defaults' : 'defaults — click to switch to overwrites', + onClick: flip, + }), + h('span', { class: ['se-toggle-label', isOverwrite ? 'active' : null] }, 'overwrite'), + ]); + }) ), ); } else if (node.type === 'bar') { diff --git a/static/style.css b/static/style.css index f2abf46..993c0e7 100644 --- a/static/style.css +++ b/static/style.css @@ -355,3 +355,46 @@ white-space: pre-wrap; font-family: monospace; } + +/* Two-state toggle (scope: defaults / overwrites). The label preceding the + switch tells the user what the two ends mean; the thumb position shows + which is active. Click anywhere on the track to flip. */ +.se-toggle { + position: relative; + width: 2.2rem; + height: 1.1rem; + flex-shrink: 0; + border: 1px solid #555; + border-radius: 0.55rem; + background: #2a2a2a; + cursor: pointer; + padding: 0; +} +.se-toggle::after { + content: ''; + position: absolute; + top: 1px; + left: 1px; + width: 0.85rem; + height: 0.85rem; + border-radius: 50%; + background: #999; + transition: transform 0.12s ease, background 0.12s ease; +} +.se-toggle[aria-checked="true"] { + background: #2d4a2d; + border-color: #4a7a4a; +} +.se-toggle[aria-checked="true"]::after { + transform: translateX(1.1rem); + background: #b0e0b0; +} +.se-toggle-label { + font-size: 0.7rem; + color: #888; + user-select: none; + min-width: 4em; +} +.se-toggle-label.active { + color: #d8d8d8; +}