{/* 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, plus checkbox width +
gap when this row is selectable). */}
{showArrow && (
<>
{" "}
{" "}
>
)}
)
}
function Section({
tone,
records,
selected,
onToggle,
onToggleAll,
renderAction,
}: {
tone: Tone
records: RecordView[]
selected?: Set
onToggle?: (key: string) => void
onToggleAll?: (checked: boolean) => void
renderAction?: (record: RecordView) => ReactNode
}) {
const meta = TONE_META[tone]
const Icon = meta.icon
// Read-only (NS/SOA) and custom records are never selectable — only
// update/delete sections receive selection props from DiffView.
const selectable =
tone !== "readonly" && tone !== "custom" && !!selected && !!onToggle && !!onToggleAll
const allSelected = selectable && records.length > 0 && records.every((r) => selected!.has(r.key))
const someSelected = selectable && records.some((r) => selected!.has(r.key))
const indeterminate = someSelected && !allSelected
return (
{selectable && records.length > 0 && (
onToggleAll!(v === true)}
aria-label={`Выбрать все — ${meta.label}`}
/>
)}
{meta.label}
{records.length}
{records.length === 0 ? (
{meta.empty}
) : (
{records.map((record) => (
))}
)}
)
}
export function DiffView({
changeset,
selectedUpdates,
selectedPrunes,
onToggleUpdate,
onTogglePrune,
onToggleAllUpdates,
onToggleAllPrunes,
onMarkCustom,
onUnmarkCustom,
footerExtra,
}: {
changeset: ChangesetResponse
selectedUpdates: Set
selectedPrunes: Set
onToggleUpdate: (key: string) => void
onTogglePrune: (key: string) => void
onToggleAllUpdates: (checked: boolean) => void
onToggleAllPrunes: (checked: boolean) => void
onMarkCustom?: (key: string) => void
onUnmarkCustom?: (key: string) => void
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