diff --git a/components.json b/components.json
index c9322ecd0c..8785c19f05 100644
--- a/components.json
+++ b/components.json
@@ -78,5 +78,6 @@
"infinite-scroll": "./packages/infinite-scroll/index.js",
"page-header": "./packages/page-header/index.js",
"cascader-panel": "./packages/cascader-panel/index.js",
- "avatar": "./packages/avatar/index.js"
+ "avatar": "./packages/avatar/index.js",
+ "drawer": "./packages/drawer/index.js"
}
diff --git a/examples/demo-styles/drawer.scss b/examples/demo-styles/drawer.scss
new file mode 100644
index 0000000000..117136171f
--- /dev/null
+++ b/examples/demo-styles/drawer.scss
@@ -0,0 +1,21 @@
+.demo-drawer {
+ &__content {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ form {
+ flex: 1;
+ }
+ }
+
+ &__footer {
+ display: flex;
+ button {
+ flex: 1;
+ }
+ }
+}
+
+.el-drawer__body {
+ padding: 20px;
+}
diff --git a/examples/demo-styles/index.scss b/examples/demo-styles/index.scss
index c838572f1f..2186b96d25 100644
--- a/examples/demo-styles/index.scss
+++ b/examples/demo-styles/index.scss
@@ -43,4 +43,5 @@
@import "./image.scss";
@import "./infinite-scroll.scss";
@import "./avatar.scss";
+@import "./drawer.scss";
diff --git a/examples/docs/en-US/drawer.md b/examples/docs/en-US/drawer.md
new file mode 100644
index 0000000000..d52ac42419
--- /dev/null
+++ b/examples/docs/en-US/drawer.md
@@ -0,0 +1,262 @@
+## Drawer
+
+Sometimes, `Dialog` does not always satisfy our requirements, let's say you have a massive form, or you need space to display something like `terms & conditions`, `Drawer` has almost identical API with `Dialog`, but it introduces different user experience.
+
+### Basic Usage
+
+Callout a temporary drawer, from multiple direction
+
+:::demo You must set `visible` for `Drawer` like `Dialog` does to control the visibility of `Drawer` itself, it's `boolean` type. `Drawer` has to parts: `title` & `body`, the `title` is a named slot, you can also set the title through attribute named `title`, default to an empty string, the `body` part is the main area of `Drawer`, which contains user defined content. When opening, `Drawer` expand itself from the **right corner to left** which size is **30%** of the browser window by default. You can change that default behavior by setting `direction` and `size` attribute. This show case also demonstrated how to use the `before-close` API, check the Attribute section for more detail
+
+```html
+
+ left to right
+ right to left
+ top to bottom
+ bottom to top
+
+
+
+ open
+
+
+
+ Hi, there!
+
+
+
+```
+:::
+
+### Customization Content
+
+Like `Dialog`, `Drawer` can do many diverse interaction as you wanted.
+
+:::demo
+
+```html
+Open Drawer with nested table
+Open Drawer with nested form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+:::
+
+### Nested Drawer
+
+You can also have multiple layer of `Drawer` just like `Dialog`.
+:::demo If you need multiple Drawer in different layer, you must set the `append-to-body` attribute to **true**
+
+```html
+
+
+ open
+
+
+
+
+
Click me!
+
+ _(:зゝ∠)_
+
+
+
+
+
+
+```
+:::
+
+:::tip
+
+The content inside Drawer should be lazy rendered, which means that the content inside Drawer will not impact the initial render performance, therefore any DOM operation should be performed through `ref` or after `open` event emitted.
+
+:::
+
+:::tip
+
+Drawer provides an API called `destroyOnClose`, which is a flag variable that indicates should destroy the children content inside Drawer after Drawer was closed. You can use this API when you need your `mounted` life cycle to be called every time the Drawer opens.
+
+:::
+
+:::tip
+
+If the variable bound to `visible` is managed in Vuex store, the `.sync` can not work properly. In this case, please remove the `.sync` modifier, listen to `open` and `close` events of Dialog, and commit Vuex mutations to update the value of that variable in the event handlers.
+
+:::
+
+### Drawer Attributes
+
+| Parameter| Description | Type | Acceptable Values | Defaults |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| append-to-body | Controls should Drawer be inserted to DocumentBody Element, nested Drawer must assign this param to **true**| boolean | — | false |
+| before-close | If set, closing procedure will be halted | function(done), done is function type that accepts a boolean as parameter, calling done with true or without parameter will abort the close procedure | — | — |
+| close-on-press-escape | Indicates whether Drawer can be closed by pressing ESC | boolean | — | true |
+| custom-class | Extra class names for Drawer | string | — | — |
+| destroy-on-close | Indicates whether children should be destroyed after Drawer closed | boolean | - | false |
+| modal | Should show shadowing layer | boolean | — | true |
+| modal-append-to-body | Indicates should shadowing layer be insert into DocumentBody element | boolean | — | true |
+| direction | Drawer's opening direction | Direction | rtl / ltr / ttb / tbb | rtl |
+| show-close | Should show close button at the top right of Drawer | boolean | — | true |
+| size | Drawer's size, if Drawer is horizontal mode, it effects the width property, otherwise it effects the height property, when size is `number` type, it describes the size by unit of pixels; when size is `string` type, it should be used with `x%` notation, other wise it will be interpreted to pixel unit | number / string | - | '30%' |
+| title | Drawer's title, can also be set by named slot, detailed descriptions can be found in the slot form | string | — | — |
+| visible | Should Drawer be displayed, also support the `.sync` notation | boolean | — | false |
+| wrapperClosable | Indicates whether user can close Drawer by clicking the shadowing layer. | boolean | - | true |
+
+### Drawer Slot
+
+| Name | Description |
+|------|--------|
+| — | Drawer's Content |
+| title | Drawer Title Section |
+
+### Drawer Methods
+
+| Name | Description |
+| ---- | --- |
+| closeDrawer | In order to close Drawer, this method will call `before-close`. |
+
+### Drawer Events
+
+| Event Name | Description | Parameter |
+|---------- |-------- |---------- |
+| open | Triggered before Drawer opening animation begins | — |
+| opened | Triggered after Drawer opening animation ended | — |
+| close | Triggered before Drawer closing animation begins | — |
+| closed | Triggered after Drawer closing animation ended | — |
diff --git a/examples/docs/es/drawer.md b/examples/docs/es/drawer.md
new file mode 100644
index 0000000000..d52ac42419
--- /dev/null
+++ b/examples/docs/es/drawer.md
@@ -0,0 +1,262 @@
+## Drawer
+
+Sometimes, `Dialog` does not always satisfy our requirements, let's say you have a massive form, or you need space to display something like `terms & conditions`, `Drawer` has almost identical API with `Dialog`, but it introduces different user experience.
+
+### Basic Usage
+
+Callout a temporary drawer, from multiple direction
+
+:::demo You must set `visible` for `Drawer` like `Dialog` does to control the visibility of `Drawer` itself, it's `boolean` type. `Drawer` has to parts: `title` & `body`, the `title` is a named slot, you can also set the title through attribute named `title`, default to an empty string, the `body` part is the main area of `Drawer`, which contains user defined content. When opening, `Drawer` expand itself from the **right corner to left** which size is **30%** of the browser window by default. You can change that default behavior by setting `direction` and `size` attribute. This show case also demonstrated how to use the `before-close` API, check the Attribute section for more detail
+
+```html
+
+ left to right
+ right to left
+ top to bottom
+ bottom to top
+
+
+
+ open
+
+
+
+ Hi, there!
+
+
+
+```
+:::
+
+### Customization Content
+
+Like `Dialog`, `Drawer` can do many diverse interaction as you wanted.
+
+:::demo
+
+```html
+Open Drawer with nested table
+Open Drawer with nested form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+:::
+
+### Nested Drawer
+
+You can also have multiple layer of `Drawer` just like `Dialog`.
+:::demo If you need multiple Drawer in different layer, you must set the `append-to-body` attribute to **true**
+
+```html
+
+
+ open
+
+
+
+
+
Click me!
+
+ _(:зゝ∠)_
+
+
+
+
+
+
+```
+:::
+
+:::tip
+
+The content inside Drawer should be lazy rendered, which means that the content inside Drawer will not impact the initial render performance, therefore any DOM operation should be performed through `ref` or after `open` event emitted.
+
+:::
+
+:::tip
+
+Drawer provides an API called `destroyOnClose`, which is a flag variable that indicates should destroy the children content inside Drawer after Drawer was closed. You can use this API when you need your `mounted` life cycle to be called every time the Drawer opens.
+
+:::
+
+:::tip
+
+If the variable bound to `visible` is managed in Vuex store, the `.sync` can not work properly. In this case, please remove the `.sync` modifier, listen to `open` and `close` events of Dialog, and commit Vuex mutations to update the value of that variable in the event handlers.
+
+:::
+
+### Drawer Attributes
+
+| Parameter| Description | Type | Acceptable Values | Defaults |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| append-to-body | Controls should Drawer be inserted to DocumentBody Element, nested Drawer must assign this param to **true**| boolean | — | false |
+| before-close | If set, closing procedure will be halted | function(done), done is function type that accepts a boolean as parameter, calling done with true or without parameter will abort the close procedure | — | — |
+| close-on-press-escape | Indicates whether Drawer can be closed by pressing ESC | boolean | — | true |
+| custom-class | Extra class names for Drawer | string | — | — |
+| destroy-on-close | Indicates whether children should be destroyed after Drawer closed | boolean | - | false |
+| modal | Should show shadowing layer | boolean | — | true |
+| modal-append-to-body | Indicates should shadowing layer be insert into DocumentBody element | boolean | — | true |
+| direction | Drawer's opening direction | Direction | rtl / ltr / ttb / tbb | rtl |
+| show-close | Should show close button at the top right of Drawer | boolean | — | true |
+| size | Drawer's size, if Drawer is horizontal mode, it effects the width property, otherwise it effects the height property, when size is `number` type, it describes the size by unit of pixels; when size is `string` type, it should be used with `x%` notation, other wise it will be interpreted to pixel unit | number / string | - | '30%' |
+| title | Drawer's title, can also be set by named slot, detailed descriptions can be found in the slot form | string | — | — |
+| visible | Should Drawer be displayed, also support the `.sync` notation | boolean | — | false |
+| wrapperClosable | Indicates whether user can close Drawer by clicking the shadowing layer. | boolean | - | true |
+
+### Drawer Slot
+
+| Name | Description |
+|------|--------|
+| — | Drawer's Content |
+| title | Drawer Title Section |
+
+### Drawer Methods
+
+| Name | Description |
+| ---- | --- |
+| closeDrawer | In order to close Drawer, this method will call `before-close`. |
+
+### Drawer Events
+
+| Event Name | Description | Parameter |
+|---------- |-------- |---------- |
+| open | Triggered before Drawer opening animation begins | — |
+| opened | Triggered after Drawer opening animation ended | — |
+| close | Triggered before Drawer closing animation begins | — |
+| closed | Triggered after Drawer closing animation ended | — |
diff --git a/examples/docs/fr-FR/drawer.md b/examples/docs/fr-FR/drawer.md
new file mode 100644
index 0000000000..d52ac42419
--- /dev/null
+++ b/examples/docs/fr-FR/drawer.md
@@ -0,0 +1,262 @@
+## Drawer
+
+Sometimes, `Dialog` does not always satisfy our requirements, let's say you have a massive form, or you need space to display something like `terms & conditions`, `Drawer` has almost identical API with `Dialog`, but it introduces different user experience.
+
+### Basic Usage
+
+Callout a temporary drawer, from multiple direction
+
+:::demo You must set `visible` for `Drawer` like `Dialog` does to control the visibility of `Drawer` itself, it's `boolean` type. `Drawer` has to parts: `title` & `body`, the `title` is a named slot, you can also set the title through attribute named `title`, default to an empty string, the `body` part is the main area of `Drawer`, which contains user defined content. When opening, `Drawer` expand itself from the **right corner to left** which size is **30%** of the browser window by default. You can change that default behavior by setting `direction` and `size` attribute. This show case also demonstrated how to use the `before-close` API, check the Attribute section for more detail
+
+```html
+
+ left to right
+ right to left
+ top to bottom
+ bottom to top
+
+
+
+ open
+
+
+
+ Hi, there!
+
+
+
+```
+:::
+
+### Customization Content
+
+Like `Dialog`, `Drawer` can do many diverse interaction as you wanted.
+
+:::demo
+
+```html
+Open Drawer with nested table
+Open Drawer with nested form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+:::
+
+### Nested Drawer
+
+You can also have multiple layer of `Drawer` just like `Dialog`.
+:::demo If you need multiple Drawer in different layer, you must set the `append-to-body` attribute to **true**
+
+```html
+
+
+ open
+
+
+
+
+
Click me!
+
+ _(:зゝ∠)_
+
+
+
+
+
+
+```
+:::
+
+:::tip
+
+The content inside Drawer should be lazy rendered, which means that the content inside Drawer will not impact the initial render performance, therefore any DOM operation should be performed through `ref` or after `open` event emitted.
+
+:::
+
+:::tip
+
+Drawer provides an API called `destroyOnClose`, which is a flag variable that indicates should destroy the children content inside Drawer after Drawer was closed. You can use this API when you need your `mounted` life cycle to be called every time the Drawer opens.
+
+:::
+
+:::tip
+
+If the variable bound to `visible` is managed in Vuex store, the `.sync` can not work properly. In this case, please remove the `.sync` modifier, listen to `open` and `close` events of Dialog, and commit Vuex mutations to update the value of that variable in the event handlers.
+
+:::
+
+### Drawer Attributes
+
+| Parameter| Description | Type | Acceptable Values | Defaults |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| append-to-body | Controls should Drawer be inserted to DocumentBody Element, nested Drawer must assign this param to **true**| boolean | — | false |
+| before-close | If set, closing procedure will be halted | function(done), done is function type that accepts a boolean as parameter, calling done with true or without parameter will abort the close procedure | — | — |
+| close-on-press-escape | Indicates whether Drawer can be closed by pressing ESC | boolean | — | true |
+| custom-class | Extra class names for Drawer | string | — | — |
+| destroy-on-close | Indicates whether children should be destroyed after Drawer closed | boolean | - | false |
+| modal | Should show shadowing layer | boolean | — | true |
+| modal-append-to-body | Indicates should shadowing layer be insert into DocumentBody element | boolean | — | true |
+| direction | Drawer's opening direction | Direction | rtl / ltr / ttb / tbb | rtl |
+| show-close | Should show close button at the top right of Drawer | boolean | — | true |
+| size | Drawer's size, if Drawer is horizontal mode, it effects the width property, otherwise it effects the height property, when size is `number` type, it describes the size by unit of pixels; when size is `string` type, it should be used with `x%` notation, other wise it will be interpreted to pixel unit | number / string | - | '30%' |
+| title | Drawer's title, can also be set by named slot, detailed descriptions can be found in the slot form | string | — | — |
+| visible | Should Drawer be displayed, also support the `.sync` notation | boolean | — | false |
+| wrapperClosable | Indicates whether user can close Drawer by clicking the shadowing layer. | boolean | - | true |
+
+### Drawer Slot
+
+| Name | Description |
+|------|--------|
+| — | Drawer's Content |
+| title | Drawer Title Section |
+
+### Drawer Methods
+
+| Name | Description |
+| ---- | --- |
+| closeDrawer | In order to close Drawer, this method will call `before-close`. |
+
+### Drawer Events
+
+| Event Name | Description | Parameter |
+|---------- |-------- |---------- |
+| open | Triggered before Drawer opening animation begins | — |
+| opened | Triggered after Drawer opening animation ended | — |
+| close | Triggered before Drawer closing animation begins | — |
+| closed | Triggered after Drawer closing animation ended | — |
diff --git a/examples/docs/zh-CN/drawer.md b/examples/docs/zh-CN/drawer.md
new file mode 100644
index 0000000000..c211c7f159
--- /dev/null
+++ b/examples/docs/zh-CN/drawer.md
@@ -0,0 +1,263 @@
+## Drawer 抽屉
+
+有些时候, `Dialog` 组件并不满足我们的需求, 比如你的表单很长, 亦或是你需要临时展示一些文档, `Drawer` 拥有和 `Dialog` 几乎相同的 API, 在 UI 上带来不一样的体验.
+
+### 基本用法
+
+呼出一个临时的侧边栏, 可以从多个方向呼出
+
+:::demo 需要设置 `visible` 属性,它的**类型**是 `boolean`,当为 **true** 时显示 Drawer。Drawer 分为两个部分:`title` 和 `body`,`title` 需要具名为 **title** 的 `slot`, 也可以通过 `title` 属性来定义,默认值为空。需要注意的是, Drawer 默认是从右往左打开, 当然可以设置对应的 `direction`, 详细请参考 `direction` 用法 最后,本例还展示了 `before-close` 的用法
+
+```html
+
+ 从左往右开
+ 从右往左开
+ 从上往下开
+ 从下往上开
+
+
+
+ 点我打开
+
+
+
+ 我来啦!
+
+
+
+```
+:::
+
+### 自定义内容
+
+和 `Dialog` 组件一样, `Drawer` 同样可以在其内部嵌套各种丰富的操作
+
+:::demo
+
+```html
+打开嵌套表格的 Drawer
+打开嵌套 Form 的 Drawer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+:::
+
+### 多层嵌套
+
+`Drawer` 组件也拥有多层嵌套的方法
+
+:::demo 同样, 如果你需要嵌套多层 `Drawer` 请一定要设置 `append-to-body` 属性为 **true**
+
+```html
+
+
+ 点我打开
+
+
+
+
+
打开里面的!
+
+ _(:зゝ∠)_
+
+
+
+
+
+
+```
+:::
+
+:::tip
+
+Drawer 的内容是懒渲染的,即在第一次被打开之前,传入的默认 slot 不会被渲染到 DOM 上。因此,如果需要执行 DOM 操作,或通过 `ref` 获取相应组件,请在 `open` 事件回调中进行。
+
+:::
+
+:::tip
+
+Drawer 提供一个 `destroyOnClose` API, 用来在关闭 Drawer 时销毁子组件内容, 例如清理表单内的状态, 在必要时可以将该属性设置为 **true** 用来保证初始状态的一致性
+
+:::
+
+:::tip
+
+如果 `visible` 属性绑定的变量位于 Vuex 的 store 内,那么 `.sync` 不会正常工作。此时需要去除 `.sync` 修饰符,同时监听 Drawer 的 `open` 和 `close` 事件,在事件回调中执行 Vuex 中对应的 mutation 更新 `visible` 属性绑定的变量的值。
+
+:::
+
+### Drawer Attributes
+
+| 参数 | 说明 | 类型 | 可选值 | 默认值 |
+|---------- |-------------- |---------- |-------------------------------- |-------- |
+| append-to-body | Drawer 自身是否插入至 body 元素上。嵌套的 Drawer 必须指定该属性并赋值为 true | boolean | — | false |
+| before-close | 关闭前的回调,会暂停 Drawer 的关闭 | function(done),done 用于关闭 Drawer | — | — |
+| close-on-press-escape | 是否可以通过按下 ESC 关闭 Drawer | boolean | — | true |
+| custom-class | Drawer 的自定义类名 | string | — | — |
+| destroy-on-close | 控制是否在关闭 Drawer 之后将子元素全部销毁 | boolean | - | false |
+| modal | 是否需要遮罩层 | boolean | — | true |
+| modal-append-to-body | 遮罩层是否插入至 body 元素上,若为 false,则遮罩层会插入至 Drawer 的父元素上 | boolean | — | true |
+| direction | Drawer 打开的方向 | Direction | rtl / ltr / ttb / tbb | rtl |
+| show-close | 是否显示关闭按钮 | boolean | — | true |
+| size | Drawer 窗体的大小, 当使用 `number` 类型时, 以像素为单位, 当使用 `string` 类型时, 请传入 'x%', 否则便会以 `number` 类型解释 | number / string | - | '30%' |
+| title | Drawer 的标题,也可通过具名 slot (见下表)传入 | string | — | — |
+| visible | 是否显示 Drawer,支持 .sync 修饰符 | boolean | — | false |
+| wrapperClosable | 点击遮罩层是否可以关闭 Drawer | boolean | - | true |
+
+### Drawer Slot
+
+| name | 说明 |
+|------|--------|
+| — | Drawer 的内容 |
+| title | Drawer 标题区的内容 |
+
+### Drawer Methods
+
+| name | 说明 |
+| ---- | --- |
+| closeDrawer | 用于关闭 Drawer, 该方法会调用传入的 `before-close` 方法 |
+
+### Drawer Events
+
+| 事件名称 | 说明 | 回调参数 |
+|---------- |-------- |---------- |
+| open | Drawer 打开的回调 | — |
+| opened | Drawer 打开动画结束时的回调 | — |
+| close | Drawer 关闭的回调 | — |
+| closed | Drawer 关闭动画结束时的回调 | — |
diff --git a/examples/nav.config.json b/examples/nav.config.json
index 36e3d3ac85..0daf262df5 100644
--- a/examples/nav.config.json
+++ b/examples/nav.config.json
@@ -283,6 +283,10 @@
{
"path": "/infiniteScroll",
"title": "InfiniteScroll 无限滚动"
+ },
+ {
+ "path": "/drawer",
+ "title": "Drawer 抽屉"
}
]
}
@@ -573,6 +577,10 @@
{
"path": "/avatar",
"title": "Avatar"
+ },
+ {
+ "path": "/drawer",
+ "title": "Drawer"
}
]
}
@@ -863,6 +871,10 @@
{
"path": "/avatar",
"title": "Avatar"
+ },
+ {
+ "path": "/drawer",
+ "title": "Drawer"
}
]
}
@@ -1153,6 +1165,10 @@
{
"path": "/avatar",
"title": "Avatar"
+ },
+ {
+ "path": "/drawer",
+ "title": "Drawer"
}
]
}
diff --git a/packages/drawer/index.js b/packages/drawer/index.js
new file mode 100644
index 0000000000..803e071d09
--- /dev/null
+++ b/packages/drawer/index.js
@@ -0,0 +1,8 @@
+import Drawer from './src/main';
+
+/* istanbul ignore next */
+Drawer.install = function(Vue) {
+ Vue.component(Drawer.name, Drawer);
+};
+
+export default Drawer;
diff --git a/packages/drawer/src/main.vue b/packages/drawer/src/main.vue
new file mode 100644
index 0000000000..e895738813
--- /dev/null
+++ b/packages/drawer/src/main.vue
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
diff --git a/packages/theme-chalk/src/drawer.scss b/packages/theme-chalk/src/drawer.scss
new file mode 100644
index 0000000000..665c81dd70
--- /dev/null
+++ b/packages/theme-chalk/src/drawer.scss
@@ -0,0 +1,207 @@
+@import "mixins/mixins";
+@import "common/var";
+
+@keyframes el-drawer-fade-in {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@mixin drawer-animation($direction) {
+
+ @keyframes #{$direction}-drawer-in {
+ 0% {
+
+ @if $direction == ltr {
+ transform: translate(-100%, 0px);
+ }
+
+ @if $direction == rtl {
+ transform: translate(100%, 0px);
+ }
+
+ @if $direction == ttb {
+ transform: translate(0px, -100%);
+ }
+
+ @if $direction == btt {
+ transform: translate(0px, 100%);
+ }
+ }
+
+ 100% {
+ @if $direction == ltr {
+ transform: translate(0px, 0px);
+ }
+
+ @if $direction == rtl {
+ transform: translate(0px, 0px);
+ }
+
+ @if $direction == ttb {
+ transform: translate(0px, 0px);
+ }
+
+ @if $direction == btt {
+ transform: translate(0px, 0px);
+ }
+ }
+ }
+
+ @keyframes #{$direction}-drawer-out {
+ 0% {
+ @if $direction == ltr {
+ transform: translate(0px, 0px);
+ }
+
+ @if $direction == rtl {
+ transform: translate(0px, 0px);;
+ }
+
+ @if $direction == ttb {
+ transform: translate(0px, 0px);
+ }
+
+ @if $direction == btt {
+ transform: translate(0px, 0);
+ }
+ }
+
+ 100% {
+ @if $direction == ltr {
+ transform: translate(-100%, 0px);
+ }
+
+ @if $direction == rtl {
+ transform: translate(100%, 0px);
+ }
+
+ @if $direction == ttb {
+ transform: translate(0px, -100%);
+ }
+
+ @if $direction == btt {
+ transform: translate(0px, 100%);
+ }
+ }
+ }
+}
+
+@mixin animation-in($direction) {
+ .el-drawer__open &.#{$direction} {
+ animation: #{$direction}-drawer-in 225ms cubic-bezier(0, 0, .2, 1) 0ms;
+ }
+}
+
+@mixin animation-out($direction) {
+ &.#{$direction} {
+ animation: #{$direction}-drawer-out 225ms cubic-bezier(0, 0, .2, 1) 0ms;
+ }
+}
+
+@include drawer-animation(rtl)
+@include drawer-animation(ltr)
+@include drawer-animation(ttb)
+@include drawer-animation(btt)
+
+$directions: rtl, ltr, ttb, btt;
+
+@include b(drawer) {
+ position: absolute;
+ box-sizing: border-box;
+ background-color: $--dialog-background-color;
+ display: flex;
+ flex-direction: column;
+ box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2),
+ 0 16px 24px 2px rgba(0, 0, 0, 0.14),
+ 0 6px 30px 5px rgba(0, 0, 0, 0.12);
+ overflow: hidden;
+
+ @each $direction in $directions {
+ @include animation-out($direction);
+ @include animation-in($direction);
+ }
+
+ &__header {
+ align-items: center;
+ color: rgb(114, 118, 123);
+ display: flex;
+ margin-bottom: 32px;
+ padding: $--dialog-padding-primary;
+ padding-bottom: 0;
+ & > :first-child {
+ flex: 1;
+ }
+ }
+
+ &__title {
+ margin: 0;
+ flex: 1;
+ line-height: inherit;
+ font-size: 1rem;
+ }
+
+ &__close-btn {
+ border: none;
+ cursor: pointer;
+ font-size: $--font-size-extra-large;
+ color: inherit;
+ background-color: transparent;
+ }
+
+ &__body {
+ flex: 1;
+ & > * {
+ box-sizing: border-box;
+ }
+ }
+
+ &.ltr, &.rtl {
+ height: 100%;
+ top: 0;
+ bottom: 0;
+ }
+
+ &.ttb, &.btt {
+ width: 100%;
+ left: 0;
+ right: 0;
+ }
+
+ &.ltr {
+ left: 0;
+ }
+
+ &.rtl {
+ right: 0;
+ }
+
+ &.ttb {
+ top: 0;
+ }
+
+ &.btt {
+ bottom: 0;
+ }
+}
+
+.el-drawer__container {
+ position: relative;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ height: 100%;
+ width: 100%;
+}
+
+.el-drawer-fade-enter-active {
+ animation: el-drawer-fade-in 225ms cubic-bezier(0, 0, 0.2, 1) 0ms;
+}
+
+.el-drawer-fade-leave-active {
+ animation: el-drawer-fade-in 225ms cubic-bezier(0, 0, 0.2, 1) 0ms reverse;
+}
diff --git a/packages/theme-chalk/src/index.scss b/packages/theme-chalk/src/index.scss
index c7b85f7446..8cdb8c13ce 100644
--- a/packages/theme-chalk/src/index.scss
+++ b/packages/theme-chalk/src/index.scss
@@ -76,3 +76,4 @@
@import "./page-header.scss";
@import "./cascader-panel.scss";
@import "./avatar.scss";
+@import "./drawer.scss";
diff --git a/src/index.js b/src/index.js
index 74f33d57b5..b6aa3ef6cd 100644
--- a/src/index.js
+++ b/src/index.js
@@ -80,6 +80,7 @@ import InfiniteScroll from '../packages/infinite-scroll/index.js';
import PageHeader from '../packages/page-header/index.js';
import CascaderPanel from '../packages/cascader-panel/index.js';
import Avatar from '../packages/avatar/index.js';
+import Drawer from '../packages/drawer/index.js';
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
@@ -159,6 +160,7 @@ const components = [
PageHeader,
CascaderPanel,
Avatar,
+ Drawer,
CollapseTransition
];
@@ -278,5 +280,6 @@ export default {
InfiniteScroll,
PageHeader,
CascaderPanel,
- Avatar
+ Avatar,
+ Drawer
};
diff --git a/test/unit/specs/drawer.spec.js b/test/unit/specs/drawer.spec.js
new file mode 100644
index 0000000000..90b96cf179
--- /dev/null
+++ b/test/unit/specs/drawer.spec.js
@@ -0,0 +1,332 @@
+import { createVue, destroyVM, waitImmediate, wait } from '../util';
+
+const title = '我是测试 title';
+const content = 'content';
+
+describe('Drawer', () => {
+ let vm;
+ afterEach(() => {
+ destroyVM(vm);
+ });
+
+ it('create', async() => {
+ vm = createVue(
+ {
+ template: `
+
+ `,
+ data() {
+ return {
+ title,
+ visible: true
+ };
+ }
+ },
+ true
+ );
+ const drawer = vm.$children[0];
+ await waitImmediate();
+ expect(document.querySelector('.v-modal')).to.exist;
+ expect(vm.$el.querySelector('.el-drawer__header').textContent).to.equal(
+ title
+ );
+ expect(drawer.$el.style.display).to.not.equal('none');
+ });
+
+ it('render correct content', async() => {
+ vm = createVue(
+ {
+ template: `
+
+ 这是一段信息
+ 取消
+ 确定
+
+ `,
+
+ data() {
+ return {
+ title: 'drawer test',
+ visible: true
+ };
+ }
+ },
+ true
+ );
+ await waitImmediate();
+ expect(vm.$el.querySelector('.el-drawer__body span').textContent).to.equal(
+ '这是一段信息'
+ );
+ const footerBtns = vm.$el.querySelectorAll('.el-button');
+ expect(footerBtns.length).to.equal(2);
+ expect(footerBtns[0].querySelector('span').textContent).to.equal('取消');
+ expect(footerBtns[1].querySelector('span').textContent).to.equal('确定');
+ });
+
+ it('should append to body, when append-to-body flag is true', async() => {
+ vm = createVue(
+ {
+ template: `
+
+ content
+
+ `,
+ data() {
+ return {
+ title,
+ visible: true
+ };
+ }
+ },
+ true
+ );
+ await waitImmediate();
+ expect(vm.$el.parentNode).to.equal(document.body);
+ });
+
+ it('should open and close drawer properly', async() => {
+ vm = createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data() {
+ return {
+ title,
+ visible: false
+ };
+ }
+ });
+ let drawer = vm.$children[0].$el;
+ expect(drawer.style.display).to.equal('none');
+ vm.visible = true;
+ await waitImmediate();
+ expect(drawer.style.display).not.to.equal('none');
+ vm.visible = false;
+ await wait(400);
+ expect(drawer.style.display).to.equal('none');
+ });
+
+ it('should destroy every child after drawer was closed when destroy-on-close flag is true', async() => {
+ vm = createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data() {
+ return {
+ title,
+ visible: true
+ };
+ }
+ });
+
+ await waitImmediate();
+ expect(vm.$el.querySelector('.el-drawer__body span').textContent).to.equal(
+ content
+ );
+ vm.$refs.drawer.closeDrawer();
+ await wait(400);
+ expect(vm.$el.querySelector('.el-drawer__body')).not.to.exist;
+ });
+
+ it('should close dialog by clicking the close button', async() => {
+ vm = createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data() {
+ return {
+ title,
+ visible: true
+ };
+ }
+ });
+
+ await waitImmediate();
+ vm.$children[0].$el.querySelector('.el-drawer__close-btn').click();
+ expect(vm.visible).to.equal(false);
+ });
+
+ it('should invoke before-close', async() => {
+ const beforeClose = sinon.spy();
+ vm = createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data() {
+ return {
+ title,
+ visible: true,
+ beforeClose
+ };
+ }
+ });
+
+ await waitImmediate();
+ vm.$refs.drawer.closeDrawer();
+ await waitImmediate();
+ expect(beforeClose.called).to.be.true;
+ });
+
+ it('should not show close button when show-close flag is false', async() => {
+ vm = createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data() {
+ return {
+ title,
+ visible: false
+ };
+ }
+ });
+ expect(vm.$el.querySelector('.el-drawer__close-btn')).not.to.exist;
+ });
+
+ it('should have custom classes when custom classes were given', async() => {
+ const classes = 'some-custom-class';
+ vm = createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data() {
+ return {
+ title,
+ visible: false
+ };
+ }
+ });
+
+ expect(vm.$el.querySelector(`.${classes}`)).to.exist;
+ });
+
+ describe('directions', () => {
+ const renderer = direction => {
+ return createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data: {
+ visible: true,
+ title
+ }
+ });
+ };
+ it('should render from left to right', async() => {
+ vm = renderer('ltr');
+ await waitImmediate();
+ expect(vm.$el.querySelector('.ltr')).to.exist;
+ });
+
+ it('should render from right to left', async() => {
+ vm = renderer('rtl');
+ await waitImmediate();
+ expect(vm.$el.querySelector('.rtl')).to.exist;
+ });
+
+ it('should render from top to bottom', async() => {
+ vm = renderer('ttb');
+ await waitImmediate();
+ expect(vm.$el.querySelector('.ttb')).to.exist;
+ });
+
+ it('should render from bottom to top', async() => {
+ vm = renderer('btt');
+ await waitImmediate();
+ expect(vm.$el.querySelector('.btt')).to.exist;
+ });
+ });
+
+ it('events', async() => {
+ const open = sinon.spy();
+ const opened = sinon.spy();
+ const close = sinon.spy();
+ const closed = sinon.spy();
+
+ vm = createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data() {
+ return {
+ content,
+ visible: false,
+ title
+ };
+ },
+ methods: {
+ close,
+ closed,
+ open,
+ opened
+ }
+ });
+ vm.visible = true;
+ await wait(400);
+ expect(open.called).to.be.true;
+ expect(opened.called).to.be.true;
+ expect(close.called).to.be.false;
+ expect(closed.called).to.be.false;
+ vm.visible = false;
+ await wait(500);
+ expect(close.called).to.be.true;
+ expect(closed.called).to.be.true;
+ });
+
+ describe('size', () => {
+ const renderer = (size, isVertical) =>
+ createVue({
+ template: `
+
+ ${content}
+
+ `,
+ data: {
+ visible: true,
+ title
+ }
+ });
+
+ it('should effect height when drawer is vertical', async() => {
+ const size = '50%';
+ vm = renderer(size, true);
+
+ expect(vm.$el.querySelector('.el-drawer').style.width).to.equal('50%');
+ });
+
+ it('should effect width when drawer is horizontal', async() => {
+ const size = '50%';
+ vm = renderer(size, false);
+ expect(vm.$el.querySelector('.el-drawer').style.height).to.equal('50%');
+ });
+ });
+});
diff --git a/types/drawer.d.ts b/types/drawer.d.ts
new file mode 100644
index 0000000000..8d2d7f3a52
--- /dev/null
+++ b/types/drawer.d.ts
@@ -0,0 +1,63 @@
+import { ElementUIComponent } from './component'
+import { VNode } from 'vue'
+
+type hide = (shouldCancel: boolean) => void
+declare enum Direction {
+ LTR = 'ltr', // left to right
+ RTL = 'rtl', // right to left
+ TTB = 'ttb', // top to bottom
+ BTT = 'btt' // bottom to top
+}
+
+interface DrawerSlots {
+ /* Main Content Slots */
+ default: VNode[];
+
+ /* Title Slots */
+ title: VNode[];
+
+ [key: string]: VNode[]
+}
+/** Drawer Component */
+export declare class ElDrawer extends ElementUIComponent {
+ /* Equivalent to `Dialog`'s append to body attribute, when applying nested drawer, make sure this one is set to true */
+ appendToBody: boolean
+
+ /* Hook method called before close drawer, the first parameter is a function which should determine if the drawer should be closed */
+ beforeClose: (done: hide) => void
+
+ /** Whether the Drawer can be closed by pressing ESC */
+ closeOnPressEscape: boolean
+
+ /** Custom class names for Dialog */
+ customClass: string
+
+ /* Determine whether the wrapped children should be destroyed, if true, children's destroyed life cycle method will be called all local state will be destroyed */
+ destroyOnClose: boolean
+
+ /* Equivalent to `Dialog`'s modal attribute, determines whether the dark shadowing background should show */
+ modal: boolean
+
+ /* Equivalent to `Dialog`'s modal-append-to-body attribute, determines whether the shadowing background should be inserted direct to DocumentBody element */
+ modalAppendToBody: boolean
+
+ /* Attributes that controls the drawer's direction of display*/
+ position: Direction
+
+ /* Whether the close button should be rendered to control the drawer's visible state */
+ showClose: boolean
+
+ /* The size of the drawer component, supporting number with unit of pixel, string by percentage e.g. 30% */
+ size: number | string
+
+ /* The Drawer's title, also can be replaced by named slot `title` */
+ title: string
+
+ /* Whether the drawer component should show, also can be decorated by `.sync` */
+ visible: boolean
+
+ /* Flag attribute whi */
+ wrapperClosable: boolean
+
+ $slots: DrawerSlots
+}
diff --git a/types/element-ui.d.ts b/types/element-ui.d.ts
index 257fa5e0ec..b23fead134 100644
--- a/types/element-ui.d.ts
+++ b/types/element-ui.d.ts
@@ -78,6 +78,7 @@ import { ElBacktop } from './backtop'
import { ElInfiniteScroll } from './infinite-scroll'
import { ElPageHeader } from './page-header'
import { ElAvatar } from './avatar'
+import { ElDrawer } from './drawer'
export interface InstallationOptions {
locale: any,
@@ -336,3 +337,6 @@ export class PageHeader extends ElPageHeader {}
/** Avatar Component */
export class Avatar extends ElAvatar {}
+
+/** Drawer Component */
+export class Drawer extends ElDrawer {}