SPB Git
5.9 KB · 74 lines tsx
1import Link from "next/link";2import { FileText } from "lucide-react";3import { fmtDate, fmtInt, href, t, type Locale } from "@/lib/i18n";4import type { PaperSummary } from "@/lib/papers/types";56export function AuthorsLine({ authors, locale, link = true }: { authors: PaperSummary["authors"]; locale: Locale; link?: boolean }) {7  return <span>{authors.map((a, i) => <span key={a.slug}>{i > 0 && (i === authors.length - 1 ? (locale === "fr" ? " et " : " and ") : ", ")}{link ? <Link href={href(locale, `/authors/${a.slug}`)} className="text-fg-2 no-underline hover:underline">{a.display_name}</Link> : a.display_name}</span>)}</span>;8}9export function StatusChips({ p, locale }: { p: PaperSummary; locale: Locale }) {10  const d = t(locale);11  return (12    <span className="inline-flex flex-wrap gap-1.5">13      {p.status !== "published" && <span className="chip chip-burgundy">{d.misc.status[p.status as keyof typeof d.misc.status]}</span>}14      {p.publication_status !== "working_paper" && <span className="chip chip-navy">{d.misc.pubStatus[p.publication_status as keyof typeof d.misc.pubStatus]}</span>}15      {p.notice && <span className="chip">{p.notice}</span>}16      {p.has_pdf ? <span className="chip"><FileText size={12} aria-hidden /> {d.papers.pdf}{p.pages ? ` · ${p.pages} ${d.papers.pages}` : ""}</span> : <span className="chip">{d.paper.notAvailable}</span>}17    </span>18  );19}20function excerpt(p: PaperSummary, n = 320) { const s = (p.abstract?.[0] ?? "").replace(/\s+/g, " "); return s.length > n ? s.slice(0, n).replace(/\s\S*$/, "") + "…" : s; }21const seriesName = (p: PaperSummary, locale: Locale) => locale === "fr" ? p.series_name_fr : p.series_name_en;2223export function PaperCard({ p, locale, badge }: { p: PaperSummary; locale: Locale; badge?: string }) {24  const d = t(locale);25  return (26    <article className="card p-5 flex flex-col gap-3 h-full">27      <div className="flex items-center justify-between gap-2 text-xs">28        <span className="font-mono text-navy">{p.identifier}</span>29        <span className="flex items-center gap-2">{badge && <span className="chip chip-burgundy">{badge}</span>}<span className="text-fg-3">{p.year ?? ""}</span></span>30      </div>31      <h3 className="text-lg leading-snug"><Link href={href(locale, `/papers/${p.slug}`)} className="no-underline hover:underline text-fg">{locale === "fr" && p.title_fr ? p.title_fr : p.title}</Link></h3>32      <p className="meta"><AuthorsLine authors={p.authors} locale={locale} /></p>33      <p className="text-sm text-fg-2 leading-relaxed">{excerpt(p, 260)}</p>34      <div className="mt-auto flex flex-wrap items-center gap-1.5 pt-1">35        {p.domain && <Link href={href(locale, `/topics/${p.domain.slug}`)} className="chip chip-navy no-underline">{locale === "fr" ? p.domain.name_fr : p.domain.name_en}</Link>}36        <StatusChips p={p} locale={locale} />37        <Link href={href(locale, `/papers/${p.slug}`)} className="ml-auto text-sm text-navy no-underline hover:underline">{d.papers.view} →</Link>38      </div>39    </article>40  );41}42export function PaperRow({ p, locale, compact = false }: { p: PaperSummary; locale: Locale; compact?: boolean }) {43  const d = t(locale);44  return (45    <article className={`py-4 ${compact ? "py-2.5" : ""} border-b border-line last:border-0`}>46      <div className="flex gap-4">47        <div className="w-16 shrink-0 pt-0.5 text-xs font-mono text-navy">{p.identifier}</div>48        <div className="min-w-0 flex-1">49          <h3 className={`${compact ? "text-base" : "text-lg"} leading-snug`}><Link href={href(locale, `/papers/${p.slug}`)} className="no-underline hover:underline text-fg">{locale === "fr" && p.title_fr ? p.title_fr : p.title}</Link></h3>50          {!compact && p.subtitle && <p className="text-sm text-fg-2 mt-0.5">{p.subtitle}</p>}51          <p className="meta mt-1"><AuthorsLine authors={p.authors} locale={locale} /> · {seriesName(p, locale)} · {fmtDate(locale, p.first_released_on, p.first_released_precision)}{p.last_revised_on && p.last_revised_on !== p.first_released_on ? ` · ${d.paper.revised.toLowerCase()} ${fmtDate(locale, p.last_revised_on, p.last_revised_precision)}` : ""}{p.version_label ? ` · ${p.version_label}` : ""}</p>52          {!compact && <p className="text-sm text-fg-2 leading-relaxed mt-2">{excerpt(p)}</p>}53          <div className="mt-2 flex flex-wrap items-center gap-1.5">54            {p.domain && <Link href={href(locale, `/topics/${p.domain.slug}`)} className="chip chip-navy no-underline">{locale === "fr" ? p.domain.name_fr : p.domain.name_en}</Link>}55            {!compact && p.keywords.slice(0, 4).map((k) => <Link key={k} href={href(locale, `/papers?keyword=${encodeURIComponent(k)}`)} className="chip no-underline">{k}</Link>)}56            <StatusChips p={p} locale={locale} />57          </div>58        </div>59      </div>60    </article>61  );62}63export function PaperTable({ items, locale }: { items: PaperSummary[]; locale: Locale }) {64  const d = t(locale);65  return (66    <div className="overflow-x-auto card">67      <table className="data">68        <thead><tr><th>{d.papers.number}</th><th>{d.paper.identifier}</th><th>{d.paper.authors}</th><th>{d.paper.year}</th><th>{d.paper.version}</th><th>{d.paper.domain}</th><th>{d.paper.pagesLabel}</th><th>{d.paper.viewsLabel}</th><th>{d.paper.downloadsLabel}</th></tr></thead>69        <tbody>{items.map((p) => <tr key={p.id}><td className="font-mono text-navy whitespace-nowrap">{p.identifier}</td><td><Link href={href(locale, `/papers/${p.slug}`)} className="text-fg no-underline hover:underline">{p.title}</Link></td><td className="text-fg-2 whitespace-nowrap"><AuthorsLine authors={p.authors} locale={locale} link={false} /></td><td>{p.year ?? ""}</td><td className="whitespace-nowrap">{p.version_label ?? ""}</td><td className="text-fg-2">{p.domain ? (locale === "fr" ? p.domain.name_fr : p.domain.name_en) : ""}</td><td>{p.pages ?? ""}</td><td>{fmtInt(locale, p.view_count)}</td><td>{fmtInt(locale, p.download_count)}</td></tr>)}</tbody>70      </table>71    </div>72  );73}74
Files · main
Menu

Theme