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

dialog: added support for the ariaHideAncestors prop. #537

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions packages/dialog/__tests__/dialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useFakeTimers, SinonFakeTimers } from "sinon";
import { axe, toHaveNoViolations } from "jest-axe";
import { fireEvent, render, act, userEvent } from "$test/utils";
import { AxeResults } from "$test/types";
import { Dialog } from "@reach/dialog";
import { Dialog, DialogProps } from "@reach/dialog";

function getOverlay(container: Element) {
return container.querySelector("[data-reach-dialog-overlay]");
Expand Down Expand Up @@ -58,6 +58,39 @@ describe("<Dialog />", () => {
);
expect(label).toHaveTextContent("I am the title now");
});

it("ARIA hides ancestors by default", async () => {
const { findByTestId } = render(
<>
<div data-testid="sibling">Hidden</div>
<BasicOpenDialog />
</>
);
const sibling = await findByTestId("sibling");
expect(isAriaHidden(sibling)).toBe(true);
});

it("ARIA hides ancestors when ariaHideAncestors is true", async () => {
const { findByTestId } = render(
<>
<div data-testid="sibling">Hidden</div>
<BasicOpenDialog ariaHideAncestors={true} />
</>
);
const sibling = await findByTestId("sibling");
expect(isAriaHidden(sibling)).toBe(true);
});

it("doesn't ARIA hide ancestors when ariaHideAncestors is false", async () => {
const { findByTestId } = render(
<>
<div data-testid="sibling">Not hidden</div>
<BasicOpenDialog ariaHideAncestors={false} />
</>
);
const sibling = await findByTestId("sibling");
expect(isAriaHidden(sibling)).toBe(false);
});
});

describe("user events", () => {
Expand All @@ -83,7 +116,7 @@ describe("<Dialog />", () => {
});
});

function BasicOpenDialog() {
function BasicOpenDialog(props: DialogProps) {
const [showDialog, setShowDialog] = useState(true);
return (
<div>
Expand All @@ -92,6 +125,7 @@ function BasicOpenDialog() {
aria-label="Announcement"
isOpen={showDialog}
onDismiss={() => setShowDialog(false)}
{...props}
>
<div data-testid="inner">
<button onClick={() => setShowDialog(false)}>Close Dialog</button>
Expand All @@ -102,3 +136,18 @@ function BasicOpenDialog() {
</div>
);
}

/**
* Checks the element and its ancestors for an aria-hidden attribute.
* If an aria-hidden is found, we assume it is set to true, which should be a
* reasonable assumption for the purposes of these tests.
*/
function isAriaHidden(el: HTMLElement | null) {
while (el) {
if (el.hasAttribute("aria-hidden")) {
return true;
}
el = el.parentElement;
}
return false;
}
30 changes: 24 additions & 6 deletions packages/dialog/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const overlayPropTypes = {
initialFocusRef: () => null,
allowPinchZoom: PropTypes.bool,
onDismiss: PropTypes.func,
ariaHideAncestors: PropTypes.bool,
};

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -92,6 +93,7 @@ const DialogInner = forwardRef<HTMLDivElement, DialogProps>(
onDismiss = noop,
onMouseDown,
onKeyDown,
ariaHideAncestors = true,
...props
},
forwardedRef
Expand Down Expand Up @@ -124,11 +126,11 @@ const DialogInner = forwardRef<HTMLDivElement, DialogProps>(
mouseDownTarget.current = event.target;
}

useEffect(
() =>
overlayNode.current ? createAriaHider(overlayNode.current) : void null,
[]
);
useEffect(() => {
if (overlayNode.current && ariaHideAncestors) {
createAriaHider(overlayNode.current);
}
}, [ariaHideAncestors]);

return (
<FocusLock autoFocus returnFocus onActivation={activateFocusLock}>
Expand Down Expand Up @@ -225,7 +227,14 @@ if (__DEV__) {
* @see Docs https://reacttraining.com/reach-ui/dialog#dialog
*/
export const Dialog = forwardRef<HTMLDivElement, DialogProps>(function Dialog(
{ isOpen, onDismiss = noop, initialFocusRef, allowPinchZoom, ...props },
{
isOpen,
onDismiss = noop,
initialFocusRef,
allowPinchZoom,
ariaHideAncestors = true,
...props
},
forwardedRef
) {
return (
Expand All @@ -234,6 +243,7 @@ export const Dialog = forwardRef<HTMLDivElement, DialogProps>(function Dialog(
allowPinchZoom={allowPinchZoom}
isOpen={isOpen}
onDismiss={onDismiss}
ariaHideAncestors={ariaHideAncestors}
>
<DialogContent ref={forwardedRef} {...props} />
</DialogOverlay>
Expand Down Expand Up @@ -265,6 +275,13 @@ export type DialogProps = {
* @see Docs https://reacttraining.com/reach-ui/dialog#dialog-ondismiss
*/
onDismiss?: (event?: React.SyntheticEvent) => void;
/**
* By default, all of the nodes at the document.body root hav aria-hidden set
* on them, except for the currently active dialog.
*
* @see Docs https://reacttraining.com/reach-ui/dialog#aria-hiding-other-elements
*/
ariaHideAncestors?: boolean;
/**
* Accepts any renderable content.
*
Expand All @@ -287,6 +304,7 @@ if (__DEV__) {
onDismiss: PropTypes.func,
"aria-label": ariaLabelType,
"aria-labelledby": ariaLabelType,
ariaHideAncestors: PropTypes.bool,
};
}

Expand Down