diff --git a/src/uix/eidos/lib/menu-indicator.css b/src/uix/eidos/lib/menu-indicator.css index 71996aebe..0cb0cafff 100644 --- a/src/uix/eidos/lib/menu-indicator.css +++ b/src/uix/eidos/lib/menu-indicator.css @@ -1,7 +1,14 @@ /* * Shared menu-item indicator recipe. * - * Targets the four pseudo-element indicators that signal `data-state='checked'` + * Mirrors the visual treatment of `` and `` so a + * menu CheckboxItem / RadioItem reads identically to its standalone + * sibling: + * + * - off → outlined box / circle (transparent fill, neutral border) + * - on → filled solid box / circle with a white SVG ✓ / dot inside + * + * Targets the four pseudo-element indicators that signal `data-state` * on menu items inside a `role="menu"` container: * * [data-context-menu-checkbox-item] @@ -9,31 +16,26 @@ * [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. + * Layout: the indicator slot is reserved on every checkbox / radio + * menu item so the row text doesn't shift when state flips. `::before` + * paints the box / circle (always present); `::after` paints the glyph + * inside (only when checked). * - * 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) + * Tokens (consumer-overridable): + * --_menu-indicator-size @default 0.875rem + * --_menu-indicator-radius @default 0.2rem (rounded square) + * --_menu-indicator-color @default var(--color-primary-solid) (filled accent) + * --_menu-indicator-glyph @default white (color of the check / dot) + * --_menu-indicator-border @default var(--color-border-default) (off-state border) + * --_menu-indicator-inset @default var(--space-2) * - * 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. + * NOT INCLUDED: standalone `` / `` / Select. + * ItemIndicator. Those use real DOM parts with size scales and their + * own SVG markers; this partial is the simplified version for items + * rendered inside a menu chrome. */ -/* 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. */ +/* Reserve the indicator slot. */ [data-context-menu-checkbox-item], [data-context-menu-radio-item], [data-dropdown-menu-checkbox-item], @@ -41,6 +43,7 @@ --_menu-indicator-size: 0.875rem; --_menu-indicator-radius: 0.2rem; --_menu-indicator-color: var(--color-primary-solid); + --_menu-indicator-glyph: var(--color-content-on-solid, #fff); --_menu-indicator-border: var(--color-border-default); --_menu-indicator-inset: var(--space-2); @@ -50,10 +53,8 @@ ); } -/* Base indicator: always painted (regardless of state) so the user can - tell at a glance that the row IS a checkbox / radio item. The empty - shape is just an outlined box / circle; the filled shape appears once - the state flips to `checked`. */ +/* ── Box / circle (::before) — always painted, regardless of state ───── */ + [data-context-menu-checkbox-item]::before, [data-context-menu-radio-item]::before, [data-dropdown-menu-checkbox-item]::before, @@ -66,22 +67,63 @@ border: 1px solid var(--_menu-indicator-border); border-radius: var(--_menu-indicator-radius); background: transparent; - transition: background var(--duration-fast) var(--ease-default); + transition: + background var(--duration-fast) var(--ease-default), + border-color var(--duration-fast) var(--ease-default); } -/* Checked state — fill the indicator with the accent color. */ +/* Checked state: box fills with accent colour, border tracks it. */ [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 { border-color: var(--_menu-indicator-color); background: var(--_menu-indicator-color); - mask-image: var(--_menu-indicator-mask, none); - -webkit-mask-image: var(--_menu-indicator-mask, none); } -/* Radio variant — circular indicator (both unchecked and checked). */ +/* Radio variant: circular box (both off and on). */ [data-context-menu-radio-item]::before, [data-dropdown-menu-radio-item]::before { border-radius: 50%; } + +/* ── Glyph (::after) — only painted when state='checked' ─────────────── */ + +/* Checkbox: SVG ✓ centred inside the box. We use mask-image so the + glyph colour follows --_menu-indicator-glyph (white over the solid + accent fill). The SVG is the same shape `` paints in the + standalone Checkbox component. */ +[data-context-menu-checkbox-item][data-state='checked']::after, +[data-dropdown-menu-checkbox-item][data-state='checked']::after { + content: ''; + position: absolute; + inset-inline-start: var(--_menu-indicator-inset); + inset-block-start: 50%; + transform: translateY(-50%); + inline-size: var(--_menu-indicator-size); + block-size: var(--_menu-indicator-size); + background-color: var(--_menu-indicator-glyph); + mask-image: url("data:image/svg+xml;utf8,"); + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: contain; +} + +/* Radio: smaller solid inner dot centred inside the circle, sized to + ~40% of the indicator (matches RadioGroup's dot ratio). */ +[data-context-menu-radio-item][data-state='checked']::after, +[data-dropdown-menu-radio-item][data-state='checked']::after { + content: ''; + position: absolute; + inset-inline-start: calc(var(--_menu-indicator-inset) + var(--_menu-indicator-size) * 0.3); + inset-block-start: 50%; + transform: translateY(-50%); + inline-size: calc(var(--_menu-indicator-size) * 0.4); + block-size: calc(var(--_menu-indicator-size) * 0.4); + border-radius: 50%; + background: var(--_menu-indicator-glyph); +}