Form dialog
The Dialog around one form. Enter in any field submits; the footer closes (Cancel, then the submit as the primary), so there is no corner close. Every dismiss path — Escape, the scrim, Cancel — funnels through one guard: a pending submit blocks them all, and a dirty form asks first through a destructive Confirm action dialog. A successful submit closes through onOpenChange(false) directly, never the guard.
<FormDialog
open={open} onOpenChange={setOpen}
title="Edit address" submitLabel="Save address" cancelLabel="Cancel" pendingLabel="Saving"
isPending={mutation.isPending} dirty={form.isDirty} discardLabels={discard}
onSubmit={(e) => { e.preventDefault(); mutation.mutate(values, { onSuccess: () => setOpen(false) }); }}
>
<Field>…</Field>
</FormDialog>Edit address
Change the street, then press Escape or Cancel: the discard confirm. Save: 1.5s busy submit, then the caller closes. Empty street disables the submit without blocking dismiss.