style(menu-indicator): align with Checkbox / RadioGroup visual

Menu CheckboxItem / RadioItem now mirror the standalone Checkbox /
RadioGroup chrome:

- off: outlined box / circle (transparent fill, neutral border)
- on : filled solid accent + white ✓ glyph (checkbox) or white inner
       dot (radio) — same shape as `<SvgCheck>` and RadioGroup.Indicator

The check glyph uses `mask-image` with the same `M5 12.5L10 17.5L19 7`
path as `<SvgCheck>` so the menu item reads visually identical to a
standalone `<Checkbox checked>`. Background-color carries the glyph
colour via `--_menu-indicator-glyph` (`--color-content-on-solid`),
matching how Checkbox sets `--_checkbox-palette-text` over the solid
accent fill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 4b5bd9f35d
commit a2709476f4

@ -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 `<Checkbox>` and `<RadioGroup>` 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):
* 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)
* --_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)
* --_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.
* NOT INCLUDED: standalone `<Checkbox>` / `<RadioGroup>` / 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 `<SvgCheck>` 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,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><path d='M5 12.5L10 17.5L19 7'/></svg>");
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><path d='M5 12.5L10 17.5L19 7'/></svg>");
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);
}

Loading…
Cancel
Save

Powered by TurnKey Linux.