SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
4 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
4.1 KB · 111 lines tsx
Raw Blame History
1import Link from "next/link";2import type { HTMLAttributes, ReactNode, TdHTMLAttributes, ThHTMLAttributes } from "react";3import { cn } from "./cn";4import { CollapsibleRows } from "./collapsible-rows";56/**7 * Dense data table. Sticky header (via .dci-table th), hairline rows, tabular numerics.8 * `responsive` turns rows into compact two-line rows under 640 px — the first line is the9 * <Td primary> cell with the optional <Td lead> figure on the right, the other cells wrap under it10 * as "LABEL value" pairs (every <Td> must carry `label`). Cells hidden with `hidden sm:table-cell`11 * stay hidden on phones.12 * `mobileCollapse` shows only the first 10 rows under 640 px with a "Show all N" toggle.13 */14export function Table({ className, responsive = true, children, dense, mobileCollapse, ...rest }: HTMLAttributes<HTMLTableElement> & { responsive?: boolean; dense?: boolean; mobileCollapse?: { total: number; label?: string } }) {15  const table = (16    <div className={cn("w-full", !responsive && "overflow-x-auto")}>17      <table className={cn("dci-table text-[13px]", responsive && "responsive", dense && "dense", className)} {...rest}>18        {children}19      </table>20    </div>21  );22  if (mobileCollapse && responsive) {23    return (24      <CollapsibleRows total={mobileCollapse.total} label={mobileCollapse.label}>25        {table}26      </CollapsibleRows>27    );28  }29  return table;30}3132export function THead({ children, className }: { children: ReactNode; className?: string }) {33  return <thead className={cn("text-left", className)}>{children}</thead>;34}35export function TBody({ children, className }: { children: ReactNode; className?: string }) {36  return <tbody className={cn("divide-y divide-hair", className)}>{children}</tbody>;37}38export function Tr({ children, className, href, ...rest }: HTMLAttributes<HTMLTableRowElement> & { href?: string }) {39  return (40    <tr className={cn("group align-top transition-colors sm:hover:bg-surface-2/60", href && "cursor-pointer", className)} {...rest}>41      {children}42    </tr>43  );44}4546export interface ThProps extends ThHTMLAttributes<HTMLTableCellElement> {47  numeric?: boolean;48  /** sort state */49  sort?: "asc" | "desc" | null;50  sortHref?: string;51}52export function Th({ children, className, numeric, sort, sortHref, ...rest }: ThProps) {53  const inner = (54    <span className={cn("inline-flex items-center gap-1", numeric && "flex-row-reverse")}>55      {children}56      {sort !== undefined && (57        <span aria-hidden className={cn("text-[9px]", sort ? "text-ink" : "text-ink-4")}>58          {sort === "asc" ? "▲" : sort === "desc" ? "▼" : "◆"}59        </span>60      )}61    </span>62  );63  return (64    <th65      scope="col"66      aria-sort={sort === "asc" ? "ascending" : sort === "desc" ? "descending" : undefined}67      className={cn("label-caps whitespace-nowrap border-b border-hair px-2 py-2 font-medium first:pl-0 last:pr-0", numeric && "text-right", className)}68      {...rest}69    >70      {sortHref ? (71        <Link href={sortHref} className="hover:text-ink" scroll={false}>72          {inner}73        </Link>74      ) : (75        inner76      )}77    </th>78  );79}8081export interface TdProps extends TdHTMLAttributes<HTMLTableCellElement> {82  label?: string;83  numeric?: boolean;84  primary?: boolean;85  muted?: boolean;86  /** key figure of the row: sits right of the primary cell on the first line under 640 px */87  lead?: boolean;88}89export function Td({ children, className, label, numeric, primary, muted, lead, ...rest }: TdProps) {90  return (91    <td92      data-label={label}93      data-primary={primary ? "" : undefined}94      data-lead={lead ? "" : undefined}95      className={cn("px-2 py-2 first:pl-0 last:pr-0", numeric && "figure text-right", muted && "text-ink-3", className)}96      {...rest}97    >98      {children}99    </td>100  );101}102103/** Row link (keeps the table semantic). Under 640 px, inside a <Td primary>, it stretches over the whole row (`.dci-rowlink`). */104export function RowLink({ href, children, className }: { href: string; children: ReactNode; className?: string }) {105  return (106    <Link href={href} className={cn("dci-rowlink font-medium text-ink hover:text-accent hover:underline", className)}>107      {children}108    </Link>109  );110}111