fix(tracks): RC3 layout regressions + multi-artist links (#976)

* fix(tracks): RC3 layout regressions + multi-artist links

- Restore vertical rhythm on the Tracks hub: the header/hero/rails/browse
  sections sat two wrapper divs below .tracks-page so its flex gap never
  reached them and they collapsed together. New .tracks-hub-stack re-applies
  the gap, fixing the tagline-touching-hero and Random-Pick-overlapping-hero
  spacing (the rail's nav buttons no longer ride into the box above).
- Stop the sticky browse-table header going transparent on hover: its base
  background became opaque but the :hover rule still forced transparent, so
  rows bled through. Header now keeps its card background on hover.
- Widen the Duration column (56px -> 72px; 56 -> 64 on mobile) so the
  "DURATION" header no longer clips/overruns.
- Split multi-artist tracks into individually clickable artist links in both
  the "Track of the moment" hero and the browse list rows (OpenSubsonic
  artists[] with single-artist fallback), matching the album tracklist.

* docs(changelog): Tracks RC3 spacing, Duration, header hover, multi-artist (#976)
This commit is contained in:
Frank Stellmacher
2026-06-04 02:28:13 +02:00
committed by GitHub
parent 908c349cfd
commit 88df194808
8 changed files with 64 additions and 19 deletions
+18 -7
View File
@@ -39,6 +39,13 @@ function SongRow({ song, showBpm }: Props) {
enqueue([songToTrack(song)]);
};
// Split multi-artist tracks into individually clickable links (OpenSubsonic
// `artists[]`), falling back to the single flat artist when absent — mirrors
// the album tracklist so a "A · B" credit isn't one link to a single artist.
const artistRefs = song.artists && song.artists.length > 0
? song.artists
: [{ id: song.artistId, name: song.artist }];
const bpmTooltip =
song.localBpmSource === 'analysis'
? t('search.bpmSourceAnalysis')
@@ -93,13 +100,17 @@ function SongRow({ song, showBpm }: Props) {
</button>
</div>
<div className="song-list-row-cell song-list-row-title truncate" title={song.title}>{song.title}</div>
<div className="song-list-row-cell truncate">
<span
className={song.artistId ? 'track-artist-link' : ''}
style={{ cursor: song.artistId ? 'pointer' : 'default' }}
onClick={(e) => { if (song.artistId) { e.stopPropagation(); navigateToArtist(song.artistId); } }}
title={song.artist}
>{song.artist}</span>
<div className="song-list-row-cell truncate" title={song.artist}>
{artistRefs.map((a, i) => (
<React.Fragment key={a.id ?? a.name ?? i}>
{i > 0 && <span className="track-artist-sep">&nbsp;·&nbsp;</span>}
<span
className={a.id ? 'track-artist-link' : ''}
style={{ cursor: a.id ? 'pointer' : 'default' }}
onClick={(e) => { if (a.id) { e.stopPropagation(); navigateToArtist(a.id!); } }}
>{a.name ?? song.artist}</span>
</React.Fragment>
))}
</div>
<div className="song-list-row-cell truncate">
{song.albumId ? (