From 2fdb6028bfc30fd2ecfff0412a94fc464485aaa6 Mon Sep 17 00:00:00 2001 From: Kalin Rudnicki Date: Thu, 13 Aug 2026 22:09:50 -0600 Subject: [PATCH 1/2] OXY-154: harden ColorModePicker into a standalone Light/Dark/System component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework the reusable color-mode picker into a first-class, standalone widget, fully independent of the theme-pack machinery (depends only on the ColorMode service). - Config-builder widget (à la ToggleThumb / HorizontalRadio) with two variants: Segmented (role=radiogroup, default) and Compact (single icon cycle button). - Config knobs: small/medium/large, label/noLabel, includeSystem/lightDarkOnly, withIcons/noIcons, idPrefix. Backward-compatible apply()/apply(label=...). - a11y: roving tabindex, role=radio + aria-checked per option, and keyboard navigation (arrows / Home / End / Space / Enter) that moves selection and DOM focus together; compact is a native button with descriptive aria-label/title. - Extracted pure decision logic (ColorModePicker.Logic) and unit-tested it. - Opt-in ColorModePicker.syncAcrossTabs subscribes to the Broadcast color-mode channel and re-highlights every mounted picker on cross-tab / programmatic changes. - Showcase (ThemePage) demos the variants; docs updated. No changes to ColorMode / Theme / ColorTheme / ThemePicker behavior. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_011YxWKdsz97QT9BD7AdpSq6 --- docs/docs/ui/builders.md | 32 +- .../ui/page/showcase/pages/ThemePage.scala | 23 +- .../ui/web/component/ColorModePicker.scala | 311 ++++++++++++++++-- .../ui/web/style/OxygenColorSystemSpec.scala | 55 +++- report/OXY-154-colormode.md | 43 +++ 5 files changed, 427 insertions(+), 37 deletions(-) create mode 100644 report/OXY-154-colormode.md diff --git a/docs/docs/ui/builders.md b/docs/docs/ui/builders.md index 7a769a27..9aca3514 100644 --- a/docs/docs/ui/builders.md +++ b/docs/docs/ui/builders.md @@ -222,7 +222,37 @@ ThemePicker(OxygenThemes.graphiteFamilyPacks) // filter to a pack family Both are self-contained (backed by a shared `GlobalState` seeded from the stored value): they reflect the current selection and call `ColorMode.setAndPersist` / `Theme.applyAndPersist` on -select. They do not re-highlight on cross-tab changes until the page re-renders. +select. + +**`ColorModePicker`** is a first-class, standalone Light/Dark/System control — independent of the +theme-pack machinery (depends only on the `ColorMode` service). It is a config builder (à la +`ToggleThumb` / `HorizontalRadio`): + +```scala +ColorModePicker() // segmented radiogroup (default) +ColorModePicker.compact // single icon button that cycles modes (top-bar friendly) +ColorModePicker.segmentedWithIcons // segmented + Light/Dark/System glyphs +ColorModePicker().lightDarkOnly // drop the System option +ColorModePicker().large.label("Theme") +``` + +- **a11y:** segmented is a `role=radiogroup` with `role=radio` + `aria-checked` per option, roving + `tabindex`, and keyboard nav (Left/Up = prev, Right/Down = next, Home/End = first/last with wrap, + Space/Enter select) that moves selection **and** DOM focus together. Compact is a native + `