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

View File

@ -16,8 +16,12 @@ import { objectType } from '../_util/type';
import { canUseDocElement } from '../_util/styleChecker';
import useConfigInject from '../config-provider/hooks/useConfigInject';
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
const getClickPosition = (e: MouseEvent) => {
mousePosition = {
@ -37,7 +41,9 @@ if (canUseDocElement()) {
export const modalProps = () => ({
prefixCls: String,
/** @deprecated Please use `open` instead. */
visible: { type: Boolean, default: undefined },
open: { type: Boolean, default: undefined },
confirmLoading: { type: Boolean, default: undefined },
title: PropTypes.any,
closable: { type: Boolean, default: undefined },
@ -76,6 +82,7 @@ export const modalProps = () => ({
wrapProps: Object,
focusTriggerAfterClose: { type: Boolean, default: undefined },
modalRender: Function as PropType<(arg: { originVNode: VueNode }) => VueNode>,
mousePosition: objectType<MousePosition>(),
});
export type ModalProps = Partial<ExtractPropTypes<ReturnType<typeof modalProps>>>;
@ -83,8 +90,9 @@ export type ModalProps = Partial<ExtractPropTypes<ReturnType<typeof modalProps>>
export interface ModalFuncProps {
prefixCls?: string;
class?: string;
visible?: boolean;
open?: boolean;
title?: string | (() => VueNode) | VueNode;
footer?: string | (() => VueNode) | VueNode;
closable?: boolean;
content?: string | (() => VueNode) | VueNode;
// TODO: find out exact types
@ -123,6 +131,9 @@ export interface ModalFuncProps {
/** @deprecated please use `appContext` instead */
parentContext?: any;
appContext?: any;
/** @deprecated please use `open` instead */
visible?: boolean;
}
type getContainerFunc = () => HTMLElement;
@ -149,7 +160,6 @@ export default defineComponent({
transitionName: 'zoom',
maskTransitionName: 'fade',
confirmLoading: false,
visible: false,
okType: 'primary',
}),
setup(props, { emit, slots, attrs }) {
@ -158,9 +168,15 @@ export default defineComponent({
'modal',
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) => {
emit('update:visible', false);
emit('update:open', false);
emit('cancel', e);
emit('change', false);
};
@ -196,6 +212,7 @@ export default defineComponent({
const {
prefixCls: customizePrefixCls,
visible,
open,
wrapClassName,
centered,
getContainer,
@ -208,15 +225,16 @@ export default defineComponent({
[`${prefixCls.value}-centered`]: !!centered,
[`${prefixCls.value}-wrap-rtl`]: direction.value === 'rtl',
});
return (
return wrapSSR(
<Dialog
{...restProps}
{...attrs}
rootClassName={hashId.value}
class={classNames(hashId.value, attrs.class)}
getContainer={getContainer || getPopupContainer.value}
prefixCls={prefixCls.value}
wrapClassName={wrapClassNameExtended}
visible={visible}
mousePosition={mousePosition}
visible={open ?? visible}
onClose={handleCancel}
focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls.value, 'zoom', props.transitionName)}
@ -225,6 +243,7 @@ export default defineComponent({
'fade',
props.maskTransitionName,
)}
mousePosition={restProps.mousePosition ?? mousePosition}
v-slots={{
...slots,
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 ConfigProvider, { globalConfigForApi } from '../config-provider';
import omit from '../_util/omit';
import InfoCircleOutlined from '@ant-design/icons-vue/InfoCircleOutlined';
import CheckCircleOutlined from '@ant-design/icons-vue/CheckCircleOutlined';
import CloseCircleOutlined from '@ant-design/icons-vue/CloseCircleOutlined';
import ExclamationCircleOutlined from '@ant-design/icons-vue/ExclamationCircleOutlined';
import { getConfirmLocale } from './locale';
type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps);
@ -21,7 +19,7 @@ const confirm = (config: ModalFuncProps) => {
let currentConfig = {
...omit(config, ['parentContext', 'appContext']),
close,
visible: true,
open: true,
} as any;
let confirmDialogInstance = null;
function destroy(...args: any[]) {
@ -33,7 +31,7 @@ const confirm = (config: ModalFuncProps) => {
}
const triggerCancel = args.some(param => param && param.triggerCancel);
if (config.onCancel && triggerCancel) {
config.onCancel(...args);
config.onCancel(() => {}, ...args.slice(1));
}
for (let i = 0; i < destroyFns.length; i++) {
const fn = destroyFns[i];
@ -47,7 +45,7 @@ const confirm = (config: ModalFuncProps) => {
function close(this: typeof close, ...args: any[]) {
currentConfig = {
...currentConfig,
visible: false,
open: false,
afterClose: () => {
if (typeof config.afterClose === 'function') {
config.afterClose();
@ -55,6 +53,10 @@ const confirm = (config: ModalFuncProps) => {
destroy.apply(this, args);
},
};
// Legacy support
if (currentConfig.visible) {
delete currentConfig.visible;
}
update(currentConfig);
}
function update(configUpdate: ConfigUpdate) {
@ -76,9 +78,18 @@ const confirm = (config: ModalFuncProps) => {
const global = globalConfigForApi;
const rootPrefixCls = global.prefixCls;
const prefixCls = p.prefixCls || `${rootPrefixCls}-modal`;
const iconPrefixCls = global.iconPrefixCls;
const runtimeLocale = getConfirmLocale();
return (
<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>
);
};
@ -101,8 +112,6 @@ export default confirm;
export function withWarn(props: ModalFuncProps): ModalFuncProps {
return {
icon: () => <ExclamationCircleOutlined />,
okCancel: false,
...props,
type: 'warning',
};
@ -110,8 +119,6 @@ export function withWarn(props: ModalFuncProps): ModalFuncProps {
export function withInfo(props: ModalFuncProps): ModalFuncProps {
return {
icon: () => <InfoCircleOutlined />,
okCancel: false,
...props,
type: 'info',
};
@ -119,8 +126,6 @@ export function withInfo(props: ModalFuncProps): ModalFuncProps {
export function withSuccess(props: ModalFuncProps): ModalFuncProps {
return {
icon: () => <CheckCircleOutlined />,
okCancel: false,
...props,
type: 'success',
};
@ -128,8 +133,6 @@ export function withSuccess(props: ModalFuncProps): ModalFuncProps {
export function withError(props: ModalFuncProps): ModalFuncProps {
return {
icon: () => <CloseCircleOutlined />,
okCancel: false,
...props,
type: 'error',
};
@ -137,8 +140,6 @@ export function withError(props: ModalFuncProps): ModalFuncProps {
export function withConfirm(props: ModalFuncProps): ModalFuncProps {
return {
icon: () => <ExclamationCircleOutlined />,
okCancel: true,
...props,
type: 'confirm',
};

View File

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

View File

@ -19,7 +19,7 @@ Basic modal.
<template>
<div>
<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>
@ -30,18 +30,18 @@ Basic modal.
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const showModal = () => {
visible.value = true;
open.value = true;
};
const handleOk = (e: MouseEvent) => {
console.log(e);
visible.value = false;
open.value = false;
};
return {
visible,
open,
showModal,
handleOk,
};

View File

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

View File

@ -23,7 +23,7 @@ You could set `footer` to `null` if you don't need default footer buttons.
<template>
<div>
<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>
<a-button key="back" @click="handleCancel">Return</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({
setup() {
const loading = ref<boolean>(false);
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const showModal = () => {
visible.value = true;
open.value = true;
};
const handleOk = () => {
loading.value = true;
setTimeout(() => {
loading.value = false;
visible.value = false;
open.value = false;
}, 2000);
};
const handleCancel = () => {
visible.value = false;
open.value = false;
};
return {
loading,
visible,
open,
showModal,
handleOk,
handleCancel,

View File

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

View File

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

View File

@ -18,12 +18,7 @@ Custom modal content render. use `vueuse` implements draggable.
<template>
<div>
<a-button type="primary" @click="showModal">Open Modal</a-button>
<a-modal
ref="modalRef"
v-model:visible="visible"
:wrap-style="{ overflow: 'hidden' }"
@ok="handleOk"
>
<a-modal ref="modalRef" v-model:open="open" :wrap-style="{ overflow: 'hidden' }" @ok="handleOk">
<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';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const modalTitleRef = ref<HTMLElement>(null);
const showModal = () => {
visible.value = true;
open.value = true;
};
const { x, y, isDragging } = useDraggable(modalTitleRef);
const handleOk = (e: MouseEvent) => {
console.log(e);
visible.value = false;
open.value = false;
};
const startX = ref<number>(0);
const startY = ref<number>(0);
@ -98,7 +93,7 @@ export default defineComponent({
};
});
return {
visible,
open,
showModal,
handleOk,
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
</a-button>
<a-modal
v-model:visible="modal1Visible"
v-model:open="modal1Visible"
title="20px to Top"
style="top: 20px"
@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
</a-button>
<a-modal
v-model:visible="modal2Visible"
v-model:open="modal2Visible"
title="Vertically centered modal dialog"
centered
@ok="modal2Visible = false"
@ -55,8 +55,8 @@ export default defineComponent({
const modal1Visible = ref<boolean>(false);
const modal2Visible = ref<boolean>(false);
const setModal1Visible = (visible: boolean) => {
modal1Visible.value = visible;
const setModal1Visible = (open: boolean) => {
modal1Visible.value = open;
};
return {
modal1Visible,

View File

@ -19,7 +19,7 @@ Use `width` to set the width of the modal dialog
<template>
<div>
<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>
@ -30,18 +30,18 @@ Use `width` to set the width of the modal dialog
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const showModal = () => {
visible.value = true;
open.value = true;
};
const handleOk = (e: MouseEvent) => {
console.log(e);
visible.value = false;
open.value = false;
};
return {
visible,
open,
showModal,
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` | |
| okType | Button `type` of the OK button | string | `primary` | |
| 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 | |
| wrapClassName | The class name of the container of the modal dialog | string | - | |
| 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 | - | |
| 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) | - | |
| 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 | - | |
| keyboard | Whether support press esc to close | boolean | true | |
| mask | Whether show mask or not. | boolean | true | |
@ -116,7 +117,7 @@ router.beforeEach((to, from, next) => {
## 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.

View File

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

View File

@ -1,5 +1,28 @@
import defaultLocale from '../locale/en_US';
export interface ModalLocale {
okText: string;
cancelText: 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 MenuComponentToken } from '../../menu/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 PopconfirmComponentToken } from '../../popconfirm/style';
// import type { ComponentToken as PopoverComponentToken } from '../../popover/style';
@ -103,7 +103,7 @@ export interface ComponentTokenMap {
// Calendar?: CalendarComponentToken;
// Steps?: StepsComponentToken;
// Menu?: MenuComponentToken;
// Modal?: ModalComponentToken;
Modal?: ModalComponentToken;
Message?: MessageComponentToken;
// Upload?: UploadComponentToken;
// Tooltip?: TooltipComponentToken;