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', () => { describe('Toggletip', () => {
it('should display toggletip after click on "Click me!" button', async () => { it('should display toggletip after click on "Click me!" button', async () => {
const onOpen = jest.fn();
render( render(
<Toggletip placement="auto" content="Tooltip text"> <Toggletip placement="auto" content="Tooltip text" onOpen={onOpen}>
<Button type="button" data-testid="myButton"> <Button type="button" data-testid="myButton">
Click me! Click me!
</Button> </Button>
@ -20,12 +21,70 @@ describe('Toggletip', () => {
await userEvent.click(button); await userEvent.click(button);
expect(screen.getByTestId('toggletip-content')).toBeInTheDocument(); 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 () => { it('should close toggletip after click on close button', async () => {
const closeSpy = jest.fn(); const onClose = jest.fn();
render( render(
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}> <Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton"> <Button type="button" data-testid="myButton">
Click me! Click me!
</Button> </Button>
@ -40,13 +99,13 @@ describe('Toggletip', () => {
expect(closeButton).toBeInTheDocument(); expect(closeButton).toBeInTheDocument();
await userEvent.click(closeButton); await userEvent.click(closeButton);
expect(closeSpy).toHaveBeenCalledTimes(1); expect(onClose).toHaveBeenCalledTimes(1);
}); });
it('should close toggletip after press ESC', async () => { it('should close toggletip after press ESC', async () => {
const closeSpy = jest.fn(); const onClose = jest.fn();
render( render(
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}> <Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton"> <Button type="button" data-testid="myButton">
Click me! Click me!
</Button> </Button>
@ -59,13 +118,13 @@ describe('Toggletip', () => {
await userEvent.keyboard('{escape}'); await userEvent.keyboard('{escape}');
expect(closeSpy).toHaveBeenCalledTimes(1); expect(onClose).toHaveBeenCalledTimes(1);
}); });
it('should display the toggletip after press ENTER', async () => { it('should display the toggletip after press ENTER', async () => {
const closeSpy = jest.fn(); const onOpen = jest.fn();
render( render(
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}> <Toggletip placement="auto" content="Tooltip text" onOpen={onOpen}>
<Button type="button" data-testid="myButton"> <Button type="button" data-testid="myButton">
Click me! Click me!
</Button> </Button>
@ -80,15 +139,16 @@ describe('Toggletip', () => {
await userEvent.keyboard('{enter}'); await userEvent.keyboard('{enter}');
expect(screen.getByTestId('toggletip-content')).toBeInTheDocument(); 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 () => { 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'; const afterInDom = 'Outside of toggletip';
render( render(
<> <>
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}> <Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton"> <Button type="button" data-testid="myButton">
Click me! Click me!
</Button> </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 () => { 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( render(
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}> <Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton"> <Button type="button" data-testid="myButton">
Click me! Click me!
</Button> </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 () => { 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'; const afterInDom = 'Outside of toggletip';
render( render(
<> <>
<Toggletip placement="auto" content="Tooltip text" onClose={closeSpy}> <Toggletip placement="auto" content="Tooltip text" onClose={onClose}>
<Button type="button" data-testid="myButton"> <Button type="button" data-testid="myButton">
Click me! Click me!
</Button> </Button>

View File

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