Skip to content
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

fix(LEMS-2183): Add optional aria label to popover #2265

Merged
merged 12 commits into from
Jul 22, 2024
5 changes: 5 additions & 0 deletions .changeset/afraid-buckets-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-popover": patch
---

adds optional aria label for popover
3 changes: 3 additions & 0 deletions __docs__/wonder-blocks-popover/popover.accessibility.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ the `PopoverContent` component to reference the `title` and `content`
elements/props. By doing this, the popover will be announced by screen readers
as a dialog with a title and content.

Alternatively, you can use `aria-label` to add screen reader accessible text to the popover dialog in case there is no
anakaren-rojas marked this conversation as resolved.
Show resolved Hide resolved
visible title and/or description inside the dialog.

## Keyboard Interaction

### Initial focus
Expand Down
18 changes: 18 additions & 0 deletions __docs__/wonder-blocks-popover/popover.stories.tsx
anakaren-rojas marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -724,3 +724,21 @@ export const PopoverAlignment: StoryComponentType = {
</View>
),
};

/**
* With custom aria-label - overrides the default aria-describedby and aria-labelledby
*/

export const CustomAriaLabel: StoryComponentType = {
args: {
children: <Button>Open popover</Button>,
content: ContentMappings.withTextOnly,
placement: "top",
dismissEnabled: true,
id: "",
initialFocusId: "",
testId: "",
onClose: () => {},
"aria-label": "Popover with custom aria label",
} as PopoverArgs,
};
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,40 @@ describe("Popover", () => {
).toBeInTheDocument();
});

it("should announce a popover correctly by reading the aria label", async () => {
// Arrange
render(
<Popover
onClose={jest.fn()}
aria-label="Popover Aria Label"
content={
<PopoverContentCore>
<button data-close-button onClick={close}>
Close Popover
</button>
</PopoverContentCore>
}
>
<Button>Open default popover</Button>
</Popover>,
);

// Act
// Open the popover
const openButton = await screen.findByRole("button", {
name: "Open default popover",
});

await userEvent.click(openButton);
const popover = await screen.findByRole("dialog");

// Assert

expect(popover).toHaveAttribute("aria-label", "Popover Aria Label");
expect(popover).not.toHaveAttribute("aria-labelledby");
expect(popover).not.toHaveAttribute("aria-describedby");
});

it("should correctly describe the popover content core's aria label", async () => {
// Arrange
render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export default class PopoverDialog extends React.Component<Props> {
showTail,
"aria-describedby": ariaDescribedby,
"aria-labelledby": ariaLabelledBy,
"aria-label": ariaLabel,
} = this.props;

const contentProps = children.props as any;
Expand All @@ -90,6 +91,7 @@ export default class PopoverDialog extends React.Component<Props> {
return (
<React.Fragment>
<View
aria-label={ariaLabel}
aria-describedby={ariaDescribedby}
aria-labelledby={ariaLabelledBy}
id={id}
Expand Down
16 changes: 13 additions & 3 deletions packages/wonder-blocks-popover/src/components/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,16 +272,26 @@ export default class Popover extends React.Component<Props, State> {
}

renderPopper(uniqueId: string): React.ReactNode {
const {initialFocusId, placement, showTail, portal} = this.props;
const {
initialFocusId,
placement,
showTail,
portal,
"aria-label": ariaLabel,
} = this.props;
const {anchorElement} = this.state;

const ariaDescribedBy = ariaLabel ? undefined : `${uniqueId}-content`;
const ariaLabelledBy = ariaLabel ? undefined : `${uniqueId}-title`;

const popperContent = (
<TooltipPopper anchorElement={anchorElement} placement={placement}>
{(props: PopperElementProps) => (
<PopoverDialog
{...props}
aria-describedby={`${uniqueId}-content`}
aria-labelledby={`${uniqueId}-title`}
aria-label={ariaLabel}
aria-describedby={ariaDescribedBy}
aria-labelledby={ariaLabelledBy}
id={uniqueId}
onUpdate={(placement) => this.setState({placement})}
showTail={showTail}
Expand Down