DialogContainer
A DialogContainer accepts a single Dialog as a child, and manages showing and hiding it in a modal. Useful in cases where there is no trigger element or when the trigger unmounts while the dialog is open.
install | yarn add @adobe/react-spectrum |
---|---|
added | 3.4.0 |
usage | import {DialogContainer, Dialog} from '@adobe/react-spectrum' |
Example#
function Example(props) {
let [isOpen, setOpen] = React.useState(false);
return (
<>
<ActionButton onPress={() => setOpen(true)}>
<Delete />
<Text>Delete</Text>
</ActionButton>
<DialogContainer onDismiss={() => setOpen(false)} {...props}>
{isOpen &&
<AlertDialog
title="Delete"
variant="destructive"
primaryActionLabel="Delete">
Are you sure you want to delete this item?
</AlertDialog>
}
</DialogContainer>
</>
);
}
function Example(props) {
let [isOpen, setOpen] = React.useState(false);
return (
<>
<ActionButton onPress={() => setOpen(true)}>
<Delete />
<Text>Delete</Text>
</ActionButton>
<DialogContainer
onDismiss={() => setOpen(false)}
{...props}
>
{isOpen &&
(
<AlertDialog
title="Delete"
variant="destructive"
primaryActionLabel="Delete"
>
Are you sure you want to delete this item?
</AlertDialog>
)}
</DialogContainer>
</>
);
}
function Example(props) {
let [isOpen, setOpen] =
React.useState(
false
);
return (
<>
<ActionButton
onPress={() =>
setOpen(true)}
>
<Delete />
<Text>
Delete
</Text>
</ActionButton>
<DialogContainer
onDismiss={() =>
setOpen(false)}
{...props}
>
{isOpen &&
(
<AlertDialog
title="Delete"
variant="destructive"
primaryActionLabel="Delete"
>
Are you
sure you
want to
delete this
item?
</AlertDialog>
)}
</DialogContainer>
</>
);
}
Dialog triggered by a menu item#
DialogContainer is useful over a DialogTrigger when your have a trigger that can unmount while the dialog is open. For example, placing a DialogTrigger around a menu item would not work because the menu closes when pressing an item, thereby unmounting the DialogTrigger. When the trigger unmounts, so does the Dialog. In these cases, it is useful to place the dialog outside the tree that unmounts, so that the dialog is not also removed.
The following example shows a MenuTrigger containing a Menu with two actions: "edit" and "delete".
Each menu item opens a different dialog. This is implemented by using a DialogContainer that displays the edit dialog,
delete dialog, or no dialog depending on the current value stored in local state. Pressing a menu item triggers the menu's
onAction
prop, which sets the state to the type of dialog to display, based on the menu item's key
. This causes the associated
dialog to be rendered within the DialogContainer.
This example also demonstrates the use of the useDialogContainer
hook, which allows the dialog to dismiss itself when a user
presses one of the buttons inside it. This triggers the DialogContainer's onDismiss
event, which resets the state storing the
open dialog back to null
. In addition, the user can close the dialog using the Escape key (unless the
isKeyboardDismissDisabled
prop is set), or by clicking outside (if the isDismissable
prop is set).
import {useDialogContainer} from '@adobe/react-spectrum';
function Example() {
let [dialog, setDialog] = React.useState(null);
return (
<>
<MenuTrigger>
<ActionButton aria-label="Actions">
<More />
</ActionButton>
<Menu onAction={setDialog}>
<Item key="edit">Edit...</Item>
<Item key="delete">Delete...</Item>
</Menu>
</MenuTrigger>
<DialogContainer onDismiss={() => setDialog(null)}>
{dialog === 'edit' &&
<EditDialog />}
{dialog === 'delete' &&
(
<AlertDialog
title="Delete"
variant="destructive"
primaryActionLabel="Delete"
>
Are you sure you want to delete this item?
</AlertDialog>
)}
</DialogContainer>
</>
);
}
function EditDialog() {
// This hook allows us to dismiss the dialog when the user
// presses one of the buttons (below).
let dialog = useDialogContainer();
return (
<Dialog>
<Heading>Edit</Heading>
<Divider />
<Content>
<Form labelPosition="side" width="100%">
<TextField autoFocus label="First Name" defaultValue="John" />
<TextField label="Last Name" defaultValue="Smith" />
</Form>
</Content>
<ButtonGroup>
<Button variant="secondary" onPress={dialog.dismiss}>Cancel</Button>
<Button variant="accent" onPress={dialog.dismiss}>Save</Button>
</ButtonGroup>
</Dialog>
);
}
import {useDialogContainer} from '@adobe/react-spectrum';
function Example() {
let [dialog, setDialog] = React.useState(null);
return (
<>
<MenuTrigger>
<ActionButton aria-label="Actions">
<More />
</ActionButton>
<Menu onAction={setDialog}>
<Item key="edit">Edit...</Item>
<Item key="delete">Delete...</Item>
</Menu>
</MenuTrigger>
<DialogContainer onDismiss={() => setDialog(null)}>
{dialog === 'edit' &&
<EditDialog />}
{dialog === 'delete' &&
(
<AlertDialog
title="Delete"
variant="destructive"
primaryActionLabel="Delete"
>
Are you sure you want to delete this item?
</AlertDialog>
)}
</DialogContainer>
</>
);
}
function EditDialog() {
// This hook allows us to dismiss the dialog when the user
// presses one of the buttons (below).
let dialog = useDialogContainer();
return (
<Dialog>
<Heading>Edit</Heading>
<Divider />
<Content>
<Form labelPosition="side" width="100%">
<TextField
autoFocus
label="First Name"
defaultValue="John"
/>
<TextField
label="Last Name"
defaultValue="Smith"
/>
</Form>
</Content>
<ButtonGroup>
<Button
variant="secondary"
onPress={dialog.dismiss}
>
Cancel
</Button>
<Button variant="accent" onPress={dialog.dismiss}>
Save
</Button>
</ButtonGroup>
</Dialog>
);
}
import {useDialogContainer} from '@adobe/react-spectrum';
function Example() {
let [
dialog,
setDialog
] = React.useState(
null
);
return (
<>
<MenuTrigger>
<ActionButton aria-label="Actions">
<More />
</ActionButton>
<Menu
onAction={setDialog}
>
<Item key="edit">
Edit...
</Item>
<Item key="delete">
Delete...
</Item>
</Menu>
</MenuTrigger>
<DialogContainer
onDismiss={() =>
setDialog(
null
)}
>
{dialog ===
'edit' &&
<EditDialog />}
{dialog ===
'delete' &&
(
<AlertDialog
title="Delete"
variant="destructive"
primaryActionLabel="Delete"
>
Are you
sure you
want to
delete this
item?
</AlertDialog>
)}
</DialogContainer>
</>
);
}
function EditDialog() {
// This hook allows us to dismiss the dialog when the user
// presses one of the buttons (below).
let dialog =
useDialogContainer();
return (
<Dialog>
<Heading>
Edit
</Heading>
<Divider />
<Content>
<Form
labelPosition="side"
width="100%"
>
<TextField
autoFocus
label="First Name"
defaultValue="John"
/>
<TextField
label="Last Name"
defaultValue="Smith"
/>
</Form>
</Content>
<ButtonGroup>
<Button
variant="secondary"
onPress={dialog
.dismiss}
>
Cancel
</Button>
<Button
variant="accent"
onPress={dialog
.dismiss}
>
Save
</Button>
</ButtonGroup>
</Dialog>
);
}
Props#
Name | Type | Default | Description |
children | ReactNode | — | The Dialog to display, if any. |
type | 'modal'
| 'fullscreen'
| 'fullscreenTakeover' | 'modal' | The type of Dialog that should be rendered. See the visual options below for examples of each. |
isDismissable | boolean | — | Whether the Dialog is dismissable. See the Dialog docs for more details. |
isKeyboardDismissDisabled | boolean | — | Whether pressing the escape key to close the dialog should be disabled. |
Events
Name | Type | Description |
onDismiss | () => void | Handler that is called when the 'x' button of a dismissable Dialog is clicked. |
useDialogContainer#
The useDialogContainer
hook can be used to allow a custom dialog component to access the type
of container
the dialog is rendered in (e.g. modal
, popover
, fullscreen
, etc.), and also to dismiss the dialog
programmatically. It works with both DialogContainer
and DialogTrigger.
useDialogContainer(): DialogContainerValue
Visual options#
Full screen#
The type
prop allows setting the type of modal to display. Set it to "fullscreen"
to display a full screen dialog, which
only reveals a small portion of the page behind the underlay. Use this variant for more complex workflows that do not fit in
the available modal dialog sizes.
function Example(props) {
let [isOpen, setOpen] = React.useState(false);
return (
<>
<ActionButton onPress={() => setOpen(true)}>
<Edit />
<Text>Edit</Text>
</ActionButton>
<DialogContainer
type="fullscreen"
onDismiss={() => setOpen(false)}
{...props}
>
{isOpen &&
<EditDialog />}
</DialogContainer>
</>
);
}
function Example(props) {
let [isOpen, setOpen] = React.useState(false);
return (
<>
<ActionButton onPress={() => setOpen(true)}>
<Edit />
<Text>Edit</Text>
</ActionButton>
<DialogContainer
type="fullscreen"
onDismiss={() => setOpen(false)}
{...props}
>
{isOpen &&
<EditDialog />}
</DialogContainer>
</>
);
}
function Example(props) {
let [isOpen, setOpen] =
React.useState(
false
);
return (
<>
<ActionButton
onPress={() =>
setOpen(true)}
>
<Edit />
<Text>Edit</Text>
</ActionButton>
<DialogContainer
type="fullscreen"
onDismiss={() =>
setOpen(false)}
{...props}
>
{isOpen &&
<EditDialog />}
</DialogContainer>
</>
);
}
Full screen takeover#
Fullscreen takeover dialogs are similar to the fullscreen variant except that the dialog covers the entire screen.
function Example(props) {
let [isOpen, setOpen] = React.useState(false);
return (
<>
<ActionButton onPress={() => setOpen(true)}>
<Edit />
<Text>Edit</Text>
</ActionButton>
<DialogContainer
type="fullscreenTakeover"
onDismiss={() => setOpen(false)}
{...props}
>
{isOpen &&
<EditDialog />}
</DialogContainer>
</>
);
}
function Example(props) {
let [isOpen, setOpen] = React.useState(false);
return (
<>
<ActionButton onPress={() => setOpen(true)}>
<Edit />
<Text>Edit</Text>
</ActionButton>
<DialogContainer
type="fullscreenTakeover"
onDismiss={() => setOpen(false)}
{...props}
>
{isOpen &&
<EditDialog />}
</DialogContainer>
</>
);
}
function Example(props) {
let [isOpen, setOpen] =
React.useState(
false
);
return (
<>
<ActionButton
onPress={() =>
setOpen(true)}
>
<Edit />
<Text>Edit</Text>
</ActionButton>
<DialogContainer
type="fullscreenTakeover"
onDismiss={() =>
setOpen(false)}
{...props}
>
{isOpen &&
<EditDialog />}
</DialogContainer>
</>
);
}
Testing#
The DialogContainer features an overlay that transitions in and out of the page as it is opened and closed. Please see the following sections in the testing docs for more information on how to handle these behaviors in your test suite.
Please also refer to React Spectrum's test suite if you find that the above isn't sufficient when resolving issues in your own test cases.