This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
import * as React from 'react';
import type { ThemeConfig } from '../config-provider';
import type { ModalFuncProps, ModalLocale } from './interface';
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 declare const ConfirmContent: React.FC<ConfirmDialogProps & {
confirmPrefixCls: string;
}>;
declare const ConfirmDialogWrapper: React.FC<ConfirmDialogProps>;
export default ConfirmDialogWrapper;
+228
View File
@@ -0,0 +1,228 @@
"use client";
import * as React from 'react';
import CheckCircleFilled from "@ant-design/icons/es/icons/CheckCircleFilled";
import CloseCircleFilled from "@ant-design/icons/es/icons/CloseCircleFilled";
import ExclamationCircleFilled from "@ant-design/icons/es/icons/ExclamationCircleFilled";
import InfoCircleFilled from "@ant-design/icons/es/icons/InfoCircleFilled";
import { clsx } from 'clsx';
import { CONTAINER_MAX_OFFSET, normalizeMaskConfig } from '../_util/hooks';
import { isNonNullable, isPlainObject } from '../_util/is';
import { getTransitionName } from '../_util/motion';
import { devUseWarning } from '../_util/warning';
import ConfigProvider from '../config-provider';
import { useComponentConfig } from '../config-provider/context';
import { useLocale } from '../locale';
import useToken from '../theme/useToken';
import CancelBtn from './components/ConfirmCancelBtn';
import OkBtn from './components/ConfirmOkBtn';
import { ModalContextProvider } from './context';
import Modal from './Modal';
import Confirm from './style/confirm';
export const ConfirmContent = props => {
const {
prefixCls,
icon,
okText,
cancelText,
confirmPrefixCls,
type,
okCancel,
footer,
// Legacy for static function usage
locale: staticLocale,
autoFocusButton,
focusable,
...restProps
} = props;
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Modal');
process.env.NODE_ENV !== "production" ? 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`) : void 0;
}
// Icon
let mergedIcon = icon;
// 支持传入{ icon: null }来隐藏`Modal.confirm`默认的Icon
if (!icon && icon !== null) {
switch (type) {
case 'info':
mergedIcon = /*#__PURE__*/React.createElement(InfoCircleFilled, null);
break;
case 'success':
mergedIcon = /*#__PURE__*/React.createElement(CheckCircleFilled, null);
break;
case 'error':
mergedIcon = /*#__PURE__*/React.createElement(CloseCircleFilled, null);
break;
default:
mergedIcon = /*#__PURE__*/React.createElement(ExclamationCircleFilled, null);
}
}
// 默认为 true,保持向下兼容
const mergedOkCancel = okCancel ?? type === 'confirm';
const mergedAutoFocusButton = React.useMemo(() => {
const base = focusable?.autoFocusButton || autoFocusButton;
return base || base === null ? base : 'ok';
}, [autoFocusButton, focusable?.autoFocusButton]);
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 {
closable
} = restProps;
const {
onClose
} = isPlainObject(closable) ? closable : {};
const memoizedValue = React.useMemo(() => {
return {
autoFocusButton: mergedAutoFocusButton,
cancelTextLocale,
okTextLocale,
mergedOkCancel,
onClose,
...restProps
};
}, [mergedAutoFocusButton, cancelTextLocale, okTextLocale, mergedOkCancel, onClose, restProps]);
// ====================== Footer Origin Node ======================
const footerOriginNode = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CancelBtn, null), /*#__PURE__*/React.createElement(OkBtn, null));
const hasTitle = isNonNullable(props.title) && props.title !== '';
const hasIcon = isNonNullable(mergedIcon);
const bodyCls = `${confirmPrefixCls}-body`;
return /*#__PURE__*/React.createElement("div", {
className: `${confirmPrefixCls}-body-wrapper`
}, /*#__PURE__*/React.createElement("div", {
className: clsx(bodyCls, {
[`${bodyCls}-has-title`]: hasTitle,
[`${bodyCls}-no-icon`]: !hasIcon
})
}, mergedIcon, /*#__PURE__*/React.createElement("div", {
className: `${confirmPrefixCls}-paragraph`
}, hasTitle && /*#__PURE__*/React.createElement("span", {
className: `${confirmPrefixCls}-title`
}, props.title), /*#__PURE__*/React.createElement("div", {
className: `${confirmPrefixCls}-content`
}, props.content))), footer === undefined || typeof footer === 'function' ? (/*#__PURE__*/React.createElement(ModalContextProvider, {
value: memoizedValue
}, /*#__PURE__*/React.createElement("div", {
className: `${confirmPrefixCls}-btns`
}, typeof footer === 'function' ? footer(footerOriginNode, {
OkBtn,
CancelBtn
}) : footerOriginNode))) : footer, /*#__PURE__*/React.createElement(Confirm, {
prefixCls: prefixCls
}));
};
const ConfirmDialog = props => {
const {
close,
zIndex,
maskStyle,
direction,
prefixCls,
wrapClassName,
rootPrefixCls,
bodyStyle,
closable = false,
onConfirm,
styles,
title,
mask,
maskClosable,
okButtonProps,
cancelButtonProps
} = props;
const {
cancelButtonProps: contextCancelButtonProps,
okButtonProps: contextOkButtonProps
} = useComponentConfig('modal');
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Modal');
[['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 classString = clsx(confirmPrefixCls, `${confirmPrefixCls}-${props.type}`, {
[`${confirmPrefixCls}-rtl`]: direction === 'rtl'
}, props.className);
// ========================== Mask ==========================
// 默认为 false,保持旧版默认行为
const mergedMask = React.useMemo(() => {
const nextMaskConfig = normalizeMaskConfig(mask, maskClosable);
nextMaskConfig.closable ?? (nextMaskConfig.closable = false);
return nextMaskConfig;
}, [mask, maskClosable]);
// ========================= 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 /*#__PURE__*/React.createElement(Modal, {
...props,
className: classString,
wrapClassName: clsx({
[`${confirmPrefixCls}-centered`]: !!props.centered
}, wrapClassName),
onCancel: () => {
close?.({
triggerCancel: true
});
onConfirm?.(false);
},
title: title,
footer: null,
transitionName: getTransitionName(rootPrefixCls || '', 'zoom', props.transitionName),
maskTransitionName: getTransitionName(rootPrefixCls || '', 'fade', props.maskTransitionName),
mask: mergedMask,
style: style,
styles: {
body: bodyStyle,
mask: maskStyle,
...styles
},
width: width,
zIndex: mergedZIndex,
closable: closable
}, /*#__PURE__*/React.createElement(ConfirmContent, {
...props,
confirmPrefixCls: confirmPrefixCls,
okButtonProps: {
...contextOkButtonProps,
...okButtonProps
},
cancelButtonProps: {
...contextCancelButtonProps,
...cancelButtonProps
}
}));
};
const ConfirmDialogWrapper = props => {
const {
rootPrefixCls,
iconPrefixCls,
direction,
theme
} = props;
return /*#__PURE__*/React.createElement(ConfigProvider, {
prefixCls: rootPrefixCls,
iconPrefixCls: iconPrefixCls,
direction: direction,
theme: theme
}, /*#__PURE__*/React.createElement(ConfirmDialog, {
...props
}));
};
if (process.env.NODE_ENV !== 'production') {
ConfirmDialog.displayName = 'ConfirmDialog';
ConfirmDialogWrapper.displayName = 'ConfirmDialogWrapper';
}
export default ConfirmDialogWrapper;
+4
View File
@@ -0,0 +1,4 @@
import * as React from 'react';
import type { ModalProps } from './interface';
declare const Modal: React.FC<ModalProps>;
export default Modal;
+248
View File
@@ -0,0 +1,248 @@
"use client";
import * as React from 'react';
import CloseOutlined from "@ant-design/icons/es/icons/CloseOutlined";
import Dialog from '@rc-component/dialog';
import { composeRef } from "@rc-component/util/es/ref";
import { clsx } from 'clsx';
import ContextIsolator from '../_util/ContextIsolator';
import { pickClosable, useClosable, useMergedMask, useMergeSemantic, useZIndex } from '../_util/hooks';
import { isNonNullable, isNumber, isPlainObject } from '../_util/is';
import { getTransitionName } from '../_util/motion';
import { canUseDocElement } from '../_util/styleChecker';
import { devUseWarning } from '../_util/warning';
import ZIndexContext from '../_util/zindexContext';
import { ConfigContext } from '../config-provider';
import { useComponentConfig } from '../config-provider/context';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import useFocusable from '../drawer/useFocusable';
import Skeleton from '../skeleton';
import { usePanelRef } from '../watermark/context';
import { Footer, renderCloseIcon } from './shared';
import useStyle from './style';
let mousePosition;
// ref: https://github.com/ant-design/ant-design/issues/15795
const getClickPosition = e => {
mousePosition = {
x: e.pageX,
y: e.pageY
};
// 100ms 内发生过点击事件,则从点击位置动画展示
// 否则直接 zoom 展示
// 这样可以兼容非点击方式展开
setTimeout(() => {
mousePosition = null;
}, 100);
};
// 只有点击事件支持从鼠标位置动画展开
if (canUseDocElement()) {
document.documentElement.addEventListener('click', getClickPosition, true);
}
const Modal = props => {
const {
prefixCls: customizePrefixCls,
className,
rootClassName,
open,
wrapClassName,
centered,
getContainer,
style,
width = 520,
footer,
classNames,
styles,
children,
loading,
confirmLoading,
zIndex: customizeZIndex,
mousePosition: customizeMousePosition,
onOk,
onCancel,
okButtonProps,
cancelButtonProps,
destroyOnHidden,
destroyOnClose,
panelRef = null,
closable,
mask: modalMask,
modalRender,
maskClosable,
// Focusable
focusTriggerAfterClose,
focusable,
...restProps
} = props;
const {
getPopupContainer: getContextPopupContainer,
getPrefixCls,
direction,
className: contextClassName,
style: contextStyle,
classNames: contextClassNames,
styles: contextStyles,
centered: contextCentered,
cancelButtonProps: contextCancelButtonProps,
okButtonProps: contextOkButtonProps,
mask: contextMask
} = useComponentConfig('modal');
const {
modal: modalContext
} = React.useContext(ConfigContext);
const [closableAfterClose, onClose] = React.useMemo(() => {
if (typeof closable === 'boolean') {
return [undefined, undefined];
}
return [closable?.afterClose, closable?.onClose];
}, [closable]);
const prefixCls = getPrefixCls('modal', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
// ============================ Mask ============================
const [mergedMask, maskBlurClassName, mergeMaskClosable] = useMergedMask(modalMask, contextMask, prefixCls, maskClosable);
// ========================== Focusable =========================
const mergedFocusable = useFocusable(focusable, mergedMask, focusTriggerAfterClose);
// ============================ Open ============================
const handleCancel = e => {
if (confirmLoading) {
return;
}
onCancel?.(e);
onClose?.();
};
const handleOk = e => {
onOk?.(e);
onClose?.();
};
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Modal');
[['bodyStyle', 'styles.body'], ['maskStyle', 'styles.mask'], ['destroyOnClose', 'destroyOnHidden'], ['autoFocusButton', 'focusable.autoFocusButton'], ['focusTriggerAfterClose', 'focusable.focusTriggerAfterClose'], ['maskClosable', 'mask.closable']].forEach(([deprecatedName, newName]) => {
warning.deprecated(!(deprecatedName in props), deprecatedName, newName);
});
}
// Style
const rootCls = useCSSVarCls(prefixCls);
const [hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const wrapClassNameExtended = clsx(wrapClassName, {
[`${prefixCls}-centered`]: centered ?? contextCentered,
[`${prefixCls}-wrap-rtl`]: direction === 'rtl'
});
const dialogFooter = footer !== null && !loading ? (/*#__PURE__*/React.createElement(Footer, {
...props,
okButtonProps: {
...contextOkButtonProps,
...okButtonProps
},
onOk: handleOk,
cancelButtonProps: {
...contextCancelButtonProps,
...cancelButtonProps
},
onCancel: handleCancel
})) : null;
const [rawClosable, mergedCloseIcon, closeBtnIsDisabled, ariaProps] = useClosable(pickClosable(props), pickClosable(modalContext), {
closable: true,
closeIcon: /*#__PURE__*/React.createElement(CloseOutlined, {
className: `${prefixCls}-close-icon`
}),
closeIconRender: icon => renderCloseIcon(prefixCls, icon)
});
const mergedClosable = rawClosable ? {
disabled: closeBtnIsDisabled,
closeIcon: mergedCloseIcon,
afterClose: closableAfterClose,
...ariaProps
} : false;
// ============================ modalRender ============================
const mergedModalRender = modalRender ? node => /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-render`
}, modalRender(node)) : undefined;
// ============================ Refs ============================
// Select `ant-modal-container` by `panelRef`
const panelClassName = `.${prefixCls}-${modalRender ? 'render' : 'container'}`;
const innerPanelRef = usePanelRef(panelClassName);
const mergedPanelRef = composeRef(panelRef, innerPanelRef);
// ============================ zIndex ============================
const [zIndex, contextZIndex] = useZIndex('Modal', customizeZIndex);
const mergedProps = {
...props,
width,
panelRef,
focusTriggerAfterClose: mergedFocusable.focusTriggerAfterClose,
focusable: mergedFocusable,
mask: mergedMask,
maskClosable: mergeMaskClosable,
zIndex
};
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames, maskBlurClassName], [contextStyles, styles], {
props: mergedProps
});
// =========================== Width ============================
const [numWidth, responsiveWidth] = React.useMemo(() => {
if (isPlainObject(width)) {
return [undefined, width];
}
return [width, undefined];
}, [width]);
const responsiveWidthVars = React.useMemo(() => {
const vars = {};
if (responsiveWidth) {
Object.keys(responsiveWidth).forEach(breakpoint => {
const breakpointWidth = responsiveWidth[breakpoint];
if (isNonNullable(breakpointWidth)) {
vars[`--${prefixCls}-${breakpoint}-width`] = isNumber(breakpointWidth) ? `${breakpointWidth}px` : breakpointWidth;
}
});
}
return vars;
}, [prefixCls, responsiveWidth]);
// =========================== Render ===========================
return /*#__PURE__*/React.createElement(ContextIsolator, {
form: true,
space: true
}, /*#__PURE__*/React.createElement(ZIndexContext.Provider, {
value: contextZIndex
}, /*#__PURE__*/React.createElement(Dialog, {
width: numWidth,
...restProps,
zIndex: zIndex,
getContainer: getContainer === undefined ? getContextPopupContainer : getContainer,
prefixCls: prefixCls,
rootClassName: clsx(hashId, rootClassName, cssVarCls, rootCls, mergedClassNames.root),
rootStyle: mergedStyles.root,
footer: dialogFooter,
visible: open,
mousePosition: customizeMousePosition ?? mousePosition,
onClose: handleCancel,
closable: mergedClosable,
closeIcon: mergedCloseIcon,
transitionName: getTransitionName(rootPrefixCls, 'zoom', props.transitionName),
maskTransitionName: getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName),
mask: mergedMask,
maskClosable: mergeMaskClosable,
className: clsx(hashId, className, contextClassName),
style: {
...contextStyle,
...style,
...responsiveWidthVars
},
classNames: {
...mergedClassNames,
wrapper: clsx(mergedClassNames.wrapper, wrapClassNameExtended)
},
styles: mergedStyles,
panelRef: mergedPanelRef,
destroyOnHidden: destroyOnHidden ?? destroyOnClose,
modalRender: mergedModalRender,
// Focusable
focusTriggerAfterClose: mergedFocusable.focusTriggerAfterClose,
focusTrap: mergedFocusable.trap
}, loading ? (/*#__PURE__*/React.createElement(Skeleton, {
active: true,
title: false,
paragraph: {
rows: 4
},
className: `${prefixCls}-body-skeleton`
})) : children)));
};
export default Modal;
+11
View File
@@ -0,0 +1,11 @@
import * as React from 'react';
import type { PanelProps } from '@rc-component/dialog/lib/Dialog/Content/Panel';
import type { ModalClassNamesType, ModalFuncProps, ModalStylesType } from './interface';
export interface PurePanelProps extends Omit<PanelProps, 'prefixCls' | 'footer' | 'classNames' | 'styles'>, Pick<ModalFuncProps, 'type' | 'footer'> {
prefixCls?: string;
style?: React.CSSProperties;
classNames?: ModalClassNamesType;
styles?: ModalStylesType;
}
declare const _default: (props: PurePanelProps) => React.JSX.Element;
export default _default;
+85
View File
@@ -0,0 +1,85 @@
"use client";
import * as React from 'react';
import { Panel } from '@rc-component/dialog';
import { clsx } from 'clsx';
import { useMergeSemantic } from '../_util/hooks';
import { withPureRenderTheme } from '../_util/PurePanel';
import { ConfigContext } from '../config-provider';
import { useComponentConfig } from '../config-provider/context';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import { ConfirmContent } from './ConfirmDialog';
import { Footer, renderCloseIcon } from './shared';
import useStyle from './style';
const PurePanel = props => {
const {
prefixCls: customizePrefixCls,
className,
closeIcon,
closable,
type,
title,
children,
footer,
classNames,
styles,
...restProps
} = props;
const {
getPrefixCls
} = React.useContext(ConfigContext);
const {
className: contextClassName,
style: contextStyle,
classNames: contextClassNames,
styles: contextStyles
} = useComponentConfig('modal');
const rootPrefixCls = getPrefixCls();
const prefixCls = customizePrefixCls || getPrefixCls('modal');
const rootCls = useCSSVarCls(rootPrefixCls);
const [hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [contextStyles, styles], {
props
});
const confirmPrefixCls = `${prefixCls}-confirm`;
// Choose target props by confirm mark
let additionalProps = {};
if (type) {
additionalProps = {
closable: closable ?? false,
title: '',
footer: '',
children: (/*#__PURE__*/React.createElement(ConfirmContent, {
...props,
prefixCls: prefixCls,
confirmPrefixCls: confirmPrefixCls,
rootPrefixCls: rootPrefixCls,
content: children
}))
};
} else {
additionalProps = {
closable: closable ?? true,
title,
footer: footer !== null && /*#__PURE__*/React.createElement(Footer, {
...props
}),
children
};
}
return /*#__PURE__*/React.createElement(Panel, {
prefixCls: prefixCls,
className: clsx(hashId, `${prefixCls}-pure-panel`, type && confirmPrefixCls, type && `${confirmPrefixCls}-${type}`, className, contextClassName, cssVarCls, rootCls, mergedClassNames.root),
style: {
...contextStyle,
...mergedStyles.root
},
...restProps,
closeIcon: renderCloseIcon(prefixCls, closeIcon),
closable: closable,
classNames: mergedClassNames,
styles: mergedStyles,
...additionalProps
});
};
export default withPureRenderTheme(PurePanel);
@@ -0,0 +1,11 @@
import type { FC } from 'react';
import React from 'react';
import type { ConfirmDialogProps } from '../ConfirmDialog';
export interface ConfirmCancelBtnProps extends Pick<ConfirmDialogProps, 'cancelButtonProps' | 'isSilent' | 'rootPrefixCls' | 'close' | 'onConfirm' | 'onCancel'> {
autoFocusButton?: false | 'ok' | 'cancel' | null;
cancelTextLocale?: React.ReactNode;
mergedOkCancel?: boolean;
onClose?: () => void;
}
declare const ConfirmCancelBtn: FC;
export default ConfirmCancelBtn;
@@ -0,0 +1,32 @@
"use client";
import React, { useContext } from 'react';
import ActionButton from '../../_util/ActionButton';
import { ModalContext } from '../context';
const ConfirmCancelBtn = () => {
const {
autoFocusButton,
cancelButtonProps,
cancelTextLocale,
isSilent,
mergedOkCancel,
rootPrefixCls,
close,
onCancel,
onConfirm,
onClose
} = useContext(ModalContext);
return mergedOkCancel ? (/*#__PURE__*/React.createElement(ActionButton, {
isSilent: isSilent,
actionFn: onCancel,
close: (...args) => {
close?.(...args);
onConfirm?.(false);
onClose?.();
},
autoFocus: autoFocusButton === 'cancel',
buttonProps: cancelButtonProps,
prefixCls: `${rootPrefixCls}-btn`
}, cancelTextLocale)) : null;
};
export default ConfirmCancelBtn;
@@ -0,0 +1,10 @@
import type { FC } from 'react';
import React from 'react';
import type { ConfirmDialogProps } from '../ConfirmDialog';
export interface ConfirmOkBtnProps extends Pick<ConfirmDialogProps, 'close' | 'isSilent' | 'okType' | 'okButtonProps' | 'rootPrefixCls' | 'onConfirm' | 'onOk'> {
autoFocusButton?: false | 'ok' | 'cancel' | null;
okTextLocale?: React.ReactNode;
onClose?: () => void;
}
declare const ConfirmOkBtn: FC;
export default ConfirmOkBtn;
+33
View File
@@ -0,0 +1,33 @@
"use client";
import React, { useContext } from 'react';
import ActionButton from '../../_util/ActionButton';
import { ModalContext } from '../context';
const ConfirmOkBtn = () => {
const {
autoFocusButton,
close,
isSilent,
okButtonProps,
rootPrefixCls,
okTextLocale,
okType,
onConfirm,
onOk,
onClose
} = useContext(ModalContext);
return /*#__PURE__*/React.createElement(ActionButton, {
isSilent: isSilent,
type: okType || 'primary',
actionFn: onOk,
close: (...args) => {
close?.(...args);
onConfirm?.(true);
onClose?.();
},
autoFocus: autoFocusButton === 'ok',
buttonProps: okButtonProps,
prefixCls: `${rootPrefixCls}-btn`
}, okTextLocale);
};
export default ConfirmOkBtn;
@@ -0,0 +1,8 @@
import type { FC } from 'react';
import React from 'react';
import type { ModalProps } from '../interface';
export interface NormalCancelBtnProps extends Pick<ModalProps, 'cancelButtonProps' | 'onCancel'> {
cancelTextLocale?: React.ReactNode;
}
declare const NormalCancelBtn: FC;
export default NormalCancelBtn;
@@ -0,0 +1,17 @@
"use client";
import React, { useContext } from 'react';
import Button from '../../button/Button';
import { ModalContext } from '../context';
const NormalCancelBtn = () => {
const {
cancelButtonProps,
cancelTextLocale,
onCancel
} = useContext(ModalContext);
return /*#__PURE__*/React.createElement(Button, {
onClick: onCancel,
...cancelButtonProps
}, cancelTextLocale);
};
export default NormalCancelBtn;
@@ -0,0 +1,8 @@
import type { FC } from 'react';
import React from 'react';
import type { ModalProps } from '../interface';
export interface NormalOkBtnProps extends Pick<ModalProps, 'confirmLoading' | 'okType' | 'okButtonProps' | 'onOk'> {
okTextLocale?: React.ReactNode;
}
declare const NormalOkBtn: FC;
export default NormalOkBtn;
+22
View File
@@ -0,0 +1,22 @@
"use client";
import React, { useContext } from 'react';
import Button from '../../button/Button';
import { convertLegacyProps } from '../../button/buttonHelpers';
import { ModalContext } from '../context';
const NormalOkBtn = () => {
const {
confirmLoading,
okButtonProps,
okType,
okTextLocale,
onOk
} = useContext(ModalContext);
return /*#__PURE__*/React.createElement(Button, {
...convertLegacyProps(okType),
loading: confirmLoading,
onClick: onOk,
...okButtonProps
}, okTextLocale);
};
export default NormalOkBtn;
+27
View File
@@ -0,0 +1,27 @@
import type { ModalFuncProps } from './interface';
export type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps);
export type ModalFunc = (props: ModalFuncProps) => {
destroy: () => void;
update: (configUpdate: ConfigUpdate) => void;
};
export type ModalStaticFunctions = {
info: ModalFunc;
success: ModalFunc;
error: ModalFunc;
warning: ModalFunc;
confirm: ModalFunc;
/** @deprecated Please use `warning` instead */
warn: ModalFunc;
};
export default function confirm(config: ModalFuncProps): {
destroy: (...args: any[]) => void;
update: (configUpdate: ConfigUpdate) => void;
};
export declare function withWarn(props: ModalFuncProps): ModalFuncProps;
export declare function withInfo(props: ModalFuncProps): ModalFuncProps;
export declare function withSuccess(props: ModalFuncProps): ModalFuncProps;
export declare function withError(props: ModalFuncProps): ModalFuncProps;
export declare function withConfirm(props: ModalFuncProps): ModalFuncProps;
export declare function modalGlobalConfig({ rootPrefixCls }: {
rootPrefixCls: string;
}): void;
+159
View File
@@ -0,0 +1,159 @@
"use client";
import React, { useContext } from 'react';
import { render, unmount } from "@rc-component/util/es/React/render";
import warning from '../_util/warning';
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
import ConfirmDialog from './ConfirmDialog';
import destroyFns from './destroyFns';
import { getConfirmLocale } from './locale';
let defaultRootPrefixCls = '';
function getRootPrefixCls() {
return defaultRootPrefixCls;
}
const ConfirmDialogWrapper = props => {
const {
prefixCls: customizePrefixCls,
getContainer,
direction
} = props;
const runtimeLocale = getConfirmLocale();
const config = useContext(ConfigContext);
const rootPrefixCls = getRootPrefixCls() || config.getPrefixCls();
// because Modal.config set rootPrefixCls, which is different from other components
const prefixCls = customizePrefixCls || `${rootPrefixCls}-modal`;
let mergedGetContainer = getContainer;
if (mergedGetContainer === false) {
mergedGetContainer = undefined;
if (process.env.NODE_ENV !== 'production') {
process.env.NODE_ENV !== "production" ? warning(false, 'Modal', 'Static method not support `getContainer` to be `false` since it do not have context env.') : void 0;
}
}
return /*#__PURE__*/React.createElement(ConfirmDialog, {
...props,
rootPrefixCls: rootPrefixCls,
prefixCls: prefixCls,
iconPrefixCls: config.iconPrefixCls,
theme: config.theme,
direction: direction ?? config.direction,
locale: config.locale?.Modal ?? runtimeLocale,
getContainer: mergedGetContainer
});
};
export default function confirm(config) {
const global = globalConfig();
if (process.env.NODE_ENV !== 'production' && !global.holderRender) {
warnContext('Modal');
}
const container = document.createDocumentFragment();
let currentConfig = {
...config,
close,
open: true
};
let timeoutId;
function destroy(...args) {
const triggerCancel = args.some(param => param?.triggerCancel);
if (triggerCancel) {
config.onCancel?.(() => {}, ...args.slice(1));
}
for (let i = 0; i < destroyFns.length; i++) {
const fn = destroyFns[i];
if (fn === close) {
destroyFns.splice(i, 1);
break;
}
}
unmount(container).then(() => {
// Do nothing
});
}
const scheduleRender = props => {
clearTimeout(timeoutId);
/**
* https://github.com/ant-design/ant-design/issues/23623
*
* Sync render blocks React event. Let's make this async.
*/
timeoutId = setTimeout(() => {
const rootPrefixCls = global.getPrefixCls(undefined, getRootPrefixCls());
const iconPrefixCls = global.getIconPrefixCls();
const theme = global.getTheme();
const dom = /*#__PURE__*/React.createElement(ConfirmDialogWrapper, {
...props
});
render(/*#__PURE__*/React.createElement(ConfigProvider, {
prefixCls: rootPrefixCls,
iconPrefixCls: iconPrefixCls,
theme: theme
}, typeof global.holderRender === 'function' ? global.holderRender(dom) : dom), container);
});
};
function close(...args) {
currentConfig = {
...currentConfig,
open: false,
afterClose: () => {
if (typeof config.afterClose === 'function') {
config.afterClose();
}
// @ts-ignore
destroy.apply(this, args);
}
};
scheduleRender(currentConfig);
}
function update(configUpdate) {
if (typeof configUpdate === 'function') {
currentConfig = configUpdate(currentConfig);
} else {
currentConfig = {
...currentConfig,
...configUpdate
};
}
scheduleRender(currentConfig);
}
scheduleRender(currentConfig);
destroyFns.push(close);
return {
destroy: close,
update
};
}
export function withWarn(props) {
return {
...props,
type: 'warning'
};
}
export function withInfo(props) {
return {
...props,
type: 'info'
};
}
export function withSuccess(props) {
return {
...props,
type: 'success'
};
}
export function withError(props) {
return {
...props,
type: 'error'
};
}
export function withConfirm(props) {
return {
...props,
type: 'confirm'
};
}
export function modalGlobalConfig({
rootPrefixCls
}) {
process.env.NODE_ENV !== "production" ? warning(false, 'Modal', 'Modal.config is deprecated. Please use ConfigProvider.config instead.') : void 0;
defaultRootPrefixCls = rootPrefixCls;
}
+8
View File
@@ -0,0 +1,8 @@
import React from 'react';
import type { ConfirmCancelBtnProps } from './components/ConfirmCancelBtn';
import type { ConfirmOkBtnProps } from './components/ConfirmOkBtn';
import type { NormalCancelBtnProps } from './components/NormalCancelBtn';
import type { NormalOkBtnProps } from './components/NormalOkBtn';
export type ModalContextProps = NormalCancelBtnProps & NormalOkBtnProps & ConfirmOkBtnProps & ConfirmCancelBtnProps;
export declare const ModalContext: React.Context<ModalContextProps>;
export declare const ModalContextProvider: React.Provider<ModalContextProps>;
+5
View File
@@ -0,0 +1,5 @@
import React from 'react';
export const ModalContext = /*#__PURE__*/React.createContext({});
export const {
Provider: ModalContextProvider
} = ModalContext;
+2
View File
@@ -0,0 +1,2 @@
declare const destroyFns: Array<() => void>;
export default destroyFns;
+2
View File
@@ -0,0 +1,2 @@
const destroyFns = [];
export default destroyFns;
+15
View File
@@ -0,0 +1,15 @@
import type { ModalStaticFunctions } from './confirm';
import { modalGlobalConfig } from './confirm';
import OriginModal from './Modal';
import PurePanel from './PurePanel';
import useModal from './useModal';
export type { ModalFuncProps, ModalLocale, ModalProps, ModalSemanticClassNames, ModalSemanticName, ModalSemanticStyles, } from './interface';
type ModalType = typeof OriginModal & ModalStaticFunctions & {
useModal: typeof useModal;
destroyAll: () => void;
config: typeof modalGlobalConfig;
/** @private Internal Component. Do not use in your production. */
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
};
declare const Modal: ModalType;
export default Modal;
+40
View File
@@ -0,0 +1,40 @@
"use client";
import confirm, { modalGlobalConfig, withConfirm, withError, withInfo, withSuccess, withWarn } from './confirm';
import destroyFns from './destroyFns';
import OriginModal from './Modal';
import PurePanel from './PurePanel';
import useModal from './useModal';
function modalWarn(props) {
return confirm(withWarn(props));
}
const Modal = OriginModal;
Modal.useModal = useModal;
Modal.info = function infoFn(props) {
return confirm(withInfo(props));
};
Modal.success = function successFn(props) {
return confirm(withSuccess(props));
};
Modal.error = function errorFn(props) {
return confirm(withError(props));
};
Modal.warning = modalWarn;
Modal.warn = modalWarn;
Modal.confirm = function confirmFn(props) {
return confirm(withConfirm(props));
};
Modal.destroyAll = function destroyAllFn() {
while (destroyFns.length) {
const close = destroyFns.pop();
if (close) {
close();
}
}
};
Modal.config = modalGlobalConfig;
Modal._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
if (process.env.NODE_ENV !== 'production') {
Modal.displayName = 'Modal';
}
export default Modal;
+167
View File
@@ -0,0 +1,167 @@
import type React from 'react';
import type { DialogProps } from '@rc-component/dialog';
import type { ClosableType, MaskType, SemanticClassNamesType, SemanticStylesType } from '../_util/hooks';
import type { Breakpoint } from '../_util/responsiveObserver';
import type { ButtonProps, LegacyButtonType } from '../button/Button';
import type { DirectionType } from '../config-provider';
import type { FocusableConfig, OmitFocusType } from '../drawer/useFocusable';
export type ModalSemanticName = keyof ModalSemanticClassNames & keyof ModalSemanticStyles;
export type ModalSemanticClassNames = {
root?: string;
header?: string;
body?: string;
footer?: string;
container?: string;
title?: string;
wrapper?: string;
mask?: string;
};
export type ModalSemanticStyles = {
root?: React.CSSProperties;
header?: React.CSSProperties;
body?: React.CSSProperties;
footer?: React.CSSProperties;
container?: React.CSSProperties;
title?: React.CSSProperties;
wrapper?: React.CSSProperties;
mask?: React.CSSProperties;
};
export type ModalClassNamesType = SemanticClassNamesType<ModalProps, ModalSemanticClassNames>;
export type ModalStylesType = SemanticStylesType<ModalProps, ModalSemanticStyles>;
interface ModalCommonProps extends Omit<DialogProps, 'footer' | 'width' | 'onClose' | 'animation' | 'maskAnimation' | 'transitionName' | 'maskTransitionName' | 'mask' | 'classNames' | 'styles' | OmitFocusType> {
footer?: React.ReactNode | ((originNode: React.ReactNode, extra: {
OkBtn: React.FC;
CancelBtn: React.FC;
}) => React.ReactNode);
closable?: boolean | (Exclude<ClosableType, boolean> & {
onClose?: () => void;
afterClose?: () => void;
});
classNames?: ModalClassNamesType;
styles?: ModalStylesType;
}
export interface ModalProps extends ModalCommonProps {
/** Whether the modal dialog is visible or not */
open?: boolean;
/** Whether to apply loading visual effect for OK button or not */
confirmLoading?: boolean;
/** The modal dialog's title */
title?: React.ReactNode;
/** Specify a function that will be called when a user clicks the OK button */
onOk?: (e: React.MouseEvent<HTMLButtonElement>) => void;
/** Specify a function that will be called when a user clicks mask, close button on top right or Cancel button, or presses Esc key */
onCancel?: (e: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLElement>) => void;
afterClose?: () => void;
/** Callback when the animation ends when Modal is turned on and off */
afterOpenChange?: (open: boolean) => void;
/** Centered Modal */
centered?: boolean;
/** Width of the modal dialog */
width?: string | number | Partial<Record<Breakpoint, string | number>>;
/** Text of the OK button */
okText?: React.ReactNode;
/** Button `type` of the OK button */
okType?: LegacyButtonType;
/** Text of the Cancel button */
cancelText?: React.ReactNode;
/**
* @deprecated Please use `mask.closable` instead
* @description Whether to close the modal dialog when the mask (area outside the modal) is clicked
*/
maskClosable?: boolean;
/** Force render Modal */
forceRender?: boolean;
okButtonProps?: ButtonProps;
cancelButtonProps?: ButtonProps;
/** @deprecated Please use `destroyOnHidden` instead */
destroyOnClose?: boolean;
/**
* @since 5.25.0
*/
destroyOnHidden?: boolean;
style?: React.CSSProperties;
wrapClassName?: string;
maskTransitionName?: string;
transitionName?: string;
className?: string;
rootClassName?: string;
rootStyle?: React.CSSProperties;
getContainer?: string | HTMLElement | getContainerFunc | false;
zIndex?: number;
/** @deprecated Please use `styles.body` instead */
bodyStyle?: React.CSSProperties;
/** @deprecated Please use `styles.mask` instead */
maskStyle?: React.CSSProperties;
mask?: MaskType;
keyboard?: boolean;
wrapProps?: any;
prefixCls?: string;
closeIcon?: React.ReactNode;
modalRender?: (node: React.ReactNode) => React.ReactNode;
children?: React.ReactNode;
mousePosition?: MousePosition;
/**
* @since 5.18.0
*/
loading?: boolean;
/** @deprecated Please use `focusable.focusTriggerAfterClose` instead */
focusTriggerAfterClose?: boolean;
focusable?: FocusableConfig;
}
type getContainerFunc = () => HTMLElement;
export interface ModalFuncProps extends ModalCommonProps {
prefixCls?: string;
className?: string;
rootClassName?: string;
open?: boolean;
title?: React.ReactNode;
content?: React.ReactNode;
onOk?: (...args: any[]) => any;
onCancel?: (...args: any[]) => any;
afterClose?: () => void;
okButtonProps?: ButtonProps;
cancelButtonProps?: ButtonProps;
centered?: boolean;
width?: string | number;
okText?: React.ReactNode;
okType?: LegacyButtonType;
cancelText?: React.ReactNode;
icon?: React.ReactNode;
/** @deprecated Please use `mask.closable` instead */
maskClosable?: boolean;
mask?: MaskType;
zIndex?: number;
okCancel?: boolean;
style?: React.CSSProperties;
wrapClassName?: string;
/** @deprecated Please use `styles.mask` instead */
maskStyle?: React.CSSProperties;
type?: 'info' | 'success' | 'error' | 'warn' | 'warning' | 'confirm';
keyboard?: boolean;
getContainer?: string | HTMLElement | getContainerFunc | false;
transitionName?: string;
maskTransitionName?: string;
direction?: DirectionType;
/** @deprecated Please use `styles.body` instead */
bodyStyle?: React.CSSProperties;
closeIcon?: React.ReactNode;
footer?: ModalProps['footer'];
modalRender?: (node: React.ReactNode) => React.ReactNode;
/** @deprecated Please use `focusable.focusTriggerAfterClose` instead */
focusTriggerAfterClose?: boolean;
/** @deprecated Please use `focusable.autoFocusButton` instead */
autoFocusButton?: null | 'ok' | 'cancel';
focusable?: FocusableConfig & {
autoFocusButton?: null | 'ok' | 'cancel';
};
}
export interface ModalLocale {
okText: string;
cancelText: string;
justOkText: string;
}
export type MousePosition = {
x: number;
y: number;
} | null;
export {};
+1
View File
@@ -0,0 +1 @@
export {};
+3
View File
@@ -0,0 +1,3 @@
import type { ModalLocale } from './interface';
export declare function changeConfirmLocale(newLocale?: ModalLocale): (() => void) | undefined;
export declare function getConfirmLocale(): ModalLocale;
+28
View File
@@ -0,0 +1,28 @@
import defaultLocale from '../locale/en_US';
let runtimeLocale = {
...defaultLocale.Modal
};
let localeList = [];
const generateLocale = () => localeList.reduce((merged, locale) => ({
...merged,
...locale
}), defaultLocale.Modal);
export function changeConfirmLocale(newLocale) {
if (newLocale) {
const cloneLocale = {
...newLocale
};
localeList.push(cloneLocale);
runtimeLocale = generateLocale();
return () => {
localeList = localeList.filter(locale => locale !== cloneLocale);
runtimeLocale = generateLocale();
};
}
runtimeLocale = {
...defaultLocale.Modal
};
}
export function getConfirmLocale() {
return runtimeLocale;
}
+9
View File
@@ -0,0 +1,9 @@
import React from 'react';
import type { ModalProps } from './interface';
export declare function renderCloseIcon(prefixCls: string, closeIcon?: React.ReactNode): React.JSX.Element;
interface FooterProps {
onOk?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
onCancel?: ModalProps['onCancel'];
}
export declare const Footer: React.FC<FooterProps & Pick<ModalProps, 'footer' | 'okText' | 'okType' | 'cancelText' | 'confirmLoading' | 'okButtonProps' | 'cancelButtonProps'>>;
export {};
+64
View File
@@ -0,0 +1,64 @@
"use client";
import React from 'react';
import CloseOutlined from "@ant-design/icons/es/icons/CloseOutlined";
import { DisabledContextProvider } from '../config-provider/DisabledContext';
import { useLocale } from '../locale';
import NormalCancelBtn from './components/NormalCancelBtn';
import NormalOkBtn from './components/NormalOkBtn';
import { ModalContextProvider } from './context';
import { getConfirmLocale } from './locale';
export function renderCloseIcon(prefixCls, closeIcon) {
return /*#__PURE__*/React.createElement("span", {
className: `${prefixCls}-close-x`
}, closeIcon || /*#__PURE__*/React.createElement(CloseOutlined, {
className: `${prefixCls}-close-icon`
}));
}
export const Footer = props => {
const {
okText,
okType = 'primary',
cancelText,
confirmLoading,
onOk,
onCancel,
okButtonProps,
cancelButtonProps,
footer
} = props;
const [locale] = useLocale('Modal', getConfirmLocale());
// ================== Locale Text ==================
const okTextLocale = okText || locale?.okText;
const cancelTextLocale = cancelText || locale?.cancelText;
const memoizedValue = React.useMemo(() => {
return {
confirmLoading,
okButtonProps,
cancelButtonProps,
okTextLocale,
cancelTextLocale,
okType,
onOk,
onCancel
};
}, [confirmLoading, okButtonProps, cancelButtonProps, okTextLocale, cancelTextLocale, okType, onOk, onCancel]);
let footerNode;
if (typeof footer === 'function' || typeof footer === 'undefined') {
footerNode = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(NormalCancelBtn, null), /*#__PURE__*/React.createElement(NormalOkBtn, null));
if (typeof footer === 'function') {
footerNode = footer(footerNode, {
OkBtn: NormalOkBtn,
CancelBtn: NormalCancelBtn
});
}
footerNode = /*#__PURE__*/React.createElement(ModalContextProvider, {
value: memoizedValue
}, footerNode);
} else {
footerNode = footer;
}
return /*#__PURE__*/React.createElement(DisabledContextProvider, {
disabled: false
}, footerNode);
};
+2
View File
@@ -0,0 +1,2 @@
declare const _default: import("react").FunctionComponent<import("@ant-design/cssinjs-utils/lib/util/genStyleUtils").SubStyleComponentProps>;
export default _default;
+106
View File
@@ -0,0 +1,106 @@
import { unit } from '@ant-design/cssinjs';
import { prepareComponentToken, prepareToken } from '.';
import { clearFix } from '../../style';
import { genSubStyleComponent } from '../../theme/internal';
// ============================= Confirm ==============================
const genModalConfirmStyle = token => {
const {
componentCls,
titleFontSize,
titleLineHeight,
modalConfirmIconSize,
fontSize,
lineHeight,
modalTitleHeight,
fontHeight,
confirmBodyPadding
} = token;
const confirmComponentCls = `${componentCls}-confirm`;
return {
[confirmComponentCls]: {
'&-rtl': {
direction: 'rtl'
},
[`${token.antCls}-modal-header`]: {
display: 'none'
},
[`${confirmComponentCls}-body-wrapper`]: {
...clearFix()
},
[`&${componentCls} ${componentCls}-body`]: {
padding: confirmBodyPadding
},
// ====================== Body ======================
[`${confirmComponentCls}-body`]: {
display: 'flex',
flexWrap: 'nowrap',
alignItems: 'start',
[`> ${token.iconCls}`]: {
flex: 'none',
fontSize: modalConfirmIconSize,
marginInlineEnd: token.confirmIconMarginInlineEnd,
marginTop: token.calc(token.calc(fontHeight).sub(modalConfirmIconSize).equal()).div(2).equal()
},
[`&-has-title > ${token.iconCls}`]: {
marginTop: token.calc(token.calc(modalTitleHeight).sub(modalConfirmIconSize).equal()).div(2).equal()
}
},
[`${confirmComponentCls}-paragraph`]: {
display: 'flex',
flexDirection: 'column',
flex: 'auto',
rowGap: token.marginXS,
// https://github.com/ant-design/ant-design/issues/51912
maxWidth: `calc(100% - ${unit(token.marginSM)})`
},
[`${confirmComponentCls}-body-no-icon ${confirmComponentCls}-paragraph`]: {
maxWidth: '100%'
},
// https://github.com/ant-design/ant-design/issues/48159
[`${token.iconCls} + ${confirmComponentCls}-paragraph`]: {
maxWidth: `calc(100% - ${unit(token.calc(token.modalConfirmIconSize).add(token.marginSM).equal())})`
},
[`${confirmComponentCls}-title`]: {
color: token.colorTextHeading,
fontWeight: token.fontWeightStrong,
fontSize: titleFontSize,
lineHeight: titleLineHeight
},
[`${confirmComponentCls}-container`]: {
color: token.colorText,
fontSize,
lineHeight
},
// ===================== Footer =====================
[`${confirmComponentCls}-btns`]: {
textAlign: 'end',
marginTop: token.confirmBtnsMarginTop,
[`${token.antCls}-btn + ${token.antCls}-btn`]: {
marginBottom: 0,
marginInlineStart: token.marginXS
}
}
},
[`${confirmComponentCls}-error ${confirmComponentCls}-body > ${token.iconCls}`]: {
color: token.colorError
},
[`${confirmComponentCls}-warning ${confirmComponentCls}-body > ${token.iconCls},
${confirmComponentCls}-confirm ${confirmComponentCls}-body > ${token.iconCls}`]: {
color: token.colorWarning
},
[`${confirmComponentCls}-info ${confirmComponentCls}-body > ${token.iconCls}`]: {
color: token.colorInfo
},
[`${confirmComponentCls}-success ${confirmComponentCls}-body > ${token.iconCls}`]: {
color: token.colorSuccess
}
};
};
// ============================== Export ==============================
export default genSubStyleComponent(['Modal', 'confirm'], token => {
const modalToken = prepareToken(token);
return genModalConfirmStyle(modalToken);
}, prepareComponentToken, {
// confirm is weak than modal since no conflict here
order: -1000
});
+110
View File
@@ -0,0 +1,110 @@
import type { AliasToken, FullToken, GenerateStyle, GenStyleFn, GlobalToken, TokenWithCommonCls } from '../../theme/internal';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
/**
* @desc 顶部背景色
* @descEN Background color of header
*/
headerBg: string;
/**
* @desc 标题行高
* @descEN Line height of title
*/
titleLineHeight: number | string;
/**
* @desc 标题字体大小
* @descEN Font size of title
*/
titleFontSize: number;
/**
* @desc 标题字体颜色
* @descEN Font color of title
*/
titleColor: string;
/**
* @desc 内容区域背景色
* @descEN Background color of content
*/
contentBg: string;
/**
* @desc 底部区域背景色
* @descEN Background color of footer
*/
footerBg: string;
}
/**
* @desc Modal 组件的 Token
* @descEN Token for Modal component
*/
export interface ModalToken extends FullToken<'Modal'> {
/**
* @desc 模态框头部高度
* @descEN Height of modal header
*/
modalHeaderHeight: number | string;
/**
* @desc 模态框底部边框颜色
* @descEN Border color of modal footer
*/
modalFooterBorderColorSplit: string;
/**
* @desc 模态框底部边框样式
* @descEN Border style of modal footer
*/
modalFooterBorderStyle: string;
/**
* @desc 模态框底部边框宽度
* @descEN Border width of modal footer
*/
modalFooterBorderWidth: number | string;
/**
* @desc 模态框关闭图标颜色
* @descEN Color of modal close icon
*/
modalCloseIconColor: string;
/**
* @desc 模态框关闭图标悬停颜色
* @descEN Hover color of modal close icon
*/
modalCloseIconHoverColor: string;
/**
* @desc 模态框关闭按钮尺寸
* @descEN Size of modal close button
*/
modalCloseBtnSize: number | string;
/**
* @desc 模态框确认图标尺寸
* @descEN Size of modal confirm icon
*/
modalConfirmIconSize: number | string;
/**
* @desc 模态框标题高度
* @descEN Height of modal title
*/
modalTitleHeight: number | string;
}
export declare const genModalMaskStyle: GenerateStyle<TokenWithCommonCls<AliasToken>>;
export declare const prepareToken: (token: Parameters<GenStyleFn<"Modal">>[0]) => ModalToken;
export declare const prepareComponentToken: (token: GlobalToken) => {
footerBg: string;
headerBg: string;
titleLineHeight: number;
titleFontSize: number;
contentBg: string;
titleColor: string;
contentPadding: string | number;
headerPadding: string | number;
headerBorderBottom: string;
headerMarginBottom: number;
bodyPadding: number;
footerPadding: string | number;
footerBorderTop: string;
footerBorderRadius: string | number;
footerMarginTop: number;
confirmBodyPadding: string | number;
confirmIconMarginInlineEnd: number;
confirmBtnsMarginTop: number;
mask: boolean;
};
declare const _default: (prefixCls: string, rootCls?: string) => readonly [string, string];
export default _default;
+320
View File
@@ -0,0 +1,320 @@
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import { unit } from '@ant-design/cssinjs';
import { getMediaSize } from '../../grid/style';
import { genFocusOutline, genFocusStyle, resetComponent } from '../../style';
import { initFadeMotion, initZoomMotion } from '../../style/motion';
import { genStyleHooks, mergeToken } from '../../theme/internal';
function box(position) {
return {
position,
inset: 0
};
}
export const genModalMaskStyle = token => {
const {
componentCls,
antCls
} = token;
return [{
[`${componentCls}-root`]: {
[`${componentCls}${antCls}-zoom-enter, ${componentCls}${antCls}-zoom-appear`]: {
// reset scale avoid mousePosition bug
transform: 'none',
opacity: 0,
animationDuration: token.motionDurationSlow,
// https://github.com/ant-design/ant-design/issues/11777
userSelect: 'none'
},
// https://github.com/ant-design/ant-design/issues/37329
// https://github.com/ant-design/ant-design/issues/40272
[`${componentCls}${antCls}-zoom-leave ${componentCls}-container`]: {
pointerEvents: 'none'
},
[`${componentCls}-mask`]: {
...box('fixed'),
zIndex: token.zIndexPopupBase,
height: '100%',
backgroundColor: token.colorBgMask,
pointerEvents: 'none',
[`&${componentCls}-mask-blur`]: {
backdropFilter: 'blur(4px)'
},
[`${componentCls}-hidden`]: {
display: 'none'
}
},
[`${componentCls}-wrap`]: {
...box('fixed'),
zIndex: token.zIndexPopupBase,
overflow: 'auto',
outline: 0,
WebkitOverflowScrolling: 'touch'
}
}
}, {
[`${componentCls}-root`]: initFadeMotion(token)
}];
};
const genModalStyle = token => {
const {
componentCls,
motionDurationMid
} = token;
return [
// ======================== Root =========================
{
[`${componentCls}-root`]: {
[`${componentCls}-wrap-rtl`]: {
direction: 'rtl'
},
[`${componentCls}-centered`]: {
textAlign: 'center',
'&::before': {
display: 'inline-block',
width: 0,
height: '100%',
verticalAlign: 'middle',
content: '""'
},
[componentCls]: {
top: 0,
display: 'inline-block',
paddingBottom: 0,
textAlign: 'start',
verticalAlign: 'middle'
}
},
[`@media (max-width: ${token.screenSMMax}px)`]: {
[componentCls]: {
maxWidth: 'calc(100vw - 16px)',
margin: `${unit(token.marginXS)} auto`
},
[`${componentCls}-centered`]: {
[componentCls]: {
flex: 1
}
}
}
}
},
// ======================== Modal ========================
{
[componentCls]: {
...resetComponent(token),
pointerEvents: 'none',
position: 'relative',
top: 100,
width: 'auto',
maxWidth: `calc(100vw - ${unit(token.calc(token.margin).mul(2).equal())})`,
margin: '0 auto',
'&:focus-visible': {
borderRadius: token.borderRadiusLG,
...genFocusOutline(token)
},
[`${componentCls}-title`]: {
margin: 0,
color: token.titleColor,
fontWeight: token.fontWeightStrong,
fontSize: token.titleFontSize,
lineHeight: token.titleLineHeight,
wordWrap: 'break-word'
},
[`${componentCls}-container`]: {
position: 'relative',
backgroundColor: token.contentBg,
backgroundClip: 'padding-box',
border: 0,
borderRadius: token.borderRadiusLG,
boxShadow: token.boxShadow,
pointerEvents: 'auto',
padding: token.contentPadding
},
[`${componentCls}-close`]: {
position: 'absolute',
top: token.calc(token.modalHeaderHeight).sub(token.modalCloseBtnSize).div(2).equal(),
insetInlineEnd: token.calc(token.modalHeaderHeight).sub(token.modalCloseBtnSize).div(2).equal(),
zIndex: token.calc(token.zIndexPopupBase).add(10).equal(),
padding: 0,
color: token.modalCloseIconColor,
fontWeight: token.fontWeightStrong,
lineHeight: 1,
textDecoration: 'none',
background: 'transparent',
borderRadius: token.borderRadiusSM,
width: token.modalCloseBtnSize,
height: token.modalCloseBtnSize,
border: 0,
outline: 0,
cursor: 'pointer',
transition: ['color', 'background-color'].map(prop => `${prop} ${motionDurationMid}`).join(', '),
'&-x': {
display: 'flex',
fontSize: token.fontSizeLG,
fontStyle: 'normal',
lineHeight: unit(token.modalCloseBtnSize),
justifyContent: 'center',
textTransform: 'none',
textRendering: 'auto'
},
'&:disabled': {
pointerEvents: 'none'
},
'&:hover': {
color: token.modalCloseIconHoverColor,
backgroundColor: token.colorBgTextHover,
textDecoration: 'none'
},
'&:active': {
backgroundColor: token.colorBgTextActive
},
...genFocusStyle(token)
},
[`${componentCls}-header`]: {
color: token.colorText,
background: token.headerBg,
borderRadius: `${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)} 0 0`,
marginBottom: token.headerMarginBottom,
padding: token.headerPadding,
borderBottom: token.headerBorderBottom
},
[`${componentCls}-body`]: {
fontSize: token.fontSize,
lineHeight: token.lineHeight,
wordWrap: 'break-word',
padding: token.bodyPadding,
[`${componentCls}-body-skeleton`]: {
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
margin: `${unit(token.margin)} auto`
}
},
[`${componentCls}-footer`]: {
textAlign: 'end',
background: token.footerBg,
marginTop: token.footerMarginTop,
padding: token.footerPadding,
borderTop: token.footerBorderTop,
borderRadius: token.footerBorderRadius,
[`> ${token.antCls}-btn + ${token.antCls}-btn`]: {
marginInlineStart: token.marginXS
}
},
[`${componentCls}-open`]: {
overflow: 'hidden'
}
}
},
// ======================== Pure =========================
{
[`${componentCls}-pure-panel`]: {
top: 'auto',
padding: 0,
display: 'flex',
flexDirection: 'column',
[`${componentCls}-container,
${componentCls}-body,
${componentCls}-confirm-body-wrapper`]: {
display: 'flex',
flexDirection: 'column',
flex: 'auto'
},
[`${componentCls}-confirm-body`]: {
marginBottom: 'auto'
}
}
}];
};
const genRTLStyle = token => {
const {
componentCls
} = token;
return {
[`${componentCls}-root`]: {
[`${componentCls}-wrap-rtl`]: {
direction: 'rtl',
[`${componentCls}-confirm-body`]: {
direction: 'rtl'
}
}
}
};
};
const genResponsiveWidthStyle = token => {
const {
componentCls
} = token;
const oriGridMediaSizesMap = getMediaSize(token);
const gridMediaSizesMap = {
...oriGridMediaSizesMap
};
delete gridMediaSizesMap.xs;
const cssVarPrefix = `--${componentCls.replace('.', '')}-`;
const responsiveStyles = Object.keys(gridMediaSizesMap).map(key => ({
[`@media (min-width: ${unit(gridMediaSizesMap[key])})`]: {
width: `var(${cssVarPrefix}${key}-width)`
}
}));
return {
[`${componentCls}-root`]: {
[componentCls]: [].concat(_toConsumableArray(Object.keys(oriGridMediaSizesMap).map((currentKey, index) => {
const previousKey = Object.keys(oriGridMediaSizesMap)[index - 1];
return previousKey ? {
[`${cssVarPrefix}${currentKey}-width`]: `var(${cssVarPrefix}${previousKey}-width)`
} : null;
})), [{
width: `var(${cssVarPrefix}xs-width)`
}], _toConsumableArray(responsiveStyles))
}
};
};
// ============================== Export ==============================
export const prepareToken = token => {
const headerPaddingVertical = token.padding;
const headerFontSize = token.fontSizeHeading5;
const headerLineHeight = token.lineHeightHeading5;
const modalToken = mergeToken(token, {
modalHeaderHeight: token.calc(token.calc(headerLineHeight).mul(headerFontSize).equal()).add(token.calc(headerPaddingVertical).mul(2).equal()).equal(),
modalFooterBorderColorSplit: token.colorSplit,
modalFooterBorderStyle: token.lineType,
modalFooterBorderWidth: token.lineWidth,
modalCloseIconColor: token.colorIcon,
modalCloseIconHoverColor: token.colorIconHover,
modalCloseBtnSize: token.controlHeight,
modalConfirmIconSize: token.fontHeight,
modalTitleHeight: token.calc(token.titleFontSize).mul(token.titleLineHeight).equal()
});
return modalToken;
};
export const prepareComponentToken = token => ({
footerBg: 'transparent',
headerBg: 'transparent',
titleLineHeight: token.lineHeightHeading5,
titleFontSize: token.fontSizeHeading5,
contentBg: token.colorBgElevated,
titleColor: token.colorTextHeading,
// internal
contentPadding: token.wireframe ? 0 : `${unit(token.paddingMD)} ${unit(token.paddingContentHorizontalLG)}`,
headerPadding: token.wireframe ? `${unit(token.padding)} ${unit(token.paddingLG)}` : 0,
headerBorderBottom: token.wireframe ? `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}` : 'none',
headerMarginBottom: token.wireframe ? 0 : token.marginXS,
bodyPadding: token.wireframe ? token.paddingLG : 0,
footerPadding: token.wireframe ? `${unit(token.paddingXS)} ${unit(token.padding)}` : 0,
footerBorderTop: token.wireframe ? `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}` : 'none',
footerBorderRadius: token.wireframe ? `0 0 ${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)}` : 0,
footerMarginTop: token.wireframe ? 0 : token.marginSM,
confirmBodyPadding: token.wireframe ? `${unit(token.padding * 2)} ${unit(token.padding * 2)} ${unit(token.paddingLG)}` : 0,
confirmIconMarginInlineEnd: token.wireframe ? token.margin : token.marginSM,
confirmBtnsMarginTop: token.wireframe ? token.marginLG : token.marginSM,
mask: true
});
export default genStyleHooks('Modal', token => {
const modalToken = prepareToken(token);
return [genModalStyle(modalToken), genRTLStyle(modalToken), genModalMaskStyle(modalToken), initZoomMotion(modalToken, 'zoom'), genResponsiveWidthStyle(modalToken)];
}, prepareComponentToken, {
unitless: {
titleLineHeight: true
}
});
+18
View File
@@ -0,0 +1,18 @@
import * as React from 'react';
import type { ConfigUpdate } from '../confirm';
import type { ModalFuncProps } from '../interface';
export interface HookModalProps {
afterClose: () => void;
config: ModalFuncProps;
onConfirm?: (confirmed: boolean) => void;
/**
* Do not throw if is await mode
*/
isSilent?: () => boolean;
}
export interface HookModalRef {
destroy: () => void;
update: (config: ConfigUpdate) => void;
}
declare const HookModal: React.ForwardRefExoticComponent<HookModalProps & React.RefAttributes<HookModalRef>>;
export default HookModal;
+60
View File
@@ -0,0 +1,60 @@
"use client";
import * as React from 'react';
import { ConfigContext } from '../../config-provider';
import defaultLocale from '../../locale/en_US';
import useLocale from '../../locale/useLocale';
import ConfirmDialog from '../ConfirmDialog';
const HookModal = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
afterClose: hookAfterClose,
config,
...restProps
} = props;
const [open, setOpen] = React.useState(true);
const [innerConfig, setInnerConfig] = React.useState(config);
const {
direction,
getPrefixCls
} = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('modal');
const rootPrefixCls = getPrefixCls();
const afterClose = () => {
hookAfterClose();
innerConfig.afterClose?.();
};
const close = (...args) => {
setOpen(false);
const triggerCancel = args.some(param => param?.triggerCancel);
if (triggerCancel) {
innerConfig.onCancel?.(() => {}, ...args.slice(1));
}
};
React.useImperativeHandle(ref, () => ({
destroy: close,
update: newConfig => {
setInnerConfig(originConfig => {
const nextConfig = typeof newConfig === 'function' ? newConfig(originConfig) : newConfig;
return {
...originConfig,
...nextConfig
};
});
}
}));
const mergedOkCancel = innerConfig.okCancel ?? innerConfig.type === 'confirm';
const [contextLocale] = useLocale('Modal', defaultLocale.Modal);
return /*#__PURE__*/React.createElement(ConfirmDialog, {
prefixCls: prefixCls,
rootPrefixCls: rootPrefixCls,
...innerConfig,
close: close,
open: open,
afterClose: afterClose,
okText: innerConfig.okText || (mergedOkCancel ? contextLocale?.okText : contextLocale?.justOkText),
direction: innerConfig.direction || direction,
cancelText: innerConfig.cancelText || contextLocale?.cancelText,
...restProps
});
});
export default HookModal;
+8
View File
@@ -0,0 +1,8 @@
import * as React from 'react';
import type { ModalFunc, ModalStaticFunctions } from '../confirm';
export type ModalFuncWithPromise = (...args: Parameters<ModalFunc>) => ReturnType<ModalFunc> & {
then: <T>(resolve: (confirmed: boolean) => T, reject: VoidFunction) => Promise<T>;
};
export type HookAPI = Omit<Record<keyof ModalStaticFunctions, ModalFuncWithPromise>, 'warn'>;
declare function useModal(): readonly [instance: HookAPI, contextHolder: React.ReactElement];
export default useModal;
+97
View File
@@ -0,0 +1,97 @@
"use client";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import * as React from 'react';
import { usePatchElement } from '../../_util/hooks';
import { withConfirm, withError, withInfo, withSuccess, withWarn } from '../confirm';
import destroyFns from '../destroyFns';
import HookModal from './HookModal';
let uuid = 0;
const ElementsHolder = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef((_props, ref) => {
const [elements, patchElement] = usePatchElement();
React.useImperativeHandle(ref, () => ({
patchElement
}), [patchElement]);
return /*#__PURE__*/React.createElement(React.Fragment, null, elements);
}));
function useModal() {
const holderRef = React.useRef(null);
// ========================== Effect ==========================
const [actionQueue, setActionQueue] = React.useState([]);
React.useEffect(() => {
if (actionQueue.length) {
const cloneQueue = _toConsumableArray(actionQueue);
cloneQueue.forEach(action => {
action();
});
setActionQueue([]);
}
}, [actionQueue]);
// =========================== Hook ===========================
const getConfirmFunc = React.useCallback(withFunc => function hookConfirm(config) {
uuid += 1;
const modalRef = /*#__PURE__*/React.createRef();
// Proxy to promise with `onClose`
let resolvePromise;
const promise = new Promise(resolve => {
resolvePromise = resolve;
});
let silent = false;
let closeFunc;
const modal = /*#__PURE__*/React.createElement(HookModal, {
key: `modal-${uuid}`,
config: withFunc(config),
ref: modalRef,
afterClose: () => {
closeFunc?.();
},
isSilent: () => silent,
onConfirm: confirmed => {
resolvePromise(confirmed);
}
});
closeFunc = holderRef.current?.patchElement(modal);
if (closeFunc) {
destroyFns.push(closeFunc);
}
const instance = {
destroy: () => {
function destroyAction() {
modalRef.current?.destroy();
}
if (modalRef.current) {
destroyAction();
} else {
setActionQueue(prev => [].concat(_toConsumableArray(prev), [destroyAction]));
}
},
update: newConfig => {
function updateAction() {
modalRef.current?.update(newConfig);
}
if (modalRef.current) {
updateAction();
} else {
setActionQueue(prev => [].concat(_toConsumableArray(prev), [updateAction]));
}
},
then: resolve => {
silent = true;
return promise.then(resolve);
}
};
return instance;
}, []);
const fns = React.useMemo(() => ({
info: getConfirmFunc(withInfo),
success: getConfirmFunc(withSuccess),
error: getConfirmFunc(withError),
warning: getConfirmFunc(withWarn),
confirm: getConfirmFunc(withConfirm)
}), [getConfirmFunc]);
return [fns, /*#__PURE__*/React.createElement(ElementsHolder, {
key: "modal-holder",
ref: holderRef
})];
}
export default useModal;