From 0c31ada67ae7fbb9673d6ee16f343ccb76163d4e Mon Sep 17 00:00:00 2001
From: ajuner <106791576@qq.com>
Date: Mon, 27 Sep 2021 17:09:18 +0800
Subject: [PATCH] refactor(drawer): use compositionAPI
---
components/drawer/demo/basic.vue | 3 +-
components/drawer/demo/extra.vue | 65 +++
components/drawer/demo/form-in-drawer.vue | 19 +-
components/drawer/demo/index.vue | 6 +
components/drawer/demo/multi-level-drawer.vue | 47 +-
components/drawer/demo/render-in-current.vue | 4 +-
components/drawer/demo/size.vue | 61 +++
components/drawer/demo/user-profile.vue | 2 +-
components/drawer/index.en-US.md | 51 +-
components/drawer/index.tsx | 479 +++++++++-------
components/drawer/index.zh-CN.md | 51 +-
components/drawer/style/drawer.less | 98 ++--
components/drawer/style/index.less | 3 +
components/drawer/style/rtl.less | 16 +
components/style/themes/default.less | 10 +-
components/vc-drawer/assets/index.less | 89 +--
components/vc-drawer/index.ts | 3 +
components/vc-drawer/src/DrawerChild.tsx | 513 ++++++++++++++++++
components/vc-drawer/src/DrawerWrapper.tsx | 118 ++++
...DrawerPropTypes.js => IDrawerPropTypes.ts} | 43 +-
components/vc-drawer/src/index.js | 5 -
.../vc-drawer/src/{utils.js => utils.ts} | 59 +-
22 files changed, 1338 insertions(+), 407 deletions(-)
create mode 100644 components/drawer/demo/extra.vue
create mode 100644 components/drawer/demo/size.vue
create mode 100644 components/drawer/style/rtl.less
create mode 100644 components/vc-drawer/index.ts
create mode 100644 components/vc-drawer/src/DrawerChild.tsx
create mode 100644 components/vc-drawer/src/DrawerWrapper.tsx
rename components/vc-drawer/src/{IDrawerPropTypes.js => IDrawerPropTypes.ts} (51%)
delete mode 100644 components/vc-drawer/src/index.js
rename components/vc-drawer/src/{utils.js => utils.ts} (57%)
diff --git a/components/drawer/demo/basic.vue b/components/drawer/demo/basic.vue
index c446dfd4e..7a203d6ce 100644
--- a/components/drawer/demo/basic.vue
+++ b/components/drawer/demo/basic.vue
@@ -22,8 +22,7 @@ Basic drawer.
v-model:visible="visible"
title="Basic Drawer"
placement="right"
- :closable="false"
- :after-visible-change="afterVisibleChange"
+ @after-visible-change="afterVisibleChange"
>
Some contents...
Some contents...
diff --git a/components/drawer/demo/extra.vue b/components/drawer/demo/extra.vue
new file mode 100644
index 000000000..e940d5310
--- /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.
+
+
+
+
+
+ top
+ right
+ bottom
+ left
+
+ Open
+
+
+ Cancel
+ Submit
+
+ Some contents...
+ Some contents...
+ Some contents...
+
+
+
diff --git a/components/drawer/demo/form-in-drawer.vue b/components/drawer/demo/form-in-drawer.vue
index 482078cdb..6d92bd650 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 bbe0817ec..2036f31f5 100644
--- a/components/drawer/demo/user-profile.vue
+++ b/components/drawer/demo/user-profile.vue
@@ -1,6 +1,6 @@
---
-order: 4
+order: 5
title:
zh-CN: 信息预览抽屉
en-US: Preview drawer
diff --git a/components/drawer/index.en-US.md b/components/drawer/index.en-US.md
index 30bbe553a..c775d33fe 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 |
-| --- | --- | --- | --- | --- |
-| 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 | - | |
+| Props | Description | Type | Default | Version |
+| --- | --- | --- | --- | --- | --- |
+| autoFocus | Whether Drawer should get focused after open | boolean | true | 3.0.0 |
+| bodyStyle | Style of the drawer content part | CSSProperties | - | |
+| className(old: wrapClassName) | The class name of the container of the Drawer dialog | string | - | 3.0.0 |
+| 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(old: wrapStyle) | Style of wrapper element which contains mask compare to drawerStyle | CSSProperties | - | 3.0.0 |
+| 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 c401f9693..9827a7136 100644
--- a/components/drawer/index.tsx
+++ b/components/drawer/index.tsx
@@ -1,189 +1,237 @@
-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,
+ onBeforeMount,
+ onUpdated,
+ onUnmounted,
+} from 'vue';
+import { getPropsSlot } 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 { tuple, withInstall } from '../_util/type';
import omit from '../_util/omit';
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.def(true),
+ closeIcon: PropTypes.VNodeChild.def(),
+ destroyOnClose: PropTypes.looseBool,
+ forceRender: PropTypes.looseBool,
+ getContainer: PropTypes.any,
+ maskClosable: PropTypes.looseBool.def(true),
+ mask: PropTypes.looseBool.def(true),
+ maskStyle: PropTypes.object,
+ style: PropTypes.object,
+ size: {
+ type: String as PropType,
+ },
+ drawerStyle: PropTypes.object,
+ headerStyle: PropTypes.object,
+ bodyStyle: PropTypes.object,
+ contentWrapperStyle: PropTypes.object,
+ title: PropTypes.VNodeChild,
+ visible: PropTypes.looseBool,
+ className: PropTypes.string,
+ 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 }]).def(
+ defaultPushState,
+ ),
+ placement: PropTypes.oneOf(PlacementTypes).def('right'),
+ keyboard: PropTypes.looseBool.def(true),
+ extra: PropTypes.VNodeChild,
+ footer: PropTypes.VNodeChild,
+ footerStyle: PropTypes.object,
+ level: PropTypes.any.def(null),
+ levelMove: PropTypes.any,
+};
+
+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) {
+ props: drawerProps,
+ emits: ['update:visible', 'close', 'afterVisibleChange'],
+ setup(props, { emit, slots, attrs }) {
+ const sPush = ref(false);
+ const preVisible = ref(props.visible);
+ const destroyClose = ref(false);
+ const vcDrawer = ref(null);
const configProvider = inject('configProvider', defaultConfigProvider);
- return {
- configProvider,
- destroyClose: false,
- preVisible: props.visible,
- parentDrawer: inject('parentDrawer', null),
- };
- },
- data() {
- return {
- sPush: false,
- };
- },
- 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();
- }
- }
- 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();
- }
- },
- 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,
+ const parentDrawerOpts = inject('parentDrawerOpts', null);
+
+ onBeforeMount(() => {
+ provide('parentDrawerOpts', {
+ setPush,
+ setPull,
});
- },
- pull() {
- this.setState(
- {
- sPush: false,
- },
- () => {
- this.domFocus();
- },
- );
- },
- onDestroyTransitionEnd() {
- const isDestroyOnClose = this.getDestroyOnClose();
+ });
+
+ onMounted(() => {
+ const { visible } = props;
+ if (visible && parentDrawerOpts) {
+ parentDrawerOpts.setPush();
+ }
+ });
+
+ onUnmounted(() => {
+ if (parentDrawerOpts) {
+ parentDrawerOpts.setPull();
+ }
+ });
+
+ onUpdated(() => {
+ const { visible } = props;
+ nextTick(() => {
+ if (preVisible.value !== visible && parentDrawerOpts) {
+ if (visible) {
+ parentDrawerOpts.setPush();
+ } else {
+ parentDrawerOpts.setPull();
+ }
+ }
+ preVisible.value = visible;
+ });
+ });
+
+ const domFocus = () => {
+ vcDrawer.value?.domFocus?.();
+ };
+
+ const close = (e: Event) => {
+ emit('update:visible', false);
+ emit('close', e);
+ };
+
+ const afterVisibleChange = (visible: boolean) => {
+ emit('afterVisibleChange', visible);
+ };
+
+ const setPush = () => {
+ sPush.value = true;
+ };
+
+ const setPull = () => {
+ sPush.value = false;
+ nextTick(() => {
+ domFocus();
+ });
+ };
+
+ const onDestroyTransitionEnd = () => {
+ const isDestroyOnClose = getDestroyOnClose();
if (!isDestroyOnClose) {
return;
}
- if (!this.visible) {
- this.destroyClose = true;
- (this as any).$forceUpdate();
+ if (!props.visible) {
+ destroyClose.value = true;
}
- },
+ };
+
+ const getDestroyOnClose = () => {
+ return props.destroyOnClose && !props.visible;
+ };
+
+ const getPushTransform = (placement?: placementType) => {
+ const { push } = props;
+ let distance: number | string;
+ if (typeof push === 'boolean') {
+ distance = push ? defaultPushState.distance : 0;
+ } else {
+ distance = push!.distance;
+ }
+
+ distance = parseFloat(String(distance || 0));
- getDestroyOnClose() {
- return this.destroyOnClose && !this.visible;
- },
- // get drawar push width or height
- getPushTransform(placement?: placementType) {
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)`;
}
- },
- getRcDrawerStyle() {
- const { zIndex, placement, wrapStyle } = this.$props;
- const { sPush: push } = this.$data;
+ };
+
+ const getRcDrawerStyle = () => {
+ const { zIndex, placement, style, mask } = props;
+ const offsetStyle = mask ? {} : getOffsetStyle();
return {
zIndex,
- transform: push ? this.getPushTransform(placement) : undefined,
- ...wrapStyle,
+ transform: sPush.value ? getPushTransform(placement) : undefined,
+ ...offsetStyle,
+ ...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 (
-