/** * useCarouselHeight * * Calculates and returns a responsive minimum height for a carousel container * based on the width of its parent element and the expected thumbnail aspect ratio. * * This ensures that the carousel container allocates enough vertical space * to prevent layout shifts when a thumbnail "grows" (e.g. when active). * * The height is calculated using: * - the container’s current width (via ResizeObserver), * - the active thumbnail aspect ratio (per shape: square, portrait, stories), * - the number of visible thumbnails (based solely on contentSize), * - a fractional width for partially visible items (default: partialItemWidth from consts), * - and an optional pixel buffer for spacing (default: 40). * * @param ref - a ref pointing to the carousel container element * @param shape - one of: 'square', 'portrait', 'stories'; determines aspect ratio * @param contentSize - max number of visible items configured * @param itemCount - actual number of carousel items * @param partialItemWidth - fraction of width reserved for a partially visible item (default: partialItemWidth from consts) * @param buffer - additional space to reserve below thumbnails (default: 40) * @returns the calculated height in pixels */ import { useEffect, useState, RefObject } from 'react'; import { activeSquareAspectRatio, activePortraitAspectRatio, activeStoriesAspectRatio, } from '../components/consts'; import { ISettings } from '../types'; const ACTIVE_SLIDE_ASPECT_RATIOS: Record = { square: activeSquareAspectRatio, portrait: activePortraitAspectRatio, stories: activeStoriesAspectRatio, }; interface UseCarouselHeightParams { ref: RefObject; shape: ISettings['thumbnail_shape']; contentSize: ISettings['content_size']; itemCount: number; partialItemWidth: number; buffer?: number; } export const useCarouselHeight = (params: UseCarouselHeightParams): number => { const { ref, shape, contentSize, itemCount, partialItemWidth, buffer = 40 } = params; const [height, setHeight] = useState(0); useEffect(() => { if (!ref.current) return; const updateHeight = (): void => { if (!ref.current) return; const containerWidth = ref.current.offsetWidth; const aspectRatioPercent = ACTIVE_SLIDE_ASPECT_RATIOS[shape] ?? 100; const aspectRatio = aspectRatioPercent / 100; const configuredVisibleItems = contentSize > 0 ? contentSize : Math.max(itemCount, 1); const isEven = configuredVisibleItems % 2 === 0; const partialWidth = isEven ? partialItemWidth : 0; const itemWidth = containerWidth / (configuredVisibleItems + partialWidth); setHeight(itemWidth * aspectRatio + buffer); }; updateHeight(); const observer = new ResizeObserver(updateHeight); observer.observe(ref.current); return () => { observer.disconnect(); }; }, [ref, shape, contentSize, itemCount, partialItemWidth, buffer]); return height; };