Paste

Design System

Modal Dialog Primitive

An unstyled and accessible basis upon which to build Modal Dialogs.

Status
beta
Version
0.1.3
Sources
Install
yarn add @twilio-paste/modal-dialog-primitive — or — yarn add @twilio-paste/core

Guidelines#

About the Modal Dialog Primitive#

The modal dialog primitive is an unstyled and barebones version of a Modal dialog. It handles the implementation details around accessibility and provides a robust API to build upon. For example, our Modal component is built on top of this primitive. If you find that our designed Modal component can’t work for your particular use case, we suggest falling back to this component to roll your own solution. We encourage using this code as a basis for all modal dialogs in order to avoid code fragmentation and accessibility issues in our products.

Looking for Paste's styled Modal?

Only use this primitive if you have a bespoke modal design. For most Twilio interfaces, we recommend using the Paste Modal component.

Usage Guide#

This package is a wrapper around @reach/dialog. If you’re wondering why we wrapped that package into our own, we reasoned that it would be best for our consumers’ developer experience. With reasons such as:

  • We can control which APIs we expose and how to expose them. For example, in this package we rename and export only some of the source package's exports.
  • If we want to migrate the underlying nuts and bolts in the future, Twilio products that depend on this primitive would need to replace all occurrences of import … from ‘@reach/dialog’ to import … from ‘@some-new/package’. By wrapping it in @twilio-paste/modal-dialog-primitive, this refactor can be avoided. The only change would be a version bump in the package.json file.
  • We can more strictly enforce semver and backwards compatibility than some of our dependencies.
  • We can control when to provide an update and which versions we whitelist, to help reduce potential bugs our consumers may face.

Installation#

This package is available individually or as part of @twilio-paste/core.

yarn add @twilio-paste/modal-dialog-primitive - or - yarn add @twilio-paste/core

Usage Example#

import * as React from 'react';
import styled from '@emotion/styled';
import {Text} from '@twilio-paste/text';
import {Button} from '@twilio-paste/button';
import {ModalDialogPrimitiveOverlay, ModalDialogPrimitiveContent} from '@twilio-paste/modal-dialog-primitive';
const StyledModalDialogOverlay = styled(ModalDialogPrimitiveOverlay)({
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
background: 'rgba(0, 0, 0, 0.7)',
});
const StyledModalDialogContent = styled(ModalDialogPrimitiveContent)({
width: '100%',
maxWidth: '560px',
maxHeight: 'calc(100% - 60px)',
background: '#f4f5f6',
borderRadius: '5px',
padding: '20px',
});
interface BasicModalDialogProps {
isOpen: boolean;
handleClose: () => void;
}
const BasicModalDialog: React.FC<BasicModalDialogProps> = ({isOpen, handleClose}) => {
const inputRef = React.useRef();
return (
<StyledModalDialogOverlay
isOpen={isOpen}
onDismiss={handleClose}
allowPinchZoom={true}
initialFocusRef={inputRef}
>
<StyledModalDialogContent>
<input type="text" value="first" />
<br />
<input ref={inputRef} type="text" value="second (initial focused)" />
<Text as="p" color="colorText">
Roll your own dialog!
</Text>
</StyledModalDialogContent>
</StyledModalDialogOverlay>
);
};
export const ModalActivator: React.FC = () => {
const [isOpen, setIsOpen] = React.useState(false);
const handleOpen = (): void => setIsOpen(true);
const handleClose = (): void => setIsOpen(false);
return (
<div>
<Button variant="primary" onClick={handleOpen}>
Open Sample Modal
</Button>
<BasicModalDialog isOpen={isOpen} handleClose={handleClose} />
</div>
);
};

API#

Much of the following is copied directly from reach-ui's docs. Because we may update at a different cadence, we're duplicating the docs here to prevent inconsistent behaviors.

ModalDialogPrimitiveOverlay Props#

All the regular HTML attributes (role, aria-*, type, and so on) including the following custom props:

PropTypeDefault
isOpenboolfalse
allowPinchZoom?boolfalse
onDismiss?(event) => voidnoop
initialFocusRef?refnull
childrennodenull
isOpen prop

Controls whether the dialog is open or not.

<ModalDialogPrimitiveOverlay isOpen={true}>
<p>I will be open</p>
</ModalDialogPrimitiveOverlay>
<ModalDialogPrimitiveOverlay isOpen={false}>
<p>I will be closed</p>
</ModalDialogPrimitiveOverlay>
allowPinchZoom prop

Controls whether the dialog should allow zoom/pinch gestures on iOS devices.

onDismiss prop

This function is called whenever the user hits "Escape" or clicks outside the dialog. It's important to close the dialog when onDismiss is fired, as seen in all the demos on this page.

The only time you shouldn't close the dialog onDismiss is when the dialog requires a choice and none of them are "cancel". For example, perhaps two records need to be merged and the user needs to pick the surviving record. Neither choice is less destructive than the other, in these cases you may want to alert the user they need to a make a choice onDismiss instead of closing the dialog.

function Example(props) {
const [showDialog, setShowDialog] = React.useState(false);
const open = () => setShowDialog(true);
const close = () => setShowDialog(false);
return (
<div>
<button onClick={open}>Show Dialog</button>
<ModalDialogPrimitiveOverlay isOpen={showDialog} onDismiss={close}>
<ModalDialogPrimitiveContent>
<Text as="p">
It is your job to close this with state when the user clicks outside
or presses escape.
</Text>
<button onClick={close}>Okay</button>
</ModalDialogPrimitiveContent>
</ModalDialogPrimitiveOverlay>
</div>
);
}
function Example(props) {
const [showDialog, setShowDialog] = React.useState(false);
const [showWarning, setShowWarning] = React.useState(false);
const open = () => {
setShowDialog(true);
setShowWarning(false);
};
const close = () => setShowDialog(false);
const dismiss = () => setShowWarning(true);
return (
<div>
<button onClick={open}>Show Dialog</button>
<ModalDialogPrimitiveOverlay isOpen={showDialog} onDismiss={close}>
<ModalDialogPrimitiveContent>
{showWarning && (
<p style={{ color: "red" }}>You must make a choice, sorry :(</p>
)}
<p>Which router should survive the merge?</p>
<button onClick={close}>React Router</button>{" "}
<button onClick={close}>@reach/router</button>
</ModalDialogPrimitiveContent>
</ModalDialogPrimitiveOverlay>
</div>
);
}
initialFocusRef prop

By default the first focusable element will receive focus when the dialog opens but you can provide a ref to focus instead.

function Example(props) {
const [showDialog, setShowDialog] = React.useState(false);
const buttonRef = React.useRef();
const open = () => setShowDialog(true);
const close = () => setShowDialog(false);
return (
<div>
<button onClick={open}>Show Dialog</button>
{showDialog && (
<ModalDialogPrimitiveOverlay initialFocusRef={buttonRef} onDismiss={close}>
<ModalDialogPrimitiveContent>
<p>Pass the button ref to DialogOverlay and the button.</p>
<button onClick={close}>Not me</button>
<button
ref={buttonRef}
onClick={close}
>
Got me!
</button>
</ModalDialogPrimitiveContent>
</ModalDialogPrimitiveOverlay>
)}
</div>
);
}

ModalDialogPrimitiveContent Props#

All the regular HTML attributes (role, aria-*, type, and so on) including the following custom props:

PropTypeDefault
childrennodenull

Change Log

All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.

0.1.3 (2020-05-01)

Note: Version bump only for package @twilio-paste/modal-dialog-primitive

0.1.2 (2020-03-17)

Note: Version bump only for package @twilio-paste/modal-dialog-primitive

0.1.1 (2020-02-26)

Bug Fixes#

  • package dependencies and deprecation warnings (#334) (0e88338)

0.1.0 (2020-02-19)

Bug Fixes#

  • modal-dialog-primitive: rename exports (fcdd574)

Features#

  • modal-dialog-primitive: add package (ba350fc)

0.0.2 (2019-10-29)

Note: Version bump only for package @twilio-paste/modal-dialog-primitive

0.0.1 (2019-08-15)#

Note: Version bump only for package @twilio-paste/modal-dialog-primitive

```

Support

If you need support, please open a new issue in our GitHub repository. Please try to provide as much detail as possible in your issue.

Contributing

The Paste design system is open source and contributions are welcome. Check out the project on GitHub to learn more about contributing.

Copyright © 2020 Twilio, Inc.