You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

360 lines
9.6 KiB

7 years ago
import * as React from 'react';
import RcDrawer from 'rc-drawer';
import getScrollBarSize from 'rc-util/lib/getScrollBarSize';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import omit from 'omit.js';
use ant design icons 4.0 (#18217) * feat: use @ant-design/icons@4.0 * feat: use createFromIconfontCN to make site works * feat: update doc for Icon * feat: use icon in component Alert * feat: use icon in component Avatar * feat: use icon in component Breadcrumb * feat: use icon in component Button * feat: use icon in component Cascader * feat: use icon in component Collapse * feat: use icon in component Datepicker * feat: use icon in component Dropdown * feat: use icon in component Form * feat: use icon in component Input * feat: use icon in component InputNumber * feat: use icon in component Layout * feat: use icon in component Mention * feat: use icon in component Message * feat: use icon in component Modal * feat: use icon in component Notification * feat: use icon in component PageHeader * feat: use icon in component Pagination * feat: use icon in component Popconfirm * feat: use icon in component Progress * feat: use icon in component Rate * feat: use icon in component Result * feat: use icon in component Select * feat: use icon in component Step * feat: use icon in component Switch * feat: use icon in component Table * feat: use icon in component Tab * feat: use icon in component Tag * feat: handle rest component which using Icon * fix: remove unused vars * feat: use latest alpha ant design icons * fix: failed test in uploadlist.test.js * test: update snapshot for icons * doc: add Icon for site * doc: use @ant-design/icons in site * chore: use latest icons * fix: tslint issue * fix: test cases * fix: types for react * fix: lint rules for import orders * fix: use @ant-design/icons@4.0.0-alpha.5 to avoid insert css in server render * fix: eslint error in demo/**.md * inject antd icons * update snapshot * fix site * doc: update docs * fix: code snippets icon in site * feat: use latest @ant-design/icons * fix: icon props in message
5 years ago
import { ConfigConsumerProps } from '../config-provider';
import { withConfigConsumer, ConfigConsumer } from '../config-provider/context';
import { tuple } from '../_util/type';
const DrawerContext = React.createContext<Drawer | null>(null);
7 years ago
type EventType =
| React.KeyboardEvent<HTMLDivElement>
| React.MouseEvent<HTMLDivElement | HTMLButtonElement>;
7 years ago
type getContainerFunc = () => HTMLElement;
7 years ago
const PlacementTypes = tuple('top', 'right', 'bottom', 'left');
type placementType = typeof PlacementTypes[number];
export interface PushState {
distance: string | number;
}
export interface DrawerProps {
7 years ago
closable?: boolean;
closeIcon?: React.ReactNode;
7 years ago
destroyOnClose?: boolean;
forceRender?: boolean;
getContainer?: string | HTMLElement | getContainerFunc | false;
7 years ago
maskClosable?: boolean;
mask?: boolean;
maskStyle?: React.CSSProperties;
style?: React.CSSProperties;
/** wrapper dom node style of header and body */
drawerStyle?: React.CSSProperties;
headerStyle?: React.CSSProperties;
bodyStyle?: React.CSSProperties;
7 years ago
title?: React.ReactNode;
visible?: boolean;
width?: number | string;
height?: number | string;
7 years ago
zIndex?: number;
prefixCls?: string;
push?: boolean | PushState;
placement?: placementType;
7 years ago
onClose?: (e: EventType) => void;
afterVisibleChange?: (visible: boolean) => void;
className?: string;
handler?: React.ReactNode;
keyboard?: boolean;
footer?: React.ReactNode;
footerStyle?: React.CSSProperties;
7 years ago
}
export interface IDrawerState {
push?: boolean;
7 years ago
}
const defaultPushState: PushState = { distance: 180 };
class Drawer extends React.Component<DrawerProps & ConfigConsumerProps, IDrawerState> {
7 years ago
static defaultProps = {
7 years ago
width: 256,
height: 256,
7 years ago
closable: true,
placement: 'right' as placementType,
7 years ago
maskClosable: true,
mask: true,
level: null,
keyboard: true,
push: defaultPushState,
7 years ago
};
7 years ago
readonly state = {
push: false,
};
parentDrawer: Drawer | null;
destroyClose: boolean;
6 years ago
public componentDidMount() {
// fix: delete drawer in child and re-render, no push started.
// <Drawer>{show && <Drawer />}</Drawer>
const { visible } = this.props;
if (visible && this.parentDrawer) {
this.parentDrawer.push();
}
}
public componentDidUpdate(preProps: DrawerProps) {
const { visible } = this.props;
if (preProps.visible !== visible && this.parentDrawer) {
if (visible) {
this.parentDrawer.push();
} else {
this.parentDrawer.pull();
}
}
}
6 years ago
public componentWillUnmount() {
// unmount drawer in child, clear push.
if (this.parentDrawer) {
this.parentDrawer.pull();
this.parentDrawer = null;
}
}
push = () => {
if (this.props.push) {
this.setState({ push: true });
}
};
6 years ago
pull = () => {
if (this.props.push) {
this.setState({ push: false });
}
};
6 years ago
onDestroyTransitionEnd = () => {
const isDestroyOnClose = this.getDestroyOnClose();
if (!isDestroyOnClose) {
return;
}
6 years ago
if (!this.props.visible) {
this.destroyClose = true;
6 years ago
this.forceUpdate();
}
};
getDestroyOnClose = () => this.props.destroyOnClose && !this.props.visible;
getPushDistance = () => {
const { push } = this.props;
let distance: number | string;
if (typeof push === 'boolean') {
distance = push ? defaultPushState.distance : 0;
} else {
distance = push!.distance;
}
return parseFloat(String(distance || 0));
};
// get drawer push width or height
getPushTransform = (placement?: placementType) => {
const distance = this.getPushDistance();
if (placement === 'left' || placement === 'right') {
return `translateX(${placement === 'left' ? distance : -distance}px)`;
}
if (placement === 'top' || placement === 'bottom') {
return `translateY(${placement === 'top' ? distance : -distance}px)`;
}
};
6 years ago
getOffsetStyle() {
const { placement, width, height, visible, mask } = this.props;
// https://github.com/ant-design/ant-design/issues/24287
if (!visible && !mask) {
return {};
}
const offsetStyle: any = {};
if (placement === 'left' || placement === 'right') {
offsetStyle.width = width;
} else {
offsetStyle.height = height;
}
return offsetStyle;
}
getRcDrawerStyle = () => {
const { zIndex, placement, mask, style } = this.props;
const { push } = this.state;
// 当无 mask 时,将 width 应用到外层容器上
// 解决 https://github.com/ant-design/ant-design/issues/12401 的问题
const offsetStyle = mask ? {} : this.getOffsetStyle();
return {
zIndex,
transform: push ? this.getPushTransform(placement) : undefined,
...offsetStyle,
...style,
};
};
renderHeader() {
const { title, prefixCls, closable, headerStyle } = this.props;
if (!title && !closable) {
return null;
}
const headerClassName = title ? `${prefixCls}-header` : `${prefixCls}-header-no-title`;
return (
<div className={headerClassName} style={headerStyle}>
{title && <div className={`${prefixCls}-title`}>{title}</div>}
{closable && this.renderCloseIcon()}
</div>
);
}
renderFooter() {
const { footer, footerStyle, prefixCls } = this.props;
if (!footer) {
return null;
}
const footerClassName = `${prefixCls}-footer`;
return (
<div className={footerClassName} style={footerStyle}>
{footer}
</div>
);
}
renderCloseIcon() {
const { closable, closeIcon = <CloseOutlined />, prefixCls, onClose } = this.props;
return (
closable && (
// eslint-disable-next-line react/button-has-type
<button
onClick={onClose}
aria-label="Close"
className={`${prefixCls}-close`}
style={
{
'--scroll-bar': `${getScrollBarSize()}px`,
} as any
}
>
{closeIcon}
</button>
)
);
}
// render drawer body dom
renderBody = () => {
const { bodyStyle, drawerStyle, prefixCls, visible } = this.props;
if (this.destroyClose && !visible) {
6 years ago
return null;
}
this.destroyClose = false;
const containerStyle: React.CSSProperties = {};
const isDestroyOnClose = this.getDestroyOnClose();
6 years ago
if (isDestroyOnClose) {
// Increase the opacity transition, delete children after closing.
6 years ago
containerStyle.opacity = 0;
containerStyle.transition = 'opacity .3s';
7 years ago
}
7 years ago
return (
6 years ago
<div
className={`${prefixCls}-wrapper-body`}
style={{
...containerStyle,
...drawerStyle,
}}
onTransitionEnd={this.onDestroyTransitionEnd}
6 years ago
>
{this.renderHeader()}
<div className={`${prefixCls}-body`} style={bodyStyle}>
{this.props.children}
</div>
{this.renderFooter()}
7 years ago
</div>
7 years ago
);
};
// render Provider for Multi-level drawer
renderProvider = (value: Drawer) => {
this.parentDrawer = value;
return (
<ConfigConsumer>
{({ getPopupContainer, getPrefixCls }) => {
const {
prefixCls: customizePrefixCls,
placement,
className,
mask,
direction,
visible,
...rest
} = this.props;
const prefixCls = getPrefixCls('select', customizePrefixCls);
const drawerClassName = classNames(className, {
'no-mask': !mask,
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const offsetStyle = mask ? this.getOffsetStyle() : {};
return (
<DrawerContext.Provider value={this}>
<RcDrawer
handler={false}
{...omit(rest, [
'zIndex',
'style',
'closable',
'closeIcon',
'destroyOnClose',
'drawerStyle',
'headerStyle',
'bodyStyle',
'footerStyle',
'footer',
'locale',
'title',
'push',
'visible',
'getPopupContainer',
'rootPrefixCls',
'getPrefixCls',
'renderEmpty',
'csp',
'pageHeader',
'autoInsertSpaceInButton',
'width',
'height',
'dropdownMatchSelectWidth',
'getTargetContainer',
])}
getContainer={
// 有可能为 false,所以不能直接判断
rest.getContainer === undefined && getPopupContainer
? () => getPopupContainer(document.body)
: rest.getContainer
}
{...offsetStyle}
prefixCls={prefixCls}
open={visible}
showMask={mask}
placement={placement}
style={this.getRcDrawerStyle()}
className={drawerClassName}
>
{this.renderBody()}
</RcDrawer>
</DrawerContext.Provider>
);
}}
</ConfigConsumer>
);
};
render() {
return <DrawerContext.Consumer>{this.renderProvider}</DrawerContext.Consumer>;
7 years ago
}
}
export default withConfigConsumer<DrawerProps>({
prefixCls: 'drawer',
})(Drawer);