refactor: modal

pull/6217/head
tangjinzhou 2023-01-28 10:02:40 +08:00
parent dfe462ec6f
commit 221b203cbb
23 changed files with 662 additions and 418 deletions

View File

@ -6,6 +6,7 @@ import type { LegacyButtonType } from '../button/buttonTypes';
import { convertLegacyProps } from '../button/buttonTypes'; import { convertLegacyProps } from '../button/buttonTypes';
import useDestroyed from './hooks/useDestroyed'; import useDestroyed from './hooks/useDestroyed';
import { objectType } from './type'; import { objectType } from './type';
import { findDOMNode } from './props-util';
const actionButtonProps = { const actionButtonProps = {
type: { type: {
@ -38,7 +39,7 @@ export default defineComponent({
const isDestroyed = useDestroyed(); const isDestroyed = useDestroyed();
onMounted(() => { onMounted(() => {
if (props.autofocus) { if (props.autofocus) {
timeoutId = setTimeout(() => buttonRef.value.$el?.focus()); timeoutId = setTimeout(() => findDOMNode(buttonRef.value)?.focus?.());
} }
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@ -1,10 +1,15 @@
import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled';
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled';
import InfoCircleFilled from '@ant-design/icons-vue/InfoCircleFilled';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import type { ModalFuncProps } from './Modal'; import type { ModalFuncProps, ModalLocale } from './Modal';
import Dialog from './Modal'; import Dialog from './Modal';
import ActionButton from '../_util/ActionButton'; import ActionButton from '../_util/ActionButton';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver'; import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
import { getTransitionName } from '../_util/transition'; import { getTransitionName } from '../_util/transition';
import warning from '../_util/warning';
interface ConfirmDialogProps extends ModalFuncProps { interface ConfirmDialogProps extends ModalFuncProps {
afterClose?: () => void; afterClose?: () => void;
@ -12,6 +17,9 @@ interface ConfirmDialogProps extends ModalFuncProps {
autoFocusButton?: null | 'ok' | 'cancel'; autoFocusButton?: null | 'ok' | 'cancel';
rootPrefixCls: string; rootPrefixCls: string;
iconPrefixCls?: string; iconPrefixCls?: string;
/** @private Internal Usage. Do not override this */
locale?: ModalLocale;
} }
function renderSomeContent(someContent: any) { function renderSomeContent(someContent: any) {
@ -33,6 +41,7 @@ export default defineComponent<ConfirmDialogProps>({
'zIndex', 'zIndex',
'afterClose', 'afterClose',
'visible', 'visible',
'open',
'keyboard', 'keyboard',
'centered', 'centered',
'getContainer', 'getContainer',
@ -60,9 +69,19 @@ export default defineComponent<ConfirmDialogProps>({
'modalRender', 'modalRender',
'focusTriggerAfterClose', 'focusTriggerAfterClose',
'wrapClassName', 'wrapClassName',
'confirmPrefixCls',
'footer',
] as any, ] as any,
setup(props, { attrs }) { setup(props, { attrs }) {
const [locale] = useLocaleReceiver('Modal'); const [locale] = useLocaleReceiver('Modal');
if (process.env.NODE_ENV !== 'production') {
warning(
props.visible === undefined,
'Modal',
`\`visible\` is deprecated, please use \`open\` instead.`,
);
}
return () => { return () => {
const { const {
icon, icon,
@ -72,18 +91,18 @@ export default defineComponent<ConfirmDialogProps>({
closable = false, closable = false,
zIndex, zIndex,
afterClose, afterClose,
visible,
keyboard, keyboard,
centered, centered,
getContainer, getContainer,
maskStyle, maskStyle,
okButtonProps, okButtonProps,
cancelButtonProps, cancelButtonProps,
okCancel = true, okCancel,
width = 416, width = 416,
mask = true, mask = true,
maskClosable = false, maskClosable = false,
type, type,
open,
title, title,
content, content,
direction, direction,
@ -93,7 +112,31 @@ export default defineComponent<ConfirmDialogProps>({
rootPrefixCls, rootPrefixCls,
bodyStyle, bodyStyle,
wrapClassName, wrapClassName,
footer,
} = props; } = props;
// Icon
let mergedIcon = 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 />;
}
}
const okType = props.okType || 'primary'; const okType = props.okType || 'primary';
const prefixCls = props.prefixCls || 'ant-modal'; const prefixCls = props.prefixCls || 'ant-modal';
const contentPrefixCls = `${prefixCls}-confirm`; const contentPrefixCls = `${prefixCls}-confirm`;
@ -101,19 +144,20 @@ export default defineComponent<ConfirmDialogProps>({
const okText = const okText =
renderSomeContent(props.okText) || renderSomeContent(props.okText) ||
(okCancel ? locale.value.okText : locale.value.justOkText); (okCancel ? locale.value.okText : locale.value.justOkText);
const cancelText = renderSomeContent(props.cancelText) || locale.value.cancelText; const mergedOkCancel = okCancel ?? type === 'confirm';
const autoFocusButton = const autoFocusButton =
props.autoFocusButton === null ? false : props.autoFocusButton || 'ok'; props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
const confirmPrefixCls = `${prefixCls}-confirm`;
const classString = classNames( const classString = classNames(
contentPrefixCls, confirmPrefixCls,
`${contentPrefixCls}-${type}`, `${confirmPrefixCls}-${props.type}`,
`${prefixCls}-${type}`, { [`${confirmPrefixCls}-rtl`]: direction === 'rtl' },
{ [`${contentPrefixCls}-rtl`]: direction === 'rtl' },
attrs.class, attrs.class,
); );
const cancelButton = okCancel && ( const cancelButton = mergedOkCancel && (
<ActionButton <ActionButton
actionFn={onCancel} actionFn={onCancel}
close={close} close={close}
@ -121,7 +165,7 @@ export default defineComponent<ConfirmDialogProps>({
buttonProps={cancelButtonProps} buttonProps={cancelButtonProps}
prefixCls={`${rootPrefixCls}-btn`} prefixCls={`${rootPrefixCls}-btn`}
> >
{cancelText} {renderSomeContent(okCancel) || locale.value.cancelText}
</ActionButton> </ActionButton>
); );
@ -130,11 +174,11 @@ export default defineComponent<ConfirmDialogProps>({
prefixCls={prefixCls} prefixCls={prefixCls}
class={classString} class={classString}
wrapClassName={classNames( wrapClassName={classNames(
{ [`${contentPrefixCls}-centered`]: !!centered }, { [`${confirmPrefixCls}-centered`]: !!centered },
wrapClassName, wrapClassName,
)} )}
onCancel={e => close({ triggerCancel: true }, e)} onCancel={e => close?.({ triggerCancel: true }, e)}
visible={visible} open={open}
title="" title=""
footer="" footer=""
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)} transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
@ -157,25 +201,29 @@ export default defineComponent<ConfirmDialogProps>({
> >
<div class={`${contentPrefixCls}-body-wrapper`}> <div class={`${contentPrefixCls}-body-wrapper`}>
<div class={`${contentPrefixCls}-body`}> <div class={`${contentPrefixCls}-body`}>
{renderSomeContent(icon)} {renderSomeContent(mergedIcon)}
{title === undefined ? null : ( {title === undefined ? null : (
<span class={`${contentPrefixCls}-title`}>{renderSomeContent(title)}</span> <span class={`${contentPrefixCls}-title`}>{renderSomeContent(title)}</span>
)} )}
<div class={`${contentPrefixCls}-content`}>{renderSomeContent(content)}</div> <div class={`${contentPrefixCls}-content`}>{renderSomeContent(content)}</div>
</div> </div>
<div class={`${contentPrefixCls}-btns`}> {footer !== undefined ? (
{cancelButton} renderSomeContent(footer)
<ActionButton ) : (
type={okType} <div class={`${contentPrefixCls}-btns`}>
actionFn={onOk} {cancelButton}
close={close} <ActionButton
autofocus={autoFocusButton === 'ok'} type={okType}
buttonProps={okButtonProps} actionFn={onOk}
prefixCls={`${rootPrefixCls}-btn`} close={close}
> autofocus={autoFocusButton === 'ok'}
{okText} buttonProps={okButtonProps}
</ActionButton> prefixCls={`${rootPrefixCls}-btn`}
</div> >
{okText}
</ActionButton>
</div>
)}
</div> </div>
</Dialog> </Dialog>
); );

View File

@ -16,8 +16,12 @@ import { objectType } from '../_util/type';
import { canUseDocElement } from '../_util/styleChecker'; import { canUseDocElement } from '../_util/styleChecker';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import { getTransitionName } from '../_util/transition'; import { getTransitionName } from '../_util/transition';
import warning from '../_util/warning';
import useStyle from './style';
let mousePosition: { x: number; y: number } | null = null; type MousePosition = { x: number; y: number } | null;
let mousePosition: MousePosition;
// ref: https://github.com/ant-design/ant-design/issues/15795 // ref: https://github.com/ant-design/ant-design/issues/15795
const getClickPosition = (e: MouseEvent) => { const getClickPosition = (e: MouseEvent) => {
mousePosition = { mousePosition = {
@ -37,7 +41,9 @@ if (canUseDocElement()) {
export const modalProps = () => ({ export const modalProps = () => ({
prefixCls: String, prefixCls: String,
/** @deprecated Please use `open` instead. */
visible: { type: Boolean, default: undefined }, visible: { type: Boolean, default: undefined },
open: { type: Boolean, default: undefined },
confirmLoading: { type: Boolean, default: undefined }, confirmLoading: { type: Boolean, default: undefined },
title: PropTypes.any, title: PropTypes.any,
closable: { type: Boolean, default: undefined }, closable: { type: Boolean, default: undefined },
@ -76,6 +82,7 @@ export const modalProps = () => ({
wrapProps: Object, wrapProps: Object,
focusTriggerAfterClose: { type: Boolean, default: undefined }, focusTriggerAfterClose: { type: Boolean, default: undefined },
modalRender: Function as PropType<(arg: { originVNode: VueNode }) => VueNode>, modalRender: Function as PropType<(arg: { originVNode: VueNode }) => VueNode>,
mousePosition: objectType<MousePosition>(),
}); });
export type ModalProps = Partial<ExtractPropTypes<ReturnType<typeof modalProps>>>; export type ModalProps = Partial<ExtractPropTypes<ReturnType<typeof modalProps>>>;
@ -83,8 +90,9 @@ export type ModalProps = Partial<ExtractPropTypes<ReturnType<typeof modalProps>>
export interface ModalFuncProps { export interface ModalFuncProps {
prefixCls?: string; prefixCls?: string;
class?: string; class?: string;
visible?: boolean; open?: boolean;
title?: string | (() => VueNode) | VueNode; title?: string | (() => VueNode) | VueNode;
footer?: string | (() => VueNode) | VueNode;
closable?: boolean; closable?: boolean;
content?: string | (() => VueNode) | VueNode; content?: string | (() => VueNode) | VueNode;
// TODO: find out exact types // TODO: find out exact types
@ -123,6 +131,9 @@ export interface ModalFuncProps {
/** @deprecated please use `appContext` instead */ /** @deprecated please use `appContext` instead */
parentContext?: any; parentContext?: any;
appContext?: any; appContext?: any;
/** @deprecated please use `open` instead */
visible?: boolean;
} }
type getContainerFunc = () => HTMLElement; type getContainerFunc = () => HTMLElement;
@ -149,7 +160,6 @@ export default defineComponent({
transitionName: 'zoom', transitionName: 'zoom',
maskTransitionName: 'fade', maskTransitionName: 'fade',
confirmLoading: false, confirmLoading: false,
visible: false,
okType: 'primary', okType: 'primary',
}), }),
setup(props, { emit, slots, attrs }) { setup(props, { emit, slots, attrs }) {
@ -158,9 +168,15 @@ export default defineComponent({
'modal', 'modal',
props, props,
); );
const [wrapSSR, hashId] = useStyle(prefixCls);
warning(
props.visible === undefined,
'Modal',
`\`visible\` will be removed in next major version, please use \`open\` instead.`,
);
const handleCancel = (e: MouseEvent) => { const handleCancel = (e: MouseEvent) => {
emit('update:visible', false); emit('update:visible', false);
emit('update:open', false);
emit('cancel', e); emit('cancel', e);
emit('change', false); emit('change', false);
}; };
@ -196,6 +212,7 @@ export default defineComponent({
const { const {
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
visible, visible,
open,
wrapClassName, wrapClassName,
centered, centered,
getContainer, getContainer,
@ -208,15 +225,16 @@ export default defineComponent({
[`${prefixCls.value}-centered`]: !!centered, [`${prefixCls.value}-centered`]: !!centered,
[`${prefixCls.value}-wrap-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-wrap-rtl`]: direction.value === 'rtl',
}); });
return ( return wrapSSR(
<Dialog <Dialog
{...restProps} {...restProps}
{...attrs} {...attrs}
rootClassName={hashId.value}
class={classNames(hashId.value, attrs.class)}
getContainer={getContainer || getPopupContainer.value} getContainer={getContainer || getPopupContainer.value}
prefixCls={prefixCls.value} prefixCls={prefixCls.value}
wrapClassName={wrapClassNameExtended} wrapClassName={wrapClassNameExtended}
visible={visible} visible={open ?? visible}
mousePosition={mousePosition}
onClose={handleCancel} onClose={handleCancel}
focusTriggerAfterClose={focusTriggerAfterClose} focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls.value, 'zoom', props.transitionName)} transitionName={getTransitionName(rootPrefixCls.value, 'zoom', props.transitionName)}
@ -225,6 +243,7 @@ export default defineComponent({
'fade', 'fade',
props.maskTransitionName, props.maskTransitionName,
)} )}
mousePosition={restProps.mousePosition ?? mousePosition}
v-slots={{ v-slots={{
...slots, ...slots,
footer: slots.footer || renderFooter, footer: slots.footer || renderFooter,
@ -236,7 +255,7 @@ export default defineComponent({
); );
}, },
}} }}
></Dialog> ></Dialog>,
); );
}; };
}, },

View File

@ -4,10 +4,8 @@ import type { ModalFuncProps } from './Modal';
import { destroyFns } from './Modal'; import { destroyFns } from './Modal';
import ConfigProvider, { globalConfigForApi } from '../config-provider'; import ConfigProvider, { globalConfigForApi } from '../config-provider';
import omit from '../_util/omit'; import omit from '../_util/omit';
import InfoCircleOutlined from '@ant-design/icons-vue/InfoCircleOutlined';
import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined'; import { getConfirmLocale } from './locale';
import CloseCircleOutlined from '@ant-design/icons-vue/CloseCircleOutlined';
import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined';
type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps); type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps);
@ -21,7 +19,7 @@ const confirm = (config: ModalFuncProps) => {
let currentConfig = { let currentConfig = {
...omit(config, ['parentContext', 'appContext']), ...omit(config, ['parentContext', 'appContext']),
close, close,
visible: true, open: true,
} as any; } as any;
let confirmDialogInstance = null; let confirmDialogInstance = null;
function destroy(...args: any[]) { function destroy(...args: any[]) {
@ -33,7 +31,7 @@ const confirm = (config: ModalFuncProps) => {
} }
const triggerCancel = args.some(param => param && param.triggerCancel); const triggerCancel = args.some(param => param && param.triggerCancel);
if (config.onCancel && triggerCancel) { if (config.onCancel && triggerCancel) {
config.onCancel(...args); config.onCancel(() => {}, ...args.slice(1));
} }
for (let i = 0; i < destroyFns.length; i++) { for (let i = 0; i < destroyFns.length; i++) {
const fn = destroyFns[i]; const fn = destroyFns[i];
@ -47,7 +45,7 @@ const confirm = (config: ModalFuncProps) => {
function close(this: typeof close, ...args: any[]) { function close(this: typeof close, ...args: any[]) {
currentConfig = { currentConfig = {
...currentConfig, ...currentConfig,
visible: false, open: false,
afterClose: () => { afterClose: () => {
if (typeof config.afterClose === 'function') { if (typeof config.afterClose === 'function') {
config.afterClose(); config.afterClose();
@ -55,6 +53,10 @@ const confirm = (config: ModalFuncProps) => {
destroy.apply(this, args); destroy.apply(this, args);
}, },
}; };
// Legacy support
if (currentConfig.visible) {
delete currentConfig.visible;
}
update(currentConfig); update(currentConfig);
} }
function update(configUpdate: ConfigUpdate) { function update(configUpdate: ConfigUpdate) {
@ -76,9 +78,18 @@ const confirm = (config: ModalFuncProps) => {
const global = globalConfigForApi; const global = globalConfigForApi;
const rootPrefixCls = global.prefixCls; const rootPrefixCls = global.prefixCls;
const prefixCls = p.prefixCls || `${rootPrefixCls}-modal`; const prefixCls = p.prefixCls || `${rootPrefixCls}-modal`;
const iconPrefixCls = global.iconPrefixCls;
const runtimeLocale = getConfirmLocale();
return ( return (
<ConfigProvider {...(global as any)} prefixCls={rootPrefixCls}> <ConfigProvider {...(global as any)} prefixCls={rootPrefixCls}>
<ConfirmDialog {...p} rootPrefixCls={rootPrefixCls} prefixCls={prefixCls}></ConfirmDialog> <ConfirmDialog
{...p}
rootPrefixCls={rootPrefixCls}
prefixCls={prefixCls}
iconPrefixCls={iconPrefixCls}
locale={runtimeLocale}
cancelText={p.cancelText || runtimeLocale.cancelText}
></ConfirmDialog>
</ConfigProvider> </ConfigProvider>
); );
}; };
@ -101,8 +112,6 @@ export default confirm;
export function withWarn(props: ModalFuncProps): ModalFuncProps { export function withWarn(props: ModalFuncProps): ModalFuncProps {
return { return {
icon: () => <ExclamationCircleOutlined />,
okCancel: false,
...props, ...props,
type: 'warning', type: 'warning',
}; };
@ -110,8 +119,6 @@ export function withWarn(props: ModalFuncProps): ModalFuncProps {
export function withInfo(props: ModalFuncProps): ModalFuncProps { export function withInfo(props: ModalFuncProps): ModalFuncProps {
return { return {
icon: () => <InfoCircleOutlined />,
okCancel: false,
...props, ...props,
type: 'info', type: 'info',
}; };
@ -119,8 +126,6 @@ export function withInfo(props: ModalFuncProps): ModalFuncProps {
export function withSuccess(props: ModalFuncProps): ModalFuncProps { export function withSuccess(props: ModalFuncProps): ModalFuncProps {
return { return {
icon: () => <CheckCircleOutlined />,
okCancel: false,
...props, ...props,
type: 'success', type: 'success',
}; };
@ -128,8 +133,6 @@ export function withSuccess(props: ModalFuncProps): ModalFuncProps {
export function withError(props: ModalFuncProps): ModalFuncProps { export function withError(props: ModalFuncProps): ModalFuncProps {
return { return {
icon: () => <CloseCircleOutlined />,
okCancel: false,
...props, ...props,
type: 'error', type: 'error',
}; };
@ -137,8 +140,6 @@ export function withError(props: ModalFuncProps): ModalFuncProps {
export function withConfirm(props: ModalFuncProps): ModalFuncProps { export function withConfirm(props: ModalFuncProps): ModalFuncProps {
return { return {
icon: () => <ExclamationCircleOutlined />,
okCancel: true,
...props, ...props,
type: 'confirm', type: 'confirm',
}; };

View File

@ -23,12 +23,7 @@ For example, you can use this pattern when you submit a form.
<template> <template>
<div> <div>
<a-button type="primary" @click="showModal">Open Modal with async logic</a-button> <a-button type="primary" @click="showModal">Open Modal with async logic</a-button>
<a-modal <a-modal v-model:open="open" title="Title" :confirm-loading="confirmLoading" @ok="handleOk">
v-model:visible="visible"
title="Title"
:confirm-loading="confirmLoading"
@ok="handleOk"
>
<p>{{ modalText }}</p> <p>{{ modalText }}</p>
</a-modal> </a-modal>
</div> </div>
@ -38,24 +33,24 @@ import { ref, defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const modalText = ref<string>('Content of the modal'); const modalText = ref<string>('Content of the modal');
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const confirmLoading = ref<boolean>(false); const confirmLoading = ref<boolean>(false);
const showModal = () => { const showModal = () => {
visible.value = true; open.value = true;
}; };
const handleOk = () => { const handleOk = () => {
modalText.value = 'The modal will be closed after two seconds'; modalText.value = 'The modal will be closed after two seconds';
confirmLoading.value = true; confirmLoading.value = true;
setTimeout(() => { setTimeout(() => {
visible.value = false; open.value = false;
confirmLoading.value = false; confirmLoading.value = false;
}, 2000); }, 2000);
}; };
return { return {
modalText, modalText,
visible, open,
confirmLoading, confirmLoading,
showModal, showModal,
handleOk, handleOk,

View File

@ -19,7 +19,7 @@ Basic modal.
<template> <template>
<div> <div>
<a-button type="primary" @click="showModal">Open Modal</a-button> <a-button type="primary" @click="showModal">Open Modal</a-button>
<a-modal v-model:visible="visible" title="Basic Modal" @ok="handleOk"> <a-modal v-model:open="open" title="Basic Modal" @ok="handleOk">
<p>Some contents...</p> <p>Some contents...</p>
<p>Some contents...</p> <p>Some contents...</p>
<p>Some contents...</p> <p>Some contents...</p>
@ -30,18 +30,18 @@ Basic modal.
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const showModal = () => { const showModal = () => {
visible.value = true; open.value = true;
}; };
const handleOk = (e: MouseEvent) => { const handleOk = (e: MouseEvent) => {
console.log(e); console.log(e);
visible.value = false; open.value = false;
}; };
return { return {
visible, open,
showModal, showModal,
handleOk, handleOk,
}; };

View File

@ -20,7 +20,7 @@ Passing `okButtonProps` and `cancelButtonProps` can customize the ok button and
<div> <div>
<a-button type="primary" @click="showModal">Open Modal with customized button props</a-button> <a-button type="primary" @click="showModal">Open Modal with customized button props</a-button>
<a-modal <a-modal
v-model:visible="visible" v-model:open="open"
title="Basic Modal" title="Basic Modal"
:ok-button-props="{ disabled: true }" :ok-button-props="{ disabled: true }"
:cancel-button-props="{ disabled: true }" :cancel-button-props="{ disabled: true }"
@ -36,23 +36,23 @@ Passing `okButtonProps` and `cancelButtonProps` can customize the ok button and
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const showModal = () => { const showModal = () => {
visible.value = true; open.value = true;
}; };
const handleOk = (e: MouseEvent) => { const handleOk = (e: MouseEvent) => {
console.log(e); console.log(e);
visible.value = false; open.value = false;
}; };
const handleCancel = (e: MouseEvent) => { const handleCancel = (e: MouseEvent) => {
console.log(e); console.log(e);
visible.value = false; open.value = false;
}; };
return { return {
visible, open,
showModal, showModal,
handleOk, handleOk,
handleCancel, handleCancel,

View File

@ -23,7 +23,7 @@ You could set `footer` to `null` if you don't need default footer buttons.
<template> <template>
<div> <div>
<a-button type="primary" @click="showModal">Open Modal with customized footer</a-button> <a-button type="primary" @click="showModal">Open Modal with customized footer</a-button>
<a-modal v-model:visible="visible" title="Title" @ok="handleOk"> <a-modal v-model:open="open" title="Title" @ok="handleOk">
<template #footer> <template #footer>
<a-button key="back" @click="handleCancel">Return</a-button> <a-button key="back" @click="handleCancel">Return</a-button>
<a-button key="submit" type="primary" :loading="loading" @click="handleOk">Submit</a-button> <a-button key="submit" type="primary" :loading="loading" @click="handleOk">Submit</a-button>
@ -41,26 +41,26 @@ import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const showModal = () => { const showModal = () => {
visible.value = true; open.value = true;
}; };
const handleOk = () => { const handleOk = () => {
loading.value = true; loading.value = true;
setTimeout(() => { setTimeout(() => {
loading.value = false; loading.value = false;
visible.value = false; open.value = false;
}, 2000); }, 2000);
}; };
const handleCancel = () => { const handleCancel = () => {
visible.value = false; open.value = false;
}; };
return { return {
loading, loading,
visible, open,
showModal, showModal,
handleOk, handleOk,
handleCancel, handleCancel,

View File

@ -20,7 +20,7 @@ Full screen by custom style.
<div> <div>
<a-button type="primary" @click="showModal">Open Modal</a-button> <a-button type="primary" @click="showModal">Open Modal</a-button>
<a-modal <a-modal
v-model:visible="visible" v-model:open="open"
title="Basic Modal" title="Basic Modal"
width="100%" width="100%"
wrap-class-name="full-modal" wrap-class-name="full-modal"
@ -36,18 +36,18 @@ Full screen by custom style.
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const showModal = () => { const showModal = () => {
visible.value = true; open.value = true;
}; };
const handleOk = (e: MouseEvent) => { const handleOk = (e: MouseEvent) => {
console.log(e); console.log(e);
visible.value = false; open.value = false;
}; };
return { return {
visible, open,
showModal, showModal,
handleOk, handleOk,
}; };

View File

@ -20,13 +20,7 @@ To customize the text of the buttons, you need to set `okText` and `cancelText`
<div> <div>
<a-button type="primary" @click="showModal">Modal</a-button> <a-button type="primary" @click="showModal">Modal</a-button>
<a-button @click="confirm">Confirm</a-button> <a-button @click="confirm">Confirm</a-button>
<a-modal <a-modal v-model:open="open" title="Modal" ok-text="" cancel-text="" @ok="hideModal">
v-model:visible="visible"
title="Modal"
ok-text="确认"
cancel-text="取消"
@ok="hideModal"
>
<p>Bla bla ...</p> <p>Bla bla ...</p>
<p>Bla bla ...</p> <p>Bla bla ...</p>
<p>Bla bla ...</p> <p>Bla bla ...</p>
@ -39,13 +33,13 @@ import { defineComponent, ref, createVNode } from 'vue';
import { Modal } from 'ant-design-vue'; import { Modal } from 'ant-design-vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const showModal = () => { const showModal = () => {
visible.value = true; open.value = true;
}; };
const hideModal = () => { const hideModal = () => {
visible.value = false; open.value = false;
}; };
const confirm = () => { const confirm = () => {
@ -59,7 +53,7 @@ export default defineComponent({
}; };
return { return {
visible, open,
showModal, showModal,
hideModal, hideModal,
confirm, confirm,

View File

@ -18,12 +18,7 @@ Custom modal content render. use `vueuse` implements draggable.
<template> <template>
<div> <div>
<a-button type="primary" @click="showModal">Open Modal</a-button> <a-button type="primary" @click="showModal">Open Modal</a-button>
<a-modal <a-modal ref="modalRef" v-model:open="open" :wrap-style="{ overflow: 'hidden' }" @ok="handleOk">
ref="modalRef"
v-model:visible="visible"
:wrap-style="{ overflow: 'hidden' }"
@ok="handleOk"
>
<p>Some contents...</p> <p>Some contents...</p>
<p>Some contents...</p> <p>Some contents...</p>
<p>Some contents...</p> <p>Some contents...</p>
@ -43,15 +38,15 @@ import { defineComponent, ref, computed, CSSProperties, watch, watchEffect } fro
import { useDraggable } from '@vueuse/core'; import { useDraggable } from '@vueuse/core';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const modalTitleRef = ref<HTMLElement>(null); const modalTitleRef = ref<HTMLElement>(null);
const showModal = () => { const showModal = () => {
visible.value = true; open.value = true;
}; };
const { x, y, isDragging } = useDraggable(modalTitleRef); const { x, y, isDragging } = useDraggable(modalTitleRef);
const handleOk = (e: MouseEvent) => { const handleOk = (e: MouseEvent) => {
console.log(e); console.log(e);
visible.value = false; open.value = false;
}; };
const startX = ref<number>(0); const startX = ref<number>(0);
const startY = ref<number>(0); const startY = ref<number>(0);
@ -98,7 +93,7 @@ export default defineComponent({
}; };
}); });
return { return {
visible, open,
showModal, showModal,
handleOk, handleOk,
modalTitleRef, modalTitleRef,

View File

@ -22,7 +22,7 @@ You can use `centered`,`style.top` or other styles to set position of modal dial
Display a modal dialog at 20px to Top Display a modal dialog at 20px to Top
</a-button> </a-button>
<a-modal <a-modal
v-model:visible="modal1Visible" v-model:open="modal1Visible"
title="20px to Top" title="20px to Top"
style="top: 20px" style="top: 20px"
@ok="setModal1Visible(false)" @ok="setModal1Visible(false)"
@ -37,7 +37,7 @@ You can use `centered`,`style.top` or other styles to set position of modal dial
Vertically centered modal dialog Vertically centered modal dialog
</a-button> </a-button>
<a-modal <a-modal
v-model:visible="modal2Visible" v-model:open="modal2Visible"
title="Vertically centered modal dialog" title="Vertically centered modal dialog"
centered centered
@ok="modal2Visible = false" @ok="modal2Visible = false"
@ -55,8 +55,8 @@ export default defineComponent({
const modal1Visible = ref<boolean>(false); const modal1Visible = ref<boolean>(false);
const modal2Visible = ref<boolean>(false); const modal2Visible = ref<boolean>(false);
const setModal1Visible = (visible: boolean) => { const setModal1Visible = (open: boolean) => {
modal1Visible.value = visible; modal1Visible.value = open;
}; };
return { return {
modal1Visible, modal1Visible,

View File

@ -19,7 +19,7 @@ Use `width` to set the width of the modal dialog
<template> <template>
<div> <div>
<a-button type="primary" @click="showModal">Open Modal of 1000px width</a-button> <a-button type="primary" @click="showModal">Open Modal of 1000px width</a-button>
<a-modal v-model:visible="visible" width="1000px" title="Basic Modal" @ok="handleOk"> <a-modal v-model:open="open" width="1000px" title="Basic Modal" @ok="handleOk">
<p>Some contents...</p> <p>Some contents...</p>
<p>Some contents...</p> <p>Some contents...</p>
<p>Some contents...</p> <p>Some contents...</p>
@ -30,18 +30,18 @@ Use `width` to set the width of the modal dialog
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const showModal = () => { const showModal = () => {
visible.value = true; open.value = true;
}; };
const handleOk = (e: MouseEvent) => { const handleOk = (e: MouseEvent) => {
console.log(e); console.log(e);
visible.value = false; open.value = false;
}; };
return { return {
visible, open,
showModal, showModal,
handleOk, handleOk,
}; };

View File

@ -36,7 +36,7 @@ When requiring users to interact with the application, but without jumping to a
| okText | Text of the OK button | string\|slot | `OK` | | | okText | Text of the OK button | string\|slot | `OK` | |
| okType | Button `type` of the OK button | string | `primary` | | | okType | Button `type` of the OK button | string | `primary` | |
| title | The modal dialog's title | string\|slot | - | | | title | The modal dialog's title | string\|slot | - | |
| visible | Whether the modal dialog is visible or not | boolean | false | | | open(v-model) | Whether the modal dialog is visible or not | boolean | false | |
| width | Width of the modal dialog | string\|number | 520 | | | width | Width of the modal dialog | string\|number | 520 | |
| wrapClassName | The class name of the container of the modal dialog | string | - | | | wrapClassName | The class name of the container of the modal dialog | string | - | |
| zIndex | The `z-index` of the Modal | number | 1000 | | | zIndex | The `z-index` of the Modal | number | 1000 | |
@ -74,6 +74,7 @@ The items listed above are all functions, expecting a settings object as paramet
| class | class of container | string | - | | | class | class of container | string | - | |
| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | `false` | | | closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | `false` | |
| content | Content | string\|VNode \|function(h) | - | | | content | Content | string\|VNode \|function(h) | - | |
| footer | Footer content, set as `footer: null` when you don't need default buttons | string\|VNode \|function(h) | - | 4.0.0 |
| icon | custom icon (`Added in 1.14.0`) | VNode \|()=>VNode | - | | | icon | custom icon (`Added in 1.14.0`) | VNode \|()=>VNode | - | |
| keyboard | Whether support press esc to close | boolean | true | | | keyboard | Whether support press esc to close | boolean | true | |
| mask | Whether show mask or not. | boolean | true | | | mask | Whether show mask or not. | boolean | true | |
@ -116,7 +117,7 @@ router.beforeEach((to, from, next) => {
## FAQ ## FAQ
### Why can't the Modal method obtain global registered components, context, vuex, etc. and ConfigProvider `locale/prefixCls` configuration, and can't update data responsively? ### Why can't the Modal method obtain global registered components, context, vuex, etc. and ConfigProvider `locale/prefixCls/theme` configuration, and can't update data responsively?
Call the Modal method directly, and the component will dynamically create a new Vue entity through `Vue.render`. Its context is not the same as the context where the current code is located, so the context information cannot be obtained. Call the Modal method directly, and the component will dynamically create a new Vue entity through `Vue.render`. Its context is not the same as the context where the current code is located, so the context information cannot be obtained.

View File

@ -40,7 +40,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3StSdUlSH/Modal.svg
| okText | 确认按钮文字 | string\|slot | 确定 | | | okText | 确认按钮文字 | string\|slot | 确定 | |
| okType | 确认按钮类型 | string | primary | | | okType | 确认按钮类型 | string | primary | |
| title | 标题 | string\|slot | 无 | | | title | 标题 | string\|slot | 无 | |
| visible(v-model) | 对话框是否可见 | boolean | 无 | | | open(v-model) | 对话框是否可见 | boolean | 无 | |
| width | 宽度 | string\|number | 520 | | | width | 宽度 | string\|number | 520 | |
| wrapClassName | 对话框外层容器的类名 | string | - | | | wrapClassName | 对话框外层容器的类名 | string | - | |
| zIndex | 设置 Modal 的 `z-index` | number | 1000 | | | zIndex | 设置 Modal 的 `z-index` | number | 1000 | |
@ -78,6 +78,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3StSdUlSH/Modal.svg
| class | 容器类名 | string | - | | | class | 容器类名 | string | - | |
| closable | 是否显示右上角的关闭按钮 | boolean | `false` | | | closable | 是否显示右上角的关闭按钮 | boolean | `false` | |
| content | 内容 | string \|VNode \|function(h) | 无 | | | content | 内容 | string \|VNode \|function(h) | 无 | |
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer: null` | string \|VNode \|function(h) | - | 4.0.0 |
| icon | 自定义图标1.14.0 新增) | VNode \| ()=>VNode | - | | | icon | 自定义图标1.14.0 新增) | VNode \| ()=>VNode | - | |
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | | | keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
| mask | 是否展示遮罩 | boolean | true | | | mask | 是否展示遮罩 | boolean | true | |
@ -120,7 +121,7 @@ router.beforeEach((to, from, next) => {
## FAQ ## FAQ
### 为什么 Modal 方法不能获取 全局注册组件、context、vuex 等内容和 ConfigProvider `locale/prefixCls` 配置, 以及不能响应式更新数据 ### 为什么 Modal 方法不能获取 全局注册组件、context、vuex 等内容和 ConfigProvider `locale/prefixCls/theme` 配置, 以及不能响应式更新数据
直接调用 Modal 方法,组件会通过 `Vue.render` 动态创建新的 Vue 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。 直接调用 Modal 方法,组件会通过 `Vue.render` 动态创建新的 Vue 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。

View File

@ -1,5 +1,28 @@
import defaultLocale from '../locale/en_US';
export interface ModalLocale { export interface ModalLocale {
okText: string; okText: string;
cancelText: string; cancelText: string;
justOkText: string; justOkText: string;
} }
let runtimeLocale: ModalLocale = {
...(defaultLocale.Modal as ModalLocale),
};
export function changeConfirmLocale(newLocale?: ModalLocale) {
if (newLocale) {
runtimeLocale = {
...runtimeLocale,
...newLocale,
};
} else {
runtimeLocale = {
...(defaultLocale.Modal as ModalLocale),
};
}
}
export function getConfirmLocale() {
return runtimeLocale;
}

View File

@ -1,74 +0,0 @@
@import '../../style/mixins/index';
@confirm-prefix-cls: ~'@{ant-prefix}-modal-confirm';
.@{confirm-prefix-cls} {
.@{ant-prefix}-modal-header {
display: none;
}
.@{ant-prefix}-modal-body {
padding: @modal-confirm-body-padding;
}
&-body-wrapper {
.clearfix();
}
&-body {
.@{confirm-prefix-cls}-title {
display: block;
// create BFC to avoid
// https://user-images.githubusercontent.com/507615/37702510-ba844e06-2d2d-11e8-9b67-8e19be57f445.png
overflow: hidden;
color: @heading-color;
font-weight: 500;
font-size: @modal-confirm-title-font-size;
line-height: 1.4;
}
.@{confirm-prefix-cls}-content {
margin-top: 8px;
color: @text-color;
font-size: @font-size-base;
}
> .@{iconfont-css-prefix} {
float: left;
margin-right: 16px;
font-size: 22px;
// `content` after `icon` should set marginLeft
+ .@{confirm-prefix-cls}-title + .@{confirm-prefix-cls}-content {
margin-left: 38px;
}
}
}
.@{confirm-prefix-cls}-btns {
float: right;
margin-top: 24px;
.@{ant-prefix}-btn + .@{ant-prefix}-btn {
margin-bottom: 0;
margin-left: 8px;
}
}
&-error &-body > .@{iconfont-css-prefix} {
color: @error-color;
}
&-warning &-body > .@{iconfont-css-prefix},
&-confirm &-body > .@{iconfont-css-prefix} {
color: @warning-color;
}
&-info &-body > .@{iconfont-css-prefix} {
color: @info-color;
}
&-success &-body > .@{iconfont-css-prefix} {
color: @success-color;
}
}

View File

@ -1,7 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './modal';
@import './confirm';
@import './rtl';
.popover-customize-bg(@dialog-prefix-cls, @popover-background);

View File

@ -0,0 +1,462 @@
import { initFadeMotion, initZoomMotion } from '../../_style/motion';
import type { AliasToken, FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook';
import { clearFix, genFocusStyle, resetComponent } from '../../_style';
import { CSSProperties } from 'vue';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
// Component token here
}
export interface ModalToken extends FullToken<'Modal'> {
// Custom token here
modalBodyPadding: number;
modalHeaderBg: string;
modalHeaderPadding: string;
modalHeaderBorderWidth: number;
modalHeaderBorderStyle: string;
modalHeaderTitleLineHeight: number;
modalHeaderTitleFontSize: number;
modalHeaderBorderColorSplit: string;
modalHeaderCloseSize: number;
modalContentBg: string;
modalHeadingColor: string;
modalCloseColor: string;
modalCloseBtnSize: number;
modalFooterBg: string;
modalFooterBorderColorSplit: string;
modalFooterBorderStyle: string;
modalFooterPaddingVertical: number;
modalFooterPaddingHorizontal: number;
modalFooterBorderWidth: number;
modalConfirmTitleFontSize: number;
modalIconHoverColor: string;
modalConfirmIconSize: number;
}
function box(position: CSSProperties['position']): CSSProperties {
return {
position,
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
};
}
export const genModalMaskStyle: GenerateStyle<TokenWithCommonCls<AliasToken>> = token => {
const { componentCls } = token;
return [
{
[`${componentCls}-root`]: {
[`${componentCls}${token.antCls}-zoom-enter, ${componentCls}${token.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',
},
[`${componentCls}-mask`]: {
...box('fixed'),
zIndex: token.zIndexPopupBase,
height: '100%',
backgroundColor: token.colorBgMask,
[`${componentCls}-hidden`]: {
display: 'none',
},
},
[`${componentCls}-wrap`]: {
...box('fixed'),
overflow: 'auto',
outline: 0,
WebkitOverflowScrolling: 'touch',
},
},
},
{ [`${componentCls}-root`]: initFadeMotion(token) },
];
};
const genModalStyle: GenerateStyle<ModalToken> = token => {
const { componentCls } = token;
return [
// ======================== Root =========================
{
[`${componentCls}-root`]: {
[`${componentCls}-wrap`]: {
zIndex: token.zIndexPopupBase,
position: 'fixed',
inset: 0,
overflow: 'auto',
outline: 0,
WebkitOverflowScrolling: 'touch',
},
[`${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})`]: {
[componentCls]: {
maxWidth: 'calc(100vw - 16px)',
margin: `${token.marginXS} auto`,
},
[`${componentCls}-centered`]: {
[componentCls]: {
flex: 1,
},
},
},
},
},
// ======================== Modal ========================
{
[componentCls]: {
...resetComponent(token),
pointerEvents: 'none',
position: 'relative',
top: 100,
width: 'auto',
maxWidth: `calc(100vw - ${token.margin * 2}px)`,
margin: '0 auto',
paddingBottom: token.paddingLG,
[`${componentCls}-title`]: {
margin: 0,
color: token.modalHeadingColor,
fontWeight: token.fontWeightStrong,
fontSize: token.modalHeaderTitleFontSize,
lineHeight: token.modalHeaderTitleLineHeight,
wordWrap: 'break-word',
},
[`${componentCls}-content`]: {
position: 'relative',
backgroundColor: token.modalContentBg,
backgroundClip: 'padding-box',
border: 0,
borderRadius: token.borderRadiusLG,
boxShadow: token.boxShadowSecondary,
pointerEvents: 'auto',
padding: `${token.paddingMD}px ${token.paddingContentHorizontalLG}px`,
},
[`${componentCls}-close`]: {
position: 'absolute',
top: (token.modalHeaderCloseSize - token.modalCloseBtnSize) / 2,
insetInlineEnd: (token.modalHeaderCloseSize - token.modalCloseBtnSize) / 2,
zIndex: token.zIndexPopupBase + 10,
padding: 0,
color: token.modalCloseColor,
fontWeight: token.fontWeightStrong,
lineHeight: 1,
textDecoration: 'none',
background: 'transparent',
borderRadius: token.borderRadiusSM,
width: token.modalConfirmIconSize,
height: token.modalConfirmIconSize,
border: 0,
outline: 0,
cursor: 'pointer',
transition: `color ${token.motionDurationMid}, background-color ${token.motionDurationMid}`,
'&-x': {
display: 'block',
fontSize: token.fontSizeLG,
fontStyle: 'normal',
lineHeight: `${token.modalCloseBtnSize}px`,
textAlign: 'center',
textTransform: 'none',
textRendering: 'auto',
},
'&:hover': {
color: token.modalIconHoverColor,
backgroundColor: token.wireframe ? 'transparent' : token.colorFillContent,
textDecoration: 'none',
},
'&:active': {
backgroundColor: token.wireframe ? 'transparent' : token.colorFillContentHover,
},
...genFocusStyle(token),
},
[`${componentCls}-header`]: {
color: token.colorText,
background: token.modalHeaderBg,
borderRadius: `${token.borderRadiusLG}px ${token.borderRadiusLG}px 0 0`,
marginBottom: token.marginXS,
},
[`${componentCls}-body`]: {
fontSize: token.fontSize,
lineHeight: token.lineHeight,
wordWrap: 'break-word',
},
[`${componentCls}-footer`]: {
textAlign: 'end',
background: token.modalFooterBg,
marginTop: token.marginSM,
[`${token.antCls}-btn + ${token.antCls}-btn:not(${token.antCls}-dropdown-trigger)`]: {
marginBottom: 0,
marginInlineStart: token.marginXS,
},
},
[`${componentCls}-open`]: {
overflow: 'hidden',
},
},
},
// ======================== Pure =========================
{
[`${componentCls}-pure-panel`]: {
top: 'auto',
padding: 0,
display: 'flex',
flexDirection: 'column',
[`${componentCls}-content,
${componentCls}-body,
${componentCls}-confirm-body-wrapper`]: {
display: 'flex',
flexDirection: 'column',
flex: 'auto',
},
[`${componentCls}-confirm-body`]: {
marginBottom: 'auto',
},
},
},
];
};
const genModalConfirmStyle: GenerateStyle<ModalToken> = token => {
const { componentCls } = token;
const confirmComponentCls = `${componentCls}-confirm`;
return {
[confirmComponentCls]: {
'&-rtl': {
direction: 'rtl',
},
[`${token.antCls}-modal-header`]: {
display: 'none',
},
[`${confirmComponentCls}-body-wrapper`]: {
...clearFix(),
},
[`${confirmComponentCls}-body`]: {
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
[`${confirmComponentCls}-title`]: {
flex: '0 0 100%',
display: 'block',
// create BFC to avoid
// https://user-images.githubusercontent.com/507615/37702510-ba844e06-2d2d-11e8-9b67-8e19be57f445.png
overflow: 'hidden',
color: token.colorTextHeading,
fontWeight: token.fontWeightStrong,
fontSize: token.modalHeaderTitleFontSize,
lineHeight: token.modalHeaderTitleLineHeight,
[`+ ${confirmComponentCls}-content`]: {
marginBlockStart: token.marginXS,
flexBasis: '100%',
maxWidth: `calc(100% - ${token.modalConfirmIconSize + token.marginSM}px)`,
},
},
[`${confirmComponentCls}-content`]: {
color: token.colorText,
fontSize: token.fontSize,
},
[`> ${token.iconCls}`]: {
flex: 'none',
marginInlineEnd: token.marginSM,
fontSize: token.modalConfirmIconSize,
[`+ ${confirmComponentCls}-title`]: {
flex: 1,
},
// `content` after `icon` should set marginLeft
[`+ ${confirmComponentCls}-title + ${confirmComponentCls}-content`]: {
marginInlineStart: token.modalConfirmIconSize + token.marginSM,
},
},
},
[`${confirmComponentCls}-btns`]: {
textAlign: 'end',
marginTop: token.marginSM,
[`${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,
},
// https://github.com/ant-design/ant-design/issues/37329
[`${componentCls}-zoom-leave ${componentCls}-btns`]: {
pointerEvents: 'none',
},
};
};
const genRTLStyle: GenerateStyle<ModalToken> = token => {
const { componentCls } = token;
return {
[`${componentCls}-root`]: {
[`${componentCls}-wrap-rtl`]: {
direction: 'rtl',
[`${componentCls}-confirm-body`]: {
direction: 'rtl',
},
},
},
};
};
const genWireframeStyle: GenerateStyle<ModalToken> = token => {
const { componentCls, antCls } = token;
const confirmComponentCls = `${componentCls}-confirm`;
return {
[componentCls]: {
[`${componentCls}-content`]: {
padding: 0,
},
[`${componentCls}-header`]: {
padding: token.modalHeaderPadding,
borderBottom: `${token.modalHeaderBorderWidth}px ${token.modalHeaderBorderStyle} ${token.modalHeaderBorderColorSplit}`,
marginBottom: 0,
},
[`${componentCls}-body`]: {
padding: token.modalBodyPadding,
},
[`${componentCls}-footer`]: {
padding: `${token.modalFooterPaddingVertical}px ${token.modalFooterPaddingHorizontal}px`,
borderTop: `${token.modalFooterBorderWidth}px ${token.modalFooterBorderStyle} ${token.modalFooterBorderColorSplit}`,
borderRadius: `0 0 ${token.borderRadiusLG}px ${token.borderRadiusLG}px`,
marginTop: 0,
},
},
[confirmComponentCls]: {
[`${antCls}-modal-body`]: {
padding: `${token.padding * 2}px ${token.padding * 2}px ${token.paddingLG}px`,
},
[`${confirmComponentCls}-body`]: {
[`> ${token.iconCls}`]: {
marginInlineEnd: token.margin,
// `content` after `icon` should set marginLeft
[`+ ${confirmComponentCls}-title + ${confirmComponentCls}-content`]: {
marginInlineStart: token.modalConfirmIconSize + token.margin,
},
},
},
[`${confirmComponentCls}-btns`]: {
marginTop: token.marginLG,
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook('Modal', token => {
const headerPaddingVertical = token.padding;
const headerFontSize = token.fontSizeHeading5;
const headerLineHeight = token.lineHeightHeading5;
const modalToken = mergeToken<ModalToken>(token, {
modalBodyPadding: token.paddingLG,
modalHeaderBg: token.colorBgElevated,
modalHeaderPadding: `${headerPaddingVertical}px ${token.paddingLG}px`,
modalHeaderBorderWidth: token.lineWidth,
modalHeaderBorderStyle: token.lineType,
modalHeaderTitleLineHeight: headerLineHeight,
modalHeaderTitleFontSize: headerFontSize,
modalHeaderBorderColorSplit: token.colorSplit,
modalHeaderCloseSize: headerLineHeight * headerFontSize + headerPaddingVertical * 2,
modalContentBg: token.colorBgElevated,
modalHeadingColor: token.colorTextHeading,
modalCloseColor: token.colorTextDescription,
modalFooterBg: 'transparent',
modalFooterBorderColorSplit: token.colorSplit,
modalFooterBorderStyle: token.lineType,
modalFooterPaddingVertical: token.paddingXS,
modalFooterPaddingHorizontal: token.padding,
modalFooterBorderWidth: token.lineWidth,
modalConfirmTitleFontSize: token.fontSizeLG,
modalIconHoverColor: token.colorIconHover,
modalConfirmIconSize: token.fontSize * token.lineHeight,
modalCloseBtnSize: token.controlHeightLG * 0.55,
});
return [
genModalStyle(modalToken),
genModalConfirmStyle(modalToken),
genRTLStyle(modalToken),
genModalMaskStyle(modalToken),
token.wireframe && genWireframeStyle(modalToken),
initZoomMotion(modalToken, 'zoom'),
];
});

View File

@ -1,5 +0,0 @@
import '../../style/index.less';
import './index.less';
// style dependencies
import '../../button/style';

View File

@ -1,136 +0,0 @@
@dialog-prefix-cls: ~'@{ant-prefix}-modal';
.@{dialog-prefix-cls} {
.reset-component();
.modal-mask();
position: relative;
top: 100px;
width: auto;
max-width: calc(100vw - 32px);
margin: 0 auto;
padding-bottom: 24px;
&-wrap {
z-index: @zindex-modal;
}
&-title {
margin: 0;
color: @modal-heading-color;
font-weight: 500;
font-size: @modal-header-title-font-size;
line-height: @modal-header-title-line-height;
word-wrap: break-word;
}
&-content {
position: relative;
background-color: @modal-content-bg;
background-clip: padding-box;
border: 0;
border-radius: @border-radius-base;
box-shadow: @shadow-2;
pointer-events: auto;
}
&-close {
position: absolute;
top: 0;
right: 0;
z-index: @zindex-popup-close;
padding: 0;
color: @modal-close-color;
font-weight: 700;
line-height: 1;
text-decoration: none;
background: transparent;
border: 0;
outline: 0;
cursor: pointer;
transition: color 0.3s;
&-x {
display: block;
width: @modal-header-close-size;
height: @modal-header-close-size;
font-size: @font-size-lg;
font-style: normal;
line-height: @modal-header-close-size;
text-align: center;
text-transform: none;
text-rendering: auto;
}
&:focus,
&:hover {
color: @icon-color-hover;
text-decoration: none;
}
}
&-header {
padding: @modal-header-padding;
color: @text-color;
background: @modal-header-bg;
border-bottom: @modal-header-border-width @modal-header-border-style
@modal-header-border-color-split;
border-radius: @border-radius-base @border-radius-base 0 0;
}
&-body {
padding: @modal-body-padding;
font-size: @font-size-base;
line-height: @line-height-base;
word-wrap: break-word;
}
&-footer {
padding: @modal-footer-padding-vertical @modal-footer-padding-horizontal;
text-align: right;
background: @modal-footer-bg;
border-top: @modal-footer-border-width @modal-footer-border-style
@modal-footer-border-color-split;
border-radius: 0 0 @border-radius-base @border-radius-base;
.@{ant-prefix}-btn + .@{ant-prefix}-btn:not(.@{ant-prefix}-dropdown-trigger) {
margin-bottom: 0;
margin-left: 8px;
}
}
&-open {
overflow: hidden;
}
}
.@{dialog-prefix-cls}-centered {
text-align: center;
&::before {
display: inline-block;
width: 0;
height: 100%;
vertical-align: middle;
content: '';
}
.@{dialog-prefix-cls} {
top: 0;
display: inline-block;
padding-bottom: 0;
text-align: left;
vertical-align: middle;
}
}
@media (max-width: @screen-sm-max) {
.@{dialog-prefix-cls} {
max-width: calc(100vw - 16px);
margin: 8px auto;
}
.@{dialog-prefix-cls}-centered {
.@{dialog-prefix-cls} {
flex: 1;
}
}
}

View File

@ -1,74 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@dialog-prefix-cls: ~'@{ant-prefix}-modal';
@confirm-prefix-cls: ~'@{ant-prefix}-modal-confirm';
@dialog-wrap-rtl-cls: ~'@{dialog-prefix-cls}-wrap-rtl';
.@{dialog-prefix-cls} {
&-wrap {
&-rtl {
direction: rtl;
}
}
&-close {
.@{dialog-wrap-rtl-cls} & {
right: initial;
left: 0;
}
}
&-footer {
.@{dialog-wrap-rtl-cls} & {
text-align: left;
}
.@{ant-prefix}-btn + .@{ant-prefix}-btn {
.@{dialog-wrap-rtl-cls} & {
margin-right: 8px;
margin-left: 0;
}
}
}
&-confirm {
&-body {
.@{dialog-wrap-rtl-cls} & {
direction: rtl;
}
> .@{iconfont-css-prefix} {
.@{dialog-wrap-rtl-cls} & {
float: right;
margin-right: 0;
margin-left: 16px;
}
+ .@{confirm-prefix-cls}-title + .@{confirm-prefix-cls}-content {
.@{dialog-wrap-rtl-cls} & {
margin-right: 38px;
margin-left: 0;
}
}
}
}
&-btns {
.@{dialog-wrap-rtl-cls} & {
float: left;
}
.@{ant-prefix}-btn + .@{ant-prefix}-btn {
.@{dialog-wrap-rtl-cls} & {
margin-right: 8px;
margin-left: 0;
}
}
}
}
}
.@{dialog-prefix-cls}-centered {
.@{dialog-prefix-cls} {
.@{dialog-wrap-rtl-cls}& {
text-align: right;
}
}
}

View File

@ -22,7 +22,7 @@ import type { ComponentToken as DividerComponentToken } from '../../divider/styl
// import type { ComponentToken as MentionsComponentToken } from '../../mentions/style'; // import type { ComponentToken as MentionsComponentToken } from '../../mentions/style';
// import type { ComponentToken as MenuComponentToken } from '../../menu/style'; // import type { ComponentToken as MenuComponentToken } from '../../menu/style';
import type { ComponentToken as MessageComponentToken } from '../../message/style'; import type { ComponentToken as MessageComponentToken } from '../../message/style';
// import type { ComponentToken as ModalComponentToken } from '../../modal/style'; import type { ComponentToken as ModalComponentToken } from '../../modal/style';
import type { ComponentToken as NotificationComponentToken } from '../../notification/style'; import type { ComponentToken as NotificationComponentToken } from '../../notification/style';
// import type { ComponentToken as PopconfirmComponentToken } from '../../popconfirm/style'; // import type { ComponentToken as PopconfirmComponentToken } from '../../popconfirm/style';
// import type { ComponentToken as PopoverComponentToken } from '../../popover/style'; // import type { ComponentToken as PopoverComponentToken } from '../../popover/style';
@ -103,7 +103,7 @@ export interface ComponentTokenMap {
// Calendar?: CalendarComponentToken; // Calendar?: CalendarComponentToken;
// Steps?: StepsComponentToken; // Steps?: StepsComponentToken;
// Menu?: MenuComponentToken; // Menu?: MenuComponentToken;
// Modal?: ModalComponentToken; Modal?: ModalComponentToken;
Message?: MessageComponentToken; Message?: MessageComponentToken;
// Upload?: UploadComponentToken; // Upload?: UploadComponentToken;
// Tooltip?: TooltipComponentToken; // Tooltip?: TooltipComponentToken;