@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.
267 lines • 9.35 kB
JavaScript
import _noop from "lodash/noop";
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 BaseComponent from '../_base/baseComponent';
import PropTypes from 'prop-types';
import Portal from '../_portal';
import cls from 'classnames';
import ConfigContext from '../configProvider/context';
import { cssClasses, strings } from '@douyinfe/semi-foundation/lib/es/sideSheet/constants';
import SideSheetContent from './SideSheetContent';
import SideSheetFoundation from '@douyinfe/semi-foundation/lib/es/sideSheet/sideSheetFoundation';
import '@douyinfe/semi-foundation/lib/es/sideSheet/sideSheet.css';
import CSSAnimation from "../_cssAnimation";
import { getDefaultPropsFromGlobalConfig, getScrollbarWidth } from '../_utils';
const prefixCls = cssClasses.PREFIX;
const defaultWidthList = strings.WIDTH;
const defaultHeight = strings.HEIGHT;
export default class SideSheet extends BaseComponent {
constructor(props) {
super(props);
this.handleCancel = e => {
this.foundation.handleCancel(e);
};
this.handleKeyDown = e => {
this.foundation.handleKeyDown(e);
};
this.updateState = () => {
this.foundation.toggleDisplayNone(!this.props.visible);
};
this.state = {
displayNone: !this.props.visible
};
this.foundation = new SideSheetFoundation(this.adapter);
this.bodyOverflow = '';
this.scrollBarWidth = 0;
this.originBodyWidth = '100%';
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
disabledBodyScroll: () => {
const {
getPopupContainer
} = this.props;
this.bodyOverflow = document.body.style.overflow || '';
if (!getPopupContainer && this.bodyOverflow !== 'hidden') {
document.body.style.overflow = 'hidden';
document.body.style.width = `calc(${this.originBodyWidth || '100%'} - ${this.scrollBarWidth}px)`;
}
},
enabledBodyScroll: () => {
const {
getPopupContainer
} = this.props;
if (!getPopupContainer && this.bodyOverflow !== 'hidden') {
document.body.style.overflow = this.bodyOverflow;
document.body.style.width = this.originBodyWidth;
}
},
notifyCancel: e => {
this.props.onCancel && this.props.onCancel(e);
},
notifyVisibleChange: visible => {
this.props.afterVisibleChange(visible);
},
setOnKeyDownListener: () => {
if (window) {
window.addEventListener('keydown', this.handleKeyDown);
}
},
removeKeyDownListener: () => {
if (window) {
window.removeEventListener('keydown', this.handleKeyDown);
}
},
toggleDisplayNone: displayNone => {
if (displayNone !== this.state.displayNone) {
this.setState({
displayNone: displayNone
});
}
}
});
}
static getDerivedStateFromProps(props, prevState) {
const newState = {};
if (props.visible && prevState.displayNone) {
newState.displayNone = false;
}
if (!props.visible && !props.motion && !prevState.displayNone) {
newState.displayNone = true;
}
return newState;
}
componentDidMount() {
this.scrollBarWidth = getScrollbarWidth();
this.originBodyWidth = document.body.style.width;
if (this.props.visible) {
this.foundation.beforeShow();
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
// hide => show
if (!prevProps.visible && this.props.visible) {
this.foundation.beforeShow();
}
// show => hide
if (prevProps.visible && !this.props.visible) {
this.foundation.afterHide();
}
if (prevState.displayNone !== this.state.displayNone) {
this.foundation.onVisibleChange(!this.state.displayNone);
}
}
componentWillUnmount() {
if (this.props.visible) {
this.foundation.destroy();
}
}
renderContent() {
const _a = this.props,
{
placement,
className,
children,
width,
height,
motion,
visible,
style,
maskStyle,
size,
zIndex,
getPopupContainer,
keepDOM
} = _a,
props = __rest(_a, ["placement", "className", "children", "width", "height", "motion", "visible", "style", "maskStyle", "size", "zIndex", "getPopupContainer", "keepDOM"]);
let wrapperStyle = {
zIndex
};
if (getPopupContainer) {
wrapperStyle = {
zIndex,
position: 'static'
};
}
const {
direction
} = this.context;
const isVertical = placement === 'left' || placement === 'right';
const isHorizontal = placement === 'top' || placement === 'bottom';
const sheetHeight = isHorizontal ? height ? height : defaultHeight : '100%';
const classList = cls(prefixCls, className, {
[`${prefixCls}-${placement}`]: placement,
[`${prefixCls}-popup`]: getPopupContainer,
[`${prefixCls}-horizontal`]: isHorizontal,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-hidden`]: keepDOM && this.state.displayNone
});
const contentProps = Object.assign(Object.assign(Object.assign({}, isVertical ? width ? {
width
} : {} : {
width: "100%"
}), props), {
visible,
motion: false,
size,
className: classList,
height: sheetHeight,
onClose: this.handleCancel
});
const shouldRender = this.props.visible || this.props.keepDOM || this.props.motion && !this.state.displayNone /* When there is animation, we use displayNone to judge whether animation is ended and judge whether to unmount content */;
// Since user could change animate duration , we don't know which animation end first. So we call updateState func twice.
return /*#__PURE__*/React.createElement(CSSAnimation, {
motion: this.props.motion,
animationState: visible ? 'enter' : 'leave',
startClassName: visible ? `${prefixCls}-animation-mask_show` : `${prefixCls}-animation-mask_hide`,
onAnimationEnd: this.updateState
}, _ref => {
let {
animationClassName: maskAnimationClassName,
animationEventsNeedBind: maskAnimationEventsNeedBind
} = _ref;
return /*#__PURE__*/React.createElement(CSSAnimation, {
motion: this.props.motion,
animationState: visible ? 'enter' : 'leave',
startClassName: visible ? `${prefixCls}-animation-content_show_${this.props.placement}` : `${prefixCls}-animation-content_hide_${this.props.placement}`,
onAnimationEnd: this.updateState /* for no mask case*/
}, _ref2 => {
let {
animationClassName,
animationStyle,
animationEventsNeedBind
} = _ref2;
return shouldRender ? /*#__PURE__*/React.createElement(Portal, {
getPopupContainer: getPopupContainer,
style: wrapperStyle
}, /*#__PURE__*/React.createElement(SideSheetContent, Object.assign({}, contentProps, {
maskExtraProps: maskAnimationEventsNeedBind,
wrapperExtraProps: animationEventsNeedBind,
dialogClassName: animationClassName,
maskClassName: maskAnimationClassName,
maskStyle: Object.assign({}, maskStyle),
style: Object.assign(Object.assign({}, animationStyle), style)
}), children)) : /*#__PURE__*/React.createElement(React.Fragment, null);
});
});
}
render() {
const {
zIndex,
getPopupContainer,
visible
} = this.props;
return this.renderContent();
}
}
SideSheet.contextType = ConfigContext;
SideSheet.propTypes = {
bodyStyle: PropTypes.object,
headerStyle: PropTypes.object,
children: PropTypes.node,
className: PropTypes.string,
closable: PropTypes.bool,
disableScroll: PropTypes.bool,
getPopupContainer: PropTypes.func,
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
mask: PropTypes.bool,
maskClosable: PropTypes.bool,
maskStyle: PropTypes.object,
motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.object, PropTypes.func]),
onCancel: PropTypes.func,
placement: PropTypes.oneOf(strings.PLACEMENT),
size: PropTypes.oneOf(strings.SIZE),
style: PropTypes.object,
title: PropTypes.node,
visible: PropTypes.bool,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
zIndex: PropTypes.number,
afterVisibleChange: PropTypes.func,
closeOnEsc: PropTypes.bool,
footer: PropTypes.node,
keepDOM: PropTypes.bool,
'aria-label': PropTypes.string
};
SideSheet.__SemiComponentName__ = "SideSheet";
SideSheet.defaultProps = getDefaultPropsFromGlobalConfig(SideSheet.__SemiComponentName__, {
visible: false,
motion: true,
mask: true,
placement: 'right',
closable: true,
footer: null,
zIndex: 1000,
maskClosable: true,
size: 'small',
disableScroll: true,
closeOnEsc: false,
afterVisibleChange: _noop,
keepDOM: false
});