From 0e9628b8f056a222631c01f07070a58b258b9235 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 23 May 2026 12:31:19 +0200 Subject: [PATCH] refactor(eidos): extract shared menu-item indicator partial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify the four pseudo-element indicator recipes — context-menu and dropdown-menu × {checkbox-item, radio-item} — into a single partial at `lib/menu-indicator.css`. The four selectors paint identical geometry (0.875rem rounded square or circle at the leading inset) so a single source keeps them in lockstep. Tokens are exposed as private vars (`--_menu-indicator-{size,radius, color,inset,mask}`) so a consumer can override per-component without re-implementing the recipe. Import order matters: the partial must load AFTER the menu recipes so its `padding-inline-start: calc(...)` wins over the row rule's `padding-inline` shorthand at equal specificity. Verified in browser on both /uix/components/dropdown-menu and /uix/components/context-menu: checked checkbox-items render 14×14 purple rounded squares, radio-items render circles, and all rows reserve the same 26px slot so toggling state doesn't shift text. Out of scope: Checkbox / RadioGroup / Select.ItemIndicator — those use `[data-*-indicator]` DOM parts with SVG markers and xs/sm/md/lg/xl size scales, an architecturally different pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/context-menu/context-menu.css | 24 ++----- .../dropdown-menu/dropdown-menu.css | 28 ++------ src/uix/eidos/index.css | 4 ++ src/uix/eidos/lib/menu-indicator.css | 72 +++++++++++++++++++ 4 files changed, 85 insertions(+), 43 deletions(-) create mode 100644 src/uix/eidos/lib/menu-indicator.css diff --git a/src/uix/eidos/components/context-menu/context-menu.css b/src/uix/eidos/components/context-menu/context-menu.css index c6adcd302..503f2b448 100644 --- a/src/uix/eidos/components/context-menu/context-menu.css +++ b/src/uix/eidos/components/context-menu/context-menu.css @@ -84,26 +84,10 @@ opacity: var(--context-menu-item-disabled-opacity, 0.55); } -[data-context-menu-checkbox-item], -[data-context-menu-radio-item] { - padding-inline-start: var(--context-menu-item-indicator-inset, calc(var(--space-2) + 1rem)); - position: relative; -} - -[data-context-menu-checkbox-item][data-state='checked']::before, -[data-context-menu-radio-item][data-state='checked']::before { - content: ''; - position: absolute; - inset-inline-start: var(--context-menu-item-px, var(--space-2)); - inline-size: 0.875rem; - block-size: 0.875rem; - border-radius: 0.2rem; - background: var(--color-primary-solid); -} - -[data-context-menu-radio-item][data-state='checked']::before { - border-radius: 50%; -} +/* Indicator slot + ::before painting for checkbox-item / radio-item shared + with dropdown-menu via `lib/menu-indicator.css` (imported by eidos + entrypoint before this recipe). Override the private vars below if a + per-component tweak is needed. */ [data-context-menu-sub-trigger]::after { content: ''; diff --git a/src/uix/eidos/components/dropdown-menu/dropdown-menu.css b/src/uix/eidos/components/dropdown-menu/dropdown-menu.css index a97bae72c..9d7cb4de3 100644 --- a/src/uix/eidos/components/dropdown-menu/dropdown-menu.css +++ b/src/uix/eidos/components/dropdown-menu/dropdown-menu.css @@ -89,29 +89,11 @@ opacity: var(--dropdown-menu-item-disabled-opacity, 0.55); } -/* Indicator slot for Checkbox/Radio — soma renders the Indicator child - * inline. The recipe reserves a leading affordance for the glyph. */ -[data-dropdown-menu-checkbox-item], -[data-dropdown-menu-radio-item] { - padding-inline-start: var(--dropdown-menu-item-indicator-inset, calc(var(--space-2) + 1rem)); - position: relative; -} - -[data-dropdown-menu-checkbox-item][data-state='checked']::before, -[data-dropdown-menu-radio-item][data-state='checked']::before { - content: ''; - position: absolute; - inset-inline-start: var(--dropdown-menu-item-px, var(--space-2)); - inline-size: 0.875rem; - block-size: 0.875rem; - border-radius: 0.2rem; - background: var(--color-primary-solid); - mask-image: var(--dropdown-menu-check-mask, none); -} - -[data-dropdown-menu-radio-item][data-state='checked']::before { - border-radius: 50%; -} +/* Indicator slot + ::before painting for checkbox-item / radio-item shared + with context-menu via `lib/menu-indicator.css` (imported by eidos + entrypoint before this recipe). Override the private vars there if a + per-component tweak is needed (e.g. `--_menu-indicator-mask` for a + custom check glyph). */ /* SubTrigger gets a chevron affordance on the trailing edge. */ [data-dropdown-menu-sub-trigger]::after { diff --git a/src/uix/eidos/index.css b/src/uix/eidos/index.css index 2d8296b62..261faf292 100644 --- a/src/uix/eidos/index.css +++ b/src/uix/eidos/index.css @@ -94,6 +94,10 @@ @import './components/alert-dialog/alert-dialog.css'; @import './components/dropdown-menu/dropdown-menu.css'; @import './components/context-menu/context-menu.css'; +/* Shared menu-item indicator partial — must load AFTER the menu recipes + so its `padding-inline-start` calc wins over the row rule's + `padding-inline` shorthand at equal specificity. */ +@import './lib/menu-indicator.css'; @import './components/pin-input/pin-input.css'; @import './components/date-range-field/date-range-field.css'; @import './components/drawer/drawer.css'; diff --git a/src/uix/eidos/lib/menu-indicator.css b/src/uix/eidos/lib/menu-indicator.css new file mode 100644 index 000000000..aefb26d83 --- /dev/null +++ b/src/uix/eidos/lib/menu-indicator.css @@ -0,0 +1,72 @@ +/* + * Shared menu-item indicator recipe. + * + * Targets the four pseudo-element indicators that signal `data-state='checked'` + * on menu items inside a `role="menu"` container: + * + * [data-context-menu-checkbox-item] + * [data-context-menu-radio-item] + * [data-dropdown-menu-checkbox-item] + * [data-dropdown-menu-radio-item] + * + * These four selectors share the exact same geometry — a 0.875rem ✕ 0.875rem + * filled element painted at the leading inset of the item row. The check + * variant is a rounded square, the radio variant is a circle. + * + * The four items are SEPARATE morfo parts (different ARIA roles — + * `menuitemcheckbox` vs `menuitemradio`) but their visual treatment is + * coherent: both represent a "selected" affordance inside the menu chrome. + * Unifying the recipe avoids drift between the two menus (context-menu vs + * dropdown-menu) without merging the contracts. + * + * Tokens (consumer-overridable via the same private vars): + * --_menu-indicator-size @default 0.875rem + * --_menu-indicator-radius @default 0.2rem (rounded square) + * --_menu-indicator-color @default var(--color-primary-solid) + * --_menu-indicator-inset @default var(--space-2) + * --_menu-indicator-mask @default none (optional CSS mask-image for + * a custom check glyph) + * + * NOT INCLUDED: Checkbox / RadioGroup / Select.ItemIndicator. Those use DOM + * parts (`[data-checkbox-indicator]`, etc.) with their own SVG markers and + * size scales (xs/sm/md/lg/xl), a different architectural pattern. + */ + +/* Reserve the indicator slot on every menu-item type that may carry one, + even when unchecked, so the row text doesn't shift when state flips. */ +[data-context-menu-checkbox-item], +[data-context-menu-radio-item], +[data-dropdown-menu-checkbox-item], +[data-dropdown-menu-radio-item] { + --_menu-indicator-size: 0.875rem; + --_menu-indicator-radius: 0.2rem; + --_menu-indicator-color: var(--color-primary-solid); + --_menu-indicator-inset: var(--space-2); + + position: relative; + padding-inline-start: calc( + var(--_menu-indicator-inset) + var(--_menu-indicator-size) + var(--space-1) + ); +} + +/* The indicator itself — only painted when state is checked. */ +[data-context-menu-checkbox-item][data-state='checked']::before, +[data-context-menu-radio-item][data-state='checked']::before, +[data-dropdown-menu-checkbox-item][data-state='checked']::before, +[data-dropdown-menu-radio-item][data-state='checked']::before { + content: ''; + position: absolute; + inset-inline-start: var(--_menu-indicator-inset); + inline-size: var(--_menu-indicator-size); + block-size: var(--_menu-indicator-size); + border-radius: var(--_menu-indicator-radius); + background: var(--_menu-indicator-color); + mask-image: var(--_menu-indicator-mask, none); + -webkit-mask-image: var(--_menu-indicator-mask, none); +} + +/* Radio variant — circular indicator. */ +[data-context-menu-radio-item][data-state='checked']::before, +[data-dropdown-menu-radio-item][data-state='checked']::before { + border-radius: 50%; +}