import type { SubsonicSong } from '../api/subsonicTypes'; import React, { useEffect, useRef } from 'react'; import SongRow, { SongListHeader } from './SongRow'; interface Props { songs: SubsonicSong[]; /** More pages available — renders the load-more sentinel. */ hasMore: boolean; /** A page fetch is in flight — shows the sentinel spinner. */ loadingMore: boolean; /** Fetch the next page. Called as the sentinel nears the viewport. */ onLoadMore: () => void; /** Show a BPM column (Advanced Search when the BPM filter is active). */ showBpm?: boolean; } /** * Shared song-list view: sticky column header + plain `SongRow`s in the page * flow, with an `IntersectionObserver` sentinel for pagination. Used by the * Tracks browse list, Search results, and Advanced Search so the three share * one chrome + paging path (no transform-positioned rows, so the sticky header * is never painted over — issue #841). */ export default function PagedSongList({ songs, hasMore, loadingMore, onLoadMore, showBpm }: Props) { const sentinelRef = useRef(null); // Re-observe whenever `onLoadMore` changes identity (callers rebuild it after // each page), so a sentinel still in view keeps loading the next page. useEffect(() => { const el = sentinelRef.current; if (!el) return; const obs = new IntersectionObserver(entries => { if (entries[0]?.isIntersecting) onLoadMore(); }, { rootMargin: '600px' }); obs.observe(el); return () => obs.disconnect(); }, [onLoadMore]); return ( <> {songs.map(song => ( ))} {hasMore && (
{loadingMore &&
}
)} ); }