# Dialog

A window over the page that asks the user to complete a short task without leaving the current view.

```tsx
import { Button } from "@zenoxai/ui/components/button"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@zenoxai/ui/components/dialog"
import { Field, FieldGroup, FieldLabel } from "@zenoxai/ui/components/field"
import { Input } from "@zenoxai/ui/components/input"

function DialogDemo() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline" />}>
        Rename asset
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Rename asset</DialogTitle>
          <DialogDescription>
            The new name appears in alerts, reports and the asset inventory.
          </DialogDescription>
        </DialogHeader>
        <FieldGroup>
          <Field>
            <FieldLabel htmlFor="asset-name">Name</FieldLabel>
            <Input id="asset-name" defaultValue="srv-db-01.prod" />
          </Field>
          <Field>
            <FieldLabel htmlFor="asset-owner">Owner</FieldLabel>
            <Input id="asset-owner" defaultValue="Infrastructure team" />
          </Field>
        </FieldGroup>
        <DialogFooter>
          <DialogClose render={<Button variant="outline" />}>
            Cancel
          </DialogClose>
          <Button>Save changes</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}
```

## Usage

```tsx
import { Button } from "@zenoxai/ui/components/button"
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@zenoxai/ui/components/dialog"
```

```tsx
<Dialog>
  <DialogTrigger render={<Button variant="outline" />}>
    Rename asset
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Rename asset</DialogTitle>
      <DialogDescription>
        The new name appears in alerts and reports.
      </DialogDescription>
    </DialogHeader>
    {/* fields */}
    <DialogFooter>
      <DialogClose render={<Button variant="outline" />}>Cancel</DialogClose>
      <Button>Save changes</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
```

Use a dialog for a short, focused task that blocks the page until it is done or dismissed:
renaming a record, creating a tenant, reviewing a list before export. The page behind it is
inert while it is open. Render the trigger as a `Button` through `render`, so it keeps the
button's look and the trigger's behavior.

Pick another overlay when the task does not fit that shape:

| Need                                                                      | Use                                      |
| ------------------------------------------------------------------------- | ---------------------------------------- |
| Confirm an irreversible or consequential action                           | [Alert Dialog](/components/alert-dialog) |
| A long form, or details next to the list that opened them                 | [Sheet](/components/sheet)               |
| A panel on touch screens that the user swipes away                        | [Drawer](/components/drawer)             |
| A few controls anchored to the button that opened them, page stays usable | [Popover](/components/popover)           |

`DialogContent` renders the portal, the backdrop and a close button in the top-right corner.
Hide the close button with `showCloseButton={false}` only when the footer offers another way out.
`DialogFooter` sits flush with the bottom edge on a muted band; pass `showCloseButton` to it to
add a "Close" button after its children.

## Examples

### Sizes

`size` sets the maximum width from the `sm` breakpoint up: `sm` (25rem, the default), `md`
(35rem), `lg` (50rem) and `xl` (68.75rem). Use `sm` for one or two fields, `md` for forms and
lists, `lg` and `xl` for tables or side-by-side content.

```tsx
import { Button } from "@zenoxai/ui/components/button"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@zenoxai/ui/components/dialog"

function DialogSizes() {
  return (
    <>
      {(["sm", "md", "lg", "xl"] as const).map((size) => (
        <Dialog key={size}>
          <DialogTrigger render={<Button variant="outline" />}>
            Open {size}
          </DialogTrigger>
          <DialogContent size={size}>
            <DialogHeader>
              <DialogTitle>Dialog size {size}</DialogTitle>
              <DialogDescription>
                The size sets the maximum width from the sm breakpoint up. Below
                it, every size fills the screen minus a 1rem margin.
              </DialogDescription>
            </DialogHeader>
            <DialogFooter showCloseButton />
          </DialogContent>
        </Dialog>
      ))}
    </>
  )
}
```

### Scrollable content

The dialog grows with its content up to the height of the viewport less a 1rem margin on each
side, then scrolls as a whole, footer included. To keep the title and the actions in view, give
the long part a maximum height and `overflow-y-auto`, and keep the header and footer outside it.

```tsx
import { Button } from "@zenoxai/ui/components/button"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@zenoxai/ui/components/dialog"

function DialogScrollable() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline" />}>
        View findings
      </DialogTrigger>
      <DialogContent size="md">
        <DialogHeader>
          <DialogTitle>Weekly vulnerability report</DialogTitle>
          <DialogDescription>
            24 findings across 3 tenants, sorted by severity.
          </DialogDescription>
        </DialogHeader>
        <ul className="-mx-4 max-h-80 overflow-y-auto border-y border-border px-4 py-2">
          {Array.from({ length: 24 }, (_, index) => (
            <li
              key={index}
              className="flex justify-between gap-4 border-b border-border py-2 last:border-0"
            >
              <span>CVE-2024-{3094 + index}</span>
              <span className="text-text-secondary">
                srv-web-{String(index + 1).padStart(2, "0")}.prod
              </span>
            </li>
          ))}
        </ul>
        <DialogFooter showCloseButton>
          <Button>Export PDF</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}
```

### Controlled

Control `open` when the dialog must stay open while a request runs and close only when it
succeeds. Disable the fields and the buttons while it is pending.

```tsx
import * as React from "react"
import { Button } from "@zenoxai/ui/components/button"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@zenoxai/ui/components/dialog"
import { Field, FieldLabel } from "@zenoxai/ui/components/field"
import { Input } from "@zenoxai/ui/components/input"
import { Spinner } from "@zenoxai/ui/components/spinner"

function DialogControlled() {
  const [open, setOpen] = React.useState(false)
  const [saving, setSaving] = React.useState(false)

  function save() {
    setSaving(true)
    // Stands in for the request that saves the tenant.
    setTimeout(() => {
      setSaving(false)
      setOpen(false)
    }, 1200)
  }

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger render={<Button />}>New tenant</DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>New tenant</DialogTitle>
          <DialogDescription>
            Tenants isolate assets, alerts and users of one customer.
          </DialogDescription>
        </DialogHeader>
        <Field>
          <FieldLabel htmlFor="tenant-name">Name</FieldLabel>
          <Input id="tenant-name" placeholder="Acme Corp" disabled={saving} />
        </Field>
        <DialogFooter>
          <DialogClose render={<Button variant="outline" />} disabled={saving}>
            Cancel
          </DialogClose>
          <Button onClick={save} disabled={saving}>
            {saving && <Spinner data-icon="inline-start" />}
            {saving ? "Creating" : "Create tenant"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}
```

## Accessibility

- Focus moves into the dialog when it opens, stays inside while it is open and returns to the
  trigger when it closes. Escape and a click on the backdrop close it.
- Always render a `DialogTitle`: it is the dialog's accessible name. `DialogDescription` is read
  after it. If the design has no visible title, keep it and hide it with `className="sr-only"`.
- The built-in close button is an icon button labelled "Close" for screen readers.

## API reference

`import { ... } from "@zenoxai/ui/components/dialog"`

### Dialog

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| actionsRef | `RefObject<DialogRootActions \| null>` |  | A ref to imperative actions. - `unmount`: Ends the closing phase of the dialog after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the dialog completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the dialog imperatively when called. |
| defaultOpen | `boolean` | `false` | Whether the dialog is initially open.  To render a controlled dialog, use the `open` prop instead. |
| defaultTriggerId | `string \| null` |  | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog. |
| disablePointerDismissal | `boolean` | `false` | Whether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it. |
| handle | `DialogHandle<unknown>` |  | A handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method. |
| modal | `boolean \| "trap-focus"` | `true` | Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled.  When `modal` is `true` or `'trap-focus'`, render `<Dialog.Close>` inside `<Dialog.Popup>` so touch screen readers can escape the popup. |
| onOpenChange | `((open: boolean, eventDetails: DialogRootChangeEventDetails) => void)` |  | Event handler called when the dialog is opened or closed. |
| onOpenChangeComplete | `((open: boolean) => void)` |  | Event handler called after any animations complete when the dialog is opened or closed. |
| open | `boolean` |  | Whether the dialog is currently open. |
| triggerId | `string \| null` |  | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set). |

### DialogClose

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| nativeButton | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogCloseState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### DialogContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| finalFocus | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| void \| HTMLElement \| null)` |  | Determines the element to focus when the dialog is closed.  - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).   Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
| initialFocus | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => boolean \| void \| HTMLElement \| null)` |  | Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.  - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).   Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogPopupState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| showCloseButton | `boolean` | `true` | Renders the X button at the top-right corner. Its accessible name is the English "Close": to translate it, turn this off and render your own DialogClose. |
| size | `"sm" \| "lg" \| "md" \| "xl"` | `sm` | Maximum width from the sm breakpoint: `sm` 400px (default), `md` 560px, `lg` 800px, `xl` 1100px. Below it the dialog spans the viewport less a 16px margin on each side. |

### DialogDescription

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogDescriptionState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### DialogFooter

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| showCloseButton | `boolean` | `false` | Appends an outline button that closes the dialog. Its text is the English "Close"; in other languages render a translated DialogClose instead. |

### DialogHeader

No props of its own.

### DialogOverlay

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| forceRender | `boolean` | `false` | Whether the backdrop is forced to render even when nested. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogBackdropState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### DialogPortal

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| container | `HTMLElement \| ShadowRoot \| RefObject<HTMLElement \| ShadowRoot \| null> \| null` |  | A parent element to render the portal element into. |
| keepMounted | `boolean` | `false` | Whether to keep the portal mounted in the DOM while the popup is hidden. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogPortalState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### DialogTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogTitleState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### DialogTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| handle | `DialogHandle<unknown>` |  | A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. |
| id | `string` |  | ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). |
| nativeButton | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| payload | `unknown` |  | A payload to pass to the dialog when it is opened. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogTriggerState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

