veza/apps/web/src/components/player/audio-player/AudioPlayerTrackInfo.tsx
senke db0489d322 fix(a11y): Sprint 7 — semantic HTML and accessibility deep-dive
S7.1: Replace div onClick with semantic button in DialogTrigger.tsx
S7.2: Replace role="button" divs with native <button> elements in 12 files
      (PlaylistCard, TrackCard, ConversationItem, NotificationMenuItem,
       AudioPlayerTrackInfo, SearchPageResults, ProjectsManagerAddCard,
       ProjectsManagerCard, GearInventoryGrid, UploadModal, dropdown.tsx,
       LibraryPageGrid)
S7.3: Add focus-visible:ring-2 to 14 form inputs with outline-none across
      9 modal files (CreateGroupModal, DataExportModal, EditPlaylistModal,
      AddToPlaylistModal, BanUserModal, RefundRequestModal, FlashSaleModal,
      TipStreamerModal, CreatePostModal)
S7.4: Add semantic landmarks — <section> in DashboardPage, <article> in
      PostCard and CourseCard

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 10:34:39 +01:00

67 lines
2 KiB
TypeScript

import { useRef, useState, useEffect } from 'react';
import { cn } from '@/lib/utils';
interface Track {
title?: string;
artist?: string;
artistId?: string;
cover?: string;
}
interface AudioPlayerTrackInfoProps {
track: Track;
onArtistClick?: (artistId: string) => void;
}
export function AudioPlayerTrackInfo({ track, onArtistClick }: AudioPlayerTrackInfoProps) {
const titleRef = useRef<HTMLParagraphElement>(null);
const [isOverflowing, setIsOverflowing] = useState(false);
useEffect(() => {
const el = titleRef.current;
if (el) {
setIsOverflowing(el.scrollWidth > el.clientWidth);
}
}, [track.title]);
return (
<div className="flex items-center gap-3 min-w-0 group/track">
{/* Album art with ambient glow */}
{track.cover && (
<div className="relative shrink-0">
{/* Ambient glow */}
<div className="absolute inset-0 rounded-lg bg-primary/20 blur-xl scale-150 opacity-0 group-hover/track:opacity-100 transition-opacity duration-500" />
<img
src={track.cover}
alt={track.title ?? ''}
className="relative z-10 w-10 h-10 rounded object-cover"
/>
</div>
)}
<div className="min-w-0 flex-1">
<div className="overflow-hidden">
<p
ref={titleRef}
className={cn(
'text-sm font-medium text-foreground whitespace-nowrap',
isOverflowing
? 'animate-marquee'
: 'truncate',
)}
>
{track.title}
</p>
</div>
{track.artist && (
<button
type="button"
className="appearance-none bg-transparent border-0 p-0 text-xs text-muted-foreground truncate hover:text-foreground hover:underline transition-colors cursor-pointer text-left"
onClick={() => onArtistClick?.(track.artistId ?? '')}
>
{track.artist}
</button>
)}
</div>
</div>
);
}