'use client' import type { ReactNode } from 'react' import { cn } from '../../lib/cn' import { Surface } from '../Surface' import { Tag } from '../Tag' import { Text } from '../Text' export interface FieldChange { field: string /** The field's name in the reader's language, not the schema's. */ label: string from?: ReactNode to?: ReactNode /** The field is being set for the first time. */ added?: boolean /** The field is being cleared. */ removed?: boolean /** Say why this one matters — a limit rise, a payee change. */ note?: string } export interface DiffSummaryProps { changes: FieldChange[] /** Accessible name. */ label: string /** Put a single field back. */ onRevert?: (field: string) => void /** Copy shown when nothing has changed. */ emptyLabel?: string /** Merged last, so it wins. */ className?: string } /** * The list of what is about to change, before it is submitted. * * A form that has been edited for ten minutes is not a thing anyone can review * by looking at it — the fields show what the values *will be*, and the reader * has long since forgotten what they *were*. This states both, per field, at * the moment it matters. * * Cleared and newly set fields are called out rather than shown as a change * from an empty string. "Reference: removed" is a different fact from * "Reference: 'ABC' → ''", and only one of them reads as deliberate. * * Reverting is per field. A single Discard forces an all-or-nothing decision on * someone who has spotted one mistake among six deliberate edits, and it is why * that person retypes everything instead. */ export function DiffSummary({ changes, label, onRevert, emptyLabel = 'Nothing has changed yet.', className, }: DiffSummaryProps) { if (changes.length === 0) { return ( {emptyLabel} ) } return ( {changes.length} {changes.length === 1 ? 'change' : 'changes'} to review ) }