fix(ui): user-menu hover, tooltip, and dismiss behavior (MAPPS-384) #446

Merged
longjacksonle merged 3 commits from fix/MAPPS-384-user-menu-hover-dismiss into main 2026-07-28 19:22:09 +02:00

Fixes MAPPS-384.

Problem

Two defects on the top-bar user-profile icon:

  • No hover highlight and no tooltip, unlike the sibling top-bar icons (theme picker, notification bell) which have both.
  • The dropdown never dismissed on an outside click or on navigation: it stayed mounted even after moving to another screen.

Fix

  • Trigger now uses the sibling top-bar icon treatment (MAPPS-359 surface tokens): p-2 rounded-full text-subtle hover:text-content hover:bg-surface-2, plus title + aria_label "User menu" and aria_expanded/aria_haspopup. This gives the hover highlight and the hover/focus tooltip.
  • Outside-click dismissal via a full-screen backdrop div (fixed inset-0 z-10), the same pattern GlobalSearch uses (MAPPS-346). It sits below the dropdown (z-10 < z-20) so menu entries stay clickable, and it unmounts with the dropdown so there is no document-level listener that could leak.
  • Route-change dismissal via use_effect(use_reactive!(|route| ...)). UserMenu lives in the persistent AppShell (MAPPS-366), so a route change does not re-mount it; the effect reacts to the route and closes the menu.
  • The identical fix is applied to the client-portal twin PortalUserMenu, which had the same two defects.

Design note

The ticket suggested adopting IconButton. I chose the sibling top-bar convention instead: IconButton's rounded-md + blue focus-ring base would visually diverge from the rounded-full top-bar icons, which is the exact drift the ticket wants to remove. Matching the siblings is what the primary acceptance criterion ("matching the other top-bar icons") actually asks for. The AC's "document-level handler is removed when the menu closes (no listener leak)" is satisfied in spirit and better: the backdrop has no listener at all, so nothing can leak.

Acceptance criteria

  • Profile icon highlights on hover, matching the other top-bar icons.
  • Profile icon shows a tooltip on hover/focus (title="User menu").
  • Dropdown closes on an outside click.
  • Dropdown closes on navigation to another screen.
  • No listener leak (backdrop unmounts with the dropdown; no document-level handler exists).

Verification

  • cargo fmt --all --check: clean.
  • cargo clippy --all-targets -- -D warnings on the CI toolchain (1.94.1): clean.
  • cargo check --target wasm32-unknown-unknown: passes.
  • cargo test --lib: 223 passed.
  • Behavior observed against the reference sibling icons (hover, tooltip, outside-click, route-change). No unit test added: Dioxus DOM-interaction behavior is not covered by the --lib harness here.
Fixes MAPPS-384. ## Problem Two defects on the top-bar user-profile icon: - No hover highlight and no tooltip, unlike the sibling top-bar icons (theme picker, notification bell) which have both. - The dropdown never dismissed on an outside click or on navigation: it stayed mounted even after moving to another screen. ## Fix - Trigger now uses the sibling top-bar icon treatment (MAPPS-359 surface tokens): `p-2 rounded-full text-subtle hover:text-content hover:bg-surface-2`, plus `title` + `aria_label` "User menu" and `aria_expanded`/`aria_haspopup`. This gives the hover highlight and the hover/focus tooltip. - Outside-click dismissal via a full-screen backdrop `div` (`fixed inset-0 z-10`), the same pattern GlobalSearch uses (MAPPS-346). It sits below the dropdown (z-10 < z-20) so menu entries stay clickable, and it unmounts with the dropdown so there is no document-level listener that could leak. - Route-change dismissal via `use_effect(use_reactive!(|route| ...))`. UserMenu lives in the persistent AppShell (MAPPS-366), so a route change does not re-mount it; the effect reacts to the route and closes the menu. - The identical fix is applied to the client-portal twin `PortalUserMenu`, which had the same two defects. ## Design note The ticket suggested adopting `IconButton`. I chose the sibling top-bar convention instead: `IconButton`'s `rounded-md` + blue focus-ring base would visually diverge from the `rounded-full` top-bar icons, which is the exact drift the ticket wants to remove. Matching the siblings is what the primary acceptance criterion ("matching the other top-bar icons") actually asks for. The AC's "document-level handler is removed when the menu closes (no listener leak)" is satisfied in spirit and better: the backdrop has no listener at all, so nothing can leak. ## Acceptance criteria - [x] Profile icon highlights on hover, matching the other top-bar icons. - [x] Profile icon shows a tooltip on hover/focus (`title="User menu"`). - [x] Dropdown closes on an outside click. - [x] Dropdown closes on navigation to another screen. - [x] No listener leak (backdrop unmounts with the dropdown; no document-level handler exists). ## Verification - `cargo fmt --all --check`: clean. - `cargo clippy --all-targets -- -D warnings` on the CI toolchain (1.94.1): clean. - `cargo check --target wasm32-unknown-unknown`: passes. - `cargo test --lib`: 223 passed. - Behavior observed against the reference sibling icons (hover, tooltip, outside-click, route-change). No unit test added: Dioxus DOM-interaction behavior is not covered by the `--lib` harness here.
fix(ui): user-menu hover, tooltip, and dismiss behavior (MAPPS-384)
Some checks failed
Check / fmt + clippy + tests (pull_request) Failing after 7s
274b3684d6
The top-bar user-profile icon did not highlight on hover and had no tooltip, unlike its sibling top-bar icons, and its dropdown never dismissed on an outside click or on navigation (it stayed mounted across screens).

Give the trigger the same surface-token hover treatment and a tooltip as the sibling top-bar icons (theme picker / notification bell, MAPPS-359): `p-2 rounded-full text-subtle hover:text-content hover:bg-surface-2` plus `title` + `aria_label` "User menu", and `aria_expanded`/`aria_haspopup`. Chose the sibling convention over `IconButton` deliberately: its `rounded-md` + blue focus-ring base would visually diverge from the `rounded-full` top-bar icons, which is the exact drift being fixed.

Dismiss the dropdown on an outside click with a full-screen backdrop div (same pattern as GlobalSearch, MAPPS-346), placed below the menu (z-10 < z-20) so entries stay clickable. It unmounts with the dropdown, so there is no document-level listener that could leak.

Dismiss on navigation with `use_effect(use_reactive!(|route| ...))`. UserMenu lives in the persistent AppShell (MAPPS-366) and is not re-mounted on a route change, so it needs to react to the route explicitly.

Applied the identical fix to the client-portal twin `PortalUserMenu`, which had the same defects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEnKozf9UzwgoT1RUELYpb
ci: install gcc on the openSUSE runner so cargo can link
Some checks failed
Check / fmt + clippy + tests (pull_request) Failing after 3s
e76e08f0de
The RUNS_ON_OPENSUSE_BASE_LATEST base image stopped shipping a C compiler, so the Check job now fails at `error: linker cc not found` while compiling the build script (before it ever reaches fmt/clippy/tests). rustc drives linking through `cc`, which the `gcc` package provides. Add a step to install it, mirroring the defensive `zypper install` pattern e2e.yml already documents for trimmed base images.

This is a runner-image regression, not a code change: it affects every Rust CI job on this runner (bunyip's check.yml is broken the same way). The durable fix is restoring the toolchain in the base image; this step unblocks CI in the meantime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEnKozf9UzwgoT1RUELYpb
ci: run the gcc install through sudo
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m37s
Create release / Create release from merged PR (pull_request) Has been skipped
f1f323e2fd
The runner user is unprivileged, so the bare `zypper install gcc` step failed immediately with `Root privileges are required to run this command` (exit 5). Prefix it with sudo, which the runner provides passwordless.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEnKozf9UzwgoT1RUELYpb
longjacksonle deleted branch fix/MAPPS-384-user-menu-hover-dismiss 2026-07-28 19:22:09 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!446
No description provided.