Skip to content

Commit 53369db

Browse files
Ensure focus returns to menu buttons for accessibility purposes (#991)
Closes #974 Note that this will not be available on the beta until later today.
1 parent 664be7e commit 53369db

26 files changed

+294
-182
lines changed

src/common/GenericDialog.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface GenericDialogProps {
2424
size?: ThemeTypings["components"]["Modal"]["sizes"];
2525
onClose: () => void;
2626
returnFocusOnClose?: boolean;
27+
finalFocusRef?: React.RefObject<HTMLButtonElement>;
2728
}
2829

2930
export const GenericDialog = ({
@@ -33,13 +34,15 @@ export const GenericDialog = ({
3334
size,
3435
onClose,
3536
returnFocusOnClose = true,
37+
finalFocusRef = undefined,
3638
}: GenericDialogProps) => {
3739
return (
3840
<Modal
3941
isOpen
4042
onClose={onClose}
4143
size={size}
4244
returnFocusOnClose={returnFocusOnClose}
45+
finalFocusRef={finalFocusRef}
4346
>
4447
<ModalOverlay>
4548
<ModalContent minWidth="560px" my="auto">

src/common/InputDialog.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface InputDialogProps<T> {
3535
size?: ThemeTypings["components"]["Modal"]["sizes"];
3636
validate?: (input: T) => string | undefined;
3737
customFocus?: boolean;
38+
finalFocusRef?: React.RefObject<HTMLButtonElement>;
3839
callback: (value: ValueOrCancelled<T>) => void;
3940
}
4041

@@ -50,6 +51,7 @@ export const InputDialog = <T,>({
5051
initialValue,
5152
size,
5253
customFocus,
54+
finalFocusRef = undefined,
5355
validate = noValidation,
5456
callback,
5557
}: InputDialogProps<T>) => {
@@ -70,6 +72,7 @@ export const InputDialog = <T,>({
7072
onClose={onCancel}
7173
size={size}
7274
initialFocusRef={customFocus ? undefined : leastDestructiveRef}
75+
finalFocusRef={finalFocusRef}
7376
>
7477
<ModalOverlay>
7578
<ModalContent>

src/common/MultipleFilesDialog.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ export const enum MultipleFilesChoice {
1616

1717
interface MultipleFilesDialogProps {
1818
callback: (value: MultipleFilesChoice) => void;
19+
finalFocusRef: React.RefObject<HTMLButtonElement>;
1920
}
2021

21-
export const MultipleFilesDialog = ({ callback }: MultipleFilesDialogProps) => {
22+
export const MultipleFilesDialog = ({
23+
callback,
24+
finalFocusRef,
25+
}: MultipleFilesDialogProps) => {
2226
return (
2327
<GenericDialog
28+
finalFocusRef={finalFocusRef}
2429
onClose={() => callback(MultipleFilesChoice.Close)}
2530
body={<MultipleFilesDialogBody />}
2631
footer={

src/common/PostSaveDialog.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,21 @@ export const enum PostSaveChoice {
1818
interface PostSaveDialogProps {
1919
callback: (value: PostSaveChoice) => void;
2020
dialogNormallyHidden: boolean;
21+
finalFocusRef: React.RefObject<HTMLButtonElement>;
2122
}
2223

2324
export const PostSaveDialog = ({
2425
callback,
2526
dialogNormallyHidden,
27+
finalFocusRef,
2628
}: PostSaveDialogProps) => {
2729
const onShowTransferHexHelp = useCallback(() => {
2830
callback(PostSaveChoice.ShowTransferHexHelp);
2931
}, [callback]);
3032
return (
3133
<GenericDialog
3234
onClose={() => callback(PostSaveChoice.Close)}
35+
finalFocusRef={finalFocusRef}
3336
body={
3437
<PostSaveDialogBody onShowTransferHexHelp={onShowTransferHexHelp} />
3538
}

src/project/FlashButton.tsx

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/project/MoreMenuButton.tsx

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,38 @@ import {
99
MenuButtonProps,
1010
ThemeTypings,
1111
} from "@chakra-ui/react";
12+
import React, { ForwardedRef } from "react";
1213
import { MdMoreVert } from "react-icons/md";
1314

1415
interface MoreMenuButtonProps extends MenuButtonProps {
1516
size?: ThemeTypings["components"]["Button"]["sizes"];
1617
variant?: string;
1718
}
1819

19-
const MoreMenuButton = ({ size, variant, ...props }: MoreMenuButtonProps) => {
20-
return (
21-
<MenuButton
22-
variant={variant}
23-
borderLeft="1px"
24-
borderRadius="button"
25-
as={IconButton}
26-
icon={
27-
<MdMoreVert
28-
style={{
29-
marginLeft: "calc(-0.15 * var(--chakra-radii-button))",
30-
}}
31-
/>
32-
}
33-
size={size}
34-
{...props}
35-
/>
36-
);
37-
};
20+
const MoreMenuButton = React.forwardRef(
21+
(
22+
{ size, variant, ...props }: MoreMenuButtonProps,
23+
ref: ForwardedRef<HTMLButtonElement>
24+
) => {
25+
return (
26+
<MenuButton
27+
ref={ref}
28+
variant={variant}
29+
borderLeft="1px"
30+
borderRadius="button"
31+
as={IconButton}
32+
icon={
33+
<MdMoreVert
34+
style={{
35+
marginLeft: "calc(-0.15 * var(--chakra-radii-button))",
36+
}}
37+
/>
38+
}
39+
size={size}
40+
{...props}
41+
/>
42+
);
43+
}
44+
);
3845

3946
export default MoreMenuButton;

src/project/ProjectActionBar.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,15 @@ import OpenButton from "./OpenButton";
1010
import { widthXl } from "../common/media-queries";
1111
import React, { ForwardedRef } from "react";
1212

13+
interface ProjectActionBarProps extends BoxProps {
14+
sendButtonRef: React.RefObject<HTMLButtonElement>;
15+
}
16+
1317
const ProjectActionBar = React.forwardRef(
14-
(props: BoxProps, ref: ForwardedRef<HTMLButtonElement>) => {
18+
(
19+
{ sendButtonRef, ...props }: ProjectActionBarProps,
20+
ref: ForwardedRef<HTMLButtonElement>
21+
) => {
1522
const [isWideScreen] = useMediaQuery(widthXl);
1623
const size = "lg";
1724
return (
@@ -21,7 +28,7 @@ const ProjectActionBar = React.forwardRef(
2128
py={5}
2229
px={isWideScreen ? 10 : 5}
2330
>
24-
<SendButton size={size} ref={ref} />
31+
<SendButton size={size} ref={ref} sendButtonRef={sendButtonRef} />
2532
<HStack spacing={2.5}>
2633
<SaveMenuButton size={size} />
2734
{/* Min-width to avoid collapsing when out of space. Needs some work on responsiveness of the action bar. */}

src/project/SaveButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* SPDX-License-Identifier: MIT
55
*/
66
import { Tooltip } from "@chakra-ui/react";
7+
import { useRef } from "react";
78
import { RiDownload2Line } from "react-icons/ri";
89
import { useIntl } from "react-intl";
910
import CollapsibleButton, {
@@ -25,6 +26,7 @@ interface SaveButtonProps
2526
const SaveButton = (props: SaveButtonProps) => {
2627
const actions = useProjectActions();
2728
const intl = useIntl();
29+
const menuButtonRef = useRef<HTMLButtonElement>(null);
2830
return (
2931
<Tooltip
3032
hasArrow
@@ -34,9 +36,10 @@ const SaveButton = (props: SaveButtonProps) => {
3436
})}
3537
>
3638
<CollapsibleButton
39+
ref={menuButtonRef}
3740
{...props}
3841
icon={<RiDownload2Line />}
39-
onClick={() => actions.save()}
42+
onClick={() => actions.save(menuButtonRef)}
4043
text={intl.formatMessage({
4144
id: "save-action",
4245
})}

src/project/SaveMenuButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { zIndexAboveTerminal } from "../common/zIndex";
1818
import SaveButton from "./SaveButton";
1919
import MoreMenuButton from "./MoreMenuButton";
2020
import { useProjectActions } from "./project-hooks";
21+
import { useRef } from "react";
2122

2223
interface SaveMenuButtonProps {
2324
size?: ThemeTypings["components"]["Button"]["sizes"];
@@ -32,12 +33,14 @@ interface SaveMenuButtonProps {
3233
const SaveMenuButton = ({ size }: SaveMenuButtonProps) => {
3334
const intl = useIntl();
3435
const actions = useProjectActions();
36+
const menuButtonRef = useRef<HTMLButtonElement>(null);
3537
return (
3638
<HStack>
3739
<Menu>
3840
<ButtonGroup isAttached>
3941
<SaveButton mode="button" size={size} borderRight="1px" />
4042
<MoreMenuButton
43+
ref={menuButtonRef}
4144
aria-label={intl.formatMessage({ id: "more-save-options" })}
4245
size={size}
4346
data-testid="more-save-options"
@@ -46,7 +49,7 @@ const SaveMenuButton = ({ size }: SaveMenuButtonProps) => {
4649
<MenuList zIndex={zIndexAboveTerminal}>
4750
<MenuItem
4851
icon={<RiDownload2Line />}
49-
onClick={actions.saveMainFile}
52+
onClick={() => actions.saveMainFile(menuButtonRef)}
5053
>
5154
<FormattedMessage id="save-python-action" />
5255
</MenuItem>

src/project/SendButton.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,22 @@ import { useProjectActions } from "./project-hooks";
2525

2626
interface SendButtonProps {
2727
size?: ThemeTypings["components"]["Button"]["sizes"];
28+
sendButtonRef: React.RefObject<HTMLButtonElement>;
2829
}
2930

3031
const SendButton = React.forwardRef(
31-
({ size }: SendButtonProps, ref: ForwardedRef<HTMLButtonElement>) => {
32+
(
33+
{ size, sendButtonRef }: SendButtonProps,
34+
ref: ForwardedRef<HTMLButtonElement>
35+
) => {
3236
const status = useConnectionStatus();
3337
const connected = status === ConnectionStatus.CONNECTED;
3438
const actions = useProjectActions();
3539
const handleToggleConnected = useCallback(async () => {
3640
if (connected) {
37-
await actions.disconnect();
41+
await actions.disconnect(menuButtonRef);
3842
} else {
39-
await actions.connect(false, ConnectionAction.CONNECT);
43+
await actions.connect(false, ConnectionAction.CONNECT, menuButtonRef);
4044
}
4145
}, [connected, actions]);
4246
const intl = useIntl();
@@ -54,14 +58,14 @@ const SendButton = React.forwardRef(
5458
lastCompleteFlash: flashing.current.lastCompleteFlash,
5559
};
5660
try {
57-
await actions.flash();
61+
await actions.flash(sendButtonRef);
5862
} finally {
5963
flashing.current = {
6064
flashing: false,
6165
lastCompleteFlash: new Date().getTime(),
6266
};
6367
}
64-
}, [flashing, actions]);
68+
}, [flashing, actions, sendButtonRef]);
6569
const handleFocus = useCallback(
6670
(e) => {
6771
const inProgress = flashing.current.flashing;
@@ -74,6 +78,7 @@ const SendButton = React.forwardRef(
7478
},
7579
[flashing]
7680
);
81+
const menuButtonRef = useRef<HTMLButtonElement>(null);
7782
return (
7883
<HStack>
7984
<Menu>
@@ -97,6 +102,7 @@ const SendButton = React.forwardRef(
97102
</Button>
98103
</Tooltip>
99104
<MoreMenuButton
105+
ref={menuButtonRef}
100106
variant="solid"
101107
aria-label={intl.formatMessage({ id: "more-connect-options" })}
102108
data-testid="more-connect-options"

0 commit comments

Comments
 (0)