Files
cyclone/src/components/ui/table.tsx
T
Cyclone UI 736bf4d333 Refine Batches page with hybrid dark/paper treatment
- Add tone='paper' prop to shared Table/TableHeader/TableRow/TableHead/
  TableCell components; paper-toned chrome swaps dark muted/20 header
  for cream paper, dark hover for warm cream hover, dark selected for
  paper blue tint, muted-foreground text for surface-ink. Dark tone
  is the default and unchanged.
- Extend BatchesList with tone='paper' variant: cream hover, warm
  surface-ink text, paper-tinted open-row (blue tint + ring). Kind
  badge (text-sky-300 / text-amber-300 + data-testid) and row
  data-testid='batch-row-{id}' preserved for the test contract.
- Replace PageHeader with editorial hero: massive 'Parsed *batches.*'
  (clamp 48-80px serif, italic for 'batches.') + ghost 'PARSED'
  watermark + Files pill (837P/835/envelope) + tap-to-open hint
- Add torn-page fold with '↘ THE REGISTER ↙' label and 48-dot perforation
- Cream paper plane (max-w-[1280px] mx-auto) with paper-grain SVG and
  double-bordered title block: 'REGISTER · PARSED BATCHES' over
  'The register.' (clamp 48-96px) plus on-file count column
- Folio system: § 01 Vital signs (4 paper KPI tiles via new
  BatchesKpiTile: On file / 837P / 835 / Claims with ink/blue/amber/
  success accent rails and Icons from lucide), § 02 The register
  (paper-toned BatchesList)
- Error/Loading/Empty states wrapped in paper-toned containers
- Footer: 'End of register / Cyclone · Batches / N rows'

Round 10/11.
2026-06-21 14:34:26 -06:00

129 lines
4.3 KiB
TypeScript

import * as React from "react";
import { cn } from "@/lib/utils";
// ---------------------------------------------------------------------------
// Table
//
// Shared table primitive used by Claims, Remittances, Batches, Acks, etc.
//
// `tone="paper"` swaps the dark-mode row chrome (muted/20 header, muted/30
// hover) for paper-toned chrome (cream surface, soft hairline border,
// tinted hover). Paper-toned tables sit inside the cream "paper plane"
// sections of the hybrid Magazine Spread layout. The default `tone="dark"`
// is unchanged from the original look so existing callers keep their
// behavior. Pages pass the tone prop once on `<Table>` and the children
// inherit the matching colors via the data-tone attribute — no need to
// rewrite every TableHead/TableRow/TableCell.
// ---------------------------------------------------------------------------
type Tone = "dark" | "paper";
type TableProps = React.HTMLAttributes<HTMLTableElement> & {
tone?: Tone;
};
const Table = React.forwardRef<HTMLTableElement, TableProps>(
({ className, tone = "dark", ...props }, ref) => (
<div
className={cn("relative w-full overflow-auto", className)}
data-tone={tone}
>
<table ref={ref} className="w-full caption-bottom text-sm" {...props} />
</div>
)
);
Table.displayName = "Table";
type TableSectionProps = React.HTMLAttributes<HTMLTableSectionElement>;
const TableHeader = React.forwardRef<HTMLTableSectionElement, TableSectionProps>(
({ className, ...props }, ref) => {
// Paper-tone: cream-papered header band, soft border, no dark muted fill.
return (
<thead
ref={ref}
className={cn(
"[&_tr]:border-b [&_tr]:border-border/60 [&_tr]:bg-muted/20",
// When the parent <Table> is paper-toned, swap to cream chrome.
"[[data-tone=paper]_&]:bg-[hsl(36_22%_92%)]",
"[[data-tone=paper]_&]:[&_tr]:bg-[hsl(36_22%_92%)]",
"[[data-tone=paper]_&]:[&_tr]:border-[hsl(30_14%_14%_/_0.10)]",
className
)}
{...props}
/>
);
}
);
TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef<HTMLTableSectionElement, TableSectionProps>(
({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
)
);
TableBody.displayName = "TableBody";
type TableRowProps = React.HTMLAttributes<HTMLTableRowElement>;
const TableRow = React.forwardRef<HTMLTableRowElement, TableRowProps>(
({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b border-border/40 transition-colors hover:bg-muted/30 focus-within:bg-muted/40 data-[state=selected]:bg-muted/50",
// Paper-tone: warm cream hover, soft hairline between rows.
"[[data-tone=paper]_&]:border-[hsl(30_14%_14%_/_0.08)]",
"[[data-tone=paper]_&]:hover:bg-[hsl(36_22%_94%)]",
"[[data-tone=paper]_&]:focus-within:bg-[hsl(36_22%_94%)]",
"[[data-tone=paper]_&]:data-[state=selected]:bg-[hsl(212_85%_95%)]",
className
)}
{...props}
/>
)
);
TableRow.displayName = "TableRow";
type TableHeadProps = React.ThHTMLAttributes<HTMLTableCellElement>;
const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
({ className, scope = "col", ...props }, ref) => (
<th
ref={ref}
scope={scope}
className={cn(
"h-9 px-4 text-left align-middle text-[10.5px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/80 [&:has([role=checkbox])]:pr-0",
// Paper-tone: surface-ink-2 (warm dark) instead of cool muted-foreground.
"[[data-tone=paper]_&]:text-[hsl(var(--surface-ink-2))]",
className
)}
{...props}
/>
)
);
TableHead.displayName = "TableHead";
const TableCell = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"px-4 py-3 align-middle [&:has([role=checkbox])]:pr-0",
// Paper-tone: warm foreground (surface-ink) for the primary text.
"[[data-tone=paper]_&]:text-[hsl(var(--surface-ink))]",
className
)}
{...props}
/>
)
);
TableCell.displayName = "TableCell";
export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell };
export type { Tone as TableTone };