diff --git a/src/components/shared/ButtonWithPanel/index.tsx b/src/components/shared/ButtonWithPanel/index.tsx index 9c7e64e448..8e5cefe971 100644 --- a/src/components/shared/ButtonWithPanel/index.tsx +++ b/src/components/shared/ButtonWithPanel/index.tsx @@ -88,8 +88,6 @@ export class ButtonWithPanel extends React.PureComponent { }; _onPanelClose = () => { - this.setState({ open: false }); - // Let's focus the delete button after dismissing the dialog, but _only_ if // the focus was part of the dialog before. // Note this branch isn't tested because jsdom doesn't support the @@ -116,6 +114,8 @@ export class ButtonWithPanel extends React.PureComponent { closePanel() { if (this._panel && this.state.open) { + // Close immediately so the button doesn't stay active while the panel animates out. + this.setState({ open: false }); this._panel.close(); } } diff --git a/src/test/components/ButtonWithPanel.test.tsx b/src/test/components/ButtonWithPanel.test.tsx index 5169a95f42..01ef47ffa2 100644 --- a/src/test/components/ButtonWithPanel.test.tsx +++ b/src/test/components/ButtonWithPanel.test.tsx @@ -175,4 +175,20 @@ describe('shared/ButtonWithPanel', () => { }); expect(container.querySelector('.arrowPanel.open')).toBe(null); }); + + it('marks the button as no longer open as soon as the panel starts closing', () => { + const { container } = setup(); + + fireFullClick(screen.getByText('My Button')); + act(() => { + jest.runAllTimers(); + }); + ensureExists(container.querySelector('.buttonWithPanel.open')); + + fireFullClick(screen.getByText('My Button')); + + // Timers are deliberately not run: the panel is still animating out. + expect(container.querySelector('.buttonWithPanel.open')).toBe(null); + ensureExists(container.querySelector('.arrowPanel')); + }); });