import type { ReactNode } from "react" import { ArrowRight, CircleCheck, Lock, Pencil, Trash2 } from "lucide-react" import { Badge } from "@/components/ui/badge" import { cn } from "@/lib/utils" import type { ChangesetResponse, RecordView } from "@/api/types" type Tone = "update" | "delete" | "readonly" const TONE_META: Record< Tone, { label: string; empty: string; icon: typeof Pencil; dot: string; ring: string } > = { update: { label: "Updates", empty: "Нет изменений — все значения совпадают.", icon: Pencil, dot: "var(--diff-update)", ring: "ring-[color-mix(in_oklch,var(--diff-update),transparent_78%)]", }, delete: { label: "Prunes", empty: "Нечего удалять.", icon: Trash2, dot: "var(--diff-delete)", ring: "ring-[color-mix(in_oklch,var(--diff-delete),transparent_78%)]", }, readonly: { label: "Read-only", empty: "Нет read-only записей.", icon: Lock, dot: "var(--diff-readonly)", ring: "ring-[color-mix(in_oklch,var(--diff-readonly),transparent_82%)]", }, } function Values({ values }: { values?: string[] }) { if (!values || values.length === 0) { return } return <>{values.join(", ")} } function RecordRow({ record, tone }: { record: RecordView; tone: Tone }) { const meta = TONE_META[tone] const showArrow = tone !== "delete" return (
{/* Top line: type badge, name, read-only flag — always single-line, never affected by how long the record values are. */}
{record.type} {record.name} {record.readOnly && ( read-only )}
{/* Values line: plain block-level text (not flex) so a long unbreakable value like a DKIM key wraps within the row's own width instead of stretching it — a flex item's content can otherwise refuse to shrink below its intrinsic width. Indented to align under the name (badge width + gap). */}
{showArrow && ( <> {" "} {" "} )}
) } function Section({ tone, records, }: { tone: Tone records: RecordView[] }) { const meta = TONE_META[tone] const Icon = meta.icon return (

{meta.label}

{records.length}
{records.length === 0 ? (

{meta.empty}

) : (
{records.map((record, i) => ( ))}
)}
) } export function DiffView({ changeset, footerExtra, }: { changeset: ChangesetResponse footerExtra?: ReactNode }) { // Defensive: a field may arrive as null (e.g. a nil slice from an older // backend) — normalise to [] so Section never calls .length/.map on null. return (
{changeset.inSyncCount} record{changeset.inSyncCount === 1 ? "" : "s"} in sync
{footerExtra}
) }