diff --git a/components/_util/Portal.jsx b/components/_util/Portal.jsx deleted file mode 100644 index 782b3cf9e2..0000000000 --- a/components/_util/Portal.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from './vue-types'; -import { - defineComponent, - nextTick, - onBeforeUnmount, - onMounted, - onUpdated, - ref, - Teleport, -} from 'vue'; - -export default defineComponent({ - name: 'Portal', - inheritAttrs: false, - props: { - getContainer: PropTypes.func.isRequired, - didUpdate: PropTypes.func, - }, - setup(props, { slots }) { - const container = ref(); - onMounted(() => { - container.value = props.getContainer(); - }); - onUpdated(() => { - nextTick(() => { - props.nextTick?.(props); - }); - }); - onBeforeUnmount(() => { - if (container.value && container.value.parentNode) { - container.value.parentNode.removeChild(container.value); - } - }); - return () => { - return container.value ? {slots.default?.()} : null; - }; - }, -}); diff --git a/components/_util/Portal.tsx b/components/_util/Portal.tsx new file mode 100644 index 0000000000..c856175b96 --- /dev/null +++ b/components/_util/Portal.tsx @@ -0,0 +1,29 @@ +import PropTypes from './vue-types'; +import { defineComponent, nextTick, onBeforeUnmount, onUpdated, Teleport } from 'vue'; + +export default defineComponent({ + name: 'Portal', + inheritAttrs: false, + props: { + getContainer: PropTypes.func.isRequired, + didUpdate: PropTypes.func, + }, + setup(props, { slots }) { + // getContainer 不会改变,不用响应式 + const container = props.getContainer(); + + onUpdated(() => { + nextTick(() => { + props.didUpdate?.(props); + }); + }); + onBeforeUnmount(() => { + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + }); + return () => { + return container ? : null; + }; + }, +}); diff --git a/components/_util/PortalWrapper.jsx b/components/_util/PortalWrapper.jsx deleted file mode 100644 index 7cf3590713..0000000000 --- a/components/_util/PortalWrapper.jsx +++ /dev/null @@ -1,150 +0,0 @@ -import PropTypes from './vue-types'; -import switchScrollingEffect from './switchScrollingEffect'; -import setStyle from './setStyle'; -import Portal from './Portal'; -import { defineComponent } from 'vue'; - -let openCount = 0; -const windowIsUndefined = !( - typeof window !== 'undefined' && - window.document && - window.document.createElement -); -// https://github.com/ant-design/ant-design/issues/19340 -// https://github.com/ant-design/ant-design/issues/19332 -let cacheOverflow = {}; - -export default defineComponent({ - name: 'PortalWrapper', - props: { - wrapperClassName: PropTypes.string, - forceRender: PropTypes.looseBool, - getContainer: PropTypes.any, - visible: PropTypes.looseBool, - }, - data() { - this._component = null; - const { visible } = this.$props; - openCount = visible ? openCount + 1 : openCount; - return {}; - }, - watch: { - visible(val) { - openCount = val ? openCount + 1 : openCount - 1; - }, - getContainer(getContainer, prevGetContainer) { - const getContainerIsFunc = - typeof getContainer === 'function' && typeof prevGetContainer === 'function'; - if ( - getContainerIsFunc - ? getContainer.toString() !== prevGetContainer.toString() - : getContainer !== prevGetContainer - ) { - this.removeCurrentContainer(false); - } - }, - }, - updated() { - this.setWrapperClassName(); - }, - beforeUnmount() { - const { visible } = this.$props; - // 离开时不会 render, 导到离开时数值不变,改用 func 。。 - openCount = visible && openCount ? openCount - 1 : openCount; - this.removeCurrentContainer(visible); - }, - methods: { - getParent() { - const { getContainer } = this.$props; - if (getContainer) { - if (typeof getContainer === 'string') { - return document.querySelectorAll(getContainer)[0]; - } - if (typeof getContainer === 'function') { - return getContainer(); - } - if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) { - return getContainer; - } - } - return document.body; - }, - - getDomContainer() { - if (windowIsUndefined) { - return null; - } - if (!this.container) { - this.container = document.createElement('div'); - const parent = this.getParent(); - if (parent) { - parent.appendChild(this.container); - } - } - this.setWrapperClassName(); - return this.container; - }, - - setWrapperClassName() { - const { wrapperClassName } = this.$props; - if (this.container && wrapperClassName && wrapperClassName !== this.container.className) { - this.container.className = wrapperClassName; - } - }, - - savePortal(c) { - // Warning: don't rename _component - // https://github.com/react-component/util/pull/65#discussion_r352407916 - this._component = c; - }, - - removeCurrentContainer() { - this.container = null; - this._component = null; - }, - - /** - * Enhance ./switchScrollingEffect - * 1. Simulate document body scroll bar with - * 2. Record body has overflow style and recover when all of PortalWrapper invisible - * 3. Disable body scroll when PortalWrapper has open - * - * @memberof PortalWrapper - */ - switchScrollingEffect() { - if (openCount === 1 && !Object.keys(cacheOverflow).length) { - switchScrollingEffect(); - // Must be set after switchScrollingEffect - cacheOverflow = setStyle({ - overflow: 'hidden', - overflowX: 'hidden', - overflowY: 'hidden', - }); - } else if (!openCount) { - setStyle(cacheOverflow); - cacheOverflow = {}; - switchScrollingEffect(true); - } - }, - }, - - render() { - const { forceRender, visible } = this.$props; - let portal = null; - const childProps = { - getOpenCount: () => openCount, - getContainer: this.getDomContainer, - switchScrollingEffect: this.switchScrollingEffect, - }; - if (forceRender || visible || this._component) { - portal = ( - this.$slots.default?.(childProps) }} - > - ); - } - return portal; - }, -}); diff --git a/components/_util/PortalWrapper.tsx b/components/_util/PortalWrapper.tsx new file mode 100644 index 0000000000..cd7c8f24fe --- /dev/null +++ b/components/_util/PortalWrapper.tsx @@ -0,0 +1,217 @@ +import PropTypes from './vue-types'; +import switchScrollingEffect from './switchScrollingEffect'; +import setStyle from './setStyle'; +import Portal from './Portal'; +import { + defineComponent, + ref, + watch, + onMounted, + onBeforeUnmount, + onUpdated, + getCurrentInstance, +} from 'vue'; +import canUseDom from './canUseDom'; +import ScrollLocker from '../vc-util/Dom/scrollLocker'; +import wrapperRaf from './raf'; +import { nextTick } from 'process'; + +let openCount = 0; +const supportDom = canUseDom(); + +/** @private Test usage only */ +export function getOpenCount() { + return process.env.NODE_ENV === 'test' ? openCount : 0; +} + +// https://github.com/ant-design/ant-design/issues/19340 +// https://github.com/ant-design/ant-design/issues/19332 +let cacheOverflow = {}; + +const getParent = (getContainer: GetContainer) => { + if (!supportDom) { + return null; + } + if (getContainer) { + if (typeof getContainer === 'string') { + return document.querySelectorAll(getContainer)[0]; + } + if (typeof getContainer === 'function') { + return getContainer(); + } + if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) { + return getContainer; + } + } + return document.body; +}; + +export type GetContainer = string | HTMLElement | (() => HTMLElement); + +export default defineComponent({ + name: 'PortalWrapper', + inheritAttrs: false, + props: { + wrapperClassName: PropTypes.string, + forceRender: PropTypes.looseBool, + getContainer: PropTypes.any, + visible: PropTypes.looseBool, + }, + + setup(props, { slots }) { + const container = ref(); + const componentRef = ref(); + const rafId = ref(); + const scrollLocker = new ScrollLocker({ + container: getParent(props.getContainer) as HTMLElement, + }); + + const removeCurrentContainer = () => { + // Portal will remove from `parentNode`. + // Let's handle this again to avoid refactor issue. + container.value?.parentNode?.removeChild(container.value); + }; + const attachToParent = (force = false) => { + if (force || (container.value && !container.value.parentNode)) { + const parent = getParent(props.getContainer); + if (parent) { + parent.appendChild(container.value); + return true; + } + + return false; + } + + return true; + }; + // attachToParent(); + + const getContainer = () => { + if (!supportDom) { + return null; + } + if (!container.value) { + container.value = document.createElement('div'); + attachToParent(true); + } + setWrapperClassName(); + return container.value; + }; + const setWrapperClassName = () => { + const { wrapperClassName } = props; + if (container.value && wrapperClassName && wrapperClassName !== container.value.className) { + container.value.className = wrapperClassName; + } + }; + onUpdated(() => { + setWrapperClassName(); + attachToParent(); + }); + /** + * Enhance ./switchScrollingEffect + * 1. Simulate document body scroll bar with + * 2. Record body has overflow style and recover when all of PortalWrapper invisible + * 3. Disable body scroll when PortalWrapper has open + * + * @memberof PortalWrapper + */ + const switchScrolling = () => { + if (openCount === 1 && !Object.keys(cacheOverflow).length) { + switchScrollingEffect(); + // Must be set after switchScrollingEffect + cacheOverflow = setStyle({ + overflow: 'hidden', + overflowX: 'hidden', + overflowY: 'hidden', + }); + } else if (!openCount) { + setStyle(cacheOverflow); + cacheOverflow = {}; + switchScrollingEffect(true); + } + }; + const instance = getCurrentInstance(); + onMounted(() => { + let init = false; + watch( + [() => props.visible, () => props.getContainer], + ([visible, getContainer], [prevVisible, prevGetContainer]) => { + // Update count + if (supportDom && getParent(props.getContainer) === document.body) { + if (visible && !prevVisible) { + openCount += 1; + } else if (init) { + openCount -= 1; + } + } + + if (init) { + // Clean up container if needed + const getContainerIsFunc = + typeof getContainer === 'function' && typeof prevGetContainer === 'function'; + if ( + getContainerIsFunc + ? getContainer.toString() !== prevGetContainer.toString() + : getContainer !== prevGetContainer + ) { + removeCurrentContainer(); + } + // updateScrollLocker + if ( + visible && + visible !== prevVisible && + supportDom && + getParent(getContainer) !== scrollLocker.getContainer() + ) { + scrollLocker.reLock({ + container: getParent(getContainer) as HTMLElement, + }); + } + } + init = true; + }, + { immediate: true, flush: 'post' }, + ); + + nextTick(() => { + if (!attachToParent()) { + rafId.value = wrapperRaf(() => { + instance.update(); + }); + } + }); + }); + + onBeforeUnmount(() => { + const { visible, getContainer } = props; + if (supportDom && getParent(getContainer) === document.body) { + // 离开时不会 render, 导到离开时数值不变,改用 func 。。 + openCount = visible && openCount ? openCount - 1 : openCount; + } + removeCurrentContainer(); + wrapperRaf.cancel(rafId.value); + }); + + return () => { + const { forceRender, visible } = props; + let portal = null; + const childProps = { + getOpenCount: () => openCount, + getContainer, + switchScrollingEffect: switchScrolling, + scrollLocker, + }; + + if (forceRender || visible || componentRef.value) { + portal = ( + slots.default?.(childProps) }} + > + ); + } + return portal; + }; + }, +}); diff --git a/components/_util/__mocks__/Portal.tsx b/components/_util/__mocks__/Portal.tsx new file mode 100644 index 0000000000..0043608ff4 --- /dev/null +++ b/components/_util/__mocks__/Portal.tsx @@ -0,0 +1,12 @@ +import { defineComponent } from 'vue'; + +export default defineComponent({ + name: 'Portal', + inheritAttrs: false, + props: ['getContainer'], + setup(_props, { slots }) { + return () => { + return slots.default?.(); + }; + }, +}); diff --git a/components/date-picker/__tests__/RangePicker.test.js b/components/date-picker/__tests__/RangePicker.test.js index 85ee3b6beb..218489ceaf 100644 --- a/components/date-picker/__tests__/RangePicker.test.js +++ b/components/date-picker/__tests__/RangePicker.test.js @@ -4,7 +4,7 @@ import dayjs from 'dayjs'; import DatePicker from '../'; import { openPicker, selectCell, closePicker } from './utils'; import focusTest from '../../../tests/shared/focusTest'; - +jest.mock('../../_util/Portal'); const { RangePicker } = DatePicker; describe('RangePicker', () => { diff --git a/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap b/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap index 9b3c319435..abdff27cb9 100644 --- a/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap +++ b/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap @@ -13,211 +13,207 @@ exports[`RangePicker customize separator 1`] = ` exports[`RangePicker show month panel according to value 1`] = `
- - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SuMoTuWeThFrSa
-
26
-
-
27
-
-
28
-
-
29
-
-
30
-
-
31
-
-
1
-
-
2
-
-
3
-
-
4
-
-
5
-
-
6
-
-
7
-
-
8
-
-
9
-
-
10
-
-
11
-
-
12
-
-
13
-
-
14
-
-
15
-
-
16
-
-
17
-
-
18
-
-
19
-
-
20
-
-
21
-
-
22
-
-
23
-
-
24
-
-
25
-
-
26
-
-
27
-
-
28
-
-
29
-
-
30
-
-
31
-
-
1
-
-
2
-
-
3
-
-
4
-
-
5
-
-
-
-
-
- - -
2000/01/01
- - -
-
-
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SuMoTuWeThFrSa
+
26
+
+
27
+
+
28
+
+
29
+
+
30
+
+
31
+
+
1
+
+
2
+
+
3
+
+
4
+
+
5
+
+
6
+
+
7
+
+
8
+
+
9
+
+
10
+
+
11
+
+
12
+
+
13
+
+
14
+
+
15
+
+
16
+
+
17
+
+
18
+
+
19
+
+
20
+
+
21
+
+
22
+
+
23
+
+
24
+
+
25
+
+
26
+
+
27
+
+
28
+
+
29
+
+
30
+
+
31
+
+
1
+
+
2
+
+
3
+
+
4
+
+
5
+
- +
+
+
+ + +
2000/01/01
+ + +
+
- +
+
-
+
+
+
+
+
+
+
`; diff --git a/components/drawer/__tests__/Drawer.test.js b/components/drawer/__tests__/Drawer.test.js index 9081fd9bde..107fef83a9 100644 --- a/components/drawer/__tests__/Drawer.test.js +++ b/components/drawer/__tests__/Drawer.test.js @@ -16,7 +16,7 @@ const DrawerCom = { type: Boolean, default: false, }, - wrapClassName: { + class: { type: String, default: '', }, @@ -106,6 +106,7 @@ describe('Drawer', () => { destroyOnClose: true, closable: false, getContainer: false, + visible: true, }, slots: { default: () => 'Here is content of Drawer', @@ -121,7 +122,8 @@ describe('Drawer', () => { it('class is test_drawer', async () => { const props = { props: { - wrapClassName: 'test_drawer', + class: 'test_drawer', + visible: true, }, sync: false, }; diff --git a/components/drawer/__tests__/MultiDrawer.test.js b/components/drawer/__tests__/MultiDrawer.test.js index 6380569d72..44d921c402 100644 --- a/components/drawer/__tests__/MultiDrawer.test.js +++ b/components/drawer/__tests__/MultiDrawer.test.js @@ -38,14 +38,14 @@ const MultiDrawer = { width: 520, visible: this.visible, getContainer: false, - wrapClassName: 'test_drawer', + class: 'test_drawer', placement: this.placement, onClose: this.onClose, }; const childrenDrawerProps = { title: 'Two-level Drawer', width: 320, - wrapClassName: 'Two-level', + class: 'Two-level', visible: this.childrenDrawer, getContainer: false, placement: this.placement, @@ -112,7 +112,7 @@ describe('Drawer', () => { wrapper.find('#open_two_drawer').trigger('click'); }, 0); await asyncExpect(() => { - const translateX = wrapper.find('.ant-drawer.test_drawer').element.style.transform; + const translateX = wrapper.find('.test_drawer').element.style.transform; expect(translateX).toEqual('translateX(-180px)'); expect(wrapper.find('#two_drawer_text').exists()).toBe(true); }, 1000); @@ -133,7 +133,7 @@ describe('Drawer', () => { wrapper.find('#open_two_drawer').trigger('click'); }, 0); await asyncExpect(() => { - const translateX = wrapper.find('.ant-drawer.test_drawer').element.style.transform; + const translateX = wrapper.find('.test_drawer').element.style.transform; expect(translateX).toEqual('translateX(180px)'); expect(wrapper.find('#two_drawer_text').exists()).toBe(true); }, 1000); @@ -153,7 +153,7 @@ describe('Drawer', () => { wrapper.find('#open_two_drawer').trigger('click'); }, 0); await asyncExpect(() => { - const translateY = wrapper.find('.ant-drawer.test_drawer').element.style.transform; + const translateY = wrapper.find('.test_drawer').element.style.transform; expect(translateY).toEqual('translateY(180px)'); expect(wrapper.find('#two_drawer_text').exists()).toBe(true); }, 1000); diff --git a/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap b/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap index be9a172016..2ff76db386 100644 --- a/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap @@ -2,15 +2,19 @@ exports[`Drawer class is test_drawer 1`] = `
-
+
-
+
-
- +
+
+ +
+
Here is content of Drawer
+
@@ -21,13 +25,14 @@ exports[`Drawer class is test_drawer 1`] = ` exports[`Drawer closable is false 1`] = `
-
+
-
+
Here is content of Drawer
+
@@ -38,13 +43,14 @@ exports[`Drawer closable is false 1`] = ` exports[`Drawer destroyOnClose is true 1`] = `
-
+
-
+
-
+
Here is content of Drawer
+
@@ -55,15 +61,19 @@ exports[`Drawer destroyOnClose is true 1`] = ` exports[`Drawer have a title 1`] = `
-
+
-
+
-
Test Title
+
+
Test Title
+
+
Here is content of Drawer
+
@@ -74,15 +84,19 @@ exports[`Drawer have a title 1`] = ` exports[`Drawer render correctly 1`] = `
-
+
-
- +
+
+ +
+
Here is content of Drawer
+
@@ -93,15 +107,19 @@ exports[`Drawer render correctly 1`] = ` exports[`Drawer render top drawer 1`] = `
-
+
-
- +
+
+ +
+
Here is content of Drawer
+
diff --git a/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap b/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap index 2f0490811c..fda0dc75d3 100644 --- a/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap @@ -5,15 +5,19 @@ exports[`Drawer render correctly 1`] = ` open
-
+
-
+
-
- +
+
+ +
+
Here is content of Drawer
+
diff --git a/components/drawer/__tests__/__snapshots__/demo.test.js.snap b/components/drawer/__tests__/__snapshots__/demo.test.js.snap index 125aa94514..5c0146173c 100644 --- a/components/drawer/__tests__/__snapshots__/demo.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/demo.test.js.snap @@ -7,6 +7,13 @@ exports[`renders ./components/drawer/demo/basic.vue correctly 1`] = ` `; +exports[`renders ./components/drawer/demo/extra.vue correctly 1`] = ` +
+ +`; + exports[`renders ./components/drawer/demo/form-in-drawer.vue correctly 1`] = `
-
+
-
+
-
Basic Drawer
+
+ +
Basic Drawer
+

Some contents...

+
@@ -54,6 +65,15 @@ exports[`renders ./components/drawer/demo/render-in-current.vue correctly 1`] =
`; +exports[`renders ./components/drawer/demo/size.vue correctly 1`] = ` + + +`; + exports[`renders ./components/drawer/demo/user-profile.vue correctly 1`] = `
diff --git a/components/drawer/demo/basic.vue b/components/drawer/demo/basic.vue index c446dfd4e4..998684be93 100644 --- a/components/drawer/demo/basic.vue +++ b/components/drawer/demo/basic.vue @@ -20,10 +20,11 @@ Basic drawer. Open

Some contents...

Some contents...

diff --git a/components/drawer/demo/extra.vue b/components/drawer/demo/extra.vue new file mode 100644 index 0000000000..e940d5310e --- /dev/null +++ b/components/drawer/demo/extra.vue @@ -0,0 +1,65 @@ + +--- +order: 2 +title: + zh-CN: 额外操作 + en-US: Extra Actions +--- + +## zh-CN + +在 Ant Design 规范中,操作按钮建议放在抽屉的右上角,可以使用 extra 属性来实现。 + +## en-US + +Extra actions should be placed at corner of drawer in Ant Design, you can using `extra` prop for that. + + + + + diff --git a/components/drawer/demo/form-in-drawer.vue b/components/drawer/demo/form-in-drawer.vue index 482078cdb2..6d92bd650a 100644 --- a/components/drawer/demo/form-in-drawer.vue +++ b/components/drawer/demo/form-in-drawer.vue @@ -1,6 +1,6 @@ --- -order: 3 +order: 4 title: zh-CN: 抽屉表单 en-US: Submit form in drawer @@ -26,6 +26,7 @@ Use form in drawer with submit button. :width="720" :visible="visible" :body-style="{ paddingBottom: '80px' }" + :footer-style="{ textAlign: 'right' }" @close="onClose" > @@ -96,22 +97,10 @@ Use form in drawer with submit button. -
+ diff --git a/components/drawer/demo/user-profile.vue b/components/drawer/demo/user-profile.vue index bbe0817ec1..4de4e4d110 100644 --- a/components/drawer/demo/user-profile.vue +++ b/components/drawer/demo/user-profile.vue @@ -1,6 +1,6 @@ --- -order: 4 +order: 6 title: zh-CN: 信息预览抽屉 en-US: Preview drawer diff --git a/components/drawer/index.en-US.md b/components/drawer/index.en-US.md index 30bbe553a1..acd01e060e 100644 --- a/components/drawer/index.en-US.md +++ b/components/drawer/index.en-US.md @@ -15,34 +15,41 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr - Processing subtasks. When subtasks are too heavy for a Popover and we still want to keep the subtasks in the context of the main task, Drawer comes very handy. - When the same Form is needed in multiple places. - ## API -| Property | Description | Type | Default | Version | +| Props | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| closable | Whether a close (x) button is visible on top right of the Drawer dialog or not. | boolean | true | | -| destroyOnClose | Whether to unmount child components on closing drawer or not. | boolean | false | | -| getContainer | Return the mounted node for Drawer. | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | | -| mask | Whether to show mask or not. | Boolean | true | | -| maskClosable | Clicking on the mask (area outside the Drawer) to close the Drawer or not. | boolean | true | | -| maskStyle | Style for Drawer's mask element. | object | {} | | -| title | The title for Drawer. | string\|slot | - | | -| visible(v-model) | Whether the Drawer dialog is visible or not. | boolean | false | | -| wrapClassName | The class name of the container of the Drawer dialog. | string | - | | -| wrapStyle | Style of wrapper element which **contains mask** compare to `drawerStyle` | object | - | | +| autoFocus | Whether Drawer should get focused after open | boolean | true | 3.0.0 | +| bodyStyle | Style of the drawer content part | CSSProperties | - | | +| class | The class name of the container of the Drawer dialog | string | - | | +| closable | Whether a close (x) button is visible on top right of the Drawer dialog or not | boolean | true | | +| closeIcon | Custom close icon | VNode \| slot | | 3.0.0 | +| contentWrapperStyle | Style of the drawer wrapper of content part | CSSProperties | 3.0.0 | +| destroyOnClose | Whether to unmount child components on closing drawer or not | boolean | false | | | drawerStyle | Style of the popup layer element | object | - | | -| headerStyle | Style of the drawer header part | object | - | | -| bodyStyle | Style of the drawer content part | object | - | | -| width | Width of the Drawer dialog. | string\|number | 256 | | -| height | placement is `top` or `bottom`, height of the Drawer dialog. | string\|number | - | | -| zIndex | The `z-index` of the Drawer. | Number | 1000 | | -| placement | The placement of the Drawer. | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | -| handle | After setting, the drawer is directly mounted on the DOM, and you can control the drawer to open or close through this `handle`. | VNode \| slot | - | | -| afterVisibleChange | Callback after the animation ends when switching drawers. | function(visible) | - | | -| keyboard | Whether support press esc to close | Boolean | true | | +| extra | Extra actions area at corner | VNode \| slot | - | 3.0.0 | +| footer | The footer for Drawer | VNode \| slot | - | 3.0.0 | +| footerStyle | Style of the drawer footer part | CSSProperties | - | 3.0.0 | +| forceRender | Prerender Drawer component forcely | boolean | false | 3.0.0 | +| getContainer | Return the mounted node for Drawer | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | | +| headerStyle | Style of the drawer header part | CSSProperties | - | 3.0.0 | +| height | Placement is `top` or `bottom`, height of the Drawer dialog | string \| number | 378 | | +| keyboard | Whether support press esc to close | boolean | true | | +| mask | Whether to show mask or not | Boolean | true | | +| maskClosable | Clicking on the mask (area outside the Drawer) to close the Drawer or not | boolean | true | | +| maskStyle | Style for Drawer's mask element | CSSProperties | {} | | +| placement | The placement of the Drawer | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | +| push | Nested drawers push behavior | boolean \| {distance: string \| number} | { distance: 180} | 3.0.0 | +| size | presetted size of drawer, default `378px` and large `736px` | `default` \| `large` | `default` | 3.0.0 | +| style | Style of wrapper element which contains mask compare to drawerStyle | CSSProperties | - | | +| title | The title for Drawer | string \| slot | - | | +| visible(v-model) | Whether the Drawer dialog is visible or not | boolean | - | | +| width | Width of the Drawer dialog | string \| number | 378 | | +| zIndex | The `z-index` of the Drawer | Number | 1000 | | ## Methods | Name | Description | Type | Default | Version | | --- | --- | --- | --- | --- | +| afterVisibleChange | Callback after the animation ends when switching drawers. | function(visible) | - | | | close | Specify a callback that will be called when a user clicks mask, close button or Cancel button. | function(e) | - | | diff --git a/components/drawer/index.tsx b/components/drawer/index.tsx index c401f96936..7ade529059 100644 --- a/components/drawer/index.tsx +++ b/components/drawer/index.tsx @@ -1,189 +1,280 @@ -import type { CSSProperties } from 'vue'; -import { inject, provide, nextTick, defineComponent } from 'vue'; +import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; +import { + inject, + nextTick, + defineComponent, + ref, + onMounted, + provide, + onUnmounted, + watch, + computed, +} from 'vue'; +import { getPropsSlot, initDefaultProps } from '../_util/props-util'; import classnames from '../_util/classNames'; -import VcDrawer from '../vc-drawer/src'; +import VcDrawer from '../vc-drawer'; import PropTypes from '../_util/vue-types'; -import BaseMixin from '../_util/BaseMixin'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; -import { getComponent, getOptionProps } from '../_util/props-util'; -import { defaultConfigProvider } from '../config-provider'; +import useConfigInject from '../_util/hooks/useConfigInject'; import { tuple, withInstall } from '../_util/type'; import omit from '../_util/omit'; +import devWarning from '../vc-util/devWarning'; const PlacementTypes = tuple('top', 'right', 'bottom', 'left'); -type placementType = typeof PlacementTypes[number]; +export type placementType = typeof PlacementTypes[number]; + +const SizeTypes = tuple('default', 'large'); +export type sizeType = typeof SizeTypes[number]; + +export interface PushState { + distance: string | number; +} + +const defaultPushState: PushState = { distance: 180 }; + +const drawerProps = () => ({ + autofocus: PropTypes.looseBool, + closable: PropTypes.looseBool, + closeIcon: PropTypes.VNodeChild, + destroyOnClose: PropTypes.looseBool, + forceRender: PropTypes.looseBool, + getContainer: PropTypes.any, + maskClosable: PropTypes.looseBool, + mask: PropTypes.looseBool, + maskStyle: PropTypes.object, + /** @deprecated Use `style` instead */ + wrapStyle: PropTypes.style, + style: PropTypes.style, + class: PropTypes.any, + /** @deprecated Use `class` instead */ + wrapClassName: PropTypes.string, + size: { + type: String as PropType, + }, + drawerStyle: PropTypes.object, + headerStyle: PropTypes.object, + bodyStyle: PropTypes.object, + contentWrapperStyle: PropTypes.object, + title: PropTypes.VNodeChild, + visible: PropTypes.looseBool.isRequired, + width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + zIndex: PropTypes.number, + prefixCls: PropTypes.string, + push: PropTypes.oneOfType([PropTypes.looseBool, { type: Object as PropType }]), + placement: PropTypes.oneOf(PlacementTypes), + keyboard: PropTypes.looseBool, + extra: PropTypes.VNodeChild, + footer: PropTypes.VNodeChild, + footerStyle: PropTypes.object, + level: PropTypes.any, + levelMove: PropTypes.any, + handle: PropTypes.VNodeChild, + /** @deprecated Use `@afterVisibleChange` instead */ + afterVisibleChange: PropTypes.func, +}); + +export type DrawerProps = Partial>; + const Drawer = defineComponent({ name: 'ADrawer', - mixins: [BaseMixin], inheritAttrs: false, - props: { - closable: PropTypes.looseBool.def(true), - destroyOnClose: PropTypes.looseBool, - getContainer: PropTypes.any, - maskClosable: PropTypes.looseBool.def(true), - mask: PropTypes.looseBool.def(true), - maskStyle: PropTypes.object, - wrapStyle: PropTypes.object, - bodyStyle: PropTypes.object, - headerStyle: PropTypes.object, - drawerStyle: PropTypes.object, - title: PropTypes.VNodeChild, - visible: PropTypes.looseBool, - width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256), - height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256), - zIndex: PropTypes.number, - prefixCls: PropTypes.string, - placement: PropTypes.oneOf(PlacementTypes).def('right'), - level: PropTypes.any.def(null), - wrapClassName: PropTypes.string, // not use class like react, vue will add class to root dom - handle: PropTypes.VNodeChild, - afterVisibleChange: PropTypes.func, - keyboard: PropTypes.looseBool.def(true), - onClose: PropTypes.func, - 'onUpdate:visible': PropTypes.func, - }, - setup(props) { - const configProvider = inject('configProvider', defaultConfigProvider); - return { - configProvider, - destroyClose: false, - preVisible: props.visible, - parentDrawer: inject('parentDrawer', null), + props: initDefaultProps(drawerProps(), { + closable: true, + placement: 'right' as placementType, + maskClosable: true, + mask: true, + level: null, + keyboard: true, + push: defaultPushState, + }), + slots: ['closeIcon', 'title', 'extra', 'footer', 'handle'], + emits: ['update:visible', 'close', 'afterVisibleChange'], + setup(props, { emit, slots, attrs }) { + const sPush = ref(false); + const destroyClose = ref(false); + const vcDrawer = ref(null); + const parentDrawerOpts = inject('parentDrawerOpts', null); + const { prefixCls } = useConfigInject('drawer', props); + devWarning( + !props.afterVisibleChange, + 'Drawer', + '`afterVisibleChange` prop is deprecated, please use `@afterVisibleChange` event instead', + ); + devWarning( + props.wrapStyle === undefined, + 'Drawer', + '`wrapStyle` prop is deprecated, please use `style` instead', + ); + devWarning( + props.wrapClassName === undefined, + 'Drawer', + '`wrapClassName` prop is deprecated, please use `class` instead', + ); + const setPush = () => { + sPush.value = true; }; - }, - data() { - return { - sPush: false, + + const setPull = () => { + sPush.value = false; + nextTick(() => { + domFocus(); + }); }; - }, - beforeCreate() { - provide('parentDrawer', this); - }, - mounted() { - // fix: delete drawer in child and re-render, no push started. - // {show && } - const { visible } = this; - if (visible && this.parentDrawer) { - this.parentDrawer.push(); - } - }, - updated() { - nextTick(() => { - if (this.preVisible !== this.visible && this.parentDrawer) { - if (this.visible) { - this.parentDrawer.push(); - } else { - this.parentDrawer.pull(); - } + provide('parentDrawerOpts', { + setPush, + setPull, + }); + + onMounted(() => { + const { visible } = props; + if (visible && parentDrawerOpts) { + parentDrawerOpts.setPush(); } - this.preVisible = this.visible; }); - }, - beforeUnmount() { - // unmount drawer in child, clear push. - if (this.parentDrawer) { - this.parentDrawer.pull(); - } - }, - methods: { - domFocus() { - if (this.$refs.vcDrawer) { - (this.$refs.vcDrawer as any).domFocus(); + + onUnmounted(() => { + if (parentDrawerOpts) { + parentDrawerOpts.setPull(); } - }, - close(e: Event) { - this.$emit('update:visible', false); - this.$emit('close', e); - }, - // onMaskClick(e) { - // if (!this.maskClosable) { - // return; - // } - // this.close(e); - // }, - push() { - this.setState({ - sPush: true, - }); - }, - pull() { - this.setState( - { - sPush: false, - }, - () => { - this.domFocus(); - }, - ); - }, - onDestroyTransitionEnd() { - const isDestroyOnClose = this.getDestroyOnClose(); + }); + + watch( + () => props.visible, + visible => { + if (parentDrawerOpts) { + if (visible) { + parentDrawerOpts.setPush(); + } else { + parentDrawerOpts.setPull(); + } + } + }, + { flush: 'post' }, + ); + + const domFocus = () => { + vcDrawer.value?.domFocus?.(); + }; + + const close = (e: Event) => { + emit('update:visible', false); + emit('close', e); + }; + + const afterVisibleChange = (visible: boolean) => { + props.afterVisibleChange?.(visible); + emit('afterVisibleChange', visible); + }; + const destroyOnClose = computed(() => props.destroyOnClose && !props.visible); + const onDestroyTransitionEnd = () => { + const isDestroyOnClose = destroyOnClose.value; if (!isDestroyOnClose) { return; } - if (!this.visible) { - this.destroyClose = true; - (this as any).$forceUpdate(); + if (!props.visible) { + destroyClose.value = true; + } + }; + + const pushTransform = computed(() => { + const { push, placement } = props; + let distance: number | string; + if (typeof push === 'boolean') { + distance = push ? defaultPushState.distance : 0; + } else { + distance = push!.distance; } - }, - getDestroyOnClose() { - return this.destroyOnClose && !this.visible; - }, - // get drawar push width or height - getPushTransform(placement?: placementType) { + distance = parseFloat(String(distance || 0)); + if (placement === 'left' || placement === 'right') { - return `translateX(${placement === 'left' ? 180 : -180}px)`; + return `translateX(${placement === 'left' ? distance : -distance}px)`; } if (placement === 'top' || placement === 'bottom') { - return `translateY(${placement === 'top' ? 180 : -180}px)`; + return `translateY(${placement === 'top' ? distance : -distance}px)`; + } + return null; + }); + + const offsetStyle = computed(() => { + // https://github.com/ant-design/ant-design/issues/24287 + const { visible, mask, placement, size, width, height } = props; + if (!visible && !mask) { + return {}; } - }, - getRcDrawerStyle() { - const { zIndex, placement, wrapStyle } = this.$props; - const { sPush: push } = this.$data; + const val: CSSProperties = {}; + if (placement === 'left' || placement === 'right') { + const defaultWidth = size === 'large' ? 736 : 378; + val.width = typeof width === 'undefined' ? defaultWidth : width; + val.width = typeof val.width === 'string' ? val.width : `${val.width}px`; + } else { + const defaultHeight = size === 'large' ? 736 : 378; + val.height = typeof height === 'undefined' ? defaultHeight : height; + val.height = typeof val.height === 'string' ? val.height : `${val.height}px`; + } + return val; + }); + + const drawerStyle = computed(() => { + const { zIndex, wrapStyle, mask, style } = props; + const val = mask ? {} : offsetStyle.value; return { zIndex, - transform: push ? this.getPushTransform(placement) : undefined, + transform: sPush.value ? pushTransform.value : undefined, + ...val, ...wrapStyle, + ...style, }; - }, - renderHeader(prefixCls: string) { - const { closable, headerStyle } = this.$props; - const title = getComponent(this, 'title'); + }); + + const renderHeader = (prefixCls: string) => { + const { closable, headerStyle } = props; + const extra = getPropsSlot(slots, props, 'extra'); + const title = getPropsSlot(slots, props, 'title'); if (!title && !closable) { return null; } - const headerClassName = title ? `${prefixCls}-header` : `${prefixCls}-header-no-title`; return ( -
- {title &&
{title}
} - {closable ? this.renderCloseIcon(prefixCls) : null} +
+
+ {renderCloseIcon(prefixCls)} + {title &&
{title}
} +
+ {extra &&
{extra}
}
); - }, - renderCloseIcon(prefixCls: string) { - const { closable } = this; + }; + + const renderCloseIcon = (prefixCls: string) => { + const { closable } = props; + const $closeIcon = props.closeIcon ? slots.closeIcon?.() : props.closeIcon; return ( closable && ( - ) ); - }, - // render drawer body dom - renderBody(prefixCls: string) { - if (this.destroyClose && !this.visible) { + }; + + const renderBody = (prefixCls: string) => { + if (destroyClose.value && !props.visible) { return null; } - this.destroyClose = false; - const { bodyStyle, drawerStyle } = this.$props; + destroyClose.value = false; + + const { bodyStyle, drawerStyle } = props; const containerStyle: CSSProperties = {}; - const isDestroyOnClose = this.getDestroyOnClose(); + const isDestroyOnClose = destroyOnClose.value; if (isDestroyOnClose) { // Increase the opacity transition, delete children after closing. containerStyle.opacity = 0; @@ -194,74 +285,85 @@ const Drawer = defineComponent({
- {this.renderHeader(prefixCls)} + {renderHeader(prefixCls)}
- {this.$slots.default?.()} + {slots.default?.()}
+ {renderFooter(prefixCls)}
); - }, - }, - render() { - const props: any = getOptionProps(this); - const { - prefixCls: customizePrefixCls, - width, - height, - visible, - placement, - wrapClassName, - mask, - ...rest - } = props; - const haveMask = mask ? '' : 'no-mask'; - const offsetStyle: CSSProperties = {}; - if (placement === 'left' || placement === 'right') { - offsetStyle.width = typeof width === 'number' ? `${width}px` : width; - } else { - offsetStyle.height = typeof height === 'number' ? `${height}px` : height; - } - const handler = getComponent(this, 'handle') || false; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('drawer', customizePrefixCls); - const { class: className } = this.$attrs; - const vcDrawerProps: any = { - ...this.$attrs, - ...omit(rest, [ - 'closable', - 'destroyOnClose', - 'drawerStyle', - 'headerStyle', - 'bodyStyle', - 'title', - 'push', - 'visible', - 'getPopupContainer', - 'rootPrefixCls', - 'getPrefixCls', - 'renderEmpty', - 'csp', - 'pageHeader', - 'autoInsertSpaceInButton', - ]), - onClose: this.close, - handler, - ...offsetStyle, - prefixCls, - open: visible, - showMask: mask, - placement, - class: classnames({ - [className as string]: !!className, - [wrapClassName]: !!wrapClassName, - [haveMask]: !!haveMask, - }), - wrapStyle: this.getRcDrawerStyle(), - ref: 'vcDrawer', }; - return {this.renderBody(prefixCls)}; + + const renderFooter = (prefixCls: string) => { + const footer = getPropsSlot(slots, props, 'footer'); + if (!footer) { + return null; + } + + const footerClassName = `${prefixCls}-footer`; + return ( +
+ {footer} +
+ ); + }; + + return () => { + const { + width, + height, + visible, + placement, + mask, + wrapClassName, + class: className, + ...rest + } = props; + + const val = mask ? offsetStyle.value : {}; + const haveMask = mask ? '' : 'no-mask'; + const vcDrawerProps: any = { + ...attrs, + ...omit(rest, [ + 'size', + 'closeIcon', + 'closable', + 'destroyOnClose', + 'drawerStyle', + 'headerStyle', + 'bodyStyle', + 'title', + 'push', + 'wrapStyle', + ]), + ...val, + onClose: close, + afterVisibleChange, + handler: false, + prefixCls: prefixCls.value, + open: visible, + showMask: mask, + placement, + class: classnames({ + [className]: className, + [wrapClassName]: !!wrapClassName, + [haveMask]: !!haveMask, + }), + style: drawerStyle.value, + ref: vcDrawer, + }; + return ( + props.handle : slots.handle, + default: () => renderBody(prefixCls.value), + }} + > + ); + }; }, }); diff --git a/components/drawer/index.zh-CN.md b/components/drawer/index.zh-CN.md index bec03bb6c6..e58b80d20d 100644 --- a/components/drawer/index.zh-CN.md +++ b/components/drawer/index.zh-CN.md @@ -6,33 +6,50 @@ subtitle: 抽屉 cover: https://gw.alipayobjects.com/zos/alicdn/7z8NJQhFb/Drawer.svg --- +屏幕边缘滑出的浮层面板。 + +## 何时使用 + +抽屉从父窗体边缘滑入,覆盖住部分父窗体内容。用户在抽屉内操作时不必离开当前任务,操作完成后,可以平滑地回到原任务。 + +- 当需要一个附加的面板来控制父窗体内容,这个面板在需要时呼出。比如,控制界面展示样式,往界面中添加内容。 +- 当需要在当前任务流中插入临时任务,创建或预览附加内容。比如展示协议条款,创建子对象。 + ## API | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | +| autoFocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 3.0.0 | +| bodyStyle | 可用于设置 Drawer 内容部分的样式 | CSSProperties | - | | +| class | 对话框外层容器的类名 | string | - | | | closable | 是否显示右上角的关闭按钮 | boolean | true | | +| closeIcon | 自定义关闭图标 | VNode \| slot | | 3.0.0 | +| contentWrapperStyle | 可用于设置 Drawer 包裹内容部分的样式 | CSSProperties | 3.0.0 | | destroyOnClose | 关闭时销毁 Drawer 里的子元素 | boolean | false | | +| drawerStyle | 用于设置 Drawer 弹出层的样式 | object | - | | +| extra | 抽屉右上角的操作区域 | VNode \| slot | - | 3.0.0 | +| footer | 抽屉的页脚 | VNode \| slot | - | 3.0.0 | +| footerStyle | 抽屉页脚部件的样式 | CSSProperties | - | 3.0.0 | +| forceRender | 预渲染 Drawer 内元素 | boolean | false | 3.0.0 | | getContainer | 指定 Drawer 挂载的 HTML 节点 | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | | -| maskClosable | 点击蒙层是否允许关闭 | boolean | true | | +| headerStyle | 用于设置 Drawer 头部的样式 | CSSProperties | - | 3.0.0 | +| height | 高度, 在 `placement` 为 `top` 或 `bottom` 时使用 | string \| number | 378 | | +| keyboard | 是否支持键盘 esc 关闭 | boolean | true | | | mask | 是否展示遮罩 | Boolean | true | | -| maskStyle | 遮罩样式 | object | {} | | +| maskClosable | 点击蒙层是否允许关闭 | boolean | true | | +| maskStyle | 遮罩样式 | CSSProperties | {} | | +| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | +| push | 用于设置多层 Drawer 的推动行为 | boolean \| {distance: string \| number} | { distance: 180} | 3.0.0 | +| size | 预设抽屉宽度(或高度),default `378px` 和 large `736px` | `default` \| `large` | `default` | 3.0.0 | +| style | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | CSSProperties | - | | | title | 标题 | string \| slot | - | | | visible(v-model) | Drawer 是否可见 | boolean | - | | -| wrapClassName | 对话框外层容器的类名 | string | - | | -| wrapStyle | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | object | - | | -| drawerStyle | 用于设置 Drawer 弹出层的样式 | object | - | | -| headerStyle | 用于设置 Drawer 头部的样式 | object | - | | -| bodyStyle | 可用于设置 Drawer 内容部分的样式 | object | - | | -| width | 宽度 | string \| number | 256 | | -| height | 高度, 在 `placement` 为 `top` 或 `bottom` 时使用 | string \| number | 256 | | +| width | 宽度 | string \| number | 378 | | | zIndex | 设置 Drawer 的 `z-index` | Number | 1000 | | -| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | -| handle | 设置后抽屉直接挂载到 DOM 上,你可以通过该 handle 控制抽屉打开关闭 | VNode \| slot | - | | -| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | 无 | | -| keyboard | 是否支持键盘 esc 关闭 | boolean | true | | ## 方法 -| 名称 | 描述 | 类型 | 默认值 | 版本 | -| ----- | ------------------------------------ | ----------- | ------ | ---- | -| close | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 | | +| 名称 | 描述 | 类型 | 默认值 | 版本 | +| ------------------ | ------------------------------------ | ----------------- | ------ | ---- | +| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | 无 | | +| close | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 | | diff --git a/components/drawer/style/drawer.less b/components/drawer/style/drawer.less index f28ad981b2..e63531a49b 100644 --- a/components/drawer/style/drawer.less +++ b/components/drawer/style/drawer.less @@ -1,12 +1,11 @@ @import '../../style/themes/index'; -// Preserve the typo for compatibility -// https://github.com/ant-design/ant-design/issues/14628 -@dawer-prefix-cls: ~'@{ant-prefix}-drawer'; - -@drawer-prefix-cls: @dawer-prefix-cls; +@drawer-prefix-cls: ~'@{ant-prefix}-drawer'; +@picker-prefix-cls: ~'@{ant-prefix}-picker'; .@{drawer-prefix-cls} { + @drawer-header-close-padding: ceil(((@drawer-header-close-size - @font-size-lg) / 2)); + position: fixed; z-index: @zindex-modal; width: 0%; @@ -20,7 +19,10 @@ &-content-wrapper { position: absolute; + width: 100%; + height: 100%; } + .@{drawer-prefix-cls}-content { width: 100%; height: 100%; @@ -38,12 +40,17 @@ width: 100%; transition: transform @animation-duration-slow @ease-base-out; } - &.@{drawer-prefix-cls}-open.no-mask { - width: 0%; - } } &-left { + left: 0; + + .@{drawer-prefix-cls} { + &-content-wrapper { + left: 0; + } + } + &.@{drawer-prefix-cls}-open { .@{drawer-prefix-cls}-content-wrapper { box-shadow: @shadow-1-right; @@ -84,9 +91,6 @@ height: 100%; transition: transform @animation-duration-slow @ease-base-out; } - &.@{drawer-prefix-cls}-open.no-mask { - height: 0%; - } } &-top { @@ -118,15 +122,12 @@ } } - &.@{drawer-prefix-cls}-open { - .@{drawer-prefix-cls} { - &-mask { - height: 100%; - opacity: 1; - transition: none; - animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out; - } - } + &.@{drawer-prefix-cls}-open .@{drawer-prefix-cls}-mask { + height: 100%; + opacity: 1; + transition: none; + animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out; + pointer-events: auto; } &-title { @@ -147,19 +148,13 @@ } &-close { - position: absolute; - top: 0; - right: 0; - z-index: @zindex-popup-close; - display: block; - width: 56px; - height: 56px; - padding: 0; - color: @text-color-secondary; + display: inline-block; + margin-right: 12px; + color: @modal-close-color; font-weight: 700; font-size: @font-size-lg; font-style: normal; - line-height: 56px; + line-height: 1; text-align: center; text-transform: none; text-decoration: none; @@ -179,27 +174,48 @@ &-header { position: relative; + display: flex; + align-items: center; + justify-content: space-between; padding: @drawer-header-padding; color: @text-color; background: @drawer-bg; border-bottom: @border-width-base @border-style-base @border-color-split; border-radius: @border-radius-base @border-radius-base 0 0; + + &-title { + display: flex; + align-items: center; + justify-content: space-between; + } + + &-close-only { + padding-bottom: 0; + border: none; + } } - &-header-no-title { - color: @text-color; - background: @drawer-bg; + &-wrapper-body { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + width: 100%; + height: 100%; } &-body { + flex-grow: 1; padding: @drawer-body-padding; + overflow: auto; font-size: @font-size-base; line-height: @line-height-base; word-wrap: break-word; } - &-wrapper-body { - height: 100%; - overflow: auto; + + &-footer { + flex-shrink: 0; + padding: @drawer-footer-padding-vertical @drawer-footer-padding-horizontal; + border-top: @border-width-base @border-style-base @border-color-split; } &-mask { @@ -212,12 +228,21 @@ opacity: 0; filter: ~'alpha(opacity=45)'; transition: opacity @animation-duration-slow linear, height 0s ease @animation-duration-slow; + pointer-events: none; } + &-open { &-content { box-shadow: @shadow-2; } } + + // =================== Hook Components =================== + .@{picker-prefix-cls} { + &-clear { + background: @popover-background; + } + } } @keyframes antdDrawerFadeIn { diff --git a/components/drawer/style/index.less b/components/drawer/style/index.less index 79d170c750..77efacf49d 100644 --- a/components/drawer/style/index.less +++ b/components/drawer/style/index.less @@ -1,3 +1,6 @@ @import '../../style/themes/index'; @import '../../style/mixins/index'; @import './drawer'; +@import './rtl'; + +.popover-customize-bg(@drawer-prefix-cls, @popover-background); diff --git a/components/drawer/style/index.ts b/components/drawer/style/index.tsx similarity index 100% rename from components/drawer/style/index.ts rename to components/drawer/style/index.tsx diff --git a/components/drawer/style/rtl.less b/components/drawer/style/rtl.less new file mode 100644 index 0000000000..f710bfa7dd --- /dev/null +++ b/components/drawer/style/rtl.less @@ -0,0 +1,16 @@ +@import '../../style/themes/index'; + +@drawer-prefix-cls: ~'@{ant-prefix}-drawer'; + +.@{drawer-prefix-cls} { + &-rtl { + direction: rtl; + } + + &-close { + .@{drawer-prefix-cls}-rtl & { + margin-right: 0; + margin-left: 12px; + } + } +} diff --git a/components/menu/__tests__/__snapshots__/demo.test.js.snap b/components/menu/__tests__/__snapshots__/demo.test.js.snap index e282bcbccd..2a1d0fe5d0 100644 --- a/components/menu/__tests__/__snapshots__/demo.test.js.snap +++ b/components/menu/__tests__/__snapshots__/demo.test.js.snap @@ -458,7 +458,7 @@ exports[`renders ./components/menu/demo/vertical.vue correctly 1`] = `
  • - +
  • diff --git a/components/modal/__tests__/Modal.test.js b/components/modal/__tests__/Modal.test.js index 5415e8eaa0..4d62ae7ca1 100644 --- a/components/modal/__tests__/Modal.test.js +++ b/components/modal/__tests__/Modal.test.js @@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'; import Modal from '..'; import mountTest from '../../../tests/shared/mountTest'; import { asyncExpect } from '../../../tests/utils'; - +jest.mock('../../_util/Portal'); const ModalTester = { props: ['footer', 'visible'], methods: { diff --git a/components/modal/__tests__/__snapshots__/Modal.test.js.snap b/components/modal/__tests__/__snapshots__/Modal.test.js.snap index ede42e3f37..c8d0991acb 100644 --- a/components/modal/__tests__/__snapshots__/Modal.test.js.snap +++ b/components/modal/__tests__/__snapshots__/Modal.test.js.snap @@ -2,88 +2,73 @@ exports[`Modal render correctly 1`] = `
    -
    -
    -
    -
    -