diff --git a/components/_util/switchScrollingEffect.js b/components/_util/switchScrollingEffect.js deleted file mode 100644 index 5346c80a7e..0000000000 --- a/components/_util/switchScrollingEffect.js +++ /dev/null @@ -1,20 +0,0 @@ -import getScrollBarSize from './getScrollBarSize'; - -export default close => { - const bodyIsOverflowing = - document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) && - window.innerWidth > document.body.offsetWidth; - if (!bodyIsOverflowing) { - return; - } - if (close) { - document.body.style.position = ''; - document.body.style.width = ''; - return; - } - const scrollBarSize = getScrollBarSize(); - if (scrollBarSize) { - document.body.style.position = 'relative'; - document.body.style.width = `calc(100% - ${scrollBarSize}px)`; - } -}; diff --git a/components/_util/switchScrollingEffect.ts b/components/_util/switchScrollingEffect.ts new file mode 100644 index 0000000000..e87985bdda --- /dev/null +++ b/components/_util/switchScrollingEffect.ts @@ -0,0 +1,42 @@ +import getScrollBarSize from './getScrollBarSize'; +import setStyle from './setStyle'; + +function isBodyOverflowing() { + return ( + document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) && + window.innerWidth > document.body.offsetWidth + ); +} + +let cacheStyle = {}; + +export default (close?: boolean) => { + if (!isBodyOverflowing() && !close) { + return; + } + + // https://github.com/ant-design/ant-design/issues/19729 + const scrollingEffectClassName = 'ant-scrolling-effect'; + const scrollingEffectClassNameReg = new RegExp(`${scrollingEffectClassName}`, 'g'); + const bodyClassName = document.body.className; + + if (close) { + if (!scrollingEffectClassNameReg.test(bodyClassName)) return; + setStyle(cacheStyle); + cacheStyle = {}; + document.body.className = bodyClassName.replace(scrollingEffectClassNameReg, '').trim(); + return; + } + + const scrollBarSize = getScrollBarSize(); + if (scrollBarSize) { + cacheStyle = setStyle({ + position: 'relative', + width: `calc(100% - ${scrollBarSize}px)`, + }); + if (!scrollingEffectClassNameReg.test(bodyClassName)) { + const addClassName = `${bodyClassName} ${scrollingEffectClassName}`; + document.body.className = addClassName.trim(); + } + } +}; diff --git a/components/modal/style/confirm.less b/components/modal/style/confirm.less index 9c9ea4a154..cf5c4047b0 100644 --- a/components/modal/style/confirm.less +++ b/components/modal/style/confirm.less @@ -8,7 +8,7 @@ } .@{ant-prefix}-modal-body { - padding: 32px 32px 24px; + padding: @modal-confirm-body-padding; } &-body-wrapper { @@ -49,7 +49,7 @@ float: right; margin-top: 24px; - button + button { + .@{ant-prefix}-btn + .@{ant-prefix}-btn { margin-bottom: 0; margin-left: 8px; } diff --git a/components/modal/style/index.less b/components/modal/style/index.less index bc682e742b..5acc2608d4 100644 --- a/components/modal/style/index.less +++ b/components/modal/style/index.less @@ -2,3 +2,6 @@ @import '../../style/mixins/index'; @import './modal'; @import './confirm'; +@import './rtl'; + +.popover-customize-bg(@dialog-prefix-cls, @popover-background); diff --git a/components/modal/style/index.ts b/components/modal/style/index.tsx similarity index 100% rename from components/modal/style/index.ts rename to components/modal/style/index.tsx diff --git a/components/modal/style/modal.less b/components/modal/style/modal.less index 8e32bc6114..6fb60849ca 100644 --- a/components/modal/style/modal.less +++ b/components/modal/style/modal.less @@ -1,36 +1,26 @@ @dialog-prefix-cls: ~'@{ant-prefix}-modal'; -@table-prefix-cls: ~'@{ant-prefix}-table'; -@modal-footer-padding-vertical: 10px; -@modal-footer-padding-horizontal: 16px; .@{dialog-prefix-cls} { .reset-component(); + .modal-mask(); position: relative; top: 100px; width: auto; + max-width: calc(100vw - 32px); margin: 0 auto; padding-bottom: 24px; - pointer-events: none; &-wrap { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; z-index: @zindex-modal; - overflow: auto; - outline: 0; - -webkit-overflow-scrolling: touch; } &-title { margin: 0; color: @modal-heading-color; font-weight: 500; - font-size: @font-size-lg; - line-height: 22px; + font-size: @modal-header-title-font-size; + line-height: @modal-header-title-line-height; word-wrap: break-word; } @@ -50,7 +40,7 @@ right: 0; z-index: @zindex-popup-close; padding: 0; - color: @text-color-secondary; + color: @modal-close-color; font-weight: 700; line-height: 1; text-decoration: none; @@ -62,11 +52,11 @@ &-x { display: block; - width: 56px; - height: 56px; + width: @modal-header-close-size; + height: @modal-header-close-size; font-size: @font-size-lg; font-style: normal; - line-height: 56px; + line-height: @modal-header-close-size; text-align: center; text-transform: none; text-rendering: auto; @@ -80,10 +70,11 @@ } &-header { - padding: 16px 24px; + padding: @modal-header-padding; color: @text-color; background: @modal-header-bg; - border-bottom: @border-width-base @border-style-base @modal-header-border-color-split; + 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; } @@ -98,38 +89,16 @@ padding: @modal-footer-padding-vertical @modal-footer-padding-horizontal; text-align: right; background: @modal-footer-bg; - border-top: @border-width-base @border-style-base @modal-footer-border-color-split; + 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; - button + button { + + .@{ant-prefix}-btn + .@{ant-prefix}-btn:not(.@{ant-prefix}-dropdown-trigger) { margin-bottom: 0; margin-left: 8px; } } - &.zoom-enter, - &.zoom-appear { - transform: none; // reset scale avoid mousePosition bug - opacity: 0; - animation-duration: @animation-duration-slow; - user-select: none; // https://github.com/ant-design/ant-design/issues/11777 - } - - &-mask { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: @zindex-modal-mask; - height: 100%; - background-color: @modal-mask-bg; - filter: ~'alpha(opacity=50)'; - - &-hidden { - display: none; - } - } - &-open { overflow: hidden; } @@ -137,6 +106,7 @@ .@{dialog-prefix-cls}-centered { text-align: center; + &::before { display: inline-block; width: 0; @@ -147,6 +117,7 @@ .@{dialog-prefix-cls} { top: 0; display: inline-block; + padding-bottom: 0; text-align: left; vertical-align: middle; } diff --git a/components/modal/style/rtl.less b/components/modal/style/rtl.less new file mode 100644 index 0000000000..8a19025b6b --- /dev/null +++ b/components/modal/style/rtl.less @@ -0,0 +1,74 @@ +@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; + } + } +} diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 7008f94c7c..a036d27539 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -522,17 +522,28 @@ // Modal // -- -@modal-body-padding: 24px; +@modal-header-padding-vertical: @padding-md; +@modal-header-padding-horizontal: @padding-lg; +@modal-body-padding: @padding-lg; @modal-header-bg: @component-background; +@modal-header-padding: @modal-header-padding-vertical @modal-header-padding-horizontal; +@modal-header-border-width: @border-width-base; +@modal-header-border-style: @border-style-base; +@modal-header-title-line-height: 22px; +@modal-header-title-font-size: @font-size-lg; @modal-header-border-color-split: @border-color-split; +@modal-header-close-size: 56px; @modal-content-bg: @component-background; @modal-heading-color: @heading-color; +@modal-close-color: @text-color-secondary; @modal-footer-bg: transparent; @modal-footer-border-color-split: @border-color-split; -@modal-mask-bg: fade(@black, 45%); -@modal-close-color: @text-color-secondary; +@modal-footer-border-style: @border-style-base; @modal-footer-padding-vertical: 10px; @modal-footer-padding-horizontal: 16px; +@modal-footer-border-width: @border-width-base; +@modal-mask-bg: fade(@black, 45%); +@modal-confirm-body-padding: 32px 32px 24px; // Progress // -- diff --git a/components/vc-dialog/Content.tsx b/components/vc-dialog/Content.tsx new file mode 100644 index 0000000000..dad3a45e3c --- /dev/null +++ b/components/vc-dialog/Content.tsx @@ -0,0 +1,150 @@ +import type { CSSProperties, PropType } from 'vue'; +import { computed, ref, defineComponent } from 'vue'; +import type { MouseEventHandler } from '../_util/EventInterface'; +import Transition, { getTransitionProps } from '../_util/transition'; +import dialogPropTypes from './IDialogPropTypes'; +import { offset } from './util'; +const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' }; + +export type ContentRef = { + focus: () => void; + changeActive: (next: boolean) => void; +}; +export default defineComponent({ + name: 'Content', + props: { + ...dialogPropTypes(), + motionName: String, + ariaId: String, + onVisibleChanged: Function as PropType<(visible: boolean) => void>, + onMousedown: Function as PropType, + onMouseup: Function as PropType, + }, + setup(props, { expose, slots }) { + const sentinelStartRef = ref(); + const sentinelEndRef = ref(); + const dialogRef = ref(); + expose({ + focus: () => { + sentinelStartRef.value?.focus(); + }, + changeActive: next => { + const { activeElement } = document; + if (next && activeElement === sentinelEndRef.value) { + sentinelStartRef.value.focus(); + } else if (!next && activeElement === sentinelStartRef.value) { + sentinelEndRef.value.focus(); + } + }, + }); + const transformOrigin = ref(); + const contentStyleRef = computed(() => { + const { width, height } = props; + const contentStyle: CSSProperties = {}; + if (width !== undefined) { + contentStyle.width = typeof width === 'number' ? `${width}px` : width; + } + if (height !== undefined) { + contentStyle.height = typeof height === 'number' ? `${height}px` : height; + } + if (transformOrigin.value) { + contentStyle.transformOrigin = transformOrigin.value; + } + return contentStyle; + }); + + const onPrepare = () => { + const elementOffset = offset(dialogRef.value); + + transformOrigin.value = props.mousePosition + ? `${props.mousePosition.x - elementOffset.left}px ${ + props.mousePosition.y - elementOffset.top + }px` + : ''; + }; + const onVisibleChanged = (visible: boolean) => { + props.onVisibleChanged(visible); + }; + return () => { + const { + prefixCls, + footer = slots.footer?.(), + title = slots.title?.(), + ariaId, + closable, + closeIcon = slots.closeIcon?.(), + onClose, + bodyStyle, + bodyProps, + onMousedown, + onMouseup, + visible, + modalRender = slots.modalRender, + destroyOnClose, + motionName, + } = props; + let footerNode: any; + if (footer) { + footerNode =
{footer}
; + } + + let headerNode: any; + if (title) { + headerNode = ( +
+
+ {title} +
+
+ ); + } + + let closer: any; + if (closable) { + closer = ( + + ); + } + + const content = ( +
+ {closer} + {headerNode} +
+ {slots.default?.()} +
+ {footerNode} +
+ ); + const transitionProps = getTransitionProps(motionName); + return ( + onVisibleChanged(true)} + onAfterLeave={() => onVisibleChanged(false)} + > + {visible || !destroyOnClose ? ( +
+