Dialog
A modal window on top of the page that asks for a decision or collects input. DialogTrigger opens it and DialogClose closes it.
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
TextField
} from 'alinea/components'
export function DialogExample() {
return (
<Dialog>
<DialogTrigger variant="outline">Rename entry</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename entry</DialogTitle>
<DialogDescription>
The URL of the entry stays the same.
</DialogDescription>
</DialogHeader>
<TextField label="Title" defaultValue="Summer collection" autoFocus />
<DialogFooter>
<DialogClose variant="ghost">Cancel</DialogClose>
<DialogClose color="primary">Save</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}Confirm a destructive action
Use role="alertdialog" for a decision editors have to make. With dismissable={false} a click outside the dialog doesn't close it.
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger
} from 'alinea/components'
import {IcRoundDelete} from 'alinea/dashboard/icons'
export function DialogAlertExample() {
return (
<Dialog>
<DialogTrigger color="destructive" variant="outline" icon={IcRoundDelete}>
Delete
</DialogTrigger>
<DialogContent
role="alertdialog"
dismissable={false}
showCloseButton={false}
>
<DialogHeader>
<DialogTitle>Delete “Linen shirt”?</DialogTitle>
<DialogDescription>
The entry and its translations are removed. This can’t be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose variant="ghost">Keep it</DialogClose>
<DialogClose color="destructive">Delete entry</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}Controlled
Pass open and onOpenChange to open a dialog from anywhere, and close it once a change is saved. useDialog gives the content of a dialog its open state and a close function.
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
TextField
} from 'alinea/components'
import {useState} from 'react'
export function DialogFormExample() {
const [open, setOpen] = useState(false)
const [name, setName] = useState('Oak & Loom')
const [draft, setDraft] = useState(name)
return (
<>
<Button onClick={() => setOpen(true)}>Site name: {name}</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Site settings</DialogTitle>
</DialogHeader>
<TextField label="Site name" value={draft} onValueChange={setDraft} />
<DialogFooter>
<Button variant="ghost" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button
color="primary"
onClick={() => {
setName(draft)
setOpen(false)
}}
>
Save
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)
}Submit a form
Pass onSubmit to DialogContent to render its content as a Form: a Button with type="submit" submits it with the values of the named fields. The dialog closes once onSubmit resolves without errors. Errors it returns, keyed by field name, show on the fields and keep the dialog open. onClose on Dialog runs whenever the dialog closes, after onOpenChange(false), for example to reset state. The form takes no room of its own (display: contents): put spacing such as a gap on DialogContent.
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
TextField
} from 'alinea/components'
// Stands in for a request to your own API, which returns errors by field name
async function movePage(data: FormData) {
await new Promise(resolve => setTimeout(resolve, 300))
if (data.get('path') === 'about') return {path: 'This path is taken'}
return undefined
}
export function DialogSubmitExample() {
return (
<Dialog>
<DialogTrigger variant="outline">Change path</DialogTrigger>
<DialogContent onSubmit={movePage}>
<DialogHeader>
<DialogTitle>Change path</DialogTitle>
<DialogDescription>Try “about” to see an error.</DialogDescription>
</DialogHeader>
<TextField label="Path" name="path" defaultValue="about" autoFocus />
<DialogFooter>
<DialogClose variant="ghost">Cancel</DialogClose>
<Button type="submit" color="primary">
Save
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}Props
<Dialog>
onOpenChange(false)<DialogTrigger>
Accepts every prop of Button.
<DialogContent>
default fits its content up to a narrow width, lg is a fixed wide dialog and full fills the viewport (minus a margin), defaults to defaulttype="submit" Button. The dialog closes once this resolves without returning errors, which are shown on the fields otherwise.Also accepts className and style, id and aria-* labelling attributes and data-* attributes.
<DialogHeader>
Accepts every prop of a <div> element.
<DialogTitle>
Also accepts className and style.
<DialogDescription>
Accepts every prop of a <p> element.
<DialogFooter>
Accepts every prop of a <div> element.
<DialogClose>
Closes the surrounding Dialog or Popover when pressed
Accepts every prop of Button.
useDialog()
The open state of the surrounding Dialog or Popover, to close it from within its content
Accessibility
Focus moves into the dialog when it opens and returns to the trigger when it closes. Focus stays inside while it is open, Escape closes it and the page behind it is hidden from screen readers.