forked from
chadtmiller.com/slices-teal-relay
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}