SPB Git

spb/autoscholar

Public
6.5 KB · 176 lines tsx
1import type { PublicPaperSummary } from "@autoscholar/schemas";2import Link from "next/link";3import { disciplineLabel, fmtDate, modeLabel } from "@/lib/format";4import { IconArrow, IconCode, IconData, IconDoc } from "./Icons";5import { StatusBadge } from "./StatusBadge";67function Avail({ p }: { p: PublicPaperSummary }) {8  const items: Array<[boolean, string, React.ReactNode]> = [9    [true, "PDF", <IconDoc key="pdf" size={14} />],10    [11      p.hasLatex,12      "LaTeX",13      <span key="tex" className="font-mono text-[10px]">14        TeX15      </span>,16    ],17    [p.hasCode, "Code", <IconCode key="code" size={14} />],18    [p.hasData, "Data", <IconData key="data" size={14} />],19  ];20  return (21    <ul className="flex items-center gap-2" aria-label="Available artifacts">22      {items23        .filter(([ok]) => ok)24        .map(([, label, icon]) => (25          <li26            key={label}27            className="subtle inline-flex items-center gap-1 text-[11px]"28            title={`${label} available`}29          >30            {icon}31            <span className="sr-only sm:not-sr-only">{label}</span>32          </li>33        ))}34    </ul>35  );36}3738/** ResearchPaperCard — premium publication card. `variant="row"` is denser for lists; `featured` is the editorial lead. */39export function PaperCard({40  p,41  featured = false,42  variant = "card",43  index,44}: {45  p: PublicPaperSummary;46  featured?: boolean;47  variant?: "card" | "row";48  index?: number;49}) {50  const href = `/research/${p.slug}`;51  const delay = index !== undefined ? `reveal reveal-${Math.min(4, (index % 4) + 1)}` : "";52  if (featured) {53    return (54      <article55        className={`surface card-link relative grid gap-6 p-6 sm:p-8 lg:grid-cols-12 lg:p-10 ${delay}`}56      >57        <div className="lg:col-span-8">58          <div className="flex flex-wrap items-center gap-x-3 gap-y-1.5">59            <span className="mono-id">{p.publicId}</span>60            <span className="badge badge-accent">Featured</span>61            <span className="muted text-xs">62              {disciplineLabel(p.discipline)} · {modeLabel(p.researchMode)}63            </span>64            {p.status !== "published" && <StatusBadge status={p.status} />}65            {p.synthetic && <span className="badge">staging sample</span>}66          </div>67          <h3 className="title-1 mt-4">68            <Link href={href} className="no-underline after:absolute after:inset-0 hover:underline">69              {p.title}70            </Link>71          </h3>72          <p className="prose-paper muted mt-4 line-clamp-4 text-[1.02rem]">{p.abstract}</p>73        </div>74        <div className="flex flex-col justify-between gap-4 border-t pt-4 hairline lg:col-span-4 lg:border-l lg:border-t-0 lg:pl-8 lg:pt-0">75          <dl className="grid grid-cols-2 gap-x-4 gap-y-3 text-sm lg:grid-cols-1">76            <div>77              <dt className="label">Released</dt>78              <dd className="mt-0.5">{fmtDate(p.firstPublishedAt)}</dd>79            </div>80            <div>81              <dt className="label">Version</dt>82              <dd className="mt-0.5">v{p.versionNo}</dd>83            </div>84            <div className="col-span-2 lg:col-span-1">85              <dt className="label">Keywords</dt>86              <dd className="mt-1 flex flex-wrap gap-1.5">87                {p.keywords.slice(0, 5).map((k) => (88                  <span key={k} className="badge normal-case tracking-normal">89                    {k}90                  </span>91                ))}92              </dd>93            </div>94          </dl>95          <div className="flex items-center justify-between gap-3">96            <Avail p={p} />97            <span98              className="inline-flex items-center gap-1 text-sm font-medium"99              style={{ color: "var(--accent-strong)" }}100            >101              Read <IconArrow size={16} />102            </span>103          </div>104        </div>105      </article>106    );107  }108  if (variant === "row") {109    return (110      <article111        className={`group relative grid gap-3 border-b py-5 hairline sm:grid-cols-12 sm:gap-6 ${delay}`}112      >113        <div className="sm:col-span-3">114          <p className="mono-id">{p.publicId}</p>115          <p className="muted mt-1 text-xs">116            {fmtDate(p.firstPublishedAt)} · v{p.versionNo}117          </p>118          <p className="muted mt-1 text-xs">119            {disciplineLabel(p.discipline)} · {modeLabel(p.researchMode)}120          </p>121        </div>122        <div className="min-w-0 sm:col-span-9">123          <div className="flex flex-wrap items-center gap-2">124            {p.status !== "published" && <StatusBadge status={p.status} />}125            {p.synthetic && <span className="badge">staging sample</span>}126          </div>127          <h3 className="title-3 mt-1">128            <Link href={href} className="no-underline after:absolute after:inset-0 group-hover:underline">129              {p.title}130            </Link>131          </h3>132          <p className="muted mt-2 line-clamp-2 text-sm leading-relaxed">{p.abstract}</p>133          <div className="mt-3 flex flex-wrap items-center justify-between gap-2">134            <ul className="flex flex-wrap gap-1.5">135              {p.keywords.slice(0, 4).map((k) => (136                <li key={k} className="badge normal-case tracking-normal">137                  {k}138                </li>139              ))}140            </ul>141            <Avail p={p} />142          </div>143        </div>144      </article>145    );146  }147  return (148    <article className={`surface card-link relative flex h-full flex-col gap-3 p-5 ${delay}`}>149      <div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs">150        <span className="mono-id">{p.publicId}</span>151        <span className="muted">152          {disciplineLabel(p.discipline)} · {modeLabel(p.researchMode)}153        </span>154        {p.status !== "published" && <StatusBadge status={p.status} />}155        {p.synthetic && <span className="badge">staging sample</span>}156      </div>157      <h3 className="title-3">158        <Link href={href} className="no-underline after:absolute after:inset-0 hover:underline">159          {p.title}160        </Link>161      </h3>162      <p className="muted line-clamp-3 text-sm leading-relaxed">{p.abstract}</p>163      <div className="mt-auto flex items-center justify-between gap-2 pt-2 text-xs">164        <span className="subtle">165          {fmtDate(p.firstPublishedAt)} · v{p.versionNo}166        </span>167        <Avail p={p} />168      </div>169    </article>170  );171}172export { PaperCard as ResearchPaperCard };173export function FeaturedPaper({ p }: { p: PublicPaperSummary }) {174  return <PaperCard p={p} featured />;175}176
Files · main
Menu

Theme