import { useRef, useState, useEffect, useCallback, RefObject } from 'react'; import { IMedia } from '../types'; interface UseAdjacentCardWidthProps { /** Ref to the container whose dimensions constrain the card width. */ containerRef: RefObject; nextPost: IMedia | null; prevPost: IMedia | null; /** * The settled card width from useMediaCardWidth (updated after the post changes * and the new image loads). Used to clear pendingCardWidth once the real width is ready. */ currentCardWidth: string; handleNextPost: () => void; handlePreviousPost: () => void; /** Maximum fraction of the container width (0–1). Defaults to 0.8, matching useMediaCardWidth. */ maxWidthRatio?: number; } interface UseAdjacentCardWidthResult { /** * Width value to apply to the card immediately on click, before the new post's image * has loaded, so the CSS resize transition starts right away instead of after load. * Clears automatically once `currentCardWidth` settles to the real value. */ pendingCardWidth: string | null; /** Wrapper: sets pendingCardWidth for the next post, then calls handleNextPost. */ handleNextWithWidth: () => void; /** Wrapper: sets pendingCardWidth for the previous post, then calls handlePreviousPost. */ handlePrevWithWidth: () => void; } /** * Pre-computes card widths for adjacent posts so the card resize animation starts * immediately when an arrow is clicked (instead of after the new image finishes loading). * * Works alongside useMediaCardWidth: that hook drives the authoritative `currentCardWidth`. * This hook only provides a `pendingCardWidth` that bridges the gap between the click * and the moment the real image loads. */ const useAdjacentCardWidth = ({ containerRef, nextPost, prevPost, currentCardWidth, handleNextPost, handlePreviousPost, maxWidthRatio = 0.8, }: UseAdjacentCardWidthProps): UseAdjacentCardWidthResult => { const precomputedWidthsRef = useRef>(new Map()); const [pendingCardWidth, setPendingCardWidth] = useState(null); const precomputeWidth = useCallback( (targetPost: IMedia | null) => { if (!targetPost || !containerRef.current) return; const imageUrl = targetPost.image || targetPost.thumbnail; if (!imageUrl) return; const container = containerRef.current; const img = new window.Image(); img.onload = () => { const containerH = container.clientHeight; const containerW = container.clientWidth; if (!containerH || !containerW || !img.naturalWidth || !img.naturalHeight) { precomputedWidthsRef.current.set(targetPost.id, 'auto'); return; } const ratio = img.naturalWidth / img.naturalHeight; const naturalWidth = containerH * ratio; const maxWidth = containerW * maxWidthRatio; precomputedWidthsRef.current.set( targetPost.id, `${Math.round(Math.min(naturalWidth, maxWidth))}px`, ); }; img.onerror = () => { precomputedWidthsRef.current.set(targetPost.id, 'auto'); }; img.src = imageUrl; }, [containerRef, maxWidthRatio], ); // Pre-load widths whenever the adjacent posts change. useEffect(() => { precomputeWidth(nextPost); precomputeWidth(prevPost); // eslint-disable-next-line react-hooks/exhaustive-deps }, [nextPost?.id, prevPost?.id]); // Once useMediaCardWidth settles on the real width, clear the pending override. useEffect(() => { setPendingCardWidth(null); }, [currentCardWidth]); const handleNextWithWidth = useCallback(() => { if (nextPost) { const w = precomputedWidthsRef.current.get(nextPost.id); if (w) setPendingCardWidth(w); } handleNextPost(); }, [nextPost, handleNextPost]); const handlePrevWithWidth = useCallback(() => { if (prevPost) { const w = precomputedWidthsRef.current.get(prevPost.id); if (w) setPendingCardWidth(w); } handlePreviousPost(); }, [prevPost, handlePreviousPost]); return { pendingCardWidth, handleNextWithWidth, handlePrevWithWidth }; }; export default useAdjacentCardWidth;