ant-design/components/modal/ConfirmDialog.tsx

293 lines
8.0 KiB
TypeScript
Raw Normal View History

import * as React from 'react';
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
import classNames from 'classnames';
import { CONTAINER_MAX_OFFSET } from '../_util/hooks/useZIndex';
import { getTransitionName } from '../_util/motion';
import { devUseWarning } from '../_util/warning';
import type { ThemeConfig } from '../config-provider';
import ConfigProvider from '../config-provider';
import { useLocale } from '../locale';
import useToken from '../theme/useToken';
import CancelBtn from './components/ConfirmCancelBtn';
import OkBtn from './components/ConfirmOkBtn';
import type { ModalContextProps } from './context';
import { ModalContextProvider } from './context';
import type { ModalFuncProps, ModalLocale } from './interface';
import Modal from './Modal';
import Confirm from './style/confirm';
export interface ConfirmDialogProps extends ModalFuncProps {
prefixCls: string;
afterClose?: () => void;
close?: (...args: any[]) => void;
/**
* `close` prop support `...args` that pass to the developer
* that we can not break this.
* Provider `onClose` for internal usage
*/
onConfirm?: (confirmed: boolean) => void;
autoFocusButton?: null | 'ok' | 'cancel';
rootPrefixCls?: string;
iconPrefixCls?: string;
/**
* Only passed by static method
*/
theme?: ThemeConfig;
/** @private Internal Usage. Do not override this */
locale?: ModalLocale;
/**
* Do not throw if is await mode
*/
isSilent?: () => boolean;
}
export function ConfirmContent(
props: ConfirmDialogProps & {
confirmPrefixCls: string;
},
) {
const {
prefixCls,
icon,
okText,
cancelText,
confirmPrefixCls,
type,
okCancel,
footer,
// Legacy for static function usage
locale: staticLocale,
...resetProps
} = props;
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Modal');
warning(
!(typeof icon === 'string' && icon.length > 2),
'breaking',
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
);
}
// Icon
let mergedIcon: React.ReactNode = icon;
// 支持传入{ icon: null }来隐藏`Modal.confirm`默认的Icon
if (!icon && icon !== null) {
switch (type) {
case 'info':
mergedIcon = <InfoCircleFilled />;
break;
case 'success':
mergedIcon = <CheckCircleFilled />;
break;
case 'error':
mergedIcon = <CloseCircleFilled />;
break;
default:
mergedIcon = <ExclamationCircleFilled />;
}
}
// 默认为 true保持向下兼容
const mergedOkCancel = okCancel ?? type === 'confirm';
const autoFocusButton = props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
const [locale] = useLocale('Modal');
const mergedLocale = staticLocale || locale;
// ================== Locale Text ==================
const okTextLocale = okText || (mergedOkCancel ? mergedLocale?.okText : mergedLocale?.justOkText);
const cancelTextLocale = cancelText || mergedLocale?.cancelText;
// ================= Context Value =================
const btnCtxValue: ModalContextProps = {
autoFocusButton,
cancelTextLocale,
okTextLocale,
mergedOkCancel,
...resetProps,
};
const btnCtxValueMemo = React.useMemo(() => btnCtxValue, [...Object.values(btnCtxValue)]);
// ====================== Footer Origin Node ======================
const footerOriginNode = (
<>
<CancelBtn />
<OkBtn />
</>
);
const hasTitle = props.title !== undefined && props.title !== null;
const bodyCls = `${confirmPrefixCls}-body`;
return (
<div className={`${confirmPrefixCls}-body-wrapper`}>
<div
className={classNames(bodyCls, {
[`${bodyCls}-has-title`]: hasTitle,
})}
>
{mergedIcon}
<div className={`${confirmPrefixCls}-paragraph`}>
{hasTitle && <span className={`${confirmPrefixCls}-title`}>{props.title}</span>}
<div className={`${confirmPrefixCls}-content`}>{props.content}</div>
</div>
</div>
{footer === undefined || typeof footer === 'function' ? (
<ModalContextProvider value={btnCtxValueMemo}>
<div className={`${confirmPrefixCls}-btns`}>
{typeof footer === 'function'
? footer(footerOriginNode, {
OkBtn,
CancelBtn,
})
: footerOriginNode}
</div>
</ModalContextProvider>
) : (
footer
)}
<Confirm prefixCls={prefixCls} />
</div>
);
}
const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {
const {
close,
zIndex,
afterClose,
open,
keyboard,
centered,
getContainer,
maskStyle,
direction,
prefixCls,
wrapClassName,
rootPrefixCls,
bodyStyle,
closable = false,
closeIcon,
modalRender,
focusTriggerAfterClose,
onConfirm,
styles,
} = props;
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Modal');
[
['visible', 'open'],
['bodyStyle', 'styles.body'],
['maskStyle', 'styles.mask'],
].forEach(([deprecatedName, newName]) => {
warning.deprecated(!(deprecatedName in props), deprecatedName, newName);
});
}
const confirmPrefixCls = `${prefixCls}-confirm`;
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 classString = classNames(
confirmPrefixCls,
`${confirmPrefixCls}-${props.type}`,
{ [`${confirmPrefixCls}-rtl`]: direction === 'rtl' },
props.className,
);
// ========================= zIndex =========================
const [, token] = useToken();
const mergedZIndex = React.useMemo(() => {
if (zIndex !== undefined) {
return zIndex;
}
// Static always use max zIndex
return token.zIndexPopupBase + CONTAINER_MAX_OFFSET;
}, [zIndex, token]);
// ========================= Render =========================
return (
<Modal
prefixCls={prefixCls}
className={classString}
wrapClassName={classNames(
{ [`${confirmPrefixCls}-centered`]: !!props.centered },
wrapClassName,
)}
onCancel={() => {
close?.({ triggerCancel: true });
onConfirm?.(false);
}}
open={open}
title=""
footer={null}
transitionName={getTransitionName(rootPrefixCls || '', 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls || '', 'fade', props.maskTransitionName)}
mask={mask}
maskClosable={maskClosable}
style={style}
styles={{ body: bodyStyle, mask: maskStyle, ...styles }}
width={width}
zIndex={mergedZIndex}
afterClose={afterClose}
keyboard={keyboard}
centered={centered}
getContainer={getContainer}
closable={closable}
closeIcon={closeIcon}
modalRender={modalRender}
focusTriggerAfterClose={focusTriggerAfterClose}
>
<ConfirmContent {...props} confirmPrefixCls={confirmPrefixCls} />
</Modal>
);
};
const ConfirmDialogWrapper: React.FC<ConfirmDialogProps> = (props) => {
const { rootPrefixCls, iconPrefixCls, direction, theme } = props;
return (
<ConfigProvider
prefixCls={rootPrefixCls}
iconPrefixCls={iconPrefixCls}
direction={direction}
theme={theme}
>
<ConfirmDialog {...props} />
</ConfigProvider>
);
};
if (process.env.NODE_ENV !== 'production') {
ConfirmDialog.displayName = 'ConfirmDialog';
ConfirmDialogWrapper.displayName = 'ConfirmDialogWrapper';
}
export default ConfirmDialogWrapper;