adjust top songs / favorite songs sections

- use table row height configuration for container calculation
- add wrapper component and use for both Top Songs and Favorite Songs
- remove the view more button, show all items by default
This commit is contained in:
jeffvli
2026-02-13 20:07:37 -08:00
parent d54baae3d9
commit e6b77e5883
@@ -225,6 +225,39 @@ const AlbumArtistMetadataBiography = ({
); );
}; };
const TABLE_ROW_HEIGHT = {
compact: 40,
default: 64,
large: 88,
} as const;
const TABLE_HEADER_HEIGHT = 40;
interface SongTableListContainerProps {
children: React.ReactNode;
enableHeader?: boolean;
itemCount: number;
maxRows?: number;
tableSize?: 'compact' | 'default' | 'large';
}
function getTableRowHeight(size: 'compact' | 'default' | 'large' | undefined): number {
return size ? TABLE_ROW_HEIGHT[size] : TABLE_ROW_HEIGHT.default;
}
const SongTableListContainer = ({
children,
enableHeader = true,
itemCount,
maxRows = 5,
tableSize = 'default',
}: SongTableListContainerProps) => {
const rowHeight = getTableRowHeight(tableSize);
const headerOffset = enableHeader ? TABLE_HEADER_HEIGHT : 0;
const height = headerOffset + rowHeight * Math.min(itemCount, maxRows);
return <div style={{ height }}>{children}</div>;
};
interface AlbumArtistMetadataTopSongsProps { interface AlbumArtistMetadataTopSongsProps {
detailQuery: ReturnType<typeof useSuspenseQuery<AlbumArtistDetailResponse>>; detailQuery: ReturnType<typeof useSuspenseQuery<AlbumArtistDetailResponse>>;
routeId: string; routeId: string;
@@ -237,7 +270,6 @@ const AlbumArtistMetadataTopSongsContent = ({
const { t } = useTranslation(); const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300); const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
const [showAll, setShowAll] = useState(false);
const [topSongsQueryType, setTopSongsQueryType] = useLocalStorage<'community' | 'personal'>({ const [topSongsQueryType, setTopSongsQueryType] = useLocalStorage<'community' | 'personal'>({
defaultValue: 'community', defaultValue: 'community',
key: 'album-artist-top-songs-query-type', key: 'album-artist-top-songs-query-type',
@@ -269,13 +301,8 @@ const AlbumArtistMetadataTopSongsContent = ({
}, [tableConfig?.columns]); }, [tableConfig?.columns]);
const filteredSongs = useMemo(() => { const filteredSongs = useMemo(() => {
const filtered = searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG); return searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG);
// When searching, show all results. Otherwise, limit to 5 if not showing all }, [songs, debouncedSearchTerm]);
if (debouncedSearchTerm?.trim() || showAll) {
return filtered;
}
return filtered.slice(0, 5);
}, [songs, debouncedSearchTerm, showAll]);
const { handleColumnReordered } = useItemListColumnReorder({ const { handleColumnReordered } = useItemListColumnReorder({
itemListKey: ItemListKey.SONG, itemListKey: ItemListKey.SONG,
@@ -459,35 +486,35 @@ const AlbumArtistMetadataTopSongsContent = ({
tableColumnsData={SONG_TABLE_COLUMNS} tableColumnsData={SONG_TABLE_COLUMNS}
/> />
</Group> </Group>
<ItemTableList <SongTableListContainer
activeRowId={currentSongId}
autoFitColumns={tableConfig.autoFitColumns}
CellComponent={ItemTableListColumn}
columns={columns}
data={filteredSongs}
enableAlternateRowColors={tableConfig.enableAlternateRowColors}
enableDrag
enableDragScroll={false}
enableExpansion={false}
enableHeader={tableConfig.enableHeader} enableHeader={tableConfig.enableHeader}
enableHorizontalBorders={tableConfig.enableHorizontalBorders} itemCount={filteredSongs.length}
enableRowHoverHighlight={tableConfig.enableRowHoverHighlight} maxRows={5}
enableSelection tableSize={tableConfig.size}
enableSelectionDialog={false} >
enableVerticalBorders={tableConfig.enableVerticalBorders} <ItemTableList
itemType={LibraryItem.SONG} activeRowId={currentSongId}
onColumnReordered={handleColumnReordered} autoFitColumns={tableConfig.autoFitColumns}
onColumnResized={handleColumnResized} CellComponent={ItemTableListColumn}
overrideControls={overrideControls} columns={columns}
size={tableConfig.size} data={filteredSongs}
/> enableAlternateRowColors={tableConfig.enableAlternateRowColors}
{!searchTerm.trim() && songs.length > 5 && !showAll && ( enableDrag
<Group justify="center" w="100%"> enableDragScroll={false}
<Button onClick={() => setShowAll(true)} variant="subtle"> enableExpansion={false}
{t('action.viewMore', { postProcess: 'sentenceCase' })} enableHeader={tableConfig.enableHeader}
</Button> enableHorizontalBorders={tableConfig.enableHorizontalBorders}
</Group> enableRowHoverHighlight={tableConfig.enableRowHoverHighlight}
)} enableSelection
enableSelectionDialog={false}
enableVerticalBorders={tableConfig.enableVerticalBorders}
itemType={LibraryItem.SONG}
onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized}
overrideControls={overrideControls}
size={tableConfig.size}
/>
</SongTableListContainer>
</> </>
) : null} ) : null}
</Stack> </Stack>
@@ -523,7 +550,6 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
const { t } = useTranslation(); const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300); const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
const [showAll, setShowAll] = useState(false);
const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.SONG]?.table); const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.SONG]?.table);
const currentSong = usePlayerSong(); const currentSong = usePlayerSong();
const player = usePlayer(); const player = usePlayer();
@@ -548,13 +574,8 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
}, [tableConfig?.columns]); }, [tableConfig?.columns]);
const filteredSongs = useMemo(() => { const filteredSongs = useMemo(() => {
const filtered = searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG); return searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG);
// When searching, show all results. Otherwise, limit to 5 if not showing all }, [songs, debouncedSearchTerm]);
if (debouncedSearchTerm?.trim() || showAll) {
return filtered;
}
return filtered.slice(0, 5);
}, [songs, debouncedSearchTerm, showAll]);
const { handleColumnReordered } = useItemListColumnReorder({ const { handleColumnReordered } = useItemListColumnReorder({
itemListKey: ItemListKey.SONG, itemListKey: ItemListKey.SONG,
@@ -717,8 +738,12 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
tableColumnsData={SONG_TABLE_COLUMNS} tableColumnsData={SONG_TABLE_COLUMNS}
/> />
</Group> </Group>
{/* Restrict the height. Rendering all items in the DOM makes for a long delay */} <SongTableListContainer
<div style={{ height: 50 + 64 * Math.min(songs.length, 5) }}> enableHeader={tableConfig.enableHeader}
itemCount={filteredSongs.length}
maxRows={5}
tableSize={tableConfig.size}
>
<ItemTableList <ItemTableList
activeRowId={currentSongId} activeRowId={currentSongId}
autoFitColumns={tableConfig.autoFitColumns} autoFitColumns={tableConfig.autoFitColumns}
@@ -741,15 +766,7 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
overrideControls={overrideControls} overrideControls={overrideControls}
size={tableConfig.size} size={tableConfig.size}
/> />
</div> </SongTableListContainer>
{!searchTerm.trim() && songs.length > 5 && !showAll && (
<Group justify="center" w="100%">
<Button onClick={() => setShowAll(true)} variant="subtle">
{t('action.viewMore', { postProcess: 'sentenceCase' })}
</Button>
</Group>
)}
</> </>
) : null} ) : null}
</Stack> </Stack>