feat: migrate composer popups to `Item` and add `ItemMeta` `truncate`

Merged
JU
1d ago
+874-395

Proposed changes (including videos or screenshots)

ARCH-2496
Adds truncate to ItemMeta, and renders the composer popups with Item.

It also makes the composer popups behave the same as menus when moving through them with a keyboard or a pointer. One row is active at a time, the pointer and the arrow keys both move it, and the focus ring shows while the keyboard drives the list:

Opens with an active rowPointer moves the active rowFocus ring
Menuthe first oneyeskeyboard only
Composer popupsthe first one, which Enter and Tab pickyeswhile typing or using the arrow keys

The composer rings its first row on open because the active row's background alone has a 1.15:1 contrast against the popup in the dark theme, below the 3:1 that WCAG 1.4.11 asks for a state indicator.

Screenshots

Captured against open.rocket.chat with yarn dev:next, develop above and this PR below.

Composer popup on open: the first row, which Enter and Tab pick, shows its ring Composer popup on open, develop vs this PR
Composer popup after the arrow keys and then the pointer: develop highlights two rows, this PR only the one under the pointer Composer popup with keyboard then pointer, develop vs this PR
Composer popup in forced colors: develop shows no indicator on the active row, this PR shows its outline Composer popup in forced colors, develop vs this PR

Fuselage

  • Adds a truncate prop to ItemMeta. With it, long trailing meta text truncates with an ellipsis instead of pushing the title.
  • The Item focus ring shows in forced colors (Windows high contrast), which drop the box-shadow it is drawn with.

Rocket.Chat

  • The composer's mention, room, emoji, canned response and slash command popups are listboxes of Item rows. Slash command parameters truncate with ItemMeta truncate, and slash command preview rows show keyboard focus with focusVisible.
  • The composer popups keep one active row: the row under the pointer becomes the one Enter and Tab pick, and the message field names the listbox it controls with aria-controls and aria-autocomplete.

Issue(s)

Replaces RocketChat/fuselage#2311, and takes over the composer popup part of #42520.

Steps to test or reproduce

  • In a room's composer, type @, #, : and /, pick a canned response in an omnichannel room, and use a slash command with a preview. Move through the popup with the arrow keys and with the pointer: one row is highlighted at a time, and Enter picks that row.
  • Check it with a screen reader, and the focus ring with Windows high contrast or Chrome's forced colors emulation.

Further comments

Based on develop. #42819 (sidebar and side panel) is stacked on this PR and reuses useActiveDescendant for the room search, so this one merges first.

Commits are ordered so the fix and the enabling refactor sit below the feature and can be extracted:

  1. regression(fuselage): the Item focus ring in forced colors
  2. refactor: useActiveDescendant, the shared state of listboxes driven from a combobox
  3. the ItemMeta truncate and composer popup commits
  4. fix: the composer popups keep one active row (needs the composer Item rows from step 3)

Part of the Item migration, split by area:

  • #42818: options, selects, menus and member lists (merged)
  • this PR: composer popups, ItemMeta truncate, and keyboard and pointer behaviour of the composer popups (merge first)
  • #42819: sidebar, side panel, and keyboard and pointer behaviour of the room search (stacked on this PR)
  • #42832: lists that only use Item (independent)

The popup changes from role=menu to listbox, so the composer e2e locators find rows as listbox options.

Summary by CodeRabbit

  • Accessibility
    • Composer suggestions are presented as labeled option lists, with screen-reader associations and disabled states announced.
    • Keyboard navigation displays a focus indicator, while pointer movement updates the active suggestion.
  • Usability
    • Enter and Tab no longer select disabled suggestions.
    • Long slash-command parameters and descriptions are truncated to keep command names visible.