UNPKG

@salesforce/design-system-react

Version:

Salesforce Lightning Design System for React

240 lines (215 loc) 6.7 kB
/* 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 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 './docs.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. * _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', }, 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; } componentWillMount() { // `checkProps` issues warnings to developers about properties (similar to React's built in development tools) checkProps(TOAST, this.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; } }; saveButtonRef = (component) => { this.closeButton = component; if (this.state.isInitialRender) { DOMElementFocus.storeActiveElement(); if (this.closeButton) { this.closeButton.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: 'info', success: 'success', warning: 'warning', error: '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', }); /* eslint-disable no-script-url */ 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 )} role="alert" style={this.props.style} > <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={this.props.onClickHeadingLink} href="javascript:void(0);" > {labels.headingLink} </a> ) : null} </h2> {labels.details ? <p>{labels.details}</p> : null} </div> <Button assistiveText={{ icon: assistiveText.closeButton }} buttonRef={this.saveButtonRef} 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;