feat: migrate options, selects, menus and member lists to `Item`
Proposed changes (including videos or screenshots)
ARCH-2496
Migrates Option, Options, Select and Menu to the Item components, and applies the result across Rocket.Chat in the same PR, so every list built on them changes together.
Fuselage
Renders Option, Options, Select and Menu with the Item family from RocketChat/fuselage#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 theirItemTitleand described by a labelledItemIcon, as anItemLinkis. - New
focusVisibleprop shows the focus ring for focus moved by a script, such as a menu's. The browser's:focus-visiblealso matches on hover there.
Menu
MenuItem, submenu triggers andMenuSectionrender asItem,ItemGroupHeaderandItemGroup.
Option, Options and Select
Optionrenders as anItemrow. TheOption*parts, and theirMenuItem*aliases, render as the matchingItempart:ItemMedia,ItemIcon,ItemContent,ItemTitle,ItemDescriptionandItemActions.OptionHeaderandOptionTitlerender asItemGroupHeader.Optionsdividers render asItemDivider, andSelectsections asItemGroupHeaderandItemGroup.- New
iconLabelprop onOptionnames what itsiconconveys. Without it, the icon stays decorative.
Fixes
Optionsscrolls to the keyboard cursor whenrenderItemrenders anItem.- The
CheckOptioncheckbox is hidden from assistive technology and made inert, so selection is announced once, througharia-selected. OptionSkeletonplaceholder rows are hidden from assistive technology.- The
Optionsempty state is a disabled option, so the listbox only contains options.
Rocket.Chat
- Autocompletes (rooms, users, teams, departments, agents, contacts, peers) and the UiKit user and channel selects use
Itemparts. Avatars useITEM_MEDIA_SIZE, and status or room-type icons are named withiconLabel. - Room members, banned users and team channels render as
Itemrows. Member groups use anItemGroupHeaderinstead ofMembersListDivider, and row menus mount throughuseDeferredMenuMount. - Call members, participants and the chat access modal render as
Itemrows. The members panel groups "In call" and "Not in the call" as named lists. - The category menu's "Create new" item uses
GenericMenuItem.
Issue(s)
Replaces RocketChat/fuselage#2310, and takes over the options, selects, menus and member-list parts of #42520.
Steps to test or reproduce
yarn workspace @rocket.chat/fuselage storybook: theMenu,Option,OptionsandSelectstories.- In the app: room/user/team autocompletes, a UiKit modal with user and channel selects, the room members, banned users and team channels contextual bars, the user menu, and a video conference's members panel. Check with a keyboard and a screen reader.
Further comments
Part of the Item migration, split by area (fuselage change + its application together, so each area looks finished when it merges):
- this PR: options, selects, menus and member lists (merge first)
- #42819: sidebar and side panel
- #42820: composer popups and
ItemMeta truncate(stacked on this PR) - #42832: lists that only use
Item(independent)
Bottom commits from other PRs, dropped on rebase once they land: #42829 (hidden-actions gap fix), #42831 (useDeferredMenuMount in ui-client), #42830 (ITEM_MEDIA_SIZE).
Commit order: fuselage commits first (replayed from the Fuselage PR), then test: adapt app tests… with the adaptations the fuselage change forces on its own (the CheckOption checkbox is hidden from assistive technology; MenuItemContent wraps its text in an ItemTitle), then the app commits.
Why Options and Menu are in one PR: the public MenuItem* parts re-export the Option* parts, so migrating one without the other breaks every menu.
Jest snapshots and the visual-regression baselines (arm64 image) were regenerated here. fuselage.api.md isn't part of this repository, so those commits were dropped.
🤖 Generated with Claude Code