fix(mobile): steady overlay entrance while the keyboard dismisses

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).
This commit is contained in:
Bohdan Triapitsyn
2026-07-05 00:17:19 +03:00
parent dbcb655e43
commit b19f65f9b6
2 changed files with 22 additions and 1 deletions
@@ -43,6 +43,12 @@ export const MobileOverlayPanel: React.FC<MobileOverlayPanelProps> = ({
}) => {
const overlayRootRef = React.useRef<HTMLElement | null>(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<MobileOverlayPanelProps> = ({
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<MobileOverlayPanelProps> = ({
<div
className={cn(
'oc-keyboard-inset-surface fixed inset-0 z-[60] flex flex-col bg-[rgb(0_0_0_/_0.45)] transition-opacity duration-200 ease-out',
!enterSettled && 'oc-keyboard-inset-snap',
entered ? 'opacity-100' : 'opacity-0',
)}
role="dialog"
+6
View File
@@ -583,6 +583,12 @@
bottom: var(--oc-keyboard-inset, 0px);
transition: bottom 0.25s cubic-bezier(0.38, 0.7, 0.125, 1), opacity 0.2s ease-out;
}
/* While a panel is playing its enter animation, its bottom anchor snaps
instead of transitioning — opening an overlay typically dismisses the
keyboard in the same moment, and animating both made the entrance jerky. */
:root.oc-capacitor-app .oc-keyboard-inset-surface.oc-keyboard-inset-snap {
transition: opacity 0.2s ease-out;
}
/* Full-screen scroll views (e.g. the connect/login screen) live outside the app
shell, so shrink them by the keyboard height the same way the shell does. Capping