-
Notifications
You must be signed in to change notification settings - Fork 943
fix: make ProxyMenu more accessible to screen readers #11312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
4e08a23
wip: commit progress on latency update
Parkreiner 1402691
chore: add stories and clean up tests
Parkreiner f003144
refactor: clean up code
Parkreiner 5f58877
fix: make sure headers aren't treated as interactive elements
Parkreiner 1348f55
refactor: clean up tests
Parkreiner b7d05de
fix: clean up stories
Parkreiner c836057
docs: add clarifying comment
Parkreiner 4af5cec
fix: update stories again
Parkreiner 9a05ee1
fix: clean up/extend prop definitions
Parkreiner cdff5fb
refactor: quick cleanup
Parkreiner 0f3471c
Merge branch 'main' into mes/a11y-proxy-menu
Parkreiner fd643ae
fix: apply Kira's feedback
Parkreiner 8853c5a
refactor: clean up abbr markup to account for pronunciation
Parkreiner 3189031
fix: more cleanup
Parkreiner 0cfc510
Merge branch 'main' into mes/a11y-proxy-menu
Parkreiner 8f38208
fix: refine screen reader output for VoiceOver
Parkreiner d534099
refactor: clean up and redefine tests
Parkreiner 03b2ac1
feature: add finishing touches
Parkreiner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,7 @@ | |
"idtoken", | ||
"Iflag", | ||
"incpatch", | ||
"initialisms", | ||
"ipnstate", | ||
"isatty", | ||
"Jobf", | ||
|
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { type PropsWithChildren } from "react"; | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { Abbr } from "./Abbr"; | ||
|
||
// Just here to make the abbreviated part more obvious in the component library | ||
const Underline = ({ children }: PropsWithChildren) => ( | ||
<span css={{ textDecoration: "underline dotted" }}>{children}</span> | ||
); | ||
|
||
const meta: Meta<typeof Abbr> = { | ||
title: "components/Abbr", | ||
component: Abbr, | ||
decorators: [ | ||
(Story) => ( | ||
<> | ||
<p>Try the following text out in a screen reader!</p> | ||
<Story /> | ||
</> | ||
), | ||
], | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof Abbr>; | ||
|
||
export const InlinedShorthand: Story = { | ||
args: { | ||
pronunciation: "shorthand", | ||
children: "ms", | ||
title: "milliseconds", | ||
}, | ||
decorators: [ | ||
(Story) => ( | ||
<p css={{ maxWidth: "40em" }}> | ||
The physical pain of getting bonked on the head with a cartoon mallet | ||
lasts precisely 593{" "} | ||
<Underline> | ||
<Story /> | ||
</Underline> | ||
. The emotional turmoil and complete embarrassment lasts forever. | ||
</p> | ||
), | ||
], | ||
}; | ||
|
||
export const Acronym: Story = { | ||
args: { | ||
pronunciation: "acronym", | ||
children: "NASA", | ||
title: "National Aeronautics and Space Administration", | ||
}, | ||
decorators: [ | ||
(Story) => ( | ||
<Underline> | ||
<Story /> | ||
</Underline> | ||
), | ||
], | ||
}; | ||
|
||
export const Initialism: Story = { | ||
args: { | ||
pronunciation: "initialism", | ||
children: "CLI", | ||
title: "Command-Line Interface", | ||
}, | ||
decorators: [ | ||
(Story) => ( | ||
<Underline> | ||
<Story /> | ||
</Underline> | ||
), | ||
], | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { render, screen } from "@testing-library/react"; | ||
import { Abbr, type Pronunciation } from "./Abbr"; | ||
|
||
type AbbreviationData = { | ||
abbreviation: string; | ||
title: string; | ||
expectedLabel: string; | ||
}; | ||
|
||
type AssertionInput = AbbreviationData & { | ||
pronunciation: Pronunciation; | ||
}; | ||
|
||
function assertAccessibleLabel({ | ||
abbreviation, | ||
title, | ||
expectedLabel, | ||
pronunciation, | ||
}: AssertionInput) { | ||
const { unmount } = render( | ||
<Abbr title={title} pronunciation={pronunciation}> | ||
{abbreviation} | ||
</Abbr>, | ||
); | ||
|
||
screen.getByLabelText(expectedLabel, { selector: "abbr" }); | ||
unmount(); | ||
} | ||
|
||
describe(Abbr.name, () => { | ||
it("Has an aria-label that equals the title if the abbreviation is shorthand", () => { | ||
const sampleShorthands: AbbreviationData[] = [ | ||
{ | ||
abbreviation: "ms", | ||
title: "milliseconds", | ||
expectedLabel: "milliseconds", | ||
}, | ||
{ | ||
abbreviation: "g", | ||
title: "grams", | ||
expectedLabel: "grams", | ||
}, | ||
]; | ||
|
||
for (const shorthand of sampleShorthands) { | ||
assertAccessibleLabel({ ...shorthand, pronunciation: "shorthand" }); | ||
} | ||
}); | ||
|
||
it("Has an aria label with title and 'flattened' pronunciation if abbreviation is acronym", () => { | ||
const sampleAcronyms: AbbreviationData[] = [ | ||
{ | ||
abbreviation: "NASA", | ||
title: "National Aeronautics and Space Administration", | ||
expectedLabel: "Nasa (National Aeronautics and Space Administration)", | ||
}, | ||
{ | ||
abbreviation: "AWOL", | ||
title: "Absent without Official Leave", | ||
expectedLabel: "Awol (Absent without Official Leave)", | ||
}, | ||
{ | ||
abbreviation: "YOLO", | ||
title: "You Only Live Once", | ||
expectedLabel: "Yolo (You Only Live Once)", | ||
}, | ||
]; | ||
|
||
for (const acronym of sampleAcronyms) { | ||
assertAccessibleLabel({ ...acronym, pronunciation: "acronym" }); | ||
} | ||
}); | ||
|
||
it("Has an aria label with title and initialized pronunciation if abbreviation is initialism", () => { | ||
const sampleInitialisms: AbbreviationData[] = [ | ||
{ | ||
abbreviation: "FBI", | ||
title: "Federal Bureau of Investigation", | ||
expectedLabel: "F.B.I. (Federal Bureau of Investigation)", | ||
}, | ||
{ | ||
abbreviation: "YMCA", | ||
title: "Young Men's Christian Association", | ||
expectedLabel: "Y.M.C.A. (Young Men's Christian Association)", | ||
}, | ||
{ | ||
abbreviation: "CLI", | ||
title: "Command-Line Interface", | ||
expectedLabel: "C.L.I. (Command-Line Interface)", | ||
}, | ||
]; | ||
|
||
for (const initialism of sampleInitialisms) { | ||
assertAccessibleLabel({ ...initialism, pronunciation: "initialism" }); | ||
} | ||
}); | ||
}); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { type FC, type HTMLAttributes } from "react"; | ||
|
||
export type Pronunciation = "shorthand" | "acronym" | "initialism"; | ||
|
||
type AbbrProps = HTMLAttributes<HTMLElement> & { | ||
children: string; | ||
title: string; | ||
pronunciation?: Pronunciation; | ||
}; | ||
|
||
/** | ||
* A more sophisticated version of the native <abbr> element. | ||
* | ||
* Features: | ||
* - Better type-safety (requiring you to include certain properties) | ||
* - All built-in HTML styling is stripped away by default | ||
* - Better integration with screen readers (like exposing the title prop to | ||
* them), with more options for influencing how they pronounce text | ||
*/ | ||
export const Abbr: FC<AbbrProps> = ({ | ||
children, | ||
title, | ||
pronunciation = "shorthand", | ||
...delegatedProps | ||
}) => { | ||
return ( | ||
<abbr | ||
// Title attributes usually aren't natively available to screen readers; | ||
// always have to supplement with aria-label | ||
title={title} | ||
aria-label={getAccessibleLabel(children, title, pronunciation)} | ||
css={{ | ||
textDecoration: "inherit", | ||
letterSpacing: children === children.toUpperCase() ? "0.02em" : "0", | ||
}} | ||
{...delegatedProps} | ||
> | ||
<span aria-hidden>{children}</span> | ||
</abbr> | ||
); | ||
}; | ||
|
||
function getAccessibleLabel( | ||
abbreviation: string, | ||
title: string, | ||
pronunciation: Pronunciation, | ||
): string { | ||
if (pronunciation === "initialism") { | ||
return `${initializeText(abbreviation)} (${title})`; | ||
} | ||
|
||
if (pronunciation === "acronym") { | ||
return `${flattenPronunciation(abbreviation)} (${title})`; | ||
} | ||
|
||
return title; | ||
} | ||
|
||
function initializeText(text: string): string { | ||
return text.trim().toUpperCase().replaceAll(/\B/g, ".") + "."; | ||
} | ||
|
||
function flattenPronunciation(text: string): string { | ||
const trimmed = text.trim(); | ||
return (trimmed[0] ?? "").toUpperCase() + trimmed.slice(1).toLowerCase(); | ||
} |
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a lot of comments in here for a relatively small component. Are there any we can shorten?