Compare commits

...
3 Commits
Author SHA1 Message Date
Lotus CIandClaude Opus 5.5 103c6f4624 fix(a11y): closing a header/sidebar menu returns focus to its button
CI / Build & Quality Checks (push) Successful in 1m53s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 6s
CI / Trigger Desktop Build (push) Successful in 5s
CI / Playwright smoke (e2e) (push) Successful in 11m15s
16 menus (room header, Home/Direct/Space sidebar tabs, create tab, lobby,
room-nav item, history visibility, client root) set
returnFocusOnDeactivate: false, so pressing Escape dropped focus to
<body> and a keyboard user had to start over from the top of the page.
That option was there so focus wouldn't be pulled back to the menu button
when an item opens a dialog; since ce8ed89f dialogs move focus into
themselves (their initial focus runs after the menu's return), so it's no
longer needed. The autocomplete menu (focus must stay in the editor) and
the two menus whose opener disappears are left as they were.

Checked: room header menu → Escape → focus on "More options" (was
<body>); menu → Leave Room → focus inside the Leave Room dialog on
Cancel, same as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-24 13:16:12 -04:00
Lotus CIandClaude Opus 5.5 7f2e93d389 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
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
2026-09-24 13:12:13 -04:00
Lotus CIandClaude Opus 5.5 ce8ed89fdc fix(a11y): 36 modal dialogs announce themselves and take focus (#185)
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
Most modals rendered a folds Dialog/Modal with no role and no name, and
their focus traps used `initialFocus: false`, so focus stayed behind the
modal and a screen reader never announced it.

- 32 dialogs with a visible heading: role="dialog", aria-modal,
  aria-labelledby → the heading (given an id), tabIndex=-1.
- 4 dialogs that already had a name (Leave Room, room topic viewer,
  server ACL, room-nav prompt): role + aria-modal.
- Their focus traps drop `initialFocus: false` for focus-trap's default
  (keep an already-focused autoFocus field, else the first tabbable
  element) with the dialog itself as fallbackFocus, so a dialog without
  a tabbable node can't crash the trap. Traps that live in a parent
  (UIA stages, Logout, Forward, Invite) get the semantics only.
- The file drop overlay is deliberately left alone (not a dialog).

Checked at runtime: Join with Address, Delete Message, Report Message,
Leave Room and Logout open as named dialogs with focus inside and close
with Escape (Tab first when a text field has focus — the shared
stopPropagation keeps Escape from discarding typed text, by design).
The axe e2e spec (6 tests) passes; eslint warnings unchanged (46).
17 modals with no heading (image/file viewers, loading screens) remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-24 13:06:10 -04:00
48 changed files with 423 additions and 113 deletions
+11 -3
View File
@@ -365,15 +365,23 @@ export function DeviceVerification({ request, onExit }: DeviceVerificationProps)
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#deviceverification-dialog-1',
clickOutsideDeactivates: false,
escapeDeactivates: false,
}}
>
<Dialog variant="Surface" style={modalStyle}>
<Dialog
id="deviceverification-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="deviceverification-dialog-1-title"
tabIndex={-1}
variant="Surface"
style={modalStyle}
>
<Header style={DialogHeaderStyles} variant="Surface" size="500">
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="deviceverification-dialog-1-title" as="h2" size="H4">
Device Verification
</Text>
</Box>
+20 -4
View File
@@ -293,7 +293,15 @@ export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerifica
const modalStyle = useModalStyle(480);
return (
<Dialog ref={ref} style={modalStyle}>
<Dialog
id="deviceverificationsetup-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="deviceverificationsetup-dialog-1-title"
tabIndex={-1}
ref={ref}
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -303,7 +311,7 @@ export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerifica
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="deviceverificationsetup-dialog-1-title" as="h2" size="H4">
Setup Device Verification
</Text>
</Box>
@@ -331,7 +339,15 @@ export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerifica
const modalStyle = useModalStyle(480);
return (
<Dialog ref={ref} style={modalStyle}>
<Dialog
id="deviceverificationsetup-dialog-2"
role="dialog"
aria-modal="true"
aria-labelledby="deviceverificationsetup-dialog-2-title"
tabIndex={-1}
ref={ref}
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -341,7 +357,7 @@ export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerifica
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="deviceverificationsetup-dialog-2-title" as="h2" size="H4">
Reset Device Verification
</Text>
</Box>
+11 -2
View File
@@ -52,7 +52,16 @@ export const LogoutDialog = forwardRef<HTMLDivElement, LogoutDialogProps>(
const ongoingLogout = logoutState.status === AsyncStatus.Loading;
return (
<Dialog variant="Surface" ref={ref} style={modalStyle}>
<Dialog
id="logoutdialog-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="logoutdialog-dialog-1-title"
tabIndex={-1}
variant="Surface"
ref={ref}
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -62,7 +71,7 @@ export const LogoutDialog = forwardRef<HTMLDivElement, LogoutDialogProps>(
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="logoutdialog-dialog-1-title" as="h2" size="H4">
Logout
</Text>
</Box>
@@ -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 }}>
@@ -189,14 +189,21 @@ export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
escapeDeactivates: stopPropagation,
}}
>
<Dialog style={modalStyle}>
<Dialog
id="inviteuserprompt-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="inviteuserprompt-dialog-1-title"
tabIndex={-1}
style={modalStyle}
>
<Box grow="Yes" direction="Column">
<Header
size="500"
style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
>
<Box grow="Yes">
<Text size="H4" truncate>
<Text id="inviteuserprompt-dialog-1-title" size="H4" truncate>
{t('Organisms.InviteUser.invite')}
</Text>
</Box>
@@ -67,13 +67,21 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#joinaddressprompt-dialog-1',
onDeactivate: onCancel,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" style={modalStyle}>
<Dialog
id="joinaddressprompt-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="joinaddressprompt-dialog-1-title"
tabIndex={-1}
variant="Surface"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -82,7 +90,7 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="joinaddressprompt-dialog-1-title" as="h2" size="H4">
Join with Address
</Text>
</Box>
@@ -52,13 +52,21 @@ export function LeaveRoomPrompt({ roomId, onDone, onCancel }: LeaveRoomPromptPro
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#leaveroomprompt-dialog',
onDeactivate: onCancel,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" aria-labelledby="leave-room-dialog-title" style={modalStyle}>
<Dialog
id="leaveroomprompt-dialog"
role="dialog"
aria-modal="true"
tabIndex={-1}
variant="Surface"
aria-labelledby="leave-room-dialog-title"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -52,13 +52,21 @@ export function LeaveSpacePrompt({ roomId, onDone, onCancel }: LeaveSpacePromptP
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#leavespaceprompt-dialog-1',
onDeactivate: onCancel,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" style={modalStyle}>
<Dialog
id="leavespaceprompt-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="leavespaceprompt-dialog-1-title"
tabIndex={-1}
variant="Surface"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -68,7 +76,7 @@ export function LeaveSpacePrompt({ roomId, onDone, onCancel }: LeaveSpacePromptP
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="leavespaceprompt-dialog-1-title" as="h2" size="H4">
Leave Space
</Text>
</Box>
@@ -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>
+9 -2
View File
@@ -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>
@@ -26,6 +26,10 @@ export const RoomTopicViewer = as<
return (
<Modal
id="roomtopicviewer-dialog"
role="dialog"
aria-modal="true"
tabIndex={-1}
size="300"
flexHeight
className={classNames(css.ModalFlex, className)}
@@ -59,13 +59,21 @@ export function SuspiciousLink({ href, safety, anchorProps, children }: Suspicio
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#suspiciouslink-dialog-1',
onDeactivate: () => setOpen(false),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" style={{ maxWidth: 440 }}>
<Dialog
id="suspiciouslink-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="suspiciouslink-dialog-1-title"
tabIndex={-1}
variant="Surface"
style={{ maxWidth: 440 }}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -75,7 +83,7 @@ export function SuspiciousLink({ href, safety, anchorProps, children }: Suspicio
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="suspiciouslink-dialog-1-title" as="h2" size="H4">
This link doesn&apos;t go where it says
</Text>
</Box>
+8 -2
View File
@@ -15,10 +15,16 @@ function DummyErrorDialog({
onCancel: () => void;
}) {
return (
<Dialog>
<Dialog
id="dummystage-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="dummystage-dialog-1-title"
tabIndex={-1}
>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Box direction="Column" gap="100">
<Text as="h2" size="H4">
<Text id="dummystage-dialog-1-title" as="h2" size="H4">
{title}
</Text>
<Text>{message}</Text>
+16 -4
View File
@@ -28,7 +28,13 @@ function EmailErrorDialog({
};
return (
<Dialog>
<Dialog
id="emailstage-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="emailstage-dialog-1-title"
tabIndex={-1}
>
<Box
as="form"
onSubmit={handleFormSubmit}
@@ -37,7 +43,7 @@ function EmailErrorDialog({
gap="400"
>
<Box direction="Column" gap="100">
<Text as="h2" size="H4">
<Text id="emailstage-dialog-1-title" as="h2" size="H4">
{title}
</Text>
<Text>{message}</Text>
@@ -145,10 +151,16 @@ export function EmailStageDialog({
if (emailTokenState.status === AsyncStatus.Success) {
return (
<Dialog>
<Dialog
id="emailstage-dialog-2"
role="dialog"
aria-modal="true"
aria-labelledby="emailstage-dialog-2-title"
tabIndex={-1}
>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Box direction="Column" gap="100">
<Text as="h2" size="H4">
<Text id="emailstage-dialog-2-title" as="h2" size="H4">
Verification Request Sent
</Text>
<Text>{`Please check your email "${emailTokenState.data.email}" and validate before continuing further.`}</Text>
@@ -36,7 +36,13 @@ export function PasswordStage({
};
return (
<Dialog>
<Dialog
id="passwordstage-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="passwordstage-dialog-1-title"
tabIndex={-1}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -45,7 +51,7 @@ export function PasswordStage({
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="passwordstage-dialog-1-title" as="h2" size="H4">
{t('Organisms.PasswordStage.account_password')}
</Text>
</Box>
@@ -14,10 +14,18 @@ function ReCaptchaErrorDialog({
onCancel: () => void;
}) {
return (
<Dialog>
<Dialog
id="recaptchastage-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="recaptchastage-dialog-1-title"
tabIndex={-1}
>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Box direction="Column" gap="100">
<Text size="H4">{title}</Text>
<Text id="recaptchastage-dialog-1-title" size="H4">
{title}
</Text>
<Text>{message}</Text>
</Box>
<Button variant="Critical" fill="None" outlined onClick={onCancel}>
@@ -26,7 +26,13 @@ function RegistrationTokenErrorDialog({
};
return (
<Dialog>
<Dialog
id="registrationtokenstage-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="registrationtokenstage-dialog-1-title"
tabIndex={-1}
>
<Box
as="form"
onSubmit={handleFormSubmit}
@@ -35,7 +41,7 @@ function RegistrationTokenErrorDialog({
gap="400"
>
<Box direction="Column" gap="100">
<Text as="h2" size="H4">
<Text id="registrationtokenstage-dialog-1-title" as="h2" size="H4">
{title}
</Text>
<Text>{message}</Text>
+8 -2
View File
@@ -45,7 +45,13 @@ export function SSOStage({
}, [ssoWindow, handleSubmit, ssoRedirectURL]);
return (
<Dialog>
<Dialog
id="ssostage-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="ssostage-dialog-1-title"
tabIndex={-1}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -54,7 +60,7 @@ export function SSOStage({
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="ssostage-dialog-1-title" as="h2" size="H4">
SSO Login
</Text>
</Box>
+8 -2
View File
@@ -15,10 +15,16 @@ function TermsErrorDialog({
onCancel: () => void;
}) {
return (
<Dialog>
<Dialog
id="termsstage-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="termsstage-dialog-1-title"
tabIndex={-1}
>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Box direction="Column" gap="100">
<Text as="h2" size="H4">
<Text id="termsstage-dialog-1-title" as="h2" size="H4">
{title}
</Text>
<Text>{message}</Text>
+20 -6
View File
@@ -55,20 +55,27 @@ function SelfDemoteAlert({ power, onCancel, onChange }: SelfDemoteAlertProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#powerchip-dialog-1',
onDeactivate: onCancel,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface">
<Dialog
id="powerchip-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="powerchip-dialog-1-title"
tabIndex={-1}
variant="Surface"
>
<Header
style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
variant="Surface"
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="powerchip-dialog-1-title" as="h2" size="H4">
Self Demotion
</Text>
</Box>
@@ -107,20 +114,27 @@ function SharedPowerAlert({ power, onCancel, onChange }: SharedPowerAlertProps)
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#powerchip-dialog-2',
onDeactivate: onCancel,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface">
<Dialog
id="powerchip-dialog-2"
role="dialog"
aria-modal="true"
aria-labelledby="powerchip-dialog-2-title"
tabIndex={-1}
variant="Surface"
>
<Header
style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
variant="Surface"
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="powerchip-dialog-2-title" as="h2" size="H4">
Shared Power
</Text>
</Box>
+7 -1
View File
@@ -79,13 +79,18 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#userhero-dialog-1',
onDeactivate: () => setViewAvatar(undefined),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Modal
id="userhero-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="userhero-dialog-1-title"
tabIndex={-1}
size="500"
className={ModalMobileFull}
onContextMenu={(evt: React.MouseEvent) => evt.stopPropagation()}
@@ -131,6 +136,7 @@ export function UserHeroName({
<Box grow="Yes" direction="Column" gap="0">
<Box alignItems="Baseline" gap="200" wrap="Wrap">
<Text
id="userhero-dialog-1-title"
size="H4"
className={classNames(BreakWord, LineClamp3)}
title={displayName ?? username}
+11 -3
View File
@@ -185,13 +185,21 @@ export function AddExistingModal({ parentId, space, requestClose }: AddExistingM
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#addexisting-dialog-1',
clickOutsideDeactivates: true,
onDeactivate: requestClose,
escapeDeactivates: stopPropagation,
}}
>
<Modal size="300" style={modalStyle}>
<Modal
id="addexisting-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="addexisting-dialog-1-title"
tabIndex={-1}
size="300"
style={modalStyle}
>
<Box grow="Yes" direction="Column">
<Header
size="500"
@@ -201,7 +209,7 @@ export function AddExistingModal({ parentId, space, requestClose }: AddExistingM
}}
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="addexisting-dialog-1-title" as="h2" size="H4">
Add Existing
</Text>
</Box>
@@ -78,13 +78,21 @@ function ConfirmDialog({ onStart, requestClose }: ConfirmDialogProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#deletemymessages-dialog-1',
onDeactivate: requestClose,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" style={modalStyle}>
<Dialog
id="deletemymessages-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="deletemymessages-dialog-1-title"
tabIndex={-1}
variant="Surface"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -94,7 +102,7 @@ function ConfirmDialog({ onStart, requestClose }: ConfirmDialogProps) {
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="deletemymessages-dialog-1-title" as="h2" size="H4">
Delete all your messages
</Text>
</Box>
@@ -108,13 +108,21 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#roomencryption-dialog-1',
onDeactivate: () => setPrompt(false),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" style={modalStyle}>
<Dialog
id="roomencryption-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="roomencryption-dialog-1-title"
tabIndex={-1}
variant="Surface"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -124,7 +132,7 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="roomencryption-dialog-1-title" as="h2" size="H4">
Enable Encryption
</Text>
</Box>
@@ -108,7 +108,6 @@ export function RoomHistoryVisibility({ permissions }: RoomHistoryVisibilityProp
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
@@ -89,13 +89,21 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#roomupgrade-dialog-1',
onDeactivate: requestClose,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" style={modalStyle}>
<Dialog
id="roomupgrade-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="roomupgrade-dialog-1-title"
tabIndex={-1}
variant="Surface"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -105,7 +113,7 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="roomupgrade-dialog-1-title" as="h2" size="H4">
{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}
</Text>
</Box>
@@ -44,13 +44,22 @@ function CreateRoomModal({ state }: CreateRoomModalProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#createroommodal-dialog-1',
clickOutsideDeactivates: true,
onDeactivate: closeDialog,
escapeDeactivates: stopPropagation,
}}
>
<Modal size="300" flexHeight style={modalStyle}>
<Modal
id="createroommodal-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="createroommodal-dialog-1-title"
tabIndex={-1}
size="300"
flexHeight
style={modalStyle}
>
<Box direction="Column">
<Header
size="500"
@@ -60,7 +69,7 @@ function CreateRoomModal({ state }: CreateRoomModalProps) {
}}
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="createroommodal-dialog-1-title" as="h2" size="H4">
{type === CreateRoomType.VoiceRoom ? 'New Voice Room' : 'New Chat Room'}
</Text>
</Box>
@@ -43,13 +43,22 @@ function CreateSpaceModal({ state }: CreateSpaceModalProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#createspacemodal-dialog-1',
clickOutsideDeactivates: true,
onDeactivate: closeDialog,
escapeDeactivates: stopPropagation,
}}
>
<Modal size="300" flexHeight style={modalStyle}>
<Modal
id="createspacemodal-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="createspacemodal-dialog-1-title"
tabIndex={-1}
size="300"
flexHeight
style={modalStyle}
>
<Box direction="Column">
<Header
size="500"
@@ -60,7 +69,7 @@ function CreateSpaceModal({ state }: CreateSpaceModalProps) {
}}
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="createspacemodal-dialog-1-title" as="h2" size="H4">
New Space
</Text>
</Box>
@@ -256,7 +256,6 @@ export function HierarchyItemMenu({
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
-1
View File
@@ -260,7 +260,6 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
+9 -3
View File
@@ -155,13 +155,20 @@ function RenameRoomDialog({ room, onClose }: RenameRoomDialogProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#roomnavitem-dialog',
onDeactivate: onClose,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" aria-labelledby="rename-room-dialog-title">
<Dialog
id="roomnavitem-dialog"
role="dialog"
aria-modal="true"
tabIndex={-1}
variant="Surface"
aria-labelledby="rename-room-dialog-title"
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -1021,7 +1028,6 @@ function RoomNavItem_({
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
@@ -392,13 +392,17 @@ export function RoomServerACL({ requestClose }: RoomServerACLProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#roomserveracl-dialog',
onDeactivate: () => setPrompt(false),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog
id="roomserveracl-dialog"
role="dialog"
aria-modal="true"
tabIndex={-1}
variant="Surface"
aria-labelledby="server-acl-confirm-title"
style={modalStyle}
+11 -2
View File
@@ -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>
-3
View File
@@ -519,7 +519,6 @@ function CallButton() {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
@@ -769,7 +768,6 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setPinMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
@@ -940,7 +938,6 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
@@ -94,13 +94,21 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#jumptotime-dialog-1',
onDeactivate: onCancel,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" style={modalStyle}>
<Dialog
id="jumptotime-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="jumptotime-dialog-1-title"
tabIndex={-1}
variant="Surface"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -110,7 +118,9 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) {
size="500"
>
<Box grow="Yes">
<Text size="H4">Jump to Time</Text>
<Text id="jumptotime-dialog-1-title" size="H4">
Jump to Time
</Text>
</Box>
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} />
@@ -420,6 +420,11 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
}}
>
<Modal
id="forwardmessagedialog-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="forwardmessagedialog-dialog-1-title"
tabIndex={-1}
size="400"
style={{
maxHeight: '480px',
@@ -435,7 +440,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
>
<Box grow="Yes">
<Text as="h2" size="H4" truncate>
<Text id="forwardmessagedialog-dialog-1-title" as="h2" size="H4" truncate>
Forward message
</Text>
</Box>
+46 -10
View File
@@ -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"
@@ -639,13 +656,20 @@ export const MessageDeleteItem = as<
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#message-dialog-1',
onDeactivate: handleClose,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface">
<Dialog
id="message-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="message-dialog-1-title"
tabIndex={-1}
variant="Surface"
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -655,7 +679,9 @@ export const MessageDeleteItem = as<
size="500"
>
<Box grow="Yes">
<Text size="H4">Delete Message</Text>
<Text id="message-dialog-1-title" size="H4">
Delete Message
</Text>
</Box>
<IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
<Icon src={Icons.Cross} />
@@ -769,13 +795,21 @@ export const MessageReportItem = as<
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#message-dialog-2',
onDeactivate: handleClose,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface" style={modalStyle}>
<Dialog
id="message-dialog-2"
role="dialog"
aria-modal="true"
aria-labelledby="message-dialog-2-title"
tabIndex={-1}
variant="Surface"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -785,7 +819,9 @@ export const MessageReportItem = as<
size="500"
>
<Box grow="Yes">
<Text size="H4">Report Message</Text>
<Text id="message-dialog-2-title" size="H4">
Report Message
</Text>
</Box>
<IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
<Icon src={Icons.Cross} />
+23 -5
View File
@@ -181,13 +181,22 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#profile-dialog-1',
onDeactivate: handleRemoveUpload,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Modal className={ModalWide} variant="Surface" size="500">
<Modal
id="profile-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="profile-dialog-1-title"
tabIndex={-1}
className={ModalWide}
variant="Surface"
size="500"
>
<ImageEditor
name={imageFile?.name ?? 'Unnamed'}
url={imageFileURL}
@@ -203,13 +212,20 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#profile-dialog-2',
onDeactivate: () => setAlertRemove(false),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface">
<Dialog
id="profile-dialog-2"
role="dialog"
aria-modal="true"
aria-labelledby="profile-dialog-1-title"
tabIndex={-1}
variant="Surface"
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -219,7 +235,9 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
size="500"
>
<Box grow="Yes">
<Text size="H4">Remove Avatar</Text>
<Text id="profile-dialog-1-title" size="H4">
Remove Avatar
</Text>
</Box>
<IconButton
size="300"
-1
View File
@@ -101,7 +101,6 @@ function ClientRootOptions({ mx }: { mx?: MatrixClient }) {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
-1
View File
@@ -130,7 +130,6 @@ function DirectHeader() {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
+10 -3
View File
@@ -78,13 +78,20 @@ export function AddServer() {
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
fallbackFocus: '#explore-dialog-1',
clickOutsideDeactivates: true,
onDeactivate: () => setDialog(false),
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface">
<Dialog
id="explore-dialog-1"
role="dialog"
aria-modal="true"
aria-labelledby="explore-dialog-1-title"
tabIndex={-1}
variant="Surface"
>
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@@ -94,7 +101,7 @@ export function AddServer() {
size="500"
>
<Box grow="Yes">
<Text as="h2" size="H4">
<Text id="explore-dialog-1-title" as="h2" size="H4">
Add Server
</Text>
</Box>
-2
View File
@@ -159,7 +159,6 @@ function HomeHeader() {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
@@ -665,7 +664,6 @@ export function Home() {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setSortMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
@@ -49,7 +49,6 @@ export function CreateTab() {
content={
<FocusTrap
focusTrapOptions={{
returnFocusOnDeactivate: false,
initialFocus: false,
onDeactivate: () => setMenuCords(undefined),
clickOutsideDeactivates: true,
@@ -121,7 +121,6 @@ export function DirectTab() {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
-1
View File
@@ -123,7 +123,6 @@ export function HomeTab() {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
@@ -525,7 +525,6 @@ function SpaceTab({
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
-1
View File
@@ -324,7 +324,6 @@ function SpaceHeader() {
<FocusTrap
focusTrapOptions={{
initialFocus: false,
returnFocusOnDeactivate: false,
onDeactivate: () => setMenuAnchor(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',