93 lines
2.8 KiB
TypeScript
93 lines
2.8 KiB
TypeScript
'use client'
|
|
|
|
import { useMusicPlayer, type Track } from './music-provider'
|
|
import { DownloadButton } from './download-button'
|
|
import { SwipeableRow } from './swipeable-row'
|
|
import { Play, Pause, ListPlus } from 'lucide-react'
|
|
|
|
function formatDuration(secs: number) {
|
|
if (!secs) return ''
|
|
const m = Math.floor(secs / 60)
|
|
const s = Math.floor(secs % 60)
|
|
return `${m}:${s.toString().padStart(2, '0')}`
|
|
}
|
|
|
|
export function SongRow({
|
|
song,
|
|
songs,
|
|
index,
|
|
showDownload = false,
|
|
}: {
|
|
song: Track
|
|
songs: Track[]
|
|
index: number
|
|
showDownload?: boolean
|
|
}) {
|
|
const { state, playTrack, togglePlay, addToQueue } = useMusicPlayer()
|
|
const isActive = state.currentTrack?.id === song.id
|
|
const isPlaying = isActive && state.isPlaying
|
|
|
|
return (
|
|
<SwipeableRow onSwipeRight={() => addToQueue(song)}>
|
|
<div
|
|
className={`flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors group ${
|
|
isActive ? 'bg-primary/5' : ''
|
|
}`}
|
|
>
|
|
{/* Play button / track number */}
|
|
<button
|
|
onClick={() => isActive ? togglePlay() : playTrack(song, songs, index)}
|
|
className="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full hover:bg-muted transition-colors"
|
|
>
|
|
{isPlaying ? (
|
|
<Pause className="h-4 w-4 text-primary" />
|
|
) : (
|
|
<Play className="h-4 w-4 text-primary ml-0.5" />
|
|
)}
|
|
</button>
|
|
|
|
{/* Cover art */}
|
|
<div className="flex-shrink-0 w-10 h-10 rounded overflow-hidden bg-muted">
|
|
{song.coverArt ? (
|
|
<img
|
|
src={`/api/music/cover/${song.coverArt}?size=80`}
|
|
alt={song.album}
|
|
className="w-full h-full object-cover"
|
|
loading="lazy"
|
|
/>
|
|
) : (
|
|
<div className="w-full h-full bg-muted" />
|
|
)}
|
|
</div>
|
|
|
|
{/* Info */}
|
|
<div className="flex-1 min-w-0">
|
|
<div className={`text-sm font-medium truncate ${isActive ? 'text-primary' : ''}`}>
|
|
{song.title}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground truncate">
|
|
{song.artist} · {song.album}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Duration */}
|
|
<span className="text-xs text-muted-foreground tabular-nums hidden sm:block">
|
|
{formatDuration(song.duration)}
|
|
</span>
|
|
|
|
{/* Download */}
|
|
{showDownload && <DownloadButton track={song} />}
|
|
|
|
{/* Add to queue (desktop hover) */}
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); addToQueue(song) }}
|
|
className="p-1.5 opacity-0 group-hover:opacity-100 hover:bg-muted/50 rounded transition-all"
|
|
title="Add to queue"
|
|
>
|
|
<ListPlus className="h-4 w-4 text-muted-foreground" />
|
|
</button>
|
|
</div>
|
|
</SwipeableRow>
|
|
)
|
|
}
|