diff --git a/components/drawer/Drawer.vue b/components/drawer/Drawer.vue
index d17cb699b..2ec8b0993 100644
--- a/components/drawer/Drawer.vue
+++ b/components/drawer/Drawer.vue
@@ -3,8 +3,30 @@
:class="classes"
class="mdc-drawer"
@MDCDrawer:closed="onClosed"
+ @MDCDrawer:opened="onOpened"
>
-
+
+
@@ -31,11 +53,20 @@ export default {
open: {
type: Boolean,
default: false
+ },
+ title: {
+ type: String,
+ default: ''
+ },
+ subtitle: {
+ type: String,
+ default: ''
}
},
data () {
return {
- mdcDrawer: undefined
+ mdcDrawer: undefined,
+ hasHeaderClass: false
}
},
computed: {
@@ -56,17 +87,39 @@ export default {
},
watch: {
modal () {
- if (!this.mdcDrawer) this.mdcDrawer = new MDCDrawer(this.$el)
+ if (!this.modal) {
+ this.mdcDrawer.destroy()
+ this.mdcDrawer = undefined
+ } else {
+ this.$nextTick(function () {
+ this.mdcDrawer = new MDCDrawer(this.$el)
+ })
+ }
},
dismissible () {
- if (!this.mdcDrawer) this.mdcDrawer = new MDCDrawer(this.$el)
+ if (!this.dismissible) {
+ this.mdcDrawer.destroy()
+ this.mdcDrawer = undefined
+ } else {
+ this.$nextTick(function () {
+ this.mdcDrawer = new MDCDrawer(this.$el)
+ })
+ }
},
open () {
- this.mdcDrawer.open = this.open
+ if (this.mdcDrawer.open !== this.open) this.mdcDrawer.open = this.open
+ },
+ 'mdcDrawer.open': function () {
+ this.model = this.mdcDrawer.open
}
},
mounted () {
- if (!this.mdcDrawer && (this.dismissible || this.modal)) { this.mdcDrawer = new MDCDrawer(this.$el) }
+ // the slots can only be accessible in nextTick
+ this.$nextTick(function () {
+ // judge whether the slots have the header class
+ this.hasHeaderClass = this.$slots.header[0].elm.classList.contains('mdc-drawer__header')
+ if (!this.mdcDrawer && (this.dismissible || this.modal)) { this.mdcDrawer = new MDCDrawer(this.$el) }
+ })
},
provide: {
mdcDrawer: this
@@ -76,8 +129,11 @@ export default {
},
methods: {
onClosed () {
- this.model = false
+ if (this.model) this.model = false
this.$emit('closed')
+ },
+ onOpened () {
+ this.$emit('opened')
}
}
}
diff --git a/components/drawer/DrawerContent.vue b/components/drawer/DrawerContent.vue
index f6cfd5f5c..bc2eefde7 100644
--- a/components/drawer/DrawerContent.vue
+++ b/components/drawer/DrawerContent.vue
@@ -2,6 +2,7 @@
diff --git a/components/drawer/DrawerHeader.vue b/components/drawer/DrawerHeader.vue
index c51e485c8..feca6dcf9 100644
--- a/components/drawer/DrawerHeader.vue
+++ b/components/drawer/DrawerHeader.vue
@@ -1,6 +1,6 @@
@@ -29,7 +29,7 @@ export default {
type: String,
default: ''
},
- subTitle: {
+ subtitle: {
type: String,
default: ''
}
diff --git a/components/drawer/README.md b/components/drawer/README.md
index c493ced6c..18b70b609 100644
--- a/components/drawer/README.md
+++ b/components/drawer/README.md
@@ -1,29 +1,279 @@
## Drawer
-* [DrawerPermanent](DrawerPermanent/README.md)
-* [DrawerPersistent](DrawerPersistent/README.md)
-* [DrawerTemporary](DrawerTemporary/README.md)
+### Markup
-## DrawerContent
+#### Minimal
+
+```html
+
+
+
+
+
+ Inbox
+
+
+
+ Star
+
+
+
+
+```
+
+#### With Header
+
+Following examples are rendered completely the same.
+
+```html
+
+
+
+
+
+```
+
+```html
+
+
+
+
+
+
+
+
+```
+
+```html
+
+
+ title
+ subtitle
+
+
+
+
+
+
+```
+
+```html
+
+
+ title
+ subtitle
+
+
+
+
+
+
+```
+
+You may find the first two ones easier to write and less verbose, and the last two ones great for customization.
+
+#### Dismissible
+
+```html
+
+
+
+
+
+
+
+```
+
+```js
+data () {
+ return {
+ open: false // change to true to open the drawer
+ }
+}
+```
+
+#### Dismissible With Top App Bar
+
+```html
+
+
+
+
+
+
+
+
+
+
+```
+
+#### Dismissible Below Top App Bar
+
+```html
+
+
+
+
+
+
+
+
+
+
+```
+
+#### Modal
+
+```html
+
+
+
+Main Content
+```
+
+```js
+data () {
+ return {
+ open: false // change to true to open the drawer
+ }
+}
+```
### Slots
| Slot | Description |
|------|-------------|
-| default | main content |
+| header | header content |
+| default | main content, usually `` |
+
+### Events
+
+| Event | Payload | Description |
+|-------|---------|-------------|
+| opened | {} | emitted when the navigation drawer is opened |
+| closed | {} | emitted when the navigation drawer is closed |
+
+### Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| title | String | '' | title in the header |
+| subtitle | String | '' | subtitle in the header |
+| modal | Boolean | '' | display drawer in modal variant |
+| dismissible | Boolean | '' | display drawer in dismissible variant |
+
+`modal` and `dismissible` can not be both true at the same times. When both are false, the drawer is shown permanently.
+
+## Drawer Content
-## DrawerHeader
+Scrollable content area of the drawer. Usually placed in the default slot in ``.
### Slots
| Slot | Description |
|------|-------------|
-| default | header content |
+| default | main content, usually `` |
+
+## Drawer Header
-## DrawerToolbarSpacer
+Non-scrollable element that exists at the top of the drawer.
### Slots
| Slot | Description |
|------|-------------|
-| default | toolbarSpacer content |
\ No newline at end of file
+| default | header content |
+
+### Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| title | String | '' | title |
+| subtitle | String | '' | subtitle |
+
+## Drawer Scrim
+
+Mandatory for modal variant only. Used for the overlay on the app content.
+
+### Markup
+
+```html
+
+
+
+```
+
+## Drawer App Content
+
+Mandatory for dismissible variant only. Sibling element that is resized when the drawer opens/closes.
+
+### Markup
+
+```html
+
+
+
+
+```
+
+## Accessibility
+
+### Focus Management
+
+It is recommended to shift focus to the first focusable element in the main content when drawer is closed or one of the destination items is activated. (By default, MDC Drawer restores focus to the menu button which opened it.)
+
+#### Dismissible Drawer
+
+Restore focus to the first focusable element when a list item is activated or after the drawer closes. Do not close the drawer upon item activation, since it should be up to the user when to show/hide the dismissible drawer.
+
+```html
+
+
+
+
+
+
+
+
+```
+
+```js
+onClosed () {
+ this.$refs.mainContent.$el.querySelector('input, button').focus();
+},
+onClick () {
+ this.$refs.mainContent.$el.querySelector('input, button').focus();
+}
+```
+
+#### Modal Drawer
+
+```html
+
+
+
+
+
+
+
+
+```
+
+```js
+data () {
+ return { open: false }
+},
+methods: {
+ onClosed () {
+ this.$refs.mainContent.$el.querySelector('input, button').focus();
+ },
+ onClick () {
+ this.open = false
+ }
+}
+```
+
+## Reference
+
+- https://github.com/material-components/material-components-web/tree/master/packages/mdc-drawer
+- https://github.com/matsp/material-components-vue/tree/master/components/list
\ No newline at end of file
diff --git a/components/drawer/__test__/Drawer.spec.js b/components/drawer/__test__/Drawer.spec.js
new file mode 100644
index 000000000..4f8e9e946
--- /dev/null
+++ b/components/drawer/__test__/Drawer.spec.js
@@ -0,0 +1,160 @@
+import 'mutationobserver-shim'
+import { mount } from '@vue/test-utils'
+import Drawer from '../Drawer.vue'
+import DrawerAppContent from '../DrawerAppContent'
+import DrawerHeader from '../DrawerHeader'
+import DrawerContent from '../DrawerContent'
+import DrawerScrim from '../DrawerScrim'
+
+describe('Drawer', () => {
+ it('should mount', () => {
+ const wrapper = mount(Drawer)
+ expect(wrapper.isVueInstance()).toBeTruthy()
+ })
+
+ it('should render with no prop', () => {
+ const wrapper = mount(Drawer)
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('mdc-drawer')
+ })
+
+ it('should render as modal', () => {
+ const wrapper = mount(Drawer, {
+ propsData: {
+ modal: true
+ }
+ })
+
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('mdc-drawer--modal')
+ })
+
+ it('should render as dismissible', () => {
+ const wrapper = mount(Drawer, {
+ propsData: {
+ dismissible: true
+ }
+ })
+
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('mdc-drawer--dismissible')
+ })
+
+ it('should render with title', () => {
+ const wrapper = mount(Drawer, {
+ propsData: {
+ title: 'title'
+ }
+ })
+
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.find('.mdc-drawer__header').exists()).toBe(true)
+ expect(wrapper.find('h3').exists()).toBe(true)
+ expect(wrapper.find('h3').classes()).toContain('mdc-drawer__title')
+ expect(wrapper.find('h3').text()).toBe('title')
+ })
+
+ it('should render with subtitle', () => {
+ const wrapper = mount(Drawer, {
+ propsData: {
+ subtitle: 'title'
+ }
+ })
+
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.find('.mdc-drawer__header').exists()).toBe(true)
+ expect(wrapper.find('h6').exists()).toBe(true)
+ expect(wrapper.find('h6').classes()).toContain('mdc-drawer__subtitle')
+ expect(wrapper.find('h6').text()).toBe('title')
+ })
+})
+
+describe('DrawerAppContent', () => {
+ it('should mount', () => {
+ const wrapper = mount(DrawerAppContent)
+ expect(wrapper.isVueInstance()).toBeTruthy()
+ })
+
+ it('should render with no prop', () => {
+ const wrapper = mount(DrawerAppContent)
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('mdc-drawer-app-content')
+ })
+})
+
+describe('DrawerScrim', () => {
+ it('should mount', () => {
+ const wrapper = mount(DrawerScrim)
+ expect(wrapper.isVueInstance()).toBeTruthy()
+ })
+
+ it('should render with no prop', () => {
+ const wrapper = mount(DrawerScrim)
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('mdc-drawer-scrim')
+ })
+})
+
+describe('DrawerHeader', () => {
+ it('should mount', () => {
+ const wrapper = mount(DrawerHeader)
+ expect(wrapper.isVueInstance()).toBeTruthy()
+ })
+
+ it('should render with no prop', () => {
+ const wrapper = mount(DrawerHeader, {
+ slots: {
+ default: '123'
+ }
+ })
+
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('mdc-drawer__header')
+ expect(wrapper.text()).toBe('123')
+ })
+
+ it('should render with title', () => {
+ const wrapper = mount(DrawerHeader, {
+ propsData: {
+ title: 'title'
+ }
+ })
+
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.find('h3').exists()).toBe(true)
+ expect(wrapper.find('h3').classes()).toContain('mdc-drawer__title')
+ expect(wrapper.find('h3').text()).toBe('title')
+ })
+
+ it('should render with subtitle', () => {
+ const wrapper = mount(DrawerHeader, {
+ propsData: {
+ subtitle: 'title'
+ }
+ })
+
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.find('h6').exists()).toBe(true)
+ expect(wrapper.find('h6').classes()).toContain('mdc-drawer__subtitle')
+ expect(wrapper.find('h6').text()).toBe('title')
+ })
+})
+
+describe('DrawerContent', () => {
+ it('should mount', () => {
+ const wrapper = mount(DrawerContent)
+ expect(wrapper.isVueInstance()).toBeTruthy()
+ })
+
+ it('should render with no prop', () => {
+ const wrapper = mount(DrawerContent, {
+ slots: {
+ default: '123'
+ }
+ })
+
+ expect(wrapper).toMatchSnapshot()
+ expect(wrapper.classes()).toContain('mdc-drawer__content')
+ expect(wrapper.text()).toBe('123')
+ })
+})
diff --git a/components/drawer/__test__/__snapshots__/Drawer.spec.js.snap b/components/drawer/__test__/__snapshots__/Drawer.spec.js.snap
new file mode 100644
index 000000000..37e1f91f2
--- /dev/null
+++ b/components/drawer/__test__/__snapshots__/Drawer.spec.js.snap
@@ -0,0 +1,80 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Drawer should render as dismissible 1`] = `
+
+`;
+
+exports[`Drawer should render as modal 1`] = `
+
+`;
+
+exports[`Drawer should render with no prop 1`] = `
+
+`;
+
+exports[`Drawer should render with subtitle 1`] = `
+
+`;
+
+exports[`Drawer should render with title 1`] = `
+
+`;
+
+exports[`DrawerAppContent should render with no prop 1`] = ``;
+
+exports[`DrawerContent should render with no prop 1`] = `123
`;
+
+exports[`DrawerHeader should render with no prop 1`] = `
+
+`;
+
+exports[`DrawerHeader should render with subtitle 1`] = `
+
+`;
+
+exports[`DrawerHeader should render with title 1`] = `
+
+`;
+
+exports[`DrawerScrim should render with no prop 1`] = ``;