Skip to content

Commit 02aa023

Browse files
committed
feat: add draft dialog component
1 parent 7a09921 commit 02aa023

File tree

10 files changed

+516
-6
lines changed

10 files changed

+516
-6
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"loadsh": "^0.0.4",
3333
"monaco-editor": "^0.21.2",
3434
"rc-collapse": "^2.0.1",
35+
"rc-dialog": "^8.4.5",
3536
"rc-tree": "^3.10.0",
3637
"react": "^16.13.1",
3738
"react-dnd": "^9.3.4",

src/components/button/index.tsx

+4-5
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ import { classNames, prefixClaName } from 'mo/common/className';
44

55
type BtnSizeType = 'normal' | 'large';
66
export interface IButton extends React.ComponentProps<'a'> {
7-
/**
8-
* Default size is normal
9-
*/
7+
disabled?: boolean;
108
size?: BtnSizeType;
119
}
1210

1311
export const defaultButtonClassName = 'btn';
1412

1513
export function Button(props: React.PropsWithChildren<IButton>) {
1614
const { className, children, size = 'normal', ...others } = props;
17-
15+
const disabled = props.disabled ? 'disabled' : null;
1816
const claNames = classNames(
1917
prefixClaName(defaultButtonClassName),
2018
size,
21-
className
19+
className,
20+
disabled
2221
);
2322

2423
return (

src/components/button/style.scss

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ $btn: 'btn';
2222
padding: 8px;
2323
}
2424

25+
&.disabled {
26+
cursor: not-allowed;
27+
}
28+
2529
&:hover {
2630
opacity: 0.9;
2731
}
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as React from 'react';
2+
import { Button, IButton } from 'mo/components/button';
3+
4+
export interface ActionButtonProps {
5+
actionFn?: (...args: any[]) => any | PromiseLike<any>;
6+
closeModal: Function;
7+
autoFocus?: boolean;
8+
buttonProps?: IButton;
9+
}
10+
11+
const ActionButton: React.FC<ActionButtonProps> = props => {
12+
const clickedRef = React.useRef<boolean>(false);
13+
const ref = React.useRef<any>();
14+
15+
React.useEffect(() => {
16+
let timeoutId: number;
17+
if (props.autoFocus) {
18+
const $this = ref.current as HTMLInputElement;
19+
timeoutId = setTimeout(() => $this.focus());
20+
}
21+
return () => {
22+
if (timeoutId) {
23+
clearTimeout(timeoutId);
24+
}
25+
};
26+
}, []);
27+
28+
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
29+
const { closeModal } = props;
30+
if (!returnValueOfOnOk || !returnValueOfOnOk.then) {
31+
return;
32+
}
33+
returnValueOfOnOk.then(
34+
(...args: any[]) => {
35+
// It's unnecessary to set loading=false, for the Modal will be unmounted after close.
36+
closeModal(...args);
37+
},
38+
(e: Error) => {
39+
// eslint-disable-next-line no-console
40+
console.error(e);
41+
clickedRef.current = false;
42+
},
43+
);
44+
};
45+
46+
const onClick = () => {
47+
const { actionFn, closeModal } = props;
48+
if (clickedRef.current) {
49+
return;
50+
}
51+
clickedRef.current = true;
52+
if (!actionFn) {
53+
closeModal();
54+
return;
55+
}
56+
let returnValueOfOnOk;
57+
if (actionFn.length) {
58+
returnValueOfOnOk = actionFn(closeModal);
59+
clickedRef.current = false;
60+
} else {
61+
returnValueOfOnOk = actionFn();
62+
if (!returnValueOfOnOk) {
63+
closeModal();
64+
return;
65+
}
66+
}
67+
handlePromiseOnOk(returnValueOfOnOk);
68+
};
69+
70+
const { children, buttonProps } = props;
71+
return (
72+
<Button
73+
onClick={onClick}
74+
{...buttonProps}
75+
ref={ref}
76+
>
77+
{children}
78+
</Button>
79+
);
80+
};
81+
82+
export default ActionButton;
+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import * as React from 'react';
2+
import classNames from 'classnames';
3+
import Dialog, { IModalFuncProps } from './Modal';
4+
import ActionButton from './ActionButton';
5+
6+
interface ConfirmDialogProps extends IModalFuncProps {
7+
afterClose?: () => void;
8+
close: (...args: any[]) => void;
9+
autoFocusButton?: null | 'ok' | 'cancel';
10+
}
11+
12+
const ConfirmDialog = (props: ConfirmDialogProps) => {
13+
const {
14+
icon,
15+
onCancel,
16+
onOk,
17+
close,
18+
zIndex,
19+
afterClose,
20+
visible,
21+
keyboard,
22+
centered,
23+
getContainer,
24+
maskStyle,
25+
okText,
26+
okButtonProps,
27+
cancelText,
28+
cancelButtonProps,
29+
prefixCls,
30+
bodyStyle,
31+
closable = false,
32+
closeIcon,
33+
modalRender,
34+
focusTriggerAfterClose,
35+
} = props;
36+
const contentPrefixCls = `${prefixCls}-confirm`;
37+
// 默认为 true,保持向下兼容
38+
const okCancel = 'okCancel' in props ? props.okCancel! : true;
39+
const width = props.width || 416;
40+
const style = props.style || {};
41+
const mask = props.mask === undefined ? true : props.mask;
42+
// 默认为 false,保持旧版默认行为
43+
const maskClosable = props.maskClosable === undefined ? false : props.maskClosable;
44+
const autoFocusButton = props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
45+
const transitionName = props.transitionName || 'zoom';
46+
const maskTransitionName = props.maskTransitionName || 'fade';
47+
48+
const classString = classNames(
49+
contentPrefixCls,
50+
`${contentPrefixCls}-${props.type}`,
51+
props.className,
52+
);
53+
54+
const cancelButton = okCancel && (
55+
<ActionButton
56+
actionFn={onCancel}
57+
closeModal={close}
58+
autoFocus={autoFocusButton === 'cancel'}
59+
buttonProps={cancelButtonProps}
60+
>
61+
{cancelText}
62+
</ActionButton>
63+
);
64+
65+
return (
66+
<Dialog
67+
prefixCls={prefixCls}
68+
className={classString}
69+
wrapClassName={classNames({ [`${contentPrefixCls}-centered`]: !!props.centered })}
70+
onCancel={() => close({ triggerCancel: true })}
71+
visible={visible}
72+
title=""
73+
transitionName={transitionName}
74+
footer=""
75+
maskTransitionName={maskTransitionName}
76+
mask={mask}
77+
maskClosable={maskClosable}
78+
maskStyle={maskStyle}
79+
style={style}
80+
width={width}
81+
zIndex={zIndex}
82+
afterClose={afterClose}
83+
keyboard={keyboard}
84+
centered={centered}
85+
getContainer={getContainer}
86+
closable={closable}
87+
closeIcon={closeIcon}
88+
modalRender={modalRender}
89+
focusTriggerAfterClose={focusTriggerAfterClose}
90+
>
91+
<div className={`${contentPrefixCls}-body-wrapper`}>
92+
<div className={`${contentPrefixCls}-body`} style={bodyStyle}>
93+
{icon}
94+
{props.title === undefined ? null : (
95+
<span className={`${contentPrefixCls}-title`}>{props.title}</span>
96+
)}
97+
<div className={`${contentPrefixCls}-content`}>{props.content}</div>
98+
</div>
99+
<div className={`${contentPrefixCls}-btns`}>
100+
{cancelButton}
101+
<ActionButton
102+
actionFn={onOk}
103+
closeModal={close}
104+
autoFocus={autoFocusButton === 'ok'}
105+
buttonProps={okButtonProps}
106+
>
107+
{okText}
108+
</ActionButton>
109+
</div>
110+
</div>
111+
</Dialog>
112+
);
113+
};
114+
115+
export default ConfirmDialog;

src/components/dialog/Modal.tsx

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import * as React from 'react';
2+
import Dialog from 'rc-dialog';
3+
import { IDialogPropTypes } from 'rc-dialog/lib/IDialogPropTypes';
4+
5+
import { classNames, prefixClaName } from 'mo/common/className';
6+
7+
import { Button, IButton } from 'mo/components/button';
8+
9+
let mousePosition
10+
11+
const getClickPosition = (e: MouseEvent) => {
12+
mousePosition = {
13+
x: e.pageX,
14+
y: e.pageY,
15+
};
16+
setTimeout(() => {
17+
mousePosition = null;
18+
}, 100);
19+
};
20+
21+
// 只有点击事件支持从鼠标位置动画展开
22+
if (typeof window !== 'undefined' && window.document?.documentElement) {
23+
document.documentElement.addEventListener('click', getClickPosition, true);
24+
}
25+
26+
export const destroyFns: Array<() => void> = [];
27+
28+
export interface IModalProps extends IDialogPropTypes {
29+
/** 点击确定回调 */
30+
onOk?: (e: React.MouseEvent<HTMLElement>) => void;
31+
/** 点击模态框右上角叉、取消按钮、Props.maskClosable 值为 true 时的遮罩层或键盘按下 Esc 时的回调 */
32+
onCancel?: (e: React.SyntheticEvent<Element, Event>) => void;
33+
/** 垂直居中 */
34+
centered?: boolean;
35+
/** 确认按钮文字 */
36+
okText?: React.ReactNode;
37+
/** 取消按钮文字 */
38+
cancelText?: React.ReactNode;
39+
okButtonProps?: IButton;
40+
cancelButtonProps?: IButton;
41+
}
42+
43+
export interface IModalFuncProps extends IDialogPropTypes {
44+
content?: React.ReactNode;
45+
onOk?: (...args: any[]) => any;
46+
onCancel?: (...args: any[]) => any;
47+
okButtonProps?: IButton;
48+
cancelButtonProps?: IButton;
49+
centered?: boolean;
50+
okText?: React.ReactNode;
51+
cancelText?: React.ReactNode;
52+
icon?: React.ReactNode;
53+
okCancel?: boolean;
54+
type?: string;
55+
autoFocusButton?: null | 'ok' | 'cancel';
56+
}
57+
58+
const Modal: React.FC<IModalProps> = (props) => {
59+
const handleCancel = (e: React.SyntheticEvent<Element, Event>) => {
60+
const { onCancel } = props;
61+
onCancel?.(e);
62+
};
63+
64+
const handleOk = (e: React.MouseEvent<HTMLButtonElement>) => {
65+
const { onOk } = props;
66+
onOk?.(e);
67+
};
68+
69+
const renderFooter = () => {
70+
const { okText, cancelText } = props;
71+
return (
72+
<>
73+
<Button onClick={handleCancel} {...props.cancelButtonProps}>
74+
{cancelText}
75+
</Button>
76+
<Button onClick={handleOk} {...props.okButtonProps}>
77+
{okText}
78+
</Button>
79+
</>
80+
);
81+
};
82+
83+
const {
84+
footer,
85+
visible,
86+
wrapClassName,
87+
centered,
88+
getContainer,
89+
closeIcon,
90+
focusTriggerAfterClose = true,
91+
...restProps
92+
} = props;
93+
94+
const prefixCls = prefixClaName('modal');
95+
const defaultFooter = renderFooter;
96+
97+
const closeIconToRender = (
98+
<span className={`${prefixCls}-close-x`}>{closeIcon}</span>
99+
);
100+
101+
const wrapClassNameExtended = classNames(wrapClassName, {
102+
[`${prefixCls}-centered`]: !!centered,
103+
});
104+
return (
105+
<Dialog
106+
{...restProps}
107+
getContainer={getContainer}
108+
prefixCls={prefixCls}
109+
wrapClassName={wrapClassNameExtended}
110+
footer={footer === undefined ? defaultFooter : footer}
111+
visible={visible}
112+
mousePosition={mousePosition}
113+
onClose={handleCancel}
114+
closeIcon={closeIconToRender}
115+
focusTriggerAfterClose={focusTriggerAfterClose}
116+
/>
117+
);
118+
};
119+
120+
Modal.defaultProps = {
121+
width: 520,
122+
transitionName: 'zoom',
123+
maskTransitionName: 'fade',
124+
visible: false,
125+
};
126+
127+
export default Modal;

0 commit comments

Comments
 (0)