@ -1,7 +1,14 @@
/ *
/ *
* Shared menu-item indicator recipe .
* 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 :
* on menu items inside a ` role = "menu" ` container :
*
*
* [ data-context-menu-checkbox-item ]
* [ data-context-menu-checkbox-item ]
@ -9,31 +16,26 @@
* [ data-dropdown-menu-checkbox-item ]
* [ data-dropdown-menu-checkbox-item ]
* [ data-dropdown-menu-radio-item ]
* [ data-dropdown-menu-radio-item ]
*
*
* These four selectors share the exact same geometry — a 0 . 875rem ✕ 0 . 875rem
* Layout : the indicator slot is reserved on every checkbox / radio
* filled element painted at the leading inset of the item row . The check
* menu item so the row text doesn ' t shift when state flips . ` :: before `
* variant is a rounded square , the radio variant is a circle .
* paints the box / circle ( always present ) ; ` :: after ` paints the glyph
*
* inside ( only when checked ) .
* 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 ) :
* Tokens ( consumer-overridable ) :
* --_menu-indicator-size @ default 0 . 875rem
* --_menu-indicator-size @ default 0 . 875rem
* --_menu-indicator-radius @ default 0 . 2rem ( rounded square )
* --_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- inset @ default var ( --space-2 )
* --_menu-indicator- glyph @ default white ( color of the check / dot )
* --_menu-indicator- mask @ default none ( optional CSS mask-image for
* --_menu-indicator- border @ default var ( --color-border-default ) ( off-state border )
* a custom check glyph )
* --_menu-indicator-inset @ default var ( --space-2 )
*
*
* NOT INCLUDED : Checkbox / RadioGroup / Select . ItemIndicator . Those use DOM
* NOT INCLUDED : standalone ` < Checkbox > ` / ` < RadioGroup > ` / Select .
* parts ( ` [ data-checkbox-indicator ] ` , etc . ) with their own SVG markers and
* ItemIndicator . Those use real DOM parts with size scales and their
* size scales ( xs / sm / md / lg / xl ) , a different architectural pattern .
* 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 ,
/* Reserve the indicator slot. */
even when unchecked , so the row text doesn ' t shift when state flips . * /
[ data-context-menu-checkbox-item ] ,
[ data-context-menu-checkbox-item ] ,
[ data-context-menu-radio-item ] ,
[ data-context-menu-radio-item ] ,
[ data-dropdown-menu-checkbox-item ] ,
[ data-dropdown-menu-checkbox-item ] ,
@ -41,6 +43,7 @@
--_menu-indicator-size : 0 . 875rem ;
--_menu-indicator-size : 0 . 875rem ;
--_menu-indicator-radius : 0 . 2rem ;
--_menu-indicator-radius : 0 . 2rem ;
--_menu-indicator-color : var ( --color-primary-solid ) ;
--_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-border : var ( --color-border-default ) ;
--_menu-indicator-inset : var ( --space-2 ) ;
--_menu-indicator-inset : var ( --space-2 ) ;
@ -50,10 +53,8 @@
) ;
) ;
}
}
/ * Base indicator : always painted ( regardless of state ) so the user can
/* ── Box / circle (::before) — always painted, regardless of state ───── */
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 ` . * /
[ data-context-menu-checkbox-item ] :: before ,
[ data-context-menu-checkbox-item ] :: before ,
[ data-context-menu-radio-item ] :: before ,
[ data-context-menu-radio-item ] :: before ,
[ data-dropdown-menu-checkbox-item ] :: before ,
[ data-dropdown-menu-checkbox-item ] :: before ,
@ -66,22 +67,63 @@
border : 1px solid var ( --_menu-indicator-border ) ;
border : 1px solid var ( --_menu-indicator-border ) ;
border-radius : var ( --_menu-indicator-radius ) ;
border-radius : var ( --_menu-indicator-radius ) ;
background : transparent ;
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-checkbox-item ] [ data-state = 'checked' ] :: before ,
[ data-context-menu-radio-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-checkbox-item ] [ data-state = 'checked' ] :: before ,
[ data-dropdown-menu-radio-item ] [ data-state = 'checked' ] :: before {
[ data-dropdown-menu-radio-item ] [ data-state = 'checked' ] :: before {
border-color : var ( --_menu-indicator-color ) ;
border-color : var ( --_menu-indicator-color ) ;
background : 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-context-menu-radio-item ] :: before ,
[ data-dropdown-menu-radio-item ] :: before {
[ data-dropdown-menu-radio-item ] :: before {
border-radius : 50 % ;
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 ) ;
}