88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
"use client"
|
|
|
|
import { Star, Code2, Repeat } from "lucide-react"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { cn } from "@/lib/utils"
|
|
import type { KnowledgeItem } from "@/lib/types"
|
|
import { DIFFICULTY_META, MASTERY_META } from "@/lib/types"
|
|
|
|
export function KnowledgeCard({
|
|
item,
|
|
onOpen,
|
|
onToggleFav,
|
|
onTagClick,
|
|
}: {
|
|
item: KnowledgeItem
|
|
onOpen: () => void
|
|
onToggleFav: () => void
|
|
onTagClick: (tag: string) => void
|
|
}) {
|
|
const diff = DIFFICULTY_META[item.difficulty]
|
|
const mastery = MASTERY_META[item.mastery]
|
|
|
|
return (
|
|
<article
|
|
onClick={onOpen}
|
|
className="group flex cursor-pointer flex-col rounded-xl border border-border bg-card/70 p-4 backdrop-blur-sm transition-colors hover:border-primary/40 hover:bg-card"
|
|
>
|
|
<div className="flex items-start justify-between gap-2">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="outline" className="font-mono text-[10px] uppercase tracking-wide">
|
|
{item.category}
|
|
</Badge>
|
|
<span
|
|
className={cn("inline-flex items-center gap-1 rounded-md border px-2 py-0.5 text-[10px]", mastery.color)}
|
|
>
|
|
<span className={cn("size-1.5 rounded-full", mastery.dot)} />
|
|
{mastery.label}
|
|
</span>
|
|
</div>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
onToggleFav()
|
|
}}
|
|
className="text-muted-foreground transition-colors hover:text-amber-400"
|
|
aria-label={item.is_favorite ? "取消收藏" : "收藏"}
|
|
>
|
|
<Star className={cn("size-4", item.is_favorite && "fill-amber-400 text-amber-400")} />
|
|
</button>
|
|
</div>
|
|
|
|
<h3 className="mt-3 text-pretty font-semibold leading-snug group-hover:text-primary">{item.title}</h3>
|
|
{item.summary && (
|
|
<p className="flex-1 mt-1.5 line-clamp-2 text-sm leading-relaxed text-muted-foreground">{item.summary}</p>
|
|
)}
|
|
|
|
{item.tags.length > 0 && (
|
|
<div className="mt-3 flex flex-wrap gap-1.5">
|
|
{item.tags.slice(0, 4).map((t) => (
|
|
<button
|
|
key={t}
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
onTagClick(t)
|
|
}}
|
|
>
|
|
<Badge variant="secondary" className="font-mono text-[10px]">
|
|
#{t}
|
|
</Badge>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-4 flex items-center justify-between border-t border-border/60 pt-3 text-xs text-muted-foreground">
|
|
<span className={cn("rounded-md border px-1.5 py-0.5", diff.color)}>{diff.label}</span>
|
|
<div className="flex items-center gap-3 font-mono">
|
|
{item.code_snippet && <Code2 className="size-3.5" />}
|
|
<span className="inline-flex items-center gap-1">
|
|
<Repeat className="size-3.5" />
|
|
{item.review_count}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
)
|
|
}
|