@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
327 lines • 11.9 kB
JavaScript
import _noop from "lodash/noop";
import _isFunction from "lodash/isFunction";
import _get from "lodash/get";
var __rest = this && this.__rest || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
};
import React from 'react';
import PropTypes from 'prop-types';
import cls from 'classnames';
import { cssClasses } from '@douyinfe/semi-foundation/lib/es/modal/constants';
import ConfigContext from '../configProvider/context';
import Button from '../iconButton';
import Typography from '../typography';
import BaseComponent from '../_base/baseComponent';
import ModalContentFoundation from '@douyinfe/semi-foundation/lib/es/modal/modalContentFoundation';
import { IconClose } from '@douyinfe/semi-icons';
import FocusTrapHandle from '@douyinfe/semi-foundation/lib/es/utils/FocusHandle';
let uuid = 0;
export default class ModalContent extends BaseComponent {
constructor(props) {
super(props);
this.onKeyDown = e => {
this.foundation.handleKeyDown(e);
};
// Record when clicking the modal box
this.onDialogMouseDown = () => {
this.foundation.handleDialogMouseDown();
};
// Cancel recording when clicking the modal box at the end
this.onMaskMouseUp = () => {
this.foundation.handleMaskMouseUp();
};
// onMaskClick will judge dialogMouseDown before onMaskMouseUp updates dialogMouseDown
this.onMaskClick = e => {
this.foundation.handleMaskClick(e);
};
this.close = e => {
this.foundation.close(e);
};
this.getMaskElement = () => {
const props = __rest(this.props, []);
const {
mask,
maskClassName
} = props;
if (mask) {
const className = cls(`${cssClasses.DIALOG}-mask`, {
// [`${cssClasses.DIALOG}-mask-hidden`]: !props.visible,
});
return /*#__PURE__*/React.createElement("div", Object.assign({
key: "mask"
}, this.props.maskExtraProps, {
className: cls(className, maskClassName),
style: props.maskStyle
}));
}
return null;
};
this.renderCloseBtn = () => {
const {
closable,
closeIcon
} = this.props;
let closer;
if (closable) {
const iconType = closeIcon || /*#__PURE__*/React.createElement(IconClose, {
"x-semi-prop": "closeIcon"
});
closer = /*#__PURE__*/React.createElement(Button, {
"aria-label": "close",
className: `${cssClasses.DIALOG}-close`,
key: "close-btn",
onClick: this.close,
type: "tertiary",
icon: iconType,
theme: "borderless",
size: "small"
});
}
return closer;
};
this.renderIcon = () => {
const {
icon
} = this.props;
return icon ? /*#__PURE__*/React.createElement("span", {
className: `${cssClasses.DIALOG}-icon-wrapper`,
"x-semi-prop": "icon"
}, icon) : null;
};
this.renderHeader = () => {
if ('header' in this.props) {
return this.props.header;
}
const {
title
} = this.props;
const closer = this.renderCloseBtn();
const icon = this.renderIcon();
return title === null || title === undefined ? null : (/*#__PURE__*/React.createElement("div", {
className: `${cssClasses.DIALOG}-header`
}, icon, /*#__PURE__*/React.createElement(Typography.Title, {
heading: 5,
className: `${cssClasses.DIALOG}-title`,
id: `${cssClasses.DIALOG}-title`,
"x-semi-prop": "title"
}, title), closer));
};
this.renderBody = () => {
const {
bodyStyle,
children,
title
} = this.props;
const bodyCls = cls(`${cssClasses.DIALOG}-body`, {
[`${cssClasses.DIALOG}-withIcon`]: this.props.icon
});
const closer = this.renderCloseBtn();
const icon = this.renderIcon();
const hasHeader = title !== null && title !== undefined || 'header' in this.props;
return hasHeader ? (/*#__PURE__*/React.createElement("div", {
className: bodyCls,
id: `${cssClasses.DIALOG}-body`,
style: bodyStyle,
"x-semi-prop": "children"
}, children)) : (/*#__PURE__*/React.createElement("div", {
className: `${cssClasses.DIALOG}-body-wrapper`
}, icon, /*#__PURE__*/React.createElement("div", {
className: bodyCls,
style: bodyStyle,
"x-semi-prop": "children"
}, children), closer));
};
this.getDialogElement = () => {
const props = __rest(this.props, []);
const style = {};
const digCls = cls(`${cssClasses.DIALOG}`, {
[`${cssClasses.DIALOG}-centered`]: props.centered,
[`${cssClasses.DIALOG}-${props.size}`]: props.size
});
if (props.width) {
style.width = props.width;
}
if (props.height) {
style.height = props.height;
}
if (props.isFullScreen) {
style.width = '100%';
style.height = '100%';
style.margin = 'unset';
}
const body = this.renderBody();
const header = this.renderHeader();
const footer = props.footer ? (/*#__PURE__*/React.createElement("div", {
className: `${cssClasses.DIALOG}-footer`,
"x-semi-prop": "footer"
}, props.footer)) : null;
const modalContentElement = /*#__PURE__*/React.createElement("div", {
role: "dialog",
ref: this.modalDialogRef,
"aria-modal": "true",
"aria-labelledby": `${cssClasses.DIALOG}-title`,
"aria-describedby": `${cssClasses.DIALOG}-body`,
onAnimationEnd: props.onAnimationEnd,
className: cls([`${cssClasses.DIALOG}-content`, props.contentClassName, {
[`${cssClasses.DIALOG}-content-fullScreen`]: props.isFullScreen,
[`${cssClasses.DIALOG}-content-height-set`]: props.height || _get(props.style, 'height')
}])
}, header, body, footer);
const dialogElement = /*#__PURE__*/React.createElement("div", {
key: "dialog-element",
className: digCls,
onMouseDown: this.onDialogMouseDown,
style: Object.assign(Object.assign({}, props.style), style),
id: this.dialogId
}, (props === null || props === void 0 ? void 0 : props.modalRender) ? props === null || props === void 0 ? void 0 : props.modalRender(modalContentElement) : modalContentElement);
return dialogElement;
};
this.state = {
dialogMouseDown: false,
prevFocusElement: FocusTrapHandle.getActiveElement()
};
this.foundation = new ModalContentFoundation(this.adapter);
this.dialogId = `dialog-${uuid++}`;
this.modalDialogRef = /*#__PURE__*/React.createRef();
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
notifyClose: e => {
this.props.onClose(e);
},
notifyDialogMouseDown: () => {
this.setState({
dialogMouseDown: true
});
},
notifyDialogMouseUp: () => {
if (this.state.dialogMouseDown) {
// Not setting setTimeout triggers close when modal external mouseUp
this.timeoutId = setTimeout(() => {
this.setState({
dialogMouseDown: false
});
}, 0);
}
},
addKeyDownEventListener: () => {
if (this.props.closeOnEsc) {
document.addEventListener('keydown', this.foundation.handleKeyDown);
}
},
removeKeyDownEventListener: () => {
if (this.props.closeOnEsc) {
document.removeEventListener('keydown', this.foundation.handleKeyDown);
}
},
getMouseState: () => this.state.dialogMouseDown,
modalDialogFocus: () => {
var _a, _b, _c;
const {
preventScroll
} = this.props;
let activeElementInDialog;
if (this.modalDialogRef) {
const activeElement = FocusTrapHandle.getActiveElement();
activeElementInDialog = this.modalDialogRef.current.contains(activeElement);
(_a = this.focusTrapHandle) === null || _a === void 0 ? void 0 : _a.destroy();
this.focusTrapHandle = new FocusTrapHandle(this.modalDialogRef.current, {
preventScroll
});
}
if (!activeElementInDialog) {
(_c = (_b = this.modalDialogRef) === null || _b === void 0 ? void 0 : _b.current) === null || _c === void 0 ? void 0 : _c.focus({
preventScroll
});
}
},
modalDialogBlur: () => {
var _a, _b;
(_a = this.modalDialogRef) === null || _a === void 0 ? void 0 : _a.current.blur();
(_b = this.focusTrapHandle) === null || _b === void 0 ? void 0 : _b.destroy();
},
prevFocusElementReFocus: () => {
const {
prevFocusElement
} = this.state;
const {
preventScroll
} = this.props;
const focus = _get(prevFocusElement, 'focus');
_isFunction(focus) && prevFocusElement.focus({
preventScroll
});
}
});
}
componentDidMount() {
var _a;
this.foundation.handleKeyDownEventListenerMount();
this.foundation.modalDialogFocus();
const nodes = FocusTrapHandle.getFocusableElements(this.modalDialogRef.current);
if (!this.modalDialogRef.current.contains(document.activeElement)) {
// focus on first focusable element
(_a = nodes[0]) === null || _a === void 0 ? void 0 : _a.focus();
}
}
componentWillUnmount() {
clearTimeout(this.timeoutId);
this.foundation.destroy();
}
render() {
var _a;
const _b = this.props,
{
maskClosable,
className,
getPopupContainer,
maskFixed,
getContainerContext
} = _b,
rest = __rest(_b, ["maskClosable", "className", "getPopupContainer", "maskFixed", "getContainerContext"]);
const {
direction
} = this.context;
const classList = cls(className, {
[`${cssClasses.DIALOG}-popup`]: getPopupContainer && getPopupContainer() !== ((_a = globalThis === null || globalThis === void 0 ? void 0 : globalThis.document) === null || _a === void 0 ? void 0 : _a.body) && !maskFixed,
[`${cssClasses.DIALOG}-fixed`]: maskFixed,
[`${cssClasses.DIALOG}-rtl`]: direction === 'rtl'
});
const containerContext = getContainerContext();
const dataAttr = this.getDataAttr(rest);
const elem = /*#__PURE__*/React.createElement("div", Object.assign({
className: classList
}, dataAttr), this.getMaskElement(), /*#__PURE__*/React.createElement("div", Object.assign({
role: "none",
className: cls({
[`${cssClasses.DIALOG}-wrap`]: true,
[`${cssClasses.DIALOG}-wrap-center`]: this.props.centered
}),
onClick: maskClosable ? this.onMaskClick : null,
onMouseUp: maskClosable ? this.onMaskMouseUp : null
}, this.props.contentExtraProps), this.getDialogElement()));
return containerContext && containerContext.Provider ? /*#__PURE__*/React.createElement(containerContext.Provider, {
value: containerContext.value
}, elem) : elem;
}
}
ModalContent.contextType = ConfigContext;
ModalContent.propTypes = {
close: PropTypes.func,
getContainerContext: PropTypes.func,
contentClassName: PropTypes.string,
maskClassName: PropTypes.string,
onAnimationEnd: PropTypes.func,
preventScroll: PropTypes.bool
};
ModalContent.defaultProps = {
close: _noop,
getContainerContext: _noop,
contentClassName: '',
maskClassName: ''
};