Skip to content

Commit 0bed63b

Browse files
Accessibility improvements (#2071)
* workaround for mui select focusVisible issue tmp * use legend for checkboxfield overall description * make focus outlines a bit more visible * add a comment about the typecasting
1 parent 131c04e commit 0bed63b

File tree

8 files changed

+113
-95
lines changed

8 files changed

+113
-95
lines changed

frontends/main/src/app-pages/DashboardPage/DashboardPage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ const TabsContainer = styled(TabList)(({ theme }) => ({
171171
a: {
172172
padding: "0",
173173
opacity: "1",
174+
175+
"&:focus-visible": {
176+
outlineOffset: "-1px",
177+
},
174178
},
175179
"&:hover": {
176180
a: {

frontends/ol-components/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
"@mitodl/smoot-design": "^3.3.0",
2121
"@mui/base": "5.0.0-beta.69",
2222
"@mui/lab": "6.0.0-beta.28",
23-
"@mui/material": "^6.4.1",
24-
"@mui/material-nextjs": "^6.3.1",
25-
"@mui/system": "^6.4.1",
23+
"@mui/material": "^6.4.5",
24+
"@mui/material-nextjs": "^6.4.3",
25+
"@mui/system": "^6.4.3",
2626
"@remixicon/react": "^4.2.0",
2727
"@testing-library/dom": "^10.4.0",
2828
"@types/react-dom": "^19",

frontends/ol-components/src/components/Checkbox/Checkbox.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ const Container = styled.div`
6464
&& input[type="checkbox"] {
6565
margin: 0;
6666
margin-right: 4px;
67+
68+
/* Help avoid focus outline from being cutoff */
69+
:focus-visible {
70+
outline-offset: -1px;
71+
}
6772
}
6873
6974
${containerStyles}

frontends/ol-components/src/components/Checkbox/CheckboxChoiceField.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const Label = styled(FormLabel)(({ theme }) => ({
3636
width: "100%",
3737
color: theme.custom.colors.darkGray2,
3838
...theme.typography.subtitle2,
39-
}))
39+
})) as typeof FormLabel // https://mui.com/material-ui/guides/typescript/?srsltid=AfmBOoo9kvRiALbxt4kAarRGiKaiJ7tbui5tstoL23DYscJPyk6UaTul#complications-with-the-component-prop
4040

4141
const CheckboxChoiceField: React.FC<CheckboxChoiceFieldProps> = ({
4242
label,
@@ -59,7 +59,7 @@ const CheckboxChoiceField: React.FC<CheckboxChoiceFieldProps> = ({
5959
className={className}
6060
disabled={disabled}
6161
>
62-
{label && <Label>{label}</Label>}
62+
{label && <Label component="legend">{label}</Label>}
6363
<_Container>
6464
{choices.map((choice) => {
6565
return (

frontends/ol-components/src/components/SelectField/SelectField.test.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React from "react"
22
import { screen } from "@testing-library/react"
3-
import { SelectField } from "./SelectField"
4-
import type { SelectFieldProps } from "./SelectField"
3+
import user from "@testing-library/user-event"
4+
import { Select, SelectField } from "./SelectField"
5+
import type { SelectFieldProps, SelectProps } from "./SelectField"
56

67
import { faker } from "@faker-js/faker/locale/en"
78
import MenuItem from "@mui/material/MenuItem"
@@ -39,3 +40,52 @@ describe("SelectField", () => {
3940
expect(input).toBeRequired()
4041
})
4142
})
43+
44+
describe("Select", () => {
45+
const setup = (props?: Partial<SelectProps>) => {
46+
const defaults = {
47+
name: "test-name",
48+
value: "",
49+
label: "test-label",
50+
}
51+
const { rerender: _rerender } = renderWithTheme(
52+
<Select {...defaults} {...props}>
53+
<MenuItem disabled value="">
54+
Please select an op
55+
</MenuItem>
56+
<MenuItem value="value-1">Option 1</MenuItem>
57+
<MenuItem value="value-2">Option 2</MenuItem>
58+
</Select>,
59+
)
60+
const rerender = (newProps: Partial<SelectFieldProps>) => {
61+
_rerender(<SelectField {...defaults} {...newProps} />)
62+
}
63+
return { rerender }
64+
}
65+
66+
/**
67+
* This test exists to ensure our workaround for
68+
* https://github.com/mui/material-ui/issues/23747
69+
* is behaving as expected.
70+
*/
71+
it("Applies class 'pointer-open' to menu if and only if opened via pointer", async () => {
72+
setup()
73+
const select = screen.getByRole("combobox")
74+
const getMenu = () => document.querySelector(".MuiMenu-root")
75+
76+
// Opened via pointer; has class pointer-open
77+
await user.click(select)
78+
expect(getMenu()).toHaveClass("pointer-open")
79+
expect(document.activeElement).toHaveTextContent("Option 1")
80+
81+
// close it
82+
await user.keyboard("{Escape}")
83+
expect(getMenu()).toBe(null)
84+
expect(document.activeElement).toBe(select)
85+
86+
// open via keyboard, does NOT have class pointer-open
87+
await user.keyboard("{Enter}")
88+
expect(getMenu()).not.toHaveClass("pointer-open")
89+
expect(document.activeElement).toHaveTextContent("Option 1")
90+
})
91+
})

frontends/ol-components/src/components/SelectField/SelectField.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ const SelectIcon = styled(RiArrowDownSLine)({
9696
width: "1em",
9797
})
9898

99+
const POINTER_CLASSNAME = "pointer-open"
99100
/**
100101
* WARNING: You likely do not need this component. Try one of
101102
*
@@ -105,12 +106,39 @@ const SelectIcon = styled(RiArrowDownSLine)({
105106
* instead.
106107
*/
107108
function Select<Value = unknown>({ size, ...props }: SelectProps<Value>) {
109+
const menu = React.useRef<HTMLDivElement | null>(null)
108110
return (
109111
<MuiSelect
110112
variant="standard"
111113
{...props}
112114
size={size}
113115
IconComponent={SelectIcon}
116+
/**
117+
* The next three properties are a workaround to deal with
118+
* https://github.com/mui/material-ui/issues/23747
119+
*
120+
* Note: Another workaround is mentioned in the issue, but breaks
121+
* accessibility (https://github.com/mui/material-ui/issues/23747#issuecomment-2596590221)
122+
*/
123+
onPointerDown={() => {
124+
// This likely isn't necessasry---the Menu unmounts on close.
125+
// But let's not rely on that.
126+
menu.current?.classList.remove(POINTER_CLASSNAME)
127+
}}
128+
onPointerUp={() => {
129+
menu.current?.classList.add(POINTER_CLASSNAME)
130+
}}
131+
MenuProps={{
132+
ref: menu,
133+
sx: {
134+
[`&.${POINTER_CLASSNAME} .MuiMenuItem-root.Mui-focusVisible`]: {
135+
outline: "none",
136+
},
137+
},
138+
onKeyDown: () => {
139+
menu.current?.classList.remove(POINTER_CLASSNAME)
140+
},
141+
}}
114142
input={<SelectInput size={size} />}
115143
/>
116144
)

frontends/ol-components/src/components/SimpleSelect/SimpleSelect.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ const SimpleSelectField: React.FC<SimpleSelectFieldProps> = ({
7575
}) => {
7676
return (
7777
<SelectField {...others}>
78-
<MenuItem style={{ display: "none" }}></MenuItem>
7978
{options.map(({ value, label, ...itemProps }) => (
8079
<MenuItem size={others.size} value={value} key={value} {...itemProps}>
8180
{label}

yarn.lock

Lines changed: 19 additions & 87 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)