Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions components/album/progressive-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import type { ProgressiveImageProps } from '~/types/props.ts'
import { useEffect, useState, useRef, Activity } from 'react'
import { createPortal } from 'react-dom'
import { useTranslations } from 'next-intl'
import { MotionImage } from '~/components/album/motion-image'
import { useBlurImageDataUrl } from '~/hooks/use-blurhash'
Expand Down Expand Up @@ -237,7 +238,14 @@ export default function ProgressiveImage(
view, and pointer-events are disabled while hidden so the invisible
overlay never blocks the page. Mounted lazily on first open so the
engine isn't built until the user actually zooms. */}
{hasOpenedFullScreen && props.keepViewerMounted !== false && (
{/* Portal the fullscreen overlay to <body> so its `fixed inset-0`
positioning is relative to the viewport. When the detail page is
opened as the intercepted-route modal, an ancestor (Radix
DialogContent) carries a `translate(-50%,-50%)` transform, which
would otherwise become the containing block for `position: fixed`
and trap the viewer inside the dialog box → blank fullscreen. The
guard keeps it inert during SSR. */}
{hasOpenedFullScreen && props.keepViewerMounted !== false && typeof document !== 'undefined' && createPortal(
webGLAvailable ? <div
className="fixed inset-0 z-[100] bg-background/95 flex items-center justify-center"
style={{ visibility: showFullScreenViewer ? 'visible' : 'hidden', pointerEvents: showFullScreenViewer ? 'auto' : 'none' }}
Expand Down Expand Up @@ -331,7 +339,8 @@ export default function ProgressiveImage(
src={highResImageUrl}
alt={props.alt || 'image'}
/>
</div>
</div>,
document.body
)}
</>
) : null}
Expand Down
Loading