fix(stories): portal the story overlay to escape overflow-auto ancestor
Real root cause of the mobile rendering bug (confirmed via an actual Android Chrome screenshot, not devtools emulation): StoryViewer's fixed inset-0 div is a descendant of <main className="overflow-auto"> in the (app) layout. On Android Chrome, a position:fixed element inside an overflow-auto ancestor can size itself against that ancestor's full scrollable content instead of the real viewport — the overlay rendered many times taller than the screen, so only a small top slice was visible, looking like a heavily zoomed crop. The previous two commits were chasing the video's object-fit/aspect-ratio, which was never the actual problem. Renders the overlay via createPortal to document.body instead, the same pattern Radix/Base UI's Sheet and Dialog already use internally for exactly this class of bug.
This commit is contained in:
1 parent
661f36475a
commit
27cb34df27
1 file changed
+17
-3
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import dynamic from "next/dynamic";
|
||||
import { X, Eye, Volume2, VolumeX, Loader2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
@@ -65,6 +66,12 @@ export function StoryViewer({
|
||||
const [isMuted, setIsMuted] = useState(true);
|
||||
const advancedRef = useRef(false);
|
||||
|
||||
// Portal target must wait for mount (document isn't available during SSR).
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const { data: stories = [] } = useQuery(
|
||||
trpc.stories.listActive.queryOptions({ petId })
|
||||
);
|
||||
@@ -183,8 +190,14 @@ export function StoryViewer({
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Full-screen overlay */}
|
||||
{open && (
|
||||
{/* Full-screen overlay — portaled to document.body so `position: fixed`
|
||||
escapes the (app) layout's <main className="overflow-auto"> ancestor.
|
||||
A fixed descendant of an overflow-auto container can, on some mobile
|
||||
browsers (confirmed on Android Chrome), size itself against that
|
||||
container's full scrollable content instead of the real viewport —
|
||||
the story then renders many times taller than the screen and only
|
||||
a small top slice is visible, looking like a heavily zoomed crop. */}
|
||||
{open && mounted && createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center"
|
||||
role="dialog"
|
||||
@@ -337,7 +350,8 @@ export function StoryViewer({
|
||||
<span>{viewers.length}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
{/* Viewer list sheet — owner only */}
|
||||
|
||||
Reference in new issue
Block a user