OXY-154: harden ColorModePicker into a standalone Light/Dark/System component - #303
OXY-154: harden ColorModePicker into a standalone Light/Dark/System component#303Kalin-Rudnicki wants to merge 2 commits into
Conversation
…omponent 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YxWKdsz97QT9BD7AdpSq6
🤖 Automated multi-agent review (confirmed findings)MediumKeyboard nav focuses wrong picker when multiple segmented pickers share default idPrefix Low
These are AI-generated, adversarially-verified findings and may still contain false positives — please confirm before acting. |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YxWKdsz97QT9BD7AdpSq6
Summary
Hardens
ColorModePicker(from OXY-154) into a first-class, standalone Light / Dark / System control. It is now fully independent of the theme-pack machinery — it depends only on theColorModeservice and stands on its own even ifThemePickerdidn't exist.ThemePickeris unchanged.What changed
ToggleThumb/HorizontalRadio), so instances are droppable widgets and fluently configurable. Two variants:role=radiogroup, default).small/.medium/.large,.label(..)/.noLabel,.includeSystem(..)/.lightDarkOnly,.withIcons/.noIcons,.idPrefix(..). Backward-compatibleapply()/apply(label = ...)retained — existing call sites unchanged.tabindex,role=radio+aria-checkedper option, 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<button>with a descriptivearia-label/title.ColorModePicker.syncAcrossTabssubscribes to theBroadcastcolor-mode channel and re-highlights every mounted picker (addresses the OXY-154 "does not re-highlight on cross-tab changes" caveat). Off by default — no bootstrap behavior change.ColorModePicker.Logicand unit-tested (6 new DOM-free tests).ThemePagenow demos the variants. Docs:docs/docs/ui/builders.mdupdated.Decoupling note
ColorModePickernever had a code-level dependency onThemePicker(they only shared a pattern). Confirmed and kept its shared state seeded viaColorMode.storageKey/ColorMode.parse— no reference toTheme/OxygenThemes/ThemePicker.A native
<select>/dropdown "form" variant was intentionally not added: labeled form contexts are already served by the genericHorizontalRadio.form[ColorMode.Mode].Verification
oxygen-ui-web/compile✅example-ui-web/compile✅oxygen-ui-web/test✅ (51 tests, incl. 6 newColorModePicker.Logictests)sbt fmtapplied. No regressions toColorMode/Theme/ColorTheme/ThemePickeror existing call sites.🤖 Generated with Claude Code
https://claude.ai/code/session_011YxWKdsz97QT9BD7AdpSq6