From b19f65f9b6a9926c83b76d08890f525836899988 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 5 Jul 2026 00:17:19 +0300 Subject: [PATCH] fix(mobile): steady overlay entrance while the keyboard dismisses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opening a bottom-sheet overlay usually dismisses the keyboard in the same moment, so the panel's keyboard-inset bottom anchor was transitioning down underneath the panel's own rise animation — a jerky, offset entrance. The anchor now snaps to its final position for the duration of the enter animation and resumes animating afterwards (so an already-open overlay still lifts smoothly when the keyboard reopens). --- .../ui/src/components/ui/MobileOverlayPanel.tsx | 17 ++++++++++++++++- packages/ui/src/styles/mobile.css | 6 ++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/ui/MobileOverlayPanel.tsx b/packages/ui/src/components/ui/MobileOverlayPanel.tsx index 55112d62..373bc6ae 100644 --- a/packages/ui/src/components/ui/MobileOverlayPanel.tsx +++ b/packages/ui/src/components/ui/MobileOverlayPanel.tsx @@ -43,6 +43,12 @@ export const MobileOverlayPanel: React.FC = ({ }) => { const overlayRootRef = React.useRef(null); const [entered, setEntered] = React.useState(false); + // True once the enter transition has finished. While entering, the panel's + // keyboard-inset bottom anchor must NOT animate: opening an overlay usually + // closes the keyboard at the same moment, and a transitioning `bottom` under + // the panel's own rise made the entrance jerky / offset. During the enter the + // anchor snaps to its final value and only the rise animates. + const [enterSettled, setEnterSettled] = React.useState(false); if (typeof document !== 'undefined' && !overlayRootRef.current) { overlayRootRef.current = ensureOverlayRoot(); @@ -52,10 +58,18 @@ export const MobileOverlayPanel: React.FC = ({ React.useEffect(() => { if (!open) { setEntered(false); + setEnterSettled(false); return; } const id = window.setTimeout(() => setEntered(true), ENTER_DELAY_MS); - return () => window.clearTimeout(id); + const settleId = window.setTimeout( + () => setEnterSettled(true), + ENTER_DELAY_MS + ENTER_DURATION_MS + 50, + ); + return () => { + window.clearTimeout(id); + window.clearTimeout(settleId); + }; }, [open]); React.useEffect(() => { @@ -86,6 +100,7 @@ export const MobileOverlayPanel: React.FC = ({