From 6cce8b7be7f327c016187d191206c80e52e49593 Mon Sep 17 00:00:00 2001 From: mwiegand Date: Tue, 19 May 2026 00:28:33 +0200 Subject: [PATCH] feat(css): add .field/.radio-row/.switch-row/.table-actions primitives Reusable form-control primitives used by the upcoming overlay create-modal and workshop-section redesigns. Custom-styled radios and a switch built from native inputs (no JS), so accessibility and form behavior come for free. Tokens unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- l4d2web/l4d2web/static/css/components.css | 144 ++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/l4d2web/l4d2web/static/css/components.css b/l4d2web/l4d2web/static/css/components.css index 7484ba0..68d238d 100644 --- a/l4d2web/l4d2web/static/css/components.css +++ b/l4d2web/l4d2web/static/css/components.css @@ -1189,3 +1189,147 @@ div.modal.modal-wide { #console-modal .modal-body .console-input-form { flex: 0 0 auto; } + +/* --- Form primitives (new vocabulary, 2026-05) -------------------------- */ + +.field { + display: grid; + gap: var(--space-xs); +} + +.field-label { + font-size: 0.8125rem; + font-weight: 600; + color: var(--color-text); +} + +/* .field-hint already defined earlier in this file. */ + +.radio-list { + display: grid; + gap: var(--space-xs); +} + +.radio-row { + display: flex; + align-items: flex-start; + gap: var(--space-s); + padding: var(--space-xs) 0; + cursor: pointer; +} + +.radio-row > input[type="radio"] { + appearance: none; + width: 1rem; + height: 1rem; + border-radius: 50%; + border: 1.5px solid var(--color-border); + background: var(--color-bg); + flex: 0 0 auto; + margin: 0.2rem 0 0 0; + display: grid; + place-items: center; + cursor: pointer; +} + +.radio-row > input[type="radio"]:checked { + border-color: var(--color-primary); +} + +.radio-row > input[type="radio"]:checked::after { + content: ""; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background: var(--color-primary); +} + +.radio-row > input[type="radio"]:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.radio-row-text { + display: grid; + gap: 0.0625rem; +} + +.radio-row-text strong { + font-weight: 600; + font-size: 0.9375rem; + color: var(--color-text); +} + +.radio-row-text span { + color: var(--color-muted); + font-size: 0.8125rem; +} + +.switch-row { + display: flex; + align-items: flex-start; + gap: var(--space-s); + padding: var(--space-xs) 0; + cursor: pointer; +} + +.switch-row > input[type="checkbox"] { + appearance: none; + position: relative; + width: 1.875rem; + height: 1rem; + background: var(--color-border); + border-radius: 999px; + flex: 0 0 auto; + margin: 0.225rem 0 0 0; + cursor: pointer; + transition: background 0.15s; +} + +.switch-row > input[type="checkbox"]::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 0.75rem; + height: 0.75rem; + background: #fff; + border-radius: 50%; + transition: transform 0.15s; +} + +.switch-row > input[type="checkbox"]:checked { + background: var(--color-button-primary); +} + +.switch-row > input[type="checkbox"]:checked::after { + transform: translateX(0.875rem); +} + +.switch-row > input[type="checkbox"]:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} + +.switch-row-text { + display: grid; + gap: 0.0625rem; +} + +.switch-row-text strong { + font-weight: 600; + font-size: 0.9375rem; + color: var(--color-text); +} + +.switch-row-text span { + color: var(--color-muted); + font-size: 0.8125rem; +} + +.table-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: var(--space-m); +}