import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'; import { getScrollBarWidth } from '@livekit/components-core'; import * as React from 'react'; import { useSize } from '../../hooks/internal'; import { useVisualStableUpdate } from '../../hooks'; import { TrackLoop } from '../TrackLoop'; const MIN_HEIGHT = 130; const MIN_WIDTH = 140; const MIN_VISIBLE_TILES = 1; const ASPECT_RATIO = 16 / 10; const ASPECT_RATIO_INVERT = (1 - ASPECT_RATIO) * -1; /** @public */ export interface CarouselLayoutProps extends React.HTMLAttributes { tracks: TrackReferenceOrPlaceholder[]; children: React.ReactNode; /** Place the tiles vertically or horizontally next to each other. * If undefined orientation is guessed by the dimensions of the container. */ orientation?: 'vertical' | 'horizontal'; } /** * The `CarouselLayout` component displays a list of tracks in a scroll container. * It will display as many tiles as possible and overflow the rest. * @remarks * To ensure visual stability when tiles are reordered due to track updates, * the component uses the `useVisualStableUpdate` hook. * @example * ```tsx * const tracks = useTracks([Track.Source.Camera]); * * * * ``` * @public */ export function CarouselLayout({ tracks, orientation, ...props }: CarouselLayoutProps) { const asideEl = React.useRef(null); const [prevTiles, setPrevTiles] = React.useState(0); const { width, height } = useSize(asideEl); const carouselOrientation = orientation ? orientation : height >= width ? 'vertical' : 'horizontal'; const tileSpan = carouselOrientation === 'vertical' ? Math.max(width * ASPECT_RATIO_INVERT, MIN_HEIGHT) : Math.max(height * ASPECT_RATIO, MIN_WIDTH); const scrollBarWidth = getScrollBarWidth(); const tilesThatFit = carouselOrientation === 'vertical' ? Math.max((height - scrollBarWidth) / tileSpan, MIN_VISIBLE_TILES) : Math.max((width - scrollBarWidth) / tileSpan, MIN_VISIBLE_TILES); let maxVisibleTiles = Math.round(tilesThatFit); if (Math.abs(tilesThatFit - prevTiles) < 0.5) { maxVisibleTiles = Math.round(prevTiles); } else if (prevTiles !== tilesThatFit) { setPrevTiles(tilesThatFit); } const sortedTiles = useVisualStableUpdate(tracks, maxVisibleTiles); React.useLayoutEffect(() => { if (asideEl.current) { asideEl.current.dataset.lkOrientation = carouselOrientation; asideEl.current.style.setProperty('--lk-max-visible-tiles', maxVisibleTiles.toString()); } }, [maxVisibleTiles, carouselOrientation]); return ( ); }