@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
250 lines (225 loc) • 7.24 kB
JSX
/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */
// # Toast Component
// Implements the [Toast design pattern](https://lightningdesignsystem.com/components/toasts/) in React.
import React from 'react';
import PropTypes from 'prop-types';
import assign from 'lodash.assign';
import classNames from '../../utilities/class-names';
import EventUtil from '../../utilities/event';
import Button from '../button';
import Icon from '../icon';
import checkProps from './check-props';
import { TOAST } from '../../utilities/constants';
import DOMElementFocus from '../../utilities/dom-element-focus';
import componentDoc from './component.json';
const propTypes = {
/**
* **Assistive text for accessibility**
* This object is merged with the default props object on every render.
* * `closeButton`: This is a visually hidden label for the close button.
* * `error`: This is a visually hidden label to mark the toast as an error variant
* * `info`: This is a visually hidden label to mark the toast as an info variant
* * `success`: This is a visually hidden label to mark the toast as an success variant
* * `warning`: This is a visually hidden label to mark the toast as an warning variant
* _Tested with snapshot testing._
*/
assistiveText: PropTypes.shape({
closeButton: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
}),
/**
* CSS classes to be added to tag with `.slds-notify_toast`. Uses `classNames` [API](https://github.com/JedWatson/classnames).
* _Tested with snapshot testing._
*/
className: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.string,
]),
/**
* If duration exists, the Toast will disappear after that amount of time. Time in milliseconds. _Tested with Mocha testing._
*/
duration: PropTypes.number,
/**
* **Text labels for internationalization**
* This object is merged with the default props object on every render.
* * `details`: Secondary text below heading
* * `heading`: text within heading tag
* * `headingLink`: Text of link that triggers `onClickHeadingLink`. Inline links should pass a keyed array of React components into `labels.heading`.
*
* _Tested with snapshot testing._
*/
labels: PropTypes.shape({
details: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
heading: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
headingLink: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
}),
/**
* Triggered by link. _Tested with Mocha testing._
*/
onClickHeadingLink: PropTypes.func,
/**
* Icon of type `~/components/icon`. This icon will be cloned and additional props appended. The default icons are:
* * info variant: `utility:info`
* * error variant: `utility:error`
* * success variant: `utility:success`
* * warning variant: `utility:warning`
*
* _Tested with snapshot testing._
*/
icon: PropTypes.node,
/**
* Triggered by close button. _Tested with Mocha testing._
*/
onRequestClose: PropTypes.func,
/**
* Custom styles to be passed to the component. _Tested with Mocha testing._
*/
style: PropTypes.object,
/**
* The type of Toast. _Tested with snapshot testing._
*/
variant: PropTypes.oneOf(['error', 'info', 'success', 'warning']).isRequired,
};
const defaultProps = {
assistiveText: {
closeButton: 'Close',
error: 'error',
info: 'info',
success: 'success',
warning: 'warning',
},
variant: 'info',
};
/**
* Toast serves as a feedback & confirmation mechanism after the user takes an action. View [banner guidelines](https://www.lightningdesignsystem.com/guidelines/messaging/components/banners/).
*/
class Toast extends React.Component {
constructor(props) {
super(props);
this.state = {
isInitialRender: true,
};
this.timeout = null;
this.toast = null;
// `checkProps` issues warnings to developers about properties (similar to React's built in development tools)
checkProps(TOAST, props, componentDoc);
}
componentDidMount() {
if (this.props.duration) {
this.timeout = setTimeout(() => {
this.onClose();
}, this.props.duration);
}
}
componentWillUnmount() {
this.clearTimeout();
DOMElementFocus.returnFocusToStoredElement();
}
onClose = () => {
this.clearTimeout();
if (this.props.onRequestClose) {
this.props.onRequestClose();
}
};
clearTimeout = () => {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
};
saveToastRef = (toast) => {
this.toast = toast;
if (this.state.isInitialRender) {
DOMElementFocus.storeActiveElement();
if (this.toast) {
this.toast.focus();
}
this.setState({ isInitialRender: false });
}
};
render() {
// Merge objects of strings with their default object
const assistiveText = assign(
{},
defaultProps.assistiveText,
this.props.assistiveText
);
const labels = assign({}, defaultProps.labels, this.props.labels);
const heading = labels.heading || this.props.content; // eslint-disable-line react/prop-types
const assistiveTextVariant = {
info: assistiveText.info,
success: assistiveText.success,
warning: assistiveText.warning,
error: assistiveText.error,
};
const defaultIcons = {
info: <Icon category="utility" name="info" />,
success: <Icon category="utility" name="success" />,
warning: <Icon category="utility" name="warning" />,
error: <Icon category="utility" name="error" />,
};
const icon = this.props.icon
? this.props.icon
: defaultIcons[this.props.variant];
const clonedIcon = React.cloneElement(icon, {
containerClassName: 'slds-m-right_small slds-no-flex slds-align-top',
inverse: true,
size: 'small',
});
return (
<div
className={classNames(
'slds-notify slds-notify_toast',
{
'slds-theme_info': this.props.variant === 'info',
'slds-theme_success': this.props.variant === 'success',
'slds-theme_warning': this.props.variant === 'warning',
'slds-theme_error': this.props.variant === 'error',
},
this.props.className
)}
ref={this.saveToastRef}
role="status"
style={this.props.style}
tabIndex={0} // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
>
<span className="slds-assistive-text">
{assistiveTextVariant[this.props.variant]}
</span>
{clonedIcon}
<div className="slds-notify__content">
<h2 className="slds-text-heading_small">
{heading}{' '}
{labels.headingLink ? (
<a
onClick={EventUtil.trappedHandler(
this.props.onClickHeadingLink
)}
href="#"
>
{labels.headingLink}
</a>
) : null}
</h2>
{labels.details ? <p>{labels.details}</p> : null}
</div>
<Button
assistiveText={{ icon: assistiveText.closeButton }}
className="slds-notify__close"
iconCategory="utility"
iconName="close"
iconSize="large"
inverse
onClick={this.props.onRequestClose}
title={assistiveText.closeButton}
variant="icon"
/>
</div>
);
}
}
Toast.defaultProps = defaultProps;
Toast.displayName = TOAST;
Toast.propTypes = propTypes;
export default Toast;