Skip to content

UI kit: shared Menu component for context and dropdown menus; ratchets on raw <button> and hand-built dialogs #914

Open

Description

@smart-ssh-planner

Goal

Context and dropdown menus share one accessible implementation (role menu, arrow keys, Escape, placement), and new raw <button> elements or hand-built modal dialogs outside the UI kit only appear with a reason.

Context

  • Menus are implemented locally: FileEntryMenu (components/FileBrowserPanel.tsx:2864), ColumnsMenu (:3050), DirectoryMenu (~:3463), and components/NewSessionMenu.tsx. Only the placement hook hooks/useMenuPlacement.ts (+ menuPlacement.ts) is shared (3 users).
  • 72 raw <button> in src/components production code (27 in FileBrowserPanel.tsx, 6 NarrowHeaderNav.tsx, 5 ChatPanel.tsx, 4 TerminalKeyRow.tsx, …) next to 229 <Button>.
  • Modals outside components/ui/DialogFrame.tsx: HostKeyDialog.tsx, SettingsScreen.tsx, NarrowHeaderNav.tsx (each with its own useDialogFocusTrap), AppHeader.tsx (fixed inset-0 overlay), and the server form's key-import prompt (moved to DialogFrame by Server form: the key-import prompt uses DialogFrame (focus trap, Escape, labelled title) #852).
  • Paths are relative to apps/smart-ssh-community/frontend/src/; line numbers will have moved if the file browser was already split into sub-components.

Requirements

  • components/ui/Menu.tsx: Menu (positioned at a point or anchored to an element, uses useMenuPlacement), MenuItem (button with role="menuitem", optional icon, shortcut hint, disabled, danger), MenuSeparator. Keyboard: arrow up/down/Home/End move focus, Enter/Space activate, Escape closes and returns focus to the opener, Tab closes. Click outside closes. Export from components/ui/index.ts; document in docs/ui-guidelines.md.
  • Move the four menus onto it; items, texts and actions unchanged.
  • Dialog guard: production .tsx files may contain <dialog or fixed inset-0 only in components/ui/ and components/ModalBackdrop.tsx, plus an allowlist (file + one-line reason) that may only shrink.
  • Guard test: count raw <button per production .tsx file outside components/ui/; compare with a checked-in JSON (file → count); a count may only fall; a new file with raw buttons needs an entry with a reason. Error message points to Button/MenuItem.

Acceptance Criteria

  • Menu tests: roles, arrow-key navigation, Escape returns focus to the opener, disabled items are skipped, click outside closes.
  • The four menus use Menu; their existing tests pass (selector updates allowed).
  • Raw <button> count in FileBrowserPanel.tsx and NewSessionMenu.tsx drops (menu items replaced); the ratchet test passes and fails when a raw button is added to a fixture.
  • The dialog guard passes and fails for a fixture with <dialog outside the allowed places.
  • Playwright accessibility spec passes; npm run lint, type-check, npm test pass.

Technical Notes

  • Frontend only. If the file browser decomposition has already moved the menus into components/fileBrowser/, migrate them there.

Spec impact: none

Related Work

Activity

  1. added this to the 0.8.0 milestone on Oct 26, 2026
  2. 5 remaining items

  3. marked this as blocked by 15 issues on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    accessibilityBarrier affecting people with disabilitiesfrontendFrontend (React/TypeScript)

    Type

    Fields

    Priority

    P3 - Low

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions