fix(frontend): add focus management to wallet modal - #1349
Merged
K1NGD4VID merged 1 commit intoAug 30, 2026
Conversation
WalletModal rolled its own Escape handler and body-scroll lock but had no focus trap and no focus restoration, so Tab walked straight out of the open dialog into the page behind it and closing the modal dropped focus on document.body. Keyboard and screen-reader users lost their place. Migrate it to the shared useModalDialog hook already used by TopUpModal, CancelConfirmModal, StreamDetailsModal, StreamCreationWizard and the navbar mobile menu: dialogRef goes on the .wallet-modal panel, and isCloseDisabled is wired to isConnecting so Escape stays inert while a connection is in flight, matching the disabled close button and the guarded backdrop click. The bespoke Escape listener and scroll-lock effect are removed as the hook covers both. Wallet selection, connection, error handling, props and styling are untouched. Adds WalletModal.test.tsx covering focus entry, Tab and Shift+Tab wrapping at both boundaries, focus restoration to the trigger on close, scroll lock and release, and Escape being ignored while connecting. The three focus tests fail against the pre-migration component. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1257
Summary
frontend/src/components/wallet/WalletModal.tsximplemented its own Escape handler and body-scroll lock but had no focus trap and no focus restoration. Tab walked straight out of the open dialog into the page behind it, and closing the modal dropped focus ondocument.body.This PR migrates the modal to the repository's existing
useModalDialoghook, the same one already used byTopUpModal,CancelConfirmModal,StreamDetailsModal,StreamCreationWizardand the navbar'sMobileMenu. No new focus-trap mechanism was written.Accessibility changes
useModalDialog({ onClose, isCloseDisabled: isConnecting })replaces the bespokeuseCallback+document.addEventListener("keydown", ...)Escape handler and the standalone body-scroll-lock effect.dialogRefis attached to the inner.wallet-modalpanel, matching howTopUpModalandCancelConfirmModalplace it (role="dialog"stays on the backdrop).isCloseDisabledis wired toisConnecting, preserving the existing rule that the modal cannot be dismissed mid-connection. That already matched the disabled close button and the guarded backdrop click.Wallet selection,
connect/clearErrorcalls, the Freighter detection poll, the install-link fallback, error banner, props, state and styling are untouched.Files changed
frontend/src/components/wallet/WalletModal.tsx(+12 / -25)frontend/src/components/wallet/WalletModal.test.tsx(new)Tests run and actual results
New
WalletModal.test.tsxcovers the acceptance criteria directly, using@testing-library/user-eventagainst a harness with a real trigger button:document.body, and wraps last -> firststatus === "connecting"All commands run from
frontend/:Verification that the tests catch the actual bug: with
WalletModal.tsxstashed back to its pre-migration state, tests 1, 2 and 3 fail (3 failed | 3 passed). They pass with the migration applied.Acceptance criteria
WalletModalusesuseModalDialogisCloseDisabled)useModalDialogandwallet-entrytests still pass; full frontend suite green (239 tests)next buildpassPre-existing issues found, not fixed here
Out of scope for this PR, recorded rather than touched:
npx tsc --noEmitreports 27 errors, all in existing test files (src/lib/api/streams.test.ts,src/hooks/useIncomingStreams.test.tsx,src/app/streams/create/__tests__/create-stream-content.test.tsx). They predate this branch;next builddoes not type-check those files, so CI is unaffected.src/components/dashboard/ActivityHistory.tsx:26emits areact-hooks/incompatible-librarylint warning.🤖 Generated with Claude Code