Teal.fm frontend powered by slices.network tealfm-slices.wisp.place
tealfm slices
1import { graphql, useFragment } from "react-relay"; 2import type { TrackItem_play$key } from "./__generated__/TrackItem_play.graphql"; 3import { useAlbumArt } from "./useAlbumArt"; 4 5interface TrackItemProps { 6 play: TrackItem_play$key; 7} 8 9export default function TrackItem({ play }: TrackItemProps) { 10 const data = useFragment( 11 graphql` 12 fragment TrackItem_play on FmTealAlphaFeedPlay { 13 trackName 14 playedTime 15 artists 16 releaseName 17 releaseMbId 18 actorHandle 19 appBskyActorProfile { 20 displayName 21 } 22 } 23 `, 24 play 25 ); 26 27 const { albumArtUrl, isLoading } = useAlbumArt(data.releaseMbId); 28 29 return ( 30 <div className="group py-3 px-4 hover:bg-zinc-900/50 transition-colors"> 31 <div className="flex items-center gap-4"> 32 <div className="flex-shrink-0"> 33 {isLoading ? ( 34 <div className="w-10 h-10 bg-zinc-800 animate-pulse" /> 35 ) : albumArtUrl ? ( 36 <img 37 src={albumArtUrl} 38 alt={`${data.trackName} album art`} 39 className="w-10 h-10 object-cover" 40 loading="lazy" 41 /> 42 ) : ( 43 <div className="w-10 h-10 bg-zinc-800 flex items-center justify-center"> 44 <svg className="w-5 h-5 text-zinc-600" fill="currentColor" viewBox="0 0 20 20"> 45 <path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" /> 46 </svg> 47 </div> 48 )} 49 </div> 50 51 <div className="flex-1 min-w-0 grid grid-cols-2 gap-4"> 52 <div className="min-w-0"> 53 <h3 className="text-sm font-medium text-zinc-100 truncate"> 54 {data.trackName} 55 </h3> 56 <p className="text-xs text-zinc-500 truncate"> 57 {Array.isArray(data.artists) 58 ? data.artists.map((a) => a.artistName).join(", ") 59 : data.artists} 60 </p> 61 </div> 62 63 <div className="text-right min-w-0"> 64 <p className="text-xs text-zinc-400 truncate"> 65 {data.releaseName} 66 </p> 67 <div className="flex items-center justify-end gap-2 mt-0.5 min-w-0 overflow-hidden"> 68 {data.playedTime && ( 69 <p className="text-xs text-zinc-600 flex-shrink-0"> 70 {new Date(data.playedTime).toLocaleTimeString("en-US", { 71 hour: "numeric", 72 minute: "2-digit", 73 })} 74 </p> 75 )} 76 <a 77 href={`/profile/${data.actorHandle}`} 78 className="text-xs text-violet-500 hover:text-violet-400 transition-colors truncate block max-w-[120px]" 79 > 80 @{data.actorHandle} 81 </a> 82 </div> 83 </div> 84 </div> 85 </div> 86 </div> 87 ); 88}