From 3116e4bedfef0c1059289248f46af0efbdb3b7e7 Mon Sep 17 00:00:00 2001 From: Guilherme Bruzzi Date: Wed, 7 Nov 2018 20:04:45 -0200 Subject: [PATCH] Add ToastMessage component --- react/components/Toast/README.md | 120 +++++++++++++++---------- react/components/Toast/ToastMessage.js | 74 +++++++++++++++ react/components/Toast/index.js | 30 ++++++- 3 files changed, 178 insertions(+), 46 deletions(-) create mode 100644 react/components/Toast/ToastMessage.js diff --git a/react/components/Toast/README.md b/react/components/Toast/README.md index 5b99415ca..adceef712 100644 --- a/react/components/Toast/README.md +++ b/react/components/Toast/README.md @@ -1,24 +1,25 @@ -#### Toasts give users instant feedback about the tasks they just did. Its main objective is to ensure tasks confirmation and success. +### Overview + +Toasts give users instant feedback about the tasks they just did. Its main objective is to ensure tasks confirmation and success. ### 👍 Dos -- Toasts are always self-dismissing, but users should be allowed to dismiss by themselves as well. -- Keep messages in a low to mild priority spectrum. Toasts are intended to be either neutral or positive. + +- Toasts are always self-dismissing, but users should be allowed to dismiss by themselves as well. +- Keep messages in a low to mild priority spectrum. Toasts are intended to be either neutral or positive. ### 👎 Don'ts + - Do not present critical or high priority actions on a Toast. If that's the case, you might consider using [Alerts](#alert) instead. - Due to its low to mild priority usage and dismissability, warning and error semantic styles do not apply to Toasts. - -``` +```js const ToastProvider = require('./index').ToastProvider const ToastConsumer = require('./index').ToastConsumer const App = () => ( // Wrap the entire application on a toast provider - - + + ) @@ -27,12 +28,10 @@ const Content = () => ( // on a ToastConsumer, with a function as a child, as // the example below:
-
- Click on a button to show the corresponding toast -
+
Click on a button to show the corresponding toast
- { ({ showToast }) => ( + {({ showToast }) => (
@@ -64,16 +61,16 @@ const Content = () => (
@@ -82,23 +79,20 @@ const Content = () => (
-
- Toast duration and control -
+
Toast duration and control
- {({showToast, hideToast}) => ( -
+ {({ showToast, hideToast }) => ( +
@@ -106,32 +100,68 @@ const Content = () => (
-
)}
+ +
+ Using with ToastMessage component so you can call immediately on render +
+ + {({ ToastMessage }) => } +
) +class ShowToastWithReact extends React.Component { + constructor(props) { + super(props) + this.state = { + active: false, + } + this.handleClick = this.handleClick.bind(this) + this.duration = 3000 + } + + handleClick() { + const { active } = this.state + this.setState({ active: true }) + setTimeout(() => { + this.setState({ active: false }) + }, this.duration) + } -; + render() { + const { active } = this.state + const { ToastMessage } = this.props + return ( +
+ + {active ? ( + + {`This message stays here for ${this.duration / 1000}s`} + + ) : null} +
+ ) + } +} +; ``` diff --git a/react/components/Toast/ToastMessage.js b/react/components/Toast/ToastMessage.js new file mode 100644 index 000000000..0f133edc7 --- /dev/null +++ b/react/components/Toast/ToastMessage.js @@ -0,0 +1,74 @@ +import { Component } from 'react' +import PropTypes from 'prop-types' + +class ToastMessage extends Component { + constructor(props) { + super(props) + this.state = { + message: '', + } + } + + showToast() { + this.props.showToast({ + message: this.props.message, + duration: this.props.duration, + action: this.props.action, + }) + } + + static getDerivedStateFromProps(props, state) { + return { + message: + state.message && props.message !== state.message + ? props.message + : state.message, + } + } + + shouldComponentUpdate(nextProps, nextState) { + return ( + (nextProps.action && + this.props.action && + nextProps.action.label !== this.props.action.label) || + nextProps.message !== this.props.message || + nextState.message !== this.state.message + ) + } + + componentDidUpdate(_, prevState) { + if (prevState.message !== this.state.message && this.state.message) { + this.showToast() + } + } + + componentDidMount() { + if (this.props.message) { + this.setState({ + message: this.props.message, + }) + } + } + + render() { + return null + } +} + +ToastMessage.defaultProps = { + duration: Infinity, + message: '', +} + +ToastMessage.propTypes = { + message: PropTypes.string.isRequired, + action: PropTypes.shape({ + label: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + }), + duration: PropTypes.number, + showToast: PropTypes.func.isRequired, + hideToast: PropTypes.func.isRequired, +} + +export default ToastMessage diff --git a/react/components/Toast/index.js b/react/components/Toast/index.js index 2952c3b6b..072e7f5b5 100644 --- a/react/components/Toast/index.js +++ b/react/components/Toast/index.js @@ -1,7 +1,10 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import Toast from './Toast' import isString from 'lodash/isString' +import isEqual from 'lodash/isEqual' + +import Toast from './Toast' +import ToastMessage from './ToastMessage' const ToastContext = React.createContext({ showToast: () => {}, @@ -28,6 +31,14 @@ class ToastProvider extends Component { const { message = '', action, duration } = args if (this.state.currentToast) { + // Check if is the same toast + if ( + isEqual(this.state.currentToast, { message, action, duration }) || + isEqual(this.state.nextToast, { message, action, duration }) + ) { + return + } + // If there is a toast present already, queue up the next toast // It will be displayed when the current toast is closed, on handleToastClose this.setState({ @@ -102,6 +113,15 @@ class ToastProvider extends Component { } } + shouldComponentUpdate(nextProps, nextState) { + return ( + nextProps.positioning !== this.props.positioning || + nextState.isToastVisible !== this.state.isToastVisible || + !isEqual(nextState.currentToast, this.state.currentToast) || + !isEqual(nextState.nextToast, this.state.nextToast) + ) + } + componentDidUpdate() { this.updateContainerBounds() } @@ -156,6 +176,14 @@ class ToastConsumer extends Component { children({ showToast: value.showToast, hideToast: value.hideToast, + ToastMessage: ({ children, ...props }) => ( + + ), }) }