Toggletip: Add support to programmatically close it (#75846)

This commit is contained in:
Adela Almasan 2023-10-05 01:58:49 -05:00 committed by GitHub
parent c869844a78
commit 586c78a636
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 91 additions and 20 deletions

View File

@ -8,8 +8,9 @@ import { Toggletip } from './Toggletip';
describe('Toggletip', () => {
it('should display toggletip after click on "Click me!" button', async () => {
const onOpen = jest.fn();
render(
<Toggletip placement="auto" content="Tooltip text">
<Toggletip placement="auto" content="Tooltip text" onOpen={onOpen}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
@ -20,12 +21,70 @@ describe('Toggletip', () => {
await userEvent.click(button);
expect(screen.getByTestId('toggletip-content')).toBeInTheDocument();
expect(onOpen).toHaveBeenCalledTimes(1);
});
it('should display toggletip if configured as `show=true`', async () => {
render(
<Toggletip placement="auto" content="Tooltip text" show={true}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
</Toggletip>
);
expect(await screen.findByTestId('toggletip-content')).toBeInTheDocument();
});
it('should not close if configured as `show=true`', async () => {
const onClose = jest.fn();
render(
<Toggletip placement="auto" content="Tooltip text" show={true} onClose={onClose}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
</Toggletip>
);
expect(await screen.findByTestId('toggletip-content')).toBeInTheDocument();
// Close button should not close the toggletip
const closeButton = screen.getByTestId('toggletip-header-close');
expect(closeButton).toBeInTheDocument();
await userEvent.click(closeButton);
expect(onClose).toHaveBeenCalledTimes(1);
// Escape should not close the toggletip
const button = screen.getByTestId('myButton');
await userEvent.click(button);
await userEvent.keyboard('{escape}');
expect(onClose).toHaveBeenCalledTimes(2);
// Either way, the toggletip should still be visible
expect(await screen.findByTestId('toggletip-content')).toBeInTheDocument();
});
it('should not open if configured as `show=false`', async () => {
const onOpen = jest.fn();
render(
<Toggletip placement="auto" content="Tooltip text" show={false} onOpen={onOpen}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
</Toggletip>
);
const button = screen.getByTestId('myButton');
await userEvent.click(button);
expect(await screen.queryByTestId('toggletip-content')).not.toBeInTheDocument();
expect(onOpen).toHaveBeenCalledTimes(1);
});
it('should close toggletip after click on close button', async () => {
const closeSpy = jest.fn();
const onClose = jest.fn();
render(
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}>
<Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
@ -40,13 +99,13 @@ describe('Toggletip', () => {
expect(closeButton).toBeInTheDocument();
await userEvent.click(closeButton);
expect(closeSpy).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
});
it('should close toggletip after press ESC', async () => {
const closeSpy = jest.fn();
const onClose = jest.fn();
render(
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}>
<Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
@ -59,13 +118,13 @@ describe('Toggletip', () => {
await userEvent.keyboard('{escape}');
expect(closeSpy).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
});
it('should display the toggletip after press ENTER', async () => {
const closeSpy = jest.fn();
const onOpen = jest.fn();
render(
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}>
<Toggletip placement="auto" content="Tooltip text" onOpen={onOpen}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
@ -80,15 +139,16 @@ describe('Toggletip', () => {
await userEvent.keyboard('{enter}');
expect(screen.getByTestId('toggletip-content')).toBeInTheDocument();
expect(onOpen).toHaveBeenCalledTimes(1);
});
it('should be able to focus toggletip content next in DOM order - forwards and backwards', async () => {
const closeSpy = jest.fn();
const onClose = jest.fn();
const afterInDom = 'Outside of toggletip';
render(
<>
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}>
<Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
@ -134,9 +194,9 @@ describe('Toggletip', () => {
});
it('should restore focus to the button that opened the toggletip when closed from within the toggletip', async () => {
const closeSpy = jest.fn();
const onClose = jest.fn();
render(
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}>
<Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton">
Click me!
</Button>
@ -156,12 +216,12 @@ describe('Toggletip', () => {
});
it('should NOT restore focus to the button that opened the toggletip when closed from outside the toggletip', async () => {
const closeSpy = jest.fn();
const onClose = jest.fn();
const afterInDom = 'Outside of toggletip';
render(
<>
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}>
<Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton">
Click me!
</Button>

View File

@ -30,6 +30,10 @@ export interface ToggletipProps {
children: JSX.Element;
/** Determine whether the toggletip should fit its content or not */
fitContent?: boolean;
/** Determine whether the toggletip should be shown or not */
show?: boolean;
/** Callback function to be called when the toggletip is opened */
onOpen?: () => void;
}
export const Toggletip = React.memo(
@ -43,24 +47,31 @@ export const Toggletip = React.memo(
onClose,
footer,
fitContent = false,
onOpen,
show,
}: ToggletipProps) => {
const styles = useStyles2(getStyles);
const style = styles[theme];
const contentRef = useRef(null);
const [controlledVisible, setControlledVisible] = React.useState(false);
const [controlledVisible, setControlledVisible] = React.useState(show);
const { getArrowProps, getTooltipProps, setTooltipRef, setTriggerRef, visible, update, tooltipRef, triggerRef } =
usePopperTooltip(
{
visible: controlledVisible,
visible: show ?? controlledVisible,
placement: placement,
interactive: true,
offset: [0, 8],
// If show is undefined, the toggletip will be shown on click
trigger: 'click',
onVisibleChange: (value: boolean) => {
setControlledVisible(value);
if (!value) {
onVisibleChange: (visible: boolean) => {
if (show === undefined) {
setControlledVisible(visible);
}
if (!visible) {
onClose?.();
} else {
onOpen?.();
}
},
},