fix(a11y): name the headless modals too — Seen by, source, file viewers (#185)
CI / Build & Quality Checks (push) Successful in 1m41s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 11s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 13m14s
CI / Build & Quality Checks (push) Successful in 1m41s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 11s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 13m14s
Follow-up to ce8ed89f for the modals that have no visible heading to
point at: the three "Seen by" reader lists (read-receipt pill, "is
following", message menu), View source, the text/PDF file viewers, the
room-card join error and the user-profile modal get role="dialog",
aria-modal and an aria-label; their traps move focus in (fallbackFocus
on the dialog) where the trap is local.
Verified: the receipt pill opens a "Seen by" dialog with focus on
Close; Escape closes it and focus returns to the pill.
The remaining unnamed Modal/Dialog uses are startup/loading and error
screens (config, feature check, spec versions, client root, password
reset) and wrappers around components that carry their own role (image
viewer).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ce8ed89fdc
commit
7f2e93d389
@@ -71,7 +71,15 @@ function UserRoomProfileContextMenu({ state }: { state: UserRoomProfileState })
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
<FocusTrap focusTrapOptions={focusTrapOptions}>
|
||||
<Modal size="500" style={MOBILE_FULLSCREEN}>
|
||||
<Modal
|
||||
id="user-profile-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="User profile"
|
||||
tabIndex={-1}
|
||||
size="500"
|
||||
style={MOBILE_FULLSCREEN}
|
||||
>
|
||||
{/* Full-screen covers the backdrop (no tap-to-dismiss) and the
|
||||
profile has no self-close, so provide an explicit close. */}
|
||||
<Header size="600" style={{ flexShrink: 0, paddingRight: config.space.S200 }}>
|
||||
|
||||
@@ -105,13 +105,18 @@ export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: Rea
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
fallbackFocus: '#file-viewer-2-dialog',
|
||||
onDeactivate: () => setTextViewer(false),
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal
|
||||
id="file-viewer-2-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`File: ${body}`}
|
||||
tabIndex={-1}
|
||||
className={ModalWide}
|
||||
size="500"
|
||||
onContextMenu={(evt: React.MouseEvent) => evt.stopPropagation()}
|
||||
@@ -194,13 +199,18 @@ export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: Read
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
fallbackFocus: '#file-viewer-1-dialog',
|
||||
onDeactivate: () => setPdfViewer(false),
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal
|
||||
id="file-viewer-1-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`File: ${body}`}
|
||||
tabIndex={-1}
|
||||
className={ModalWide}
|
||||
size="500"
|
||||
onContextMenu={(evt: React.MouseEvent) => evt.stopPropagation()}
|
||||
|
||||
@@ -76,13 +76,22 @@ export function ReadReceiptAvatars({
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
fallbackFocus: '#readreceipt-seenby-dialog',
|
||||
onDeactivate: () => setOpen(false),
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal variant="Surface" size="300" style={modalStyle}>
|
||||
<Modal
|
||||
id="readreceipt-seenby-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Seen by"
|
||||
tabIndex={-1}
|
||||
variant="Surface"
|
||||
size="300"
|
||||
style={modalStyle}
|
||||
>
|
||||
<EventReaders room={room} eventId={eventId} requestClose={() => setOpen(false)} />
|
||||
</Modal>
|
||||
</FocusTrap>
|
||||
|
||||
@@ -105,13 +105,20 @@ function ErrorDialog({
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
fallbackFocus: '#roomcard-error-dialog',
|
||||
clickOutsideDeactivates: true,
|
||||
onDeactivate: closeError,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Dialog variant="Surface">
|
||||
<Dialog
|
||||
id="roomcard-error-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={title}
|
||||
tabIndex={-1}
|
||||
variant="Surface"
|
||||
>
|
||||
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
|
||||
<Box direction="Column" gap="100">
|
||||
<Text>{title}</Text>
|
||||
|
||||
@@ -51,13 +51,22 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>(
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
fallbackFocus: '#following-seenby-dialog',
|
||||
onDeactivate: () => setOpen(false),
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal variant="Surface" size="300" style={modalStyle}>
|
||||
<Modal
|
||||
id="following-seenby-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Seen by"
|
||||
tabIndex={-1}
|
||||
variant="Surface"
|
||||
size="300"
|
||||
style={modalStyle}
|
||||
>
|
||||
<EventReaders room={room} eventId={eventId} requestClose={() => setOpen(false)} />
|
||||
</Modal>
|
||||
</FocusTrap>
|
||||
|
||||
@@ -290,13 +290,22 @@ export const MessageReadReceiptItem = as<
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
fallbackFocus: '#message-seenby-dialog',
|
||||
onDeactivate: handleClose,
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal variant="Surface" size="300" style={modalStyle}>
|
||||
<Modal
|
||||
id="message-seenby-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Seen by"
|
||||
tabIndex={-1}
|
||||
variant="Surface"
|
||||
size="300"
|
||||
style={modalStyle}
|
||||
>
|
||||
<EventReaders room={room} eventId={eventId} requestClose={handleClose} />
|
||||
</Modal>
|
||||
</FocusTrap>
|
||||
@@ -368,13 +377,21 @@ export const MessageSourceCodeItem = as<
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
fallbackFocus: '#message-source-dialog',
|
||||
onDeactivate: handleClose,
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal variant="Surface" size="500">
|
||||
<Modal
|
||||
id="message-source-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="View source"
|
||||
tabIndex={-1}
|
||||
variant="Surface"
|
||||
size="500"
|
||||
>
|
||||
<TextViewer
|
||||
name="Source Code"
|
||||
langName="json"
|
||||
|
||||
Reference in New Issue
Block a user