-
Notifications
You must be signed in to change notification settings - Fork 153
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
fix(Popover): popover content now closes on ESC key press #4557
base: master
Are you sure you want to change the base?
Conversation
Storybook staging is available at https://kiwicom-orbit-sarka-popover-esc-close.surge.sh |
Size Change: +111 B (+0.02%) Total Size: 459 kB
ℹ️ View Unchanged
|
7e5c738
to
fcb64e7
Compare
Deploying orbit with Cloudflare Pages
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key Issues
The else
block in handleOut
incorrectly closes the popover when interacting with its internal content, disrupting expected behavior by not distinguishing between clicks inside and outside the popover.
} else { | ||
setShown(false); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐛 Possible Bug
The new else
block in handleOut
will close the popover when clicking inside it, which breaks the expected behavior. The popover should only close when clicking outside (!ref.current.contains(ev.target)
) or when explicitly triggered. The current implementation will make the popover close when interacting with any content inside it, including buttons or form elements.
} else { | |
setShown(false); | |
} | |
} else if (typeof opened === "undefined" && onClose) { | |
onClose(); | |
} |
a565143
to
002b225
Compare
@@ -66,6 +66,9 @@ const Popover = ({ | |||
setRenderWithTimeout(false); | |||
resolveCallback(false); | |||
} else if (onClose) onClose(); | |||
} else { | |||
setShown(false); | |||
resolveCallback(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was checking Storybook and there are several issues now:
- On Popover open, both
onOpen
&onClose
events are fired (on mouse click, for keyboard only on second focus) - When I close the Popover with ESC,
onClose
event is firing on every ESC click even if the Popover has been closed - When I used
opened
prop and provided my ownonOpen
&onClose
functions, using keyboard I was able to close the Popover with ESC but I couldn't open it afterwards on the second focus. BothonOpen
&onClose
events been firing though. When I go back once more (shift + tab) to the first focus, I could open it normally. It worked with mouse click but then theonClose
event was firing twice. Some issues are present on production already, but adding ESC functionality is adding more mess 😅
I am not sure if fixing also the current issues (double focus) is in the scope of this MR but maybe we should expand the scope if all the issues are connected 🤔
002b225
to
8c88931
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key Issues
The event listener is not removed when the popover is closed through means other than pressing the Escape key, potentially causing memory leaks due to multiple event listeners being added.
const handleEsc = React.useCallback( | ||
(ev: { key: string }) => { | ||
if (ev.key === "Escape") { | ||
setShown(false); | ||
setRenderWithTimeout(false); | ||
document.removeEventListener("keydown", handleEsc); | ||
} | ||
}, | ||
[setShown, setRenderWithTimeout], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐛 Possible Bug
The event listener is only removed when the Escape key is pressed, but not when the popover is closed through other means (clicking outside or programmatically). This can lead to multiple event listeners being added and memory leaks.
const handleEsc = React.useCallback( | |
(ev: { key: string }) => { | |
if (ev.key === "Escape") { | |
setShown(false); | |
setRenderWithTimeout(false); | |
document.removeEventListener("keydown", handleEsc); | |
} | |
}, | |
[setShown, setRenderWithTimeout], | |
); | |
const handleEsc = React.useCallback( | |
(ev: { key: string }) => { | |
if (ev.key === "Escape") { | |
setShown(false); | |
setRenderWithTimeout(false); | |
} | |
}, | |
[setShown, setRenderWithTimeout], | |
); |
8c88931
to
5349315
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key Issues
The handleEsc
callback is missing resolveCallback
in its dependency array, which can cause it to use outdated data when the escape key is pressed.
const handleEsc = React.useCallback( | ||
(ev: { key: string }) => { | ||
if (ev.key === "Escape") { | ||
setShown(false); | ||
resolveCallback(false); | ||
setRenderWithTimeout(false); | ||
document.removeEventListener("keydown", handleEsc); | ||
} | ||
}, | ||
[setShown, setRenderWithTimeout], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐛 Possible Bug
The handleEsc
callback is missing resolveCallback
in its dependency array. This can lead to stale closures where the callback uses an outdated version of resolveCallback
when the escape key is pressed.
const handleEsc = React.useCallback( | |
(ev: { key: string }) => { | |
if (ev.key === "Escape") { | |
setShown(false); | |
resolveCallback(false); | |
setRenderWithTimeout(false); | |
document.removeEventListener("keydown", handleEsc); | |
} | |
}, | |
[setShown, setRenderWithTimeout], | |
); | |
const handleEsc = React.useCallback( | |
(ev: { key: string }) => { | |
if (ev.key === "Escape") { | |
setShown(false); | |
resolveCallback(false); | |
setRenderWithTimeout(false); | |
document.removeEventListener("keydown", handleEsc); | |
} | |
}, | |
[setShown, setRenderWithTimeout, resolveCallback], | |
); |
5349315
to
3efb26a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key Issues
The 'keydown' event listener is not properly removed when the popover is closed through means other than pressing the Escape key, potentially causing memory leaks and multiple event listeners to accumulate.
const handleEsc = React.useCallback( | ||
(ev: { key: string }) => { | ||
if (ev.key === "Escape") { | ||
setShown(false); | ||
clearShownTimeout(); | ||
resolveCallback(false); | ||
setRenderWithTimeout(false); | ||
document.removeEventListener("keydown", handleEsc); | ||
} | ||
}, | ||
[setShown, clearShownTimeout, resolveCallback, setRenderWithTimeout], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐛 Possible Bug
The 'keydown' event listener is only removed when the Escape key is pressed, but not when the popover is closed through other means (like clicking outside or programmatically). This can lead to memory leaks and multiple event listeners being attached if the popover is opened/closed repeatedly.
const handleEsc = React.useCallback( | |
(ev: { key: string }) => { | |
if (ev.key === "Escape") { | |
setShown(false); | |
clearShownTimeout(); | |
resolveCallback(false); | |
setRenderWithTimeout(false); | |
document.removeEventListener("keydown", handleEsc); | |
} | |
}, | |
[setShown, clearShownTimeout, resolveCallback, setRenderWithTimeout], | |
); | |
const handleEsc = React.useCallback( | |
(ev: { key: string }) => { | |
if (ev.key === "Escape") { | |
setShown(false); | |
clearShownTimeout(); | |
resolveCallback(false); | |
setRenderWithTimeout(false); | |
} | |
}, | |
[setShown, clearShownTimeout, resolveCallback, setRenderWithTimeout], | |
); |
3efb26a
to
a37e4d7
Compare
ev.stopPropagation(); | ||
setIsOpenedPopover(true); | ||
title="Card title" | ||
actions={ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved component to an actions
prop. After CardSection changes, the children
part of CardSection component is not clickable, so we couldn't demonstrate opening modal on wrapper click.
a37e4d7
to
a386339
Compare
closes https://kiwicom.atlassian.net/browse/FEPLT-2185
This Pull Request meets the following criteria:
For new components:
d.ts
files and are exported inindex.d.ts
✨
Description by Callstackai
This PR adds functionality to close the Popover component when the ESC key is pressed, along with visual regression tests for this feature.
Diagrams of code changes
Files Changed