feat(fuselage): Migrate `Option`, `Options`, `Select` and `Menu` to `Item` components

Draft
JU
7d ago
+3279-381

Proposed changes (including videos or screenshots)

Renders Option, Options, Select and Menu with the Item family from #2281. Rows share one layout and one set of states, and the public rcx-option classes stay in place.

Item

  • Menu-item rows (role="menuitem*") are named by their ItemTitle and described by a labelled ItemIcon, as an ItemLink is.
  • New focusVisible prop shows the focus ring for focus moved by a script, such as a menu's. The browser's :focus-visible also matches on hover there.

Menu

  • MenuItem, submenu triggers and MenuSection render as Item, ItemGroupHeader and ItemGroup.

Option, Options and Select

  • Option renders as an Item row. The Option* parts, and their MenuItem* aliases, render as the matching Item part: ItemMedia, ItemIcon, ItemContent, ItemTitle, ItemDescription and ItemActions.
  • OptionHeader and OptionTitle render as ItemGroupHeader. Options dividers render as ItemDivider, and Select sections as ItemGroupHeader and ItemGroup.
  • New iconLabel prop on Option names what its icon conveys. Without it, the icon stays decorative.

Fixes

  • Options scrolls to the keyboard cursor when renderItem renders an Item.
  • The CheckOption checkbox is hidden from assistive technology and made inert, so selection is announced once, through aria-selected.
  • OptionSkeleton placeholder rows are hidden from assistive technology.
  • The Options empty state is a disabled option, so the listbox only contains options.

Issue(s)

Related: RocketChat/Rocket.Chat#42520, which applies the Item components in Rocket.Chat.

Further comments

Part of the Item migration, split into three independent PRs off main:

  • #2295: Sidebar and Sidepanel
  • this PR: Options, Select and Menu
  • #2311: ItemMeta truncate

Why Options and Menu are in one PR: the public MenuItem* parts re-export the Option* parts.

  • Migrating Option alone would put Item parts inside the old MenuOption rows. Those parts are only styled inside .rcx-item, and the rcx-option__wrapper rule that menus still use would be gone, so every menu would break.
  • Migrating Menu alone would leave old Option content inside Item rows.

Merge order: please merge this PR first. Adding focusVisible and the ItemTitle ids changes generated React ids, so the other two PRs only need a rebase and yarn jest -u once this lands.

Commits are ordered so each one builds and passes on its own: Item and Menu first, then Option/Options/Select, then the alias tests. Each component change has jest-axe coverage and updated snapshots. fuselage.api.md and the visual-regression snapshots were regenerated for this branch.

šŸ¤– Generated with Claude Code