refactor(eidos): extract shared menu-item indicator partial

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) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent dc03773ca5
commit 0e9628b8f0

@ -84,26 +84,10 @@
opacity: var(--context-menu-item-disabled-opacity, 0.55); opacity: var(--context-menu-item-disabled-opacity, 0.55);
} }
[data-context-menu-checkbox-item], /* Indicator slot + ::before painting for checkbox-item / radio-item shared
[data-context-menu-radio-item] { with dropdown-menu via `lib/menu-indicator.css` (imported by eidos
padding-inline-start: var(--context-menu-item-indicator-inset, calc(var(--space-2) + 1rem)); entrypoint before this recipe). Override the private vars below if a
position: relative; per-component tweak is needed. */
}
[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%;
}
[data-context-menu-sub-trigger]::after { [data-context-menu-sub-trigger]::after {
content: ''; content: '';

@ -89,29 +89,11 @@
opacity: var(--dropdown-menu-item-disabled-opacity, 0.55); opacity: var(--dropdown-menu-item-disabled-opacity, 0.55);
} }
/* Indicator slot for Checkbox/Radio — soma renders the Indicator child /* Indicator slot + ::before painting for checkbox-item / radio-item shared
* inline. The recipe reserves a leading affordance for the glyph. */ with context-menu via `lib/menu-indicator.css` (imported by eidos
[data-dropdown-menu-checkbox-item], entrypoint before this recipe). Override the private vars there if a
[data-dropdown-menu-radio-item] { per-component tweak is needed (e.g. `--_menu-indicator-mask` for a
padding-inline-start: var(--dropdown-menu-item-indicator-inset, calc(var(--space-2) + 1rem)); custom check glyph). */
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%;
}
/* SubTrigger gets a chevron affordance on the trailing edge. */ /* SubTrigger gets a chevron affordance on the trailing edge. */
[data-dropdown-menu-sub-trigger]::after { [data-dropdown-menu-sub-trigger]::after {

@ -94,6 +94,10 @@
@import './components/alert-dialog/alert-dialog.css'; @import './components/alert-dialog/alert-dialog.css';
@import './components/dropdown-menu/dropdown-menu.css'; @import './components/dropdown-menu/dropdown-menu.css';
@import './components/context-menu/context-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/pin-input/pin-input.css';
@import './components/date-range-field/date-range-field.css'; @import './components/date-range-field/date-range-field.css';
@import './components/drawer/drawer.css'; @import './components/drawer/drawer.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%;
}
Loading…
Cancel
Save

Powered by TurnKey Linux.