+ );
+}
+
+/** Column header with the same grid as . Optional — pages can render it above the list. */
+export function SongListHeader() {
+ const { t } = useTranslation();
+ return (
+
+
+
{t('albumDetail.trackTitle')}
+
{t('albumDetail.trackArtist')}
+
{t('albumDetail.trackAlbum')}
+
{t('randomMix.trackGenre')}
+
{t('albumDetail.trackDuration')}
+
+ );
+}
+
+export default memo(SongRow);
diff --git a/src/components/VirtualSongList.tsx b/src/components/VirtualSongList.tsx
index e5174bd2..fb155c59 100644
--- a/src/components/VirtualSongList.tsx
+++ b/src/components/VirtualSongList.tsx
@@ -1,12 +1,10 @@
-import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { Play, Search as SearchIcon, X, ListPlus } from 'lucide-react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { Search as SearchIcon, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useVirtualizer } from '@tanstack/react-virtual';
import { SubsonicSong, searchSongsPaged } from '../api/subsonic';
import { ndListSongs } from '../api/navidromeBrowse';
-import { usePlayerStore, songToTrack } from '../store/playerStore';
-import { enqueueAndPlay } from '../utils/playSong';
+import SongRow, { SongListHeader } from './SongRow';
const PAGE_SIZE = 50;
const SEARCH_DEBOUNCE_MS = 300;
@@ -29,72 +27,6 @@ async function fetchSongPage(query: string, offset: number): Promise s.enqueue);
- const openContextMenu = usePlayerStore(s => s.openContextMenu);
-
- return (
-