@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
487 lines (436 loc) • 16.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _button = require('../button');
var _button2 = _interopRequireDefault(_button);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _reactModal = require('react-modal');
var _reactModal2 = _interopRequireDefault(_reactModal);
var _lodash = require('lodash.isboolean');
var _lodash2 = _interopRequireDefault(_lodash);
var _shortid = require('shortid');
var _shortid2 = _interopRequireDefault(_shortid);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */
/* eslint-disable react/prefer-es6-class */
// Implements the [Modal design pattern](https://lightningdesignsystem.com/components/modals/) in React.
// Based on SLDS v2.2.1
// ### isBoolean
// ### shortid
// [npmjs.com/package/shortid](https://www.npmjs.com/package/shortid)
// shortid is a short, non-sequential, url-friendly, unique id generator
var displayName = 'Modal';
var propTypes = {
/**
* Vertical alignment of Modal.
*/
align: _propTypes2.default.oneOf(['top', 'center']),
/**
* Modal content.
*/
children: _propTypes2.default.node.isRequired,
/**
* Text read aloud by screen readers when the user focuses on the Close Button.
*/
closeButtonAssistiveText: _propTypes2.default.string,
/**
* Custom CSS classes for the modal's container. This is the element with `.slds-modal__container`. Use `classNames` [API](https://github.com/JedWatson/classnames).
*/
containerClassName: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.object, _propTypes2.default.string]),
/**
* Custom CSS classes for the modal's body. This is the element that has overflow rules and should be used to set a static height if desired. Use `classNames` [API](https://github.com/JedWatson/classnames).
*/
contentClassName: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.object, _propTypes2.default.string]),
/**
* Custom styles for the modal's body. This is the element that has overflow rules and should be used to set a static height if desired.
*/
contentStyle: _propTypes2.default.object,
/**
* If true, modal footer buttons render left and right. An example use case would be for "back" and "next" buttons.
*/
directional: _propTypes2.default.bool,
/**
* If true, Modals can be dismissed by clicking on the close icon or pressing esc key.
*/
dismissible: _propTypes2.default.bool,
/**
* If true, Modals can be dismissed by clicking outside of modal. If unspecified, defaults to dismissible.
*/
dismissOnClickOutside: _propTypes2.default.bool,
/**
* Callback to fire with Modal is dismissed
*/
onRequestClose: _propTypes2.default.func,
/**
* Accepts either a node or array of buttons to be placed in the footer. If array, the buttons render on the right side by default but are floated left and right if <code>directional</code> is true.
*/
footer: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.node]),
/**
* Allows for a custom modal header that does not scroll with modal content. If this is defined, `title` and `tagline` will be ignored. The close button will still be present.
*/
header: _propTypes2.default.node,
/**
* Adds CSS classes to the container surrounding the modal header and the close button. Use `classNames` [API](https://github.com/JedWatson/classnames).
*/
headerClassName: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.object, _propTypes2.default.string]),
/**
* Forces the modal to be open or closed.
*/
isOpen: _propTypes2.default.bool.isRequired,
/**
* Function that returns parent node to contain Modal. Should return document.querySelector('#myModalContainer').
*/
parentSelector: _propTypes2.default.func,
/**
* Custom CSS classes for the portal DOM node. This node is a direct descendant of the `body` and is the parent of `ReactModal__Overlay`. Use `classNames` [API](https://github.com/JedWatson/classnames).
*/
portalClassName: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.object, _propTypes2.default.string]),
/**
* Styles the modal as a prompt.
*/
prompt: _propTypes2.default.oneOf(['success', 'warning', 'error', 'wrench', 'offline', 'info']),
/**
* Specifiies the modal's width. May be deprecated in favor of `width` in the future.
*/
size: _propTypes2.default.oneOf(['medium', 'large']),
/**
* Content underneath the title in the modal header.
*/
tagline: _propTypes2.default.node,
/**
* Text heading at the top of a modal.
*/
title: _propTypes2.default.node,
/**
* Allows adding additional notifications within the modal.
*/
toast: _propTypes2.default.node
};
var defaultProps = {
align: 'center',
dismissible: true
};
/**
* The Modal component is used for the Lightning Design System Modal and Notification > Prompt components. The Modal opens from a state change outside of the component itself (pass this state to the <code>isOpen</code> prop). For more details on the Prompt markup, please review the <a href="http://www.lightningdesignsystem.com/components/notifications#prompt">Notifications > Prompt</a>.
*
* By default, `Modal` will add `aria-hidden=true` to the `body` tag, but this disables some assistive technologies. To prevent this you can add the following to your application with `#mount` being the root node of your application that you would like to hide from assistive technologies when the `Modal` is open.
* ```
* import settings from 'design-system-react/components/settings';
* settings.setAppElement('#mount');
* ```
* This component uses a portalMount (a disconnected React subtree mount) to create a modal as a child of `body`.
*/
var Modal = function (_React$Component) {
_inherits(Modal, _React$Component);
function Modal(props) {
_classCallCheck(this, Modal);
var _this = _possibleConstructorReturn(this, (Modal.__proto__ || Object.getPrototypeOf(Modal)).call(this, props));
_this.state = {
isClosing: false,
revealed: false
};
// Bind
_this.handleModalClick = _this.handleModalClick.bind(_this);
_this.closeModal = _this.closeModal.bind(_this);
_this.dismissModalOnClickOutside = _this.dismissModalOnClickOutside.bind(_this);
return _this;
}
_createClass(Modal, [{
key: 'setReturnFocus',
value: function setReturnFocus() {
this.setState({
returnFocusTo: document.activeElement
});
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
var _this2 = this;
this.setReturnFocus();
if (!this.state.revealed) {
setTimeout(function () {
_this2.setState({ revealed: true });
});
}
this.updateBodyScroll();
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
if (this.props.isOpen !== prevProps.isOpen) {
this.updateBodyScroll();
}
if (this.state.isClosing !== prevState.isClosing) {
if (this.state.isClosing) {
// console.log("CLOSING: ');
if (!this.isUnmounting) {
var el = _reactDom2.default.findDOMNode(this).parentNode; // eslint-disable-line react/no-find-dom-node
if (el && el.getAttribute('data-slds-modal')) {
_reactDom2.default.unmountComponentAtNode(el);
document.body.removeChild(el);
}
}
}
}
}
}, {
key: 'componentWillMount',
value: function componentWillMount() {
this.generatedId = _shortid2.default.generate();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.isUnmounting = true;
this.clearBodyScroll();
}
}, {
key: 'getId',
value: function getId() {
return this.props.id || this.generatedId;
}
}, {
key: 'dismissModalOnClickOutside',
value: function dismissModalOnClickOutside() {
// if dismissOnClickOutside is not set, default its value to dismissible
var dismissOnClickOutside = (0, _lodash2.default)(this.props.dismissOnClickOutside) ? this.props.dismissOnClickOutside : this.props.dismissible;
if (dismissOnClickOutside) {
this.dismissModal();
}
}
}, {
key: 'closeModal',
value: function closeModal() {
if (this.props.dismissible) {
this.dismissModal();
}
}
}, {
key: 'dismissModal',
value: function dismissModal() {
this.setState({ isClosing: true });
if (this.state.returnFocusTo && this.state.returnFocusTo.focus) {
this.state.returnFocusTo.focus();
}
if (this.props.onRequestClose) {
this.props.onRequestClose();
}
}
}, {
key: 'handleSubmitModal',
value: function handleSubmitModal() {
this.closeModal();
}
}, {
key: 'updateBodyScroll',
value: function updateBodyScroll() {
if (window && document && document.body) {
if (this.props.isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'inherit';
}
}
}
}, {
key: 'clearBodyScroll',
value: function clearBodyScroll() {
// eslint-disable-line class-methods-use-this
if (window && document && document.body) {
document.body.style.overflow = 'inherit';
}
}
}, {
key: 'handleModalClick',
value: function handleModalClick(event) {
// eslint-disable-line class-methods-use-this
if (event && event.stopPropagation) {
event.stopPropagation();
}
}
}, {
key: 'isPrompt',
value: function isPrompt() {
return this.props.prompt !== undefined;
}
}, {
key: 'footerComponent',
value: function footerComponent() {
var footer = null;
var hasFooter = this.props.footer;
var footerClass = {
'slds-modal__footer': true,
'slds-modal__footer--directional': this.props.directional,
'slds-theme--default': this.isPrompt()
};
if (hasFooter) {
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
footer = _react2.default.createElement(
'footer',
{
className: (0, _classnames2.default)(footerClass, this.props.footerClassNames),
onClick: this.handleModalClick
},
this.props.footer
);
}
return footer;
}
}, {
key: 'headerComponent',
value: function headerComponent() {
var _classNames;
var headerContent = this.props.header;
var headerEmpty = !headerContent && !this.props.title && !this.props.tagline;
var closeButtonAssistiveText = this.props.closeButtonAssistiveText || 'Close';
var closeButton = _react2.default.createElement(_button2.default, {
assistiveText: closeButtonAssistiveText,
iconName: 'close',
iconSize: 'large',
inverse: true,
className: 'slds-modal__close',
onClick: this.closeModal,
title: closeButtonAssistiveText,
variant: 'icon'
});
if (!headerContent && this.props.title || this.props.tagline) {
headerContent = _react2.default.createElement(
'div',
null,
this.props.toast,
_react2.default.createElement(
'h2',
{
className: (0, _classnames2.default)({
'slds-text-heading--small': this.isPrompt(),
'slds-text-heading--medium': !this.isPrompt()
}),
id: this.getId()
},
this.props.title
),
this.props.tagline ? _react2.default.createElement(
'p',
{ className: 'slds-m-top--x-small' },
this.props.tagline
) : null
);
}
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
_react2.default.createElement(
'header',
{
className: (0, _classnames2.default)('slds-modal__header', (_classNames = {
'slds-modal__header--empty': headerEmpty
}, _defineProperty(_classNames, 'slds-theme--' + this.props.prompt, this.isPrompt()), _defineProperty(_classNames, 'slds-theme--alert-texture', this.isPrompt()), _classNames), this.props.headerClassName),
onClick: this.handleModalClick
},
this.props.dismissible ? closeButton : null,
headerContent
)
);
}
}, {
key: 'getModal',
value: function getModal() {
var modalStyle = this.props.align === 'top' ? { justifyContent: 'flex-start' } : null;
var borderRadius = this.props.title || this.props.header ? {} : { borderRadius: '.25rem' };
var contentStyleFromProps = this.props.contentStyle || {};
var contentStyle = _extends({}, borderRadius, contentStyleFromProps);
return (
// temporarily disabling eslint for the onClicks on the div tags
/* eslint-disable */
_react2.default.createElement(
'div',
{
'aria-labelledby': this.getId(),
className: (0, _classnames2.default)({
'slds-modal': true,
'slds-fade-in-open': this.state.revealed,
'slds-modal--large': this.props.size === 'large',
'slds-modal--prompt': this.isPrompt()
}),
onClick: this.dismissModalOnClickOutside,
role: 'dialog'
},
_react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-modal__container', this.props.containerClassName),
style: modalStyle
},
this.headerComponent(),
_react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-modal__content', this.props.contentClassName),
style: contentStyle,
onClick: this.handleModalClick
},
this.props.children
),
this.footerComponent()
)
)
/* eslint-enable */
);
}
}, {
key: 'render',
value: function render() {
var customStyles = {
content: {
position: 'default',
top: 'default',
left: 'default',
right: 'default',
bottom: 'default',
border: 'default',
background: 'default',
overflow: 'default',
WebkitOverflowScrolling: 'default',
borderRadius: 'default',
outline: 'default',
padding: 'default'
},
overlay: {
position: 'static',
backgroundColor: 'default'
}
};
return _react2.default.createElement(
_reactModal2.default,
{
contentLabel: 'Modal',
isOpen: this.props.isOpen,
onRequestClose: this.closeModal,
style: customStyles,
parentSelector: this.props.parentSelector,
portalClassName: (0, _classnames2.default)('ReactModalPortal', this.props.portalClassName)
},
this.getModal(),
_react2.default.createElement('div', { className: 'slds-backdrop slds-backdrop--open' })
);
}
}]);
return Modal;
}(_react2.default.Component);
Modal.displayName = displayName;
Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;
exports.default = Modal;