diff --git a/apps/web/src/modules/viewer/PhotoViewer.tsx b/apps/web/src/modules/viewer/PhotoViewer.tsx index d0d98665..5aa6928f 100644 --- a/apps/web/src/modules/viewer/PhotoViewer.tsx +++ b/apps/web/src/modules/viewer/PhotoViewer.tsx @@ -32,6 +32,7 @@ interface PhotoViewerProps { onClose: () => void onIndexChange: (index: number) => void triggerElement: HTMLElement | null + onExitComplete?: () => void } export const PhotoViewer = ({ @@ -41,6 +42,7 @@ export const PhotoViewer = ({ onClose, onIndexChange, triggerElement, + onExitComplete, }: PhotoViewerProps) => { const { t } = useTranslation() const isMobile = useMobile() @@ -68,6 +70,7 @@ export const PhotoViewer = ({ currentPhoto, currentBlobSrc, isMobile, + onExitComplete, }) useEffect(() => { diff --git a/apps/web/src/modules/viewer/animations/usePhotoViewerTransitions.ts b/apps/web/src/modules/viewer/animations/usePhotoViewerTransitions.ts index afcd7649..47fa3d02 100644 --- a/apps/web/src/modules/viewer/animations/usePhotoViewerTransitions.ts +++ b/apps/web/src/modules/viewer/animations/usePhotoViewerTransitions.ts @@ -12,6 +12,7 @@ interface UsePhotoViewerTransitionsParams { currentPhoto: PhotoManifest | undefined currentBlobSrc: string | null isMobile: boolean + onExitComplete?: () => void } interface UsePhotoViewerTransitionsResult { @@ -33,6 +34,7 @@ export const usePhotoViewerTransitions = ({ currentPhoto, currentBlobSrc, isMobile, + onExitComplete, }: UsePhotoViewerTransitionsParams): UsePhotoViewerTransitionsResult => { const containerRef = useRef(null) const cachedTriggerRef = useRef(triggerElement) @@ -208,6 +210,7 @@ export const usePhotoViewerTransitions = ({ if (!wasOpenRef.current || !currentPhoto) { wasOpenRef.current = false restoreTriggerElementVisibility() + onExitComplete?.() return } @@ -217,6 +220,7 @@ export const usePhotoViewerTransitions = ({ wasOpenRef.current = false restoreTriggerElementVisibility() setExitTransition(null) + onExitComplete?.() return } @@ -225,6 +229,7 @@ export const usePhotoViewerTransitions = ({ wasOpenRef.current = false restoreTriggerElementVisibility() setExitTransition(null) + onExitComplete?.() return } @@ -242,6 +247,7 @@ export const usePhotoViewerTransitions = ({ wasOpenRef.current = false restoreTriggerElementVisibility() setExitTransition(null) + onExitComplete?.() return } @@ -257,6 +263,7 @@ export const usePhotoViewerTransitions = ({ wasOpenRef.current = false restoreTriggerElementVisibility() setExitTransition(null) + onExitComplete?.() return } @@ -294,6 +301,7 @@ export const usePhotoViewerTransitions = ({ resolveTriggerElement, restoreTriggerElementVisibility, hideTriggerElement, + onExitComplete, ]) useLayoutEffect(() => { @@ -321,7 +329,8 @@ export const usePhotoViewerTransitions = ({ const handleExitAnimationComplete = useCallback(() => { restoreTriggerElementVisibility() setExitTransition(null) - }, [restoreTriggerElementVisibility]) + onExitComplete?.() + }, [restoreTriggerElementVisibility, onExitComplete]) const isEntryAnimating = Boolean(entryTransition) const shouldRenderBackdrop = isOpen || Boolean(exitTransition) || Boolean(entryTransition) diff --git a/apps/web/src/pages/(main)/photos/[photoId]/index.tsx b/apps/web/src/pages/(main)/photos/[photoId]/index.tsx index 52424e36..9dc665c4 100644 --- a/apps/web/src/pages/(main)/photos/[photoId]/index.tsx +++ b/apps/web/src/pages/(main)/photos/[photoId]/index.tsx @@ -1,6 +1,6 @@ import { RootPortal, RootPortalProvider } from '@afilmory/ui' import clsx from 'clsx' -import { useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { RemoveScroll } from 'react-remove-scroll' import { NotFound } from '~/components/common/NotFound' @@ -24,6 +24,20 @@ export const Component = () => { const [accentColor, setAccentColor] = useState(null) + // Track closing state to allow exit animation before navigation + const [isClosing, setIsClosing] = useState(false) + const closeViewerRef = useRef(photoViewer.closeViewer) + closeViewerRef.current = photoViewer.closeViewer + + const handleClose = useCallback(() => { + setIsClosing(true) + }, []) + + const handleExitComplete = useCallback(() => { + setIsClosing(false) + closeViewerRef.current() + }, []) + useEffect(() => { const current = photos[photoViewer.currentIndex] if (!current) return @@ -65,6 +79,8 @@ export const Component = () => { return } + const isOpen = photoViewer.isOpen && !isClosing + return ( @@ -75,15 +91,16 @@ export const Component = () => { } as React.CSSProperties } ref={setRef} - className={clsx(photoViewer.isOpen ? 'fixed inset-0 z-9999' : 'pointer-events-none fixed inset-0 z-40')} + className={clsx(isOpen ? 'fixed inset-0 z-9999' : 'pointer-events-none fixed inset-0 z-40')} >