SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%
2.9 KB · 53 lines tsx
Raw Blame History
1import type { FieldChange } from "@/lib/api";2import { fmtPctDelta } from "@/lib/format";34/**5 * WHAT CHANGED — field-level before → after table (spec §21, §78). Pure server-renderable component.6 */7export function FieldChanges({ items, compact = false, max }: { items: FieldChange[] | null | undefined; compact?: boolean; max?: number }) {8  if (!items?.length) return null;9  const rows = max ? items.slice(0, max) : items;10  return (11    <div className={`overflow-hidden rounded-md border border-line ${compact ? "text-[12px]" : "text-[12.5px]"}`}>12      <table className="w-full">13        <tbody className="divide-y divide-line">14          {rows.map((f, i) => (15            <tr key={i} className="align-top">16              <td className={`${compact ? "px-2 py-1" : "px-3 py-1.5"} w-[34%] text-fg-muted`}>17                <span className="block truncate" title={f.label}>{f.label}</span>18                <span className="label !text-[9.5px] !tracking-wider">{f.kind}</span>19              </td>20              <td className={`${compact ? "px-2 py-1" : "px-3 py-1.5"} font-mono tabular`}>21                <span className="diff-line-del rounded px-1 break-words">{f.before ?? "∅"}</span>22                <span className="mx-1.5 text-fg-subtle">→</span>23                <span className="diff-line-add rounded px-1 break-words">{f.after ?? "∅"}</span>24                {f.deltaPct !== null && f.deltaPct !== undefined && <span className={`ml-2 font-semibold ${f.deltaPct > 0 ? "text-high" : "text-signal"}`}>{fmtPctDelta(f.deltaPct)}</span>}25              </td>26            </tr>27          ))}28        </tbody>29      </table>30      {max && items.length > max && <div className="border-t border-line px-3 py-1 text-[11px] text-fg-subtle">+{items.length - max} more field change{items.length - max === 1 ? "" : "s"}</div>}31    </div>32  );33}3435/** One-line inline version for feed rows / drawer headers. */36export function FieldChangeInline({ items, max = 2 }: { items: FieldChange[] | null | undefined; max?: number }) {37  if (!items?.length) return null;38  return (39    <span className="inline-flex flex-wrap items-center gap-x-2 font-mono text-[11px] text-fg-muted tabular">40      {items.slice(0, max).map((f, i) => (41        <span key={i} className="inline-flex items-center gap-1 whitespace-nowrap">42          <span className="text-fg-subtle">{f.label.length > 22 ? f.label.slice(0, 21) + "…" : f.label}</span>43          <span className="diff-line-del rounded px-0.5">{f.before ?? "∅"}</span>44          <span className="text-fg-subtle">→</span>45          <span className="diff-line-add rounded px-0.5">{f.after ?? "∅"}</span>46          {f.deltaPct !== null && f.deltaPct !== undefined && <span className={f.deltaPct > 0 ? "text-high" : "text-signal"}>{fmtPctDelta(f.deltaPct)}</span>}47        </span>48      ))}49      {items.length > max && <span className="text-fg-subtle">+{items.length - max}</span>}50    </span>51  );52}53