-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 373-disclosure: Disclosure Component * 373-disclosure: Fix toggle and add test
- Loading branch information
Showing
5 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,115 @@ | ||
/* | ||
Copyright (C) 2018 The Trustees of Indiana University | ||
SPDX-License-Identifier: BSD-3-Clause | ||
*/ | ||
import classNames from "classnames"; | ||
import * as PropTypes from "prop-types"; | ||
import * as React from "react"; | ||
import { createRef, useEffect, useState } from "react"; | ||
import * as Rivet from "../../util/Rivet"; | ||
import { | ||
handler, | ||
isKeyEvent, | ||
isRightMouseClick, | ||
isTabKeyPress, | ||
targets, | ||
} from "../../util/EventUtils.js"; | ||
import { TestUtils } from "../../util/TestUtils.js"; | ||
|
||
const disclosureClass = "rvt-disclosure"; | ||
|
||
const Disclosure = ({ | ||
children, | ||
className, | ||
closeClickOutside, | ||
id = Rivet.shortuid(), | ||
isOpen, | ||
title, | ||
...attrs | ||
}) => { | ||
const [isOpenState, setIsOpenState] = useState(isOpen); | ||
|
||
useEffect(() => { | ||
handleEventRegistration(); | ||
return () => { | ||
eventHandler.deregister(); | ||
}; | ||
}); | ||
|
||
const handleClickOutside = (event) => { | ||
if (event && shouldToggleDisclosure(event)) { | ||
toggleDisclosure(event); | ||
} | ||
}; | ||
|
||
const disclosureWrapDiv = createRef(); | ||
const eventHandler = handler(handleClickOutside); | ||
|
||
const toggleDisclosure = (event) => { | ||
setIsOpenState(!isOpenState); | ||
// if there is a stopPropagation method on the event we need to call it to prevent additional events from firing | ||
event.stopPropagation && event.stopPropagation(); | ||
}; | ||
|
||
const shouldToggleDisclosure = (event) => { | ||
if (isRightMouseClick(event) || isKeyEvent(event)) { | ||
// If the user right clicks anywhere on the screen or they press an unhandled key do not close the menu | ||
return false; | ||
} else if ( | ||
targets(disclosureWrapDiv.current, event) && | ||
(!isKeyEvent(event) || isTabKeyPress(event)) | ||
) { | ||
// If the user clicks, touches or tabs inside the disclosure do not close the menu | ||
return false; | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
const handleEventRegistration = () => { | ||
if (isOpenState && closeClickOutside) { | ||
eventHandler.register(); | ||
} else { | ||
eventHandler.deregister(); | ||
} | ||
}; | ||
|
||
return ( | ||
<div | ||
id={id} | ||
className={classNames(disclosureClass, className)} | ||
ref={disclosureWrapDiv} | ||
{...attrs} | ||
> | ||
<button | ||
className="rvt-disclosure__toggle" | ||
onClick={toggleDisclosure} | ||
aria-expanded={isOpenState ? "true" : "false"} | ||
> | ||
{title} | ||
</button> | ||
{isOpenState && ( | ||
<div | ||
className="rvt-disclosure__content" | ||
data-testid={TestUtils.Disclosure.testId} | ||
> | ||
{children} | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
Disclosure.displayName = "Disclosure"; | ||
Disclosure.propTypes = { | ||
/** Determines whether the Disclosure closes when the user clicks anywhere outside of it */ | ||
closeClickOutside: PropTypes.bool, | ||
/** A unique identifier for the badge */ | ||
id: PropTypes.string, | ||
/** Determines whether the Disclosure is open or not */ | ||
isOpen: PropTypes.bool, | ||
/** The content of the Disclosure button */ | ||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), | ||
}; | ||
|
||
export default Rivet.rivetize(Disclosure); |
This file contains 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,15 @@ | ||
Use the disclosure component to allow the user to show or hide additional content about a topic. | ||
|
||
View the [Rivet documentation for Disclosure](https://rivet.iu.edu/components/disclosure/). | ||
|
||
### Disclosure Examples | ||
|
||
<!-- prettier-ignore-start --> | ||
```jsx | ||
<Disclosure title="Take a look at the numbers" closeClickOutside={true}> | ||
<div class="rvt-prose rvt-flow"> | ||
<p>Tuition and fees vary at each Indiana University campus. As you look at total costs, keep in mind that financial aid, scholarships and awards, a part-time job, and student loans can all factor into what you will pay for your degree.</p> | ||
</div> | ||
</Disclosure> | ||
``` | ||
<!-- prettier-ignore-end --> |
123 changes: 123 additions & 0 deletions
123
src/components/PageContent/Disclosure/Disclosure.test.jsx
This file contains 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,123 @@ | ||
import { fireEvent, render, screen } from "@testing-library/react"; | ||
import "@testing-library/jest-dom"; | ||
import React from "react"; | ||
import Disclosure from "./Disclosure"; | ||
import { TestUtils } from "../../util/TestUtils.js"; | ||
import userEvent from "@testing-library/user-event"; | ||
|
||
const user = userEvent.setup(); | ||
|
||
describe("<Disclosure />", () => { | ||
const disclosureTestId = "test-disclosure"; | ||
const title = "Test Title"; | ||
const child = "Test Child"; | ||
|
||
describe("Rendering", () => { | ||
it("should render without throwing an error", () => { | ||
render( | ||
<Disclosure data-testid={disclosureTestId} title={title}> | ||
{child} | ||
</Disclosure> | ||
); | ||
const disclosure = screen.getByTestId(disclosureTestId, {}); | ||
expect(disclosure).toBeVisible(); | ||
expect(disclosure).toHaveClass("rvt-disclosure"); | ||
|
||
const button = screen.getByRole("button", {}); | ||
expect(button).toBeVisible(); | ||
expect(button).toHaveClass("rvt-disclosure__toggle"); | ||
|
||
expect( | ||
screen.queryByTestId(TestUtils.Disclosure.testId, {}) | ||
).not.toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
describe("Rendering with isOpen", () => { | ||
it("should have visible content with isOpen", async () => { | ||
render( | ||
<Disclosure title={title} isOpen={true}> | ||
{child} | ||
</Disclosure> | ||
); | ||
|
||
const children = screen.queryByTestId(TestUtils.Disclosure.testId, {}); | ||
expect(children).toBeVisible(); | ||
expect(children).toHaveClass("rvt-disclosure__content"); | ||
}); | ||
}); | ||
|
||
describe("Toggle behavior", () => { | ||
it("should have a button that toggles visibility", async () => { | ||
render(<Disclosure title={title}>{child}</Disclosure>); | ||
|
||
await user.click(screen.getByRole("button", {})); | ||
const button = screen.getByRole("button", {}); | ||
const children = screen.queryByTestId(TestUtils.Disclosure.testId, {}); | ||
expect(children).toBeVisible(); | ||
expect(children).toHaveClass("rvt-disclosure__content"); | ||
expect(button).toHaveAttribute("aria-expanded", "true"); | ||
expect(screen.queryByText(child, {})).toBeVisible(); | ||
|
||
await user.click(screen.getByRole("button", {})); | ||
expect(button).toHaveAttribute("aria-expanded", "false"); | ||
expect( | ||
screen.queryByTestId(TestUtils.Disclosure.testId, {}) | ||
).not.toBeInTheDocument(); | ||
expect(screen.queryByText(child, {})).not.toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
describe("Toggle closeClickOutside behavior", () => { | ||
it("clicking outside should not close the Disclosure when closeClickOutside=false", async () => { | ||
render( | ||
<React.Fragment> | ||
<div data-testid={disclosureTestId}>Outside element</div> | ||
<Disclosure title={title} isOpen={true}> | ||
{child} | ||
</Disclosure> | ||
</React.Fragment> | ||
); | ||
|
||
expect(screen.queryByText(child, {})).toBeVisible(); | ||
await user.click(screen.getByTestId(disclosureTestId, {})); | ||
expect(screen.queryByText(child, {})).toBeVisible(); | ||
}); | ||
|
||
it("clicking outside should close the Disclosure when closeClickOutside=true", async () => { | ||
render( | ||
<React.Fragment> | ||
<div data-testid={disclosureTestId}>Outside element</div> | ||
<Disclosure title={title} isOpen={true} closeClickOutside={true}> | ||
{child} | ||
</Disclosure> | ||
</React.Fragment> | ||
); | ||
|
||
expect(screen.queryByText(child, {})).toBeVisible(); | ||
await user.click(screen.getByTestId(disclosureTestId, {})); | ||
expect(screen.queryByText(child, {})).not.toBeInTheDocument(); | ||
}); | ||
|
||
it("clicking inside or a button should not close the Disclosure when closeClickOutside=true", async () => { | ||
render( | ||
<React.Fragment> | ||
<div data-testid={disclosureTestId}>Outside element</div> | ||
<Disclosure title={title} isOpen={true} closeClickOutside={true}> | ||
{child} | ||
</Disclosure> | ||
</React.Fragment> | ||
); | ||
|
||
expect(screen.queryByText(child, {})).toBeVisible(); | ||
await user.click(screen.queryByText(child, {})); | ||
fireEvent.keyUp(document.body, { | ||
key: "Escape", | ||
}); | ||
fireEvent.keyUp(document.body, { | ||
key: "Tab", | ||
}); | ||
expect(screen.queryByText(child, {})).toBeVisible(); | ||
}); | ||
}); | ||
}); |
This file contains 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
This file contains 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