mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
fix(library): browse-all-tracks shares the Search song-list view (#854)
* fix(library): browse-all-tracks shares the Search song-list view (#841) "Browse all tracks" rendered virtualized, transform-positioned rows inside its own scroll box, so the sticky column header got painted over while scrolling. Extract the Search / Advanced-Search song-list chrome (sticky header + plain SongRows + IntersectionObserver sentinel paging) into a shared PagedSongList and route all three through it; Browse now flows in the page like the Search pages, so the header can no longer be overlapped. Trade-off: Browse-all drops its bespoke row virtualization to match the Search pages (DOM grows with scroll as they do); paging is unchanged. Also removes the duplicated sentinel/observer in SearchResults and AdvancedSearch. # Conflicts: # src/components/VirtualSongList.tsx # src/pages/SearchResults.tsx * docs(changelog): browse-all-tracks sticky header fix (#854)
This commit is contained in:
committed by
GitHub
parent
cc8e6cc811
commit
cb4d331f99
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 }: Props) {
|
||||
const sentinelRef = useRef<HTMLDivElement>(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 (
|
||||
<>
|
||||
<SongListHeader />
|
||||
{songs.map(song => (
|
||||
<SongRow key={song.id} song={song} />
|
||||
))}
|
||||
{hasMore && (
|
||||
<div ref={sentinelRef} style={{ display: 'flex', justifyContent: 'center', padding: '1rem' }}>
|
||||
{loadingMore && <div className="spinner" style={{ width: 20, height: 20 }} />}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user