Files
cydweb/components/firmware-card.tsx

85 lines
2.8 KiB
TypeScript

"use client";
import { FileText, LayoutTemplate } from "lucide-react";
import type { FirmwareInfo } from "@/lib/firmware";
import { formatBytes, cn } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";
import { FirmwarePreview } from "@/components/firmware-preview";
export function FirmwareCard({
fw,
active,
onSelect,
}: {
fw: FirmwareInfo;
active: boolean;
onSelect: () => void;
}) {
const hasPreview = Boolean(fw.media.preview);
const hasDocs = Boolean(fw.media.docs);
return (
<button
type="button"
role="option"
aria-selected={active}
onClick={onSelect}
className={cn(
"group flex flex-col overflow-hidden rounded-[14px] border text-left transition-all duration-200",
active
? "border-[var(--accent)] bg-[rgba(240,197,10,0.08)] shadow-[0_0_0_1px_rgba(240,197,10,0.3),0_12px_32px_rgba(0,0,0,0.35)]"
: "border-[var(--line)] bg-[rgba(0,0,0,0.22)] hover:-translate-y-0.5 hover:border-[rgba(240,197,10,0.35)] hover:bg-[rgba(240,197,10,0.04)]"
)}
>
<div
className={
fw.boardId === "esp32-3248s035"
? "relative aspect-[3/2] w-full overflow-hidden border-b border-[var(--line)]"
: "relative aspect-[4/3] w-full overflow-hidden border-b border-[var(--line)]"
}
>
<FirmwarePreview fw={fw} />
<div className="pointer-events-none absolute bottom-2 left-2 flex gap-1">
{hasPreview && (
<span className="rounded bg-black/55 p-1 text-[var(--ink)] backdrop-blur-sm">
<LayoutTemplate className="size-3" aria-hidden />
</span>
)}
{hasDocs && (
<span className="rounded bg-black/55 p-1 text-[var(--ink)] backdrop-blur-sm">
<FileText className="size-3" aria-hidden />
</span>
)}
</div>
<Badge
variant="muted"
className="pointer-events-none absolute bottom-2 right-2 bg-black/55 text-[0.65rem] backdrop-blur-sm"
>
{formatBytes(fw.size)}
</Badge>
</div>
<div className="flex flex-1 flex-col gap-2 p-3.5">
<h3 className="m-0 line-clamp-2 font-semibold leading-snug">{fw.name}</h3>
{fw.summary && (
<p className="m-0 line-clamp-2 text-xs leading-relaxed text-[var(--muted)]">
{fw.summary}
</p>
)}
<div className="mt-auto flex flex-wrap gap-1.5 pt-1">
{fw.tags.slice(0, 3).map((tag) => (
<Badge key={tag} variant="default" className="max-w-full truncate">
{tag}
</Badge>
))}
{fw.date && (
<Badge variant="muted" className="tabular-nums">
{fw.date}
</Badge>
)}
</div>
</div>
</button>
);
}