Dialog component for React
July 31, 2019 ยท View on GitHub
Back to Polythene Dialog main page
Dialog component for React
Options
Usage
Calling a Dialog
Other than most other components, Dialog is not rendered directly but invoked through function calls show and hide.
Dialog spawner
Dialogs will be spawned from the component invocation (<Dialog />). To show a dialog instance, use Dialog.show() - more on that later.
Because a dialog should float on top of everything else, outside of the context of the caller, it can be considered a global component. It is best placed in the root view, so that it is not obstructed by other components:
import React from "react"
import { Dialog } from "polythene-react"
// ...
render() {
return (<div>
// ... app content
<Dialog />
</div>)
}
The Dialog component itself does not accept any appearance options. Instead, you pass options when calling show - allowing to show custom dialogs from anywhere in the app.
Styling side notes:
- Writing the dialog at the bottom makes for less surprises (instead of using CSS only for positioning); Mobile Safari sometimes has surprises with
position: fixed, so placing it here will most likely work as intended. - The order of elements in the root view may differ - CSS attribute
z-indexis set higher than other content.
Multiple dialog spawners
Usually you'll use only one location for dialogs - on top of all content and centered on screen - but it is possible to have a dialog instance spawned from a different location.
When you are using multiple spawners, differentiate them with option spawn:
<Dialog spawn="special" />
Calls to show the that particular dialog will then also need to pass the same spawn name:
Dialog.show(dialogOptions, { spawn: "special" })
Multiple dialogs
Multiple dialogs can co-exist for the same spawn. Add a unique id to each dialog. When using an array of dialogs, differentiate with unique keys.
Showing and hiding dialogs
Dialog functions:
Dialog.show(options)
Dialog.hide(options)
show
Shows a new dialog instance.
Dialog.show(dialogOptions, spawnOptions) : Promise
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
| dialogOptions | required | Options object or Function that returns an options object | See Dialog options |
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
| spawnOptions.id | optional | String | "default_dialog" | Dialog instance id; use to differentiate simultaneous dialogs. |
| spawnOptions.spawn | optional | String | "default_dialog" | Spawn id. Use with multiple spawn locations. spawn must also be passed as option at the spawning Dialog. |
Examples:
const dialogOptions = {
body: "some text"
}
// variations:
Dialog.show(dialogOptions)
Dialog.show(dialogOptions, { id: "confirm" })
Dialog.show(dialogOptions, { spawn: "special" })
Dialog.show(dialogOptions).then(() => console.log("dialog shown"))
Calling show a second time with the same id will redraw the dialog with new options:
Dialog.show(
{ title: "Log in" },
{ id: "login" }
)
// some time later:
Dialog.show(
{ title: "Log in again" },
{ id: "login" }
)
hide
Hides the current dialog instance.
Dialog.hide(spawnOptions) : Promise
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
| spawnOptions.id | optional | String | Dialog instance id; use to differentiate simultaneous dialogs. | |
| spawnOptions.spawn | optional | String | Spawn id. Use with multiple spawn locations. spawn must also be passed as option at the spawning Dialog. |
Examples:
Dialog.hide()
Dialog.hide({ id: "confirm" })
Dialog.hide({ spawn: "special" })
Dialog.hide().then(() => console.log("dialog hidden"))
Callbacks
Two optional callback options can be used after the transition: didShow and didHide. Useful when a route change is needed after the dialog is displayed or hidden:
const dialogOptions = {
didHide: id => history.push("/")
}
Drawing a Dialog
A dialog pane consist of the elements:
headerbodyfooter
Variations:
- The
headercan be substibuted with convenience optiontitle: this draws the title text according to Material Design specs. - The
footercan be substibuted with convenience optionfooterButtons: this accepts an array of buttons and will be drawn right-aligned according to Material Design specs. - Use
fullBleedto remove the padding from the body area - Use
bordersto conditionally add a top and bottom border to the body
Example with a Toolbar as custom header and footer
A dialog header can contain any content, but using a Toolbar is convenient to display action buttons (not according to Material Design specs, but nonetheless used in many interfaces).
import React from "react"
import { Dialog, Toolbar, ToolbarTitle } from "polythene-react"
const dialogOptions = {
header: <Toolbar><ToolbarTitle>Title</ToolbarTitle></Toolbar>,
body: "Body",
footer: <Toolbar><ToolbarTitle>Footer</ToolbarTitle></Toolbar>,
}
Dialog.show(dialogOptions)
Example with modal and backdrop
A modal dialog is a dialog that can only be closed with an explicit choice; clicking the background does not count as a choice.
To make this behavior explicit, a modal dialog often has a tinted backdrop. This also gives focus to the dialog contents.
import React from "react"
import { Dialog } from "polythene-react"
const footerButtons = [
/* Note that we are passing JSX elements in an array, hence the comma separator and keys */
<Button key="cancel" label="Cancel" events={{ onClick: () => Dialog.hide() }} />,
<Button key="discard" label="Discard" events={{ onClick: () => Dialog.hide() }} />
]
const dialogOptions = {
body: "Discard draft?",
modal: true,
backdrop: true,
footerButtons
}
Dialog.show(dialogOptions)
Full screen dialog
A full screen dialog uses Toolbar to implement its own header (options title and footer are not used):
import React from "react"
import { Dialog, Button, Toolbar, IconButton } from "polythene-react"
import { addLayoutStyles } from "polythene-css"
addLayoutStyles() // to use <span className="flex" />
const DIALOG_CONFIRM = "confirm-fullscreen"
const iconClose = <svg width="24" height="24" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
const shortText = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
const BodyText = () => (
<div>
{[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].map(num => <p key={num}>{shortText}</p>)}
</div>
)
// Second dialog
const confirmDialogOpts = ({
body: "This event is not yet saved. Are you sure you want to delete this event?",
modal: true,
backdrop: true,
footerButtons: [
<Button
label="Cancel"
events={{
onClick: () => Dialog.hide({ id: DIALOG_CONFIRM })
}}
/>,
<Button
label="Delete"
events={{
onClick: () => (
Dialog
.hide({ id: DIALOG_CONFIRM }) // hide confirm dialog
.then(Dialog.hide) // hide main dialog
)
}}
/>
],
})
Dialog.show({
fullScreen: true,
backdrop: true,
header: <Toolbar content={toolbarRow("New event")} />,
body: <BodyText />
})
Dynamic content
There are 2 ways to keep the dialog contents up to date:
- By passing dialog options as a function.
- By continuously calling
Dialog.show(attrs)with possibly changed attrs.
Examples of both are shown below.
Passing dialog options as a function
When using static dialog content, passing a POJO as dialog options to Dialog.show works just fine. This falls short when the content needs to be updated with outside state changes. By passing the options as a function, you ensure that the options are read afresh with the new state:
const optionsFn = () => {
return {
body: "some text"
}
}
Dialog.show(optionsFn)
The more elaborate example below shows a file upload form, where the submit button is disabled until a file has been selected.
import React, { Component } from "react"
import { Button, Dialog, DialogPane } from "polythene-react"
class ConditionalDialogPane extends Component {
constructor(props) {
super(props)
this.state = {
file: undefined
}
}
render() {
const disabled = this.state.file === undefined
return (
<DialogPane
title="Select a file..."
body={<input
type="file"
id="file"
name="file"
onChange={e => this.setState({file: e.target.value})}
/>}
formOptions={{
name: "demo",
method: "post",
encType: "multipart/form-data",
onSubmit: e => {
e.preventDefault()
alert("Posted: " + this.state.file)
Dialog.hide()
this.setState({file: null})
}
}}
footerButtons={<div>
<Button
label="Cancel"
events={{
onClick: () => Dialog.hide()
}}
/>
<Button
disabled={disabled}
label="Post"
type="submit"
element="button"
events={{
onClick: () => Dialog.hide()
}}
/>
</div>}
didHide={() => this.setState({file: null})}
/>
)
}
}
Dialog.show({
panes: [<ConditionalDialogPane />]
})
Continuously calling Dialog.show
The example shows a counter that is reflected in the dialog.
import React, { Component } from "react"
import { Dialog, Button } from "polythene-react"
const longText = <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
class Updating extends Component {
constructor(props) {
super(props)
this.state = {
count: 0,
dialogVisible: false
}
setInterval(() => this.setState({ count: this.state.count + 1 }), 1000)
}
componentDidUpdate() {
if (this.state.dialogVisible) {
const dialogProps = {
title: this.state.count,
body: longText,
didHide: () => this.setState({ dialogVisible: false })
}
Dialog.show(dialogProps)
}
}
render () {
return <div>
{this.state.count}
<Button
raised
label="Show Dialog"
events={{
onClick: () => this.setState({ dialogVisible: !this.state.dialogVisible })
}}
/>
</div>
}
}
Appearance
Styling
Below are examples how to change the Dialog appearance, either with a theme or with CSS.
You can find more information about theming in Theming.
Themed component
import { DialogCSS } from "polythene-css"
DialogCSS.addStyle(".themed-dialog", {
color_light_content_background: "#2196F3",
color_light_body_text: "#fff",
border_radius: 5
})
<Dialog className="themed-dialog" />
CSS
Change CSS using:
Class names can be imported with:
import classes from "polythene-css-classes/dialog"
import classes from "polythene-css-classes/dialog-pane"
Style
Some style attributes can be set using option style. For example:
Dialog.show({
style: {
background: "#fff59d",
padding: "1.5rem"
}
})
RTL (right-to-left) support
The direction of Dialog content is reversed when the dialog element either:
- has attribute
dir="rtl" - has className
pe-rtl
Dark or light tone
If the component - or a component's parent - has option tone set to "dark", the component will be rendered with light colors on dark.
- Use
tone: "dark"to render light on dark - Use
tone: "light"to locally render normally when dark tone is set
Transitions
See Transitions