A personal media tracker built on the AT Protocol
opnshelf.xyz
1import type { ListSummaryDto } from "@opnshelf/api";
2import { Link } from "@tanstack/react-router";
3import { List, Star } from "lucide-react";
4import { useTheme } from "@/components/theme-provider";
5import {
6 M3Card,
7 M3CardContent,
8 M3CardDescription,
9 M3CardHeader,
10 M3CardTitle,
11} from "@/components/ui/m3-card";
12import { getProfileListDetailRoute } from "@/lib/profile-routes";
13
14interface ListCardProps {
15 handle: string;
16 list: ListSummaryDto;
17}
18
19export function ListCard({ handle, list }: ListCardProps) {
20 const { seedColor } = useTheme();
21
22 const getIcon = () => {
23 if (list.slug.includes("watchlist")) {
24 return <List className="w-5 h-5" />;
25 }
26 if (list.slug.includes("favorites")) {
27 return <Star className="w-5 h-5" />;
28 }
29 return <List className="w-5 h-5" />;
30 };
31
32 return (
33 <Link
34 {...getProfileListDetailRoute(handle, list.slug)}
35 search={{ page: 1 }}
36 >
37 <M3Card
38 variant="elevated"
39 className="cursor-pointer h-full transition-all hover:md-elevation-2"
40 >
41 <M3CardHeader className="pb-2">
42 <div className="flex items-center gap-2">
43 <div
44 className="p-2 rounded-lg"
45 style={{
46 backgroundColor: `${seedColor}20`,
47 color: seedColor,
48 }}
49 >
50 {getIcon()}
51 </div>
52 <div className="flex-1 min-w-0">
53 <M3CardTitle className="md-title-medium truncate">
54 {list.name}
55 </M3CardTitle>
56 {list.isDefault && (
57 <span className="md-label-small" style={{ color: seedColor }}>
58 Default list
59 </span>
60 )}
61 </div>
62 </div>
63 </M3CardHeader>
64 <M3CardContent>
65 {list.description && (
66 <M3CardDescription className="line-clamp-2 mb-2">
67 {list.description}
68 </M3CardDescription>
69 )}
70 <p
71 className="md-body-medium"
72 style={{ color: "var(--md-sys-color-on-surface-variant)" }}
73 >
74 {list.itemCount} item{list.itemCount !== 1 ? "s" : ""}
75 </p>
76 </M3CardContent>
77 </M3Card>
78 </Link>
79 );
80}