diff --git a/packages/ui/src/components/ui/MobileOverlayPanel.tsx b/packages/ui/src/components/ui/MobileOverlayPanel.tsx index 9a3b2044..b43b1502 100644 --- a/packages/ui/src/components/ui/MobileOverlayPanel.tsx +++ b/packages/ui/src/components/ui/MobileOverlayPanel.tsx @@ -16,6 +16,9 @@ interface MobileOverlayPanelProps { } const OVERLAY_ROOT_ID = 'mobile-overlay-root'; +// Entrance animation: classic slide up from the bottom + scrim fade. +const ENTER_DELAY_MS = 16; +const ENTER_DURATION_MS = 200; const ensureOverlayRoot = () => { if (typeof document === 'undefined') return null; @@ -39,11 +42,22 @@ export const MobileOverlayPanel: React.FC = ({ renderHeader, }) => { const overlayRootRef = React.useRef(null); + const [entered, setEntered] = React.useState(false); if (typeof document !== 'undefined' && !overlayRootRef.current) { overlayRootRef.current = ensureOverlayRoot(); } + // Replay the enter transition on each open (rise + scrim fade). + React.useEffect(() => { + if (!open) { + setEntered(false); + return; + } + const id = window.setTimeout(() => setEntered(true), ENTER_DELAY_MS); + return () => window.clearTimeout(id); + }, [open]); + React.useEffect(() => { if (!open) { return; @@ -70,7 +84,10 @@ export const MobileOverlayPanel: React.FC = ({ const content = (
= ({ 'mx-auto max-w-lg', className )} + style={{ + transform: entered ? 'none' : 'translateY(100%)', + transition: `transform ${ENTER_DURATION_MS}ms cubic-bezier(0.32, 0.72, 0, 1)`, + }} onClick={(event) => event.stopPropagation()} > {(() => {