Files
muzick/frontend/src/components/ArtistLinks.tsx
T

68 lines
2.4 KiB
TypeScript

import { Link } from '@tanstack/react-router';
import type { TrackArtist } from '../types';
interface ArtistLinksProps {
/** Ordered artists (first = main, rest = featured). */
artists?: TrackArtist[] | null;
/** Shown when there are no structured artists (plain text, not a link). */
fallback?: string;
className?: string;
/** Stop row/card click handlers from firing when an artist link is clicked. */
stopPropagation?: boolean;
}
/**
* Deduplicates artists by ID, preferring `main` over `featured` when the
* same artist has both roles (can happen because track_artists has a composite
* PK of track_id + artist_id + role).
*/
function deduplicateArtists(artists: TrackArtist[]): TrackArtist[] {
const map = new Map<string, TrackArtist>();
for (const a of artists) {
const existing = map.get(a.id);
if (!existing || (existing.role === 'featured' && a.role === 'main')) {
map.set(a.id, a);
}
}
// Preserve original order, skipping duplicates.
const seen = new Set<string>();
return artists.filter((a) => {
if (seen.has(a.id)) return false;
seen.add(a.id);
return true;
});
}
/**
* Renders a track/album's artists as clickable links — main artist(s) then
* "feat." guests. Single source of truth used by TrackRow, the playback bar,
* the now-playing panel and album pages so artist navigation looks and behaves
* the same everywhere.
*
* Artists are deduplicated by id — if the same artist appears as both main
* and featured, only the main entry is shown.
*/
export function ArtistLinks({ artists, fallback, className = '', stopPropagation }: ArtistLinksProps) {
if (!artists || artists.length === 0) {
return <span className={className}>{fallback || 'Unknown artist'}</span>;
}
const unique = deduplicateArtists(artists);
return (
<span className={className}>
{unique.map((a, i) => (
<span key={a.id}>
{i > 0 && <span className="opacity-60">{a.role === 'featured' && unique[i - 1].role !== 'featured' ? ' feat. ' : ', '}</span>}
<Link
to="/artists/$artistId"
params={{ artistId: a.id }}
onClick={stopPropagation ? (e) => e.stopPropagation() : undefined}
className={`hover:text-text hover:underline ${a.role === 'featured' ? 'opacity-75' : ''}`}
>
{a.name}
</Link>
</span>
))}
</span>
);
}