-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
516 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,10 @@ $btn: 'btn'; | |
padding: 8px; | ||
} | ||
|
||
&.disabled { | ||
cursor: not-allowed; | ||
} | ||
|
||
&:hover { | ||
opacity: 0.9; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import * as React from 'react'; | ||
import { Button, IButton } from 'mo/components/button'; | ||
|
||
export interface ActionButtonProps { | ||
actionFn?: (...args: any[]) => any | PromiseLike<any>; | ||
closeModal: Function; | ||
autoFocus?: boolean; | ||
buttonProps?: IButton; | ||
} | ||
|
||
const ActionButton: React.FC<ActionButtonProps> = props => { | ||
const clickedRef = React.useRef<boolean>(false); | ||
const ref = React.useRef<any>(); | ||
|
||
React.useEffect(() => { | ||
let timeoutId: number; | ||
if (props.autoFocus) { | ||
const $this = ref.current as HTMLInputElement; | ||
timeoutId = setTimeout(() => $this.focus()); | ||
} | ||
return () => { | ||
if (timeoutId) { | ||
clearTimeout(timeoutId); | ||
} | ||
}; | ||
}, []); | ||
|
||
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => { | ||
const { closeModal } = props; | ||
if (!returnValueOfOnOk || !returnValueOfOnOk.then) { | ||
return; | ||
} | ||
returnValueOfOnOk.then( | ||
(...args: any[]) => { | ||
// It's unnecessary to set loading=false, for the Modal will be unmounted after close. | ||
closeModal(...args); | ||
}, | ||
(e: Error) => { | ||
// eslint-disable-next-line no-console | ||
console.error(e); | ||
clickedRef.current = false; | ||
}, | ||
); | ||
}; | ||
|
||
const onClick = () => { | ||
const { actionFn, closeModal } = props; | ||
if (clickedRef.current) { | ||
return; | ||
} | ||
clickedRef.current = true; | ||
if (!actionFn) { | ||
closeModal(); | ||
return; | ||
} | ||
let returnValueOfOnOk; | ||
if (actionFn.length) { | ||
returnValueOfOnOk = actionFn(closeModal); | ||
clickedRef.current = false; | ||
} else { | ||
returnValueOfOnOk = actionFn(); | ||
if (!returnValueOfOnOk) { | ||
closeModal(); | ||
return; | ||
} | ||
} | ||
handlePromiseOnOk(returnValueOfOnOk); | ||
}; | ||
|
||
const { children, buttonProps } = props; | ||
return ( | ||
<Button | ||
onClick={onClick} | ||
{...buttonProps} | ||
ref={ref} | ||
> | ||
{children} | ||
</Button> | ||
); | ||
}; | ||
|
||
export default ActionButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import * as React from 'react'; | ||
import classNames from 'classnames'; | ||
import Dialog, { IModalFuncProps } from './Modal'; | ||
import ActionButton from './ActionButton'; | ||
|
||
interface ConfirmDialogProps extends IModalFuncProps { | ||
afterClose?: () => void; | ||
close: (...args: any[]) => void; | ||
autoFocusButton?: null | 'ok' | 'cancel'; | ||
} | ||
|
||
const ConfirmDialog = (props: ConfirmDialogProps) => { | ||
const { | ||
icon, | ||
onCancel, | ||
onOk, | ||
close, | ||
zIndex, | ||
afterClose, | ||
visible, | ||
keyboard, | ||
centered, | ||
getContainer, | ||
maskStyle, | ||
okText, | ||
okButtonProps, | ||
cancelText, | ||
cancelButtonProps, | ||
prefixCls, | ||
bodyStyle, | ||
closable = false, | ||
closeIcon, | ||
modalRender, | ||
focusTriggerAfterClose, | ||
} = props; | ||
const contentPrefixCls = `${prefixCls}-confirm`; | ||
// 默认为 true,保持向下兼容 | ||
const okCancel = 'okCancel' in props ? props.okCancel! : true; | ||
const width = props.width || 416; | ||
const style = props.style || {}; | ||
const mask = props.mask === undefined ? true : props.mask; | ||
// 默认为 false,保持旧版默认行为 | ||
const maskClosable = props.maskClosable === undefined ? false : props.maskClosable; | ||
const autoFocusButton = props.autoFocusButton === null ? false : props.autoFocusButton || 'ok'; | ||
const transitionName = props.transitionName || 'zoom'; | ||
const maskTransitionName = props.maskTransitionName || 'fade'; | ||
|
||
const classString = classNames( | ||
contentPrefixCls, | ||
`${contentPrefixCls}-${props.type}`, | ||
props.className, | ||
); | ||
|
||
const cancelButton = okCancel && ( | ||
<ActionButton | ||
actionFn={onCancel} | ||
closeModal={close} | ||
autoFocus={autoFocusButton === 'cancel'} | ||
buttonProps={cancelButtonProps} | ||
> | ||
{cancelText} | ||
</ActionButton> | ||
); | ||
|
||
return ( | ||
<Dialog | ||
prefixCls={prefixCls} | ||
className={classString} | ||
wrapClassName={classNames({ [`${contentPrefixCls}-centered`]: !!props.centered })} | ||
onCancel={() => close({ triggerCancel: true })} | ||
visible={visible} | ||
title="" | ||
transitionName={transitionName} | ||
footer="" | ||
maskTransitionName={maskTransitionName} | ||
mask={mask} | ||
maskClosable={maskClosable} | ||
maskStyle={maskStyle} | ||
style={style} | ||
width={width} | ||
zIndex={zIndex} | ||
afterClose={afterClose} | ||
keyboard={keyboard} | ||
centered={centered} | ||
getContainer={getContainer} | ||
closable={closable} | ||
closeIcon={closeIcon} | ||
modalRender={modalRender} | ||
focusTriggerAfterClose={focusTriggerAfterClose} | ||
> | ||
<div className={`${contentPrefixCls}-body-wrapper`}> | ||
<div className={`${contentPrefixCls}-body`} style={bodyStyle}> | ||
{icon} | ||
{props.title === undefined ? null : ( | ||
<span className={`${contentPrefixCls}-title`}>{props.title}</span> | ||
)} | ||
<div className={`${contentPrefixCls}-content`}>{props.content}</div> | ||
</div> | ||
<div className={`${contentPrefixCls}-btns`}> | ||
{cancelButton} | ||
<ActionButton | ||
actionFn={onOk} | ||
closeModal={close} | ||
autoFocus={autoFocusButton === 'ok'} | ||
buttonProps={okButtonProps} | ||
> | ||
{okText} | ||
</ActionButton> | ||
</div> | ||
</div> | ||
</Dialog> | ||
); | ||
}; | ||
|
||
export default ConfirmDialog; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import * as React from 'react'; | ||
import Dialog from 'rc-dialog'; | ||
import { IDialogPropTypes } from 'rc-dialog/lib/IDialogPropTypes'; | ||
|
||
import { classNames, prefixClaName } from 'mo/common/className'; | ||
|
||
import { Button, IButton } from 'mo/components/button'; | ||
|
||
let mousePosition | ||
|
||
const getClickPosition = (e: MouseEvent) => { | ||
mousePosition = { | ||
x: e.pageX, | ||
y: e.pageY, | ||
}; | ||
setTimeout(() => { | ||
mousePosition = null; | ||
}, 100); | ||
}; | ||
|
||
// 只有点击事件支持从鼠标位置动画展开 | ||
if (typeof window !== 'undefined' && window.document?.documentElement) { | ||
document.documentElement.addEventListener('click', getClickPosition, true); | ||
} | ||
|
||
export const destroyFns: Array<() => void> = []; | ||
|
||
export interface IModalProps extends IDialogPropTypes { | ||
/** 点击确定回调 */ | ||
onOk?: (e: React.MouseEvent<HTMLElement>) => void; | ||
/** 点击模态框右上角叉、取消按钮、Props.maskClosable 值为 true 时的遮罩层或键盘按下 Esc 时的回调 */ | ||
onCancel?: (e: React.SyntheticEvent<Element, Event>) => void; | ||
/** 垂直居中 */ | ||
centered?: boolean; | ||
/** 确认按钮文字 */ | ||
okText?: React.ReactNode; | ||
/** 取消按钮文字 */ | ||
cancelText?: React.ReactNode; | ||
okButtonProps?: IButton; | ||
cancelButtonProps?: IButton; | ||
} | ||
|
||
export interface IModalFuncProps extends IDialogPropTypes { | ||
content?: React.ReactNode; | ||
onOk?: (...args: any[]) => any; | ||
onCancel?: (...args: any[]) => any; | ||
okButtonProps?: IButton; | ||
cancelButtonProps?: IButton; | ||
centered?: boolean; | ||
okText?: React.ReactNode; | ||
cancelText?: React.ReactNode; | ||
icon?: React.ReactNode; | ||
okCancel?: boolean; | ||
type?: string; | ||
autoFocusButton?: null | 'ok' | 'cancel'; | ||
} | ||
|
||
const Modal: React.FC<IModalProps> = (props) => { | ||
const handleCancel = (e: React.SyntheticEvent<Element, Event>) => { | ||
const { onCancel } = props; | ||
onCancel?.(e); | ||
}; | ||
|
||
const handleOk = (e: React.MouseEvent<HTMLButtonElement>) => { | ||
const { onOk } = props; | ||
onOk?.(e); | ||
}; | ||
|
||
const renderFooter = () => { | ||
const { okText, cancelText } = props; | ||
return ( | ||
<> | ||
<Button onClick={handleCancel} {...props.cancelButtonProps}> | ||
{cancelText} | ||
</Button> | ||
<Button onClick={handleOk} {...props.okButtonProps}> | ||
{okText} | ||
</Button> | ||
</> | ||
); | ||
}; | ||
|
||
const { | ||
footer, | ||
visible, | ||
wrapClassName, | ||
centered, | ||
getContainer, | ||
closeIcon, | ||
focusTriggerAfterClose = true, | ||
...restProps | ||
} = props; | ||
|
||
const prefixCls = prefixClaName('modal'); | ||
const defaultFooter = renderFooter; | ||
|
||
const closeIconToRender = ( | ||
<span className={`${prefixCls}-close-x`}>{closeIcon}</span> | ||
); | ||
|
||
const wrapClassNameExtended = classNames(wrapClassName, { | ||
[`${prefixCls}-centered`]: !!centered, | ||
}); | ||
return ( | ||
<Dialog | ||
{...restProps} | ||
getContainer={getContainer} | ||
prefixCls={prefixCls} | ||
wrapClassName={wrapClassNameExtended} | ||
footer={footer === undefined ? defaultFooter : footer} | ||
visible={visible} | ||
mousePosition={mousePosition} | ||
onClose={handleCancel} | ||
closeIcon={closeIconToRender} | ||
focusTriggerAfterClose={focusTriggerAfterClose} | ||
/> | ||
); | ||
}; | ||
|
||
Modal.defaultProps = { | ||
width: 520, | ||
transitionName: 'zoom', | ||
maskTransitionName: 'fade', | ||
visible: false, | ||
}; | ||
|
||
export default Modal; |
Oops, something went wrong.