Skip to content

Align context-menu to architecture goals#198

Merged
lifeiscontent merged 3 commits into
mainfrom
align/context-menu
Jun 24, 2026
Merged

Align context-menu to architecture goals#198
lifeiscontent merged 3 commits into
mainfrom
align/context-menu

Conversation

@lifeiscontent

Copy link
Copy Markdown
Collaborator

Closes #125

What the spec says

From the designer's comment on #125:

Always the same — shadow/elevation, border-radius, item height and padding, separator appearance, keyboard/dismiss behavior, z-index stacking, max-height scroll, shortcut alignment, submenu arrow position, focus highlight.

Depends (adjustable) — item labels/icons, shortcut labels, groups, disabled items, submenu items, checked/radio items, and destructive items (visually distinct color).

What changed

packages/propel/src/ui/context-menu/variants.ts

  • Added a tone variant (neutral | danger) to contextMenuItemVariants. This is the architectural hook for the spec's "destructive items" axis — it owns the color shift in one place rather than scattering text-danger-primary across call sites.
  • Added contextMenuSubmenuTriggerVariants — a dedicated cva for the submenu trigger that bakes in group/item and the popup-open highlight. This removes all cx() usage from component .tsx files, which now only call their cva.

packages/propel/src/ui/context-menu/context-menu-{item,link-item,checkbox-item,radio-item}.tsx

  • Each item-row part now accepts a required tone prop and passes it to its cva call. No defaultVariants — callers choose explicitly.

packages/propel/src/ui/context-menu/context-menu-submenu-trigger.tsx

  • Removed the cx import and inline class merging. Now calls contextMenuSubmenuTriggerVariants({ tone }) directly, keeping all styling in variants.ts.

packages/propel/src/components/context-menu/context-menu.stories.tsx

  • Delete row uses tone="danger" instead of passing className="text-danger-primary" into label and inlineStartNode children.

packages/propel/src/ui/context-menu/context-menu.stories.tsx

  • All ContextMenuItem usages now pass an explicit tone prop.

Notes

  • Visuals are equivalent for tone="neutral" items; the danger row previously relied on caller-applied color classes, which this PR replaces with a first-class prop.
  • The indicator default-child issue (baked <Check> in CheckboxItemIndicator / RadioItemIndicator) is tracked in task Add Badge component #4 and left unchanged here — that's a broader menu-surface anatomy refactor.
  • Needs design approval from @bhaveshraja before merge.

@github-actions

Copy link
Copy Markdown

📚 Storybook preview: https://pr-198-propel-storybook.vamsi-906.workers.dev

Add a `tone` variant (neutral/danger) to all item-row parts so destructive
actions are expressed via a prop rather than ad-hoc colored children.
Move the submenu trigger's popup-open highlight out of an inline cx() call
into a dedicated `contextMenuSubmenuTriggerVariants` in variants.ts — component
files now contain no cx() calls.  Update both the ui- and components-tier
stories to pass explicit tone props.

Closes #125
text-danger-secondary (red-700, #e7000b) on bg-layer-1 yields 4.36:1 — just
below the 4.5:1 WCAG AA threshold and fails the axe a11y gate. Promote the
resting state to text-danger-primary (red-800) which passes, keeping the same
token used in badge, banner and field for danger text.
The menu rows still composed their layout in the components tier with raw
className spans (label, shortcut, trailing check) and baked a sized icon into
the submenu trigger, so the components tier carried styling and the checkbox/
radio indicators baked in a default Check child.

Add the missing item-region parts as ui parts, each rendering one element with
its cva in ui/context-menu/variants.ts:

- ContextMenuItemIcon — leading icon slot (node-slot, sized to the row --node-size)
- ContextMenuItemLabel — the growing label
- ContextMenuItemShortcut — trailing keyboard-shortcut text
- ContextMenuItemIndicator — trailing single-select check slot (node-slot)
- ContextMenuSubmenuTriggerIndicator — submenu caret slot (node-slot, RTL-mirrored)

The checkbox/radio item indicators no longer bake a Check child or a size
literal; they size whatever icon is passed via the shared node-slot class. The
components tier now only composes these parts, so it holds no className, cx or
cva. Stories compose the new parts and register them as subcomponents.
@lifeiscontent lifeiscontent merged commit 5b3df06 into main Jun 24, 2026
2 of 3 checks passed
@lifeiscontent lifeiscontent deleted the align/context-menu branch June 24, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Right-click menu: what should always look the same, and what should be adjustable?

1 participant