chore: moved lion-drawer to correct directories for new lion/ui setup
This commit is contained in:
parent
3cc3d2960f
commit
c7ea03577c
20 changed files with 1255 additions and 5 deletions
10
.changeset/giant-scissors-grab.md
Normal file
10
.changeset/giant-scissors-grab.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
'@lion/ui/checkbox-group': patch
|
||||
'@lion/ui/core': patch
|
||||
'@lion/ui/drawer': patch
|
||||
'@lion/ui/form-core': patch
|
||||
'@lion/ui/input-tel': patch
|
||||
'@lion/ui/input-tel-dropdown': patch
|
||||
---
|
||||
|
||||
replaced import('lit-element') with import('lit') to fix tests, fixed test for SlotMixin
|
||||
5
.changeset/hot-poets-shout.md
Normal file
5
.changeset/hot-poets-shout.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/ui/drawer': patch
|
||||
---
|
||||
|
||||
implemented lion-drawer
|
||||
3
docs/components/drawer/index.md
Normal file
3
docs/components/drawer/index.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Drawer ||20
|
||||
|
||||
-> go to Overview
|
||||
68
docs/components/drawer/overview.md
Normal file
68
docs/components/drawer/overview.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Drawer >> Overview ||10
|
||||
|
||||
A combination of a button (the invoker) and a chunk of 'extra content'. This web component can be extended with an
|
||||
animation to disclose the extra content.
|
||||
|
||||
There are three slots available respectively; `invoker` to specify the
|
||||
drawer's invoker, `headline` for the optional headline and `content` for the content of the drawer.
|
||||
|
||||
Through the `position` property, the drawer can be placed on the `top`, `left` or `right` of the viewport.
|
||||
|
||||
```js script
|
||||
import { html } from '@mdjs/mdjs-preview';
|
||||
import '@lion/ui/define/lion-drawer.js';
|
||||
import '@lion/ui/define/lion-icon.js';
|
||||
import { icons } from '@lion/ui/icon.js';
|
||||
import { demoStyle } from './src/demoStyle.js';
|
||||
|
||||
icons.addIconResolver('lion', (iconset, name) => {
|
||||
switch (iconset) {
|
||||
case 'misc':
|
||||
return import('../icon/assets/iconset-misc.js').then(module => module[name]);
|
||||
default:
|
||||
throw new Error(`Unknown iconset ${iconset}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```js preview-story
|
||||
export const main = () => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<div class="demo-container">
|
||||
<lion-drawer>
|
||||
<button slot="invoker">
|
||||
<lion-icon icon-id="lion:misc:arrowLeft" style="width: 16px; height: 16px;"></lion-icon>
|
||||
</button>
|
||||
<p slot="headline">Headline</p>
|
||||
<div slot="content" class="drawer">Hello! This is the content of the drawer</div>
|
||||
<button slot="bottom-invoker">
|
||||
<lion-icon icon-id="lion:misc:arrowLeft" style="width: 16px; height: 16px;"></lion-icon>
|
||||
</button>
|
||||
</lion-drawer>
|
||||
<div>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum convallis, lorem sit amet
|
||||
sollicitudin egestas, dui lectus sodales leo, quis luctus nulla metus vitae lacus. In at
|
||||
imperdiet augue. Mauris mauris dolor, faucibus non nulla vel, vulputate hendrerit mauris.
|
||||
Praesent dapibus leo nec libero scelerisque, ac venenatis ante tincidunt. Nulla maximus
|
||||
vestibulum orci, ac viverra nisi molestie vel. Vivamus eget elit et turpis elementum tempor
|
||||
ultricies at turpis. Ut pretium aliquet finibus. Duis ullamcorper ultrices velit id luctus.
|
||||
Phasellus in ex luctus, interdum ex vel, eleifend dolor. Cras massa odio, sodales quis
|
||||
consectetur a, blandit eu purus. Donec ut gravida libero, sed accumsan arcu.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm i --save @lion/ui
|
||||
```
|
||||
|
||||
```js
|
||||
import { LionDrawer } from '@lion/ui/drawer.js';
|
||||
// or
|
||||
import '@lion/ui/define/lion-drawer.js';
|
||||
```
|
||||
71
docs/components/drawer/src/demoStyle.js
Normal file
71
docs/components/drawer/src/demoStyle.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { css } from 'lit';
|
||||
|
||||
export const demoStyle = css`
|
||||
.demo-container {
|
||||
height: 400px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.demo-container > div {
|
||||
padding: 8px;
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
lion-drawer {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
button {
|
||||
all: revert !important;
|
||||
border: 2px solid #000000;
|
||||
background-color: rgb(239, 239, 239);
|
||||
}
|
||||
|
||||
.demo-container-top {
|
||||
height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.demo-container-top > div {
|
||||
padding: 8px;
|
||||
height: 100%;
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.demo-container-top lion-drawer {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.demo-container-right {
|
||||
height: 400px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.demo-container-right > div {
|
||||
padding: 8px;
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.demo-container-right lion-drawer {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.demo-container-opened {
|
||||
height: 400px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.demo-container-opened > div {
|
||||
padding: 8px;
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.demo-container-opened lion-drawer {
|
||||
height: 400px;
|
||||
}
|
||||
`;
|
||||
194
docs/components/drawer/use-cases.md
Normal file
194
docs/components/drawer/use-cases.md
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
# Drawer >> Use Cases ||20
|
||||
|
||||
```js script
|
||||
import { html } from '@mdjs/mdjs-preview';
|
||||
import { icons } from '@lion/ui/icon.js';
|
||||
import '@lion/ui/define/lion-drawer.js';
|
||||
import '@lion/ui/define/lion-icon.js';
|
||||
import { demoStyle } from './src/demoStyle.js';
|
||||
|
||||
icons.addIconResolver('lion', (iconset, name) => {
|
||||
switch (iconset) {
|
||||
case 'misc':
|
||||
return import('../icon/assets/iconset-misc.js').then(module => module[name]);
|
||||
default:
|
||||
throw new Error(`Unknown iconset ${iconset}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Positioning
|
||||
|
||||
### Default left
|
||||
|
||||
By default, the drawer is positioned on the left side of the viewport.
|
||||
|
||||
With the `position` property it can be positioned at the top or on the right of the screen.
|
||||
|
||||
### Top
|
||||
|
||||
```js preview-story
|
||||
export const top = () => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<div class="demo-container-top">
|
||||
<lion-drawer position="top">
|
||||
<button slot="invoker">
|
||||
<lion-icon icon-id="lion:misc:arrowLeft" style="width: 16px; height: 16px;"></lion-icon>
|
||||
</button>
|
||||
<p slot="headline">Headline</p>
|
||||
<div slot="content" class="drawer">Hello! This is the content of the drawer</div>
|
||||
</lion-drawer>
|
||||
<div>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum convallis, lorem sit amet
|
||||
sollicitudin egestas, dui lectus sodales leo, quis luctus nulla metus vitae lacus. In at
|
||||
imperdiet augue. Mauris mauris dolor, faucibus non nulla vel, vulputate hendrerit mauris.
|
||||
Praesent dapibus leo nec libero scelerisque, ac venenatis ante tincidunt. Nulla maximus
|
||||
vestibulum orci, ac viverra nisi molestie vel. Vivamus eget elit et turpis elementum tempor
|
||||
ultricies at turpis. Ut pretium aliquet finibus. Duis ullamcorper ultrices velit id luctus.
|
||||
Phasellus in ex luctus, interdum ex vel, eleifend dolor. Cras massa odio, sodales quis
|
||||
consectetur a, blandit eu purus. Donec ut gravida libero, sed accumsan arcu.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
```
|
||||
|
||||
### Right
|
||||
|
||||
```js preview-story
|
||||
export const right = () => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<div class="demo-container-right">
|
||||
<lion-drawer position="right">
|
||||
<button slot="invoker">
|
||||
<lion-icon icon-id="lion:misc:arrowLeft" style="width: 16px; height: 16px;"></lion-icon>
|
||||
</button>
|
||||
<p slot="headline">Headline</p>
|
||||
<div slot="content" class="drawer">Hello! This is the content of the drawer</div>
|
||||
</lion-drawer>
|
||||
<div>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum convallis, lorem sit amet
|
||||
sollicitudin egestas, dui lectus sodales leo, quis luctus nulla metus vitae lacus. In at
|
||||
imperdiet augue. Mauris mauris dolor, faucibus non nulla vel, vulputate hendrerit mauris.
|
||||
Praesent dapibus leo nec libero scelerisque, ac venenatis ante tincidunt. Nulla maximus
|
||||
vestibulum orci, ac viverra nisi molestie vel. Vivamus eget elit et turpis elementum tempor
|
||||
ultricies at turpis. Ut pretium aliquet finibus. Duis ullamcorper ultrices velit id luctus.
|
||||
Phasellus in ex luctus, interdum ex vel, eleifend dolor. Cras massa odio, sodales quis
|
||||
consectetur a, blandit eu purus. Donec ut gravida libero, sed accumsan arcu.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
```
|
||||
|
||||
## Opened
|
||||
|
||||
Add the `opened` attribute to display the drawer opened.
|
||||
|
||||
```js preview-story
|
||||
export const opened = () => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<div class="demo-container">
|
||||
<lion-drawer opened>
|
||||
<p slot="headline">Headline</p>
|
||||
<div slot="content" class="drawer">Hello! This is the content of the drawer</div>
|
||||
</lion-drawer>
|
||||
<div>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum convallis, lorem sit amet
|
||||
sollicitudin egestas, dui lectus sodales leo, quis luctus nulla metus vitae lacus. In at
|
||||
imperdiet augue. Mauris mauris dolor, faucibus non nulla vel, vulputate hendrerit mauris.
|
||||
Praesent dapibus leo nec libero scelerisque, ac venenatis ante tincidunt. Nulla maximus
|
||||
vestibulum orci, ac viverra nisi molestie vel. Vivamus eget elit et turpis elementum tempor
|
||||
ultricies at turpis. Ut pretium aliquet finibus. Duis ullamcorper ultrices velit id luctus.
|
||||
Phasellus in ex luctus, interdum ex vel, eleifend dolor. Cras massa odio, sodales quis
|
||||
consectetur a, blandit eu purus. Donec ut gravida libero, sed accumsan arcu.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
There are the following methods available to control the extra content for the drawer.
|
||||
|
||||
- `toggle()`: toggle the extra content
|
||||
- `show()`: show the extra content
|
||||
- `hide()`: hide the extra content
|
||||
|
||||
```js preview-story
|
||||
export const methods = ({ shadowRoot }) => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<section style="margin-top:16px">
|
||||
<button @click=${() => shadowRoot.querySelector('#drawer').toggle()}>Toggle content</button>
|
||||
<button @click=${() => shadowRoot.querySelector('#drawer').show()}>Show content</button>
|
||||
<button @click=${() => shadowRoot.querySelector('#drawer').hide()}>Hide content</button>
|
||||
</section>
|
||||
|
||||
<div class="demo-container">
|
||||
<lion-drawer id="drawer">
|
||||
<button slot="invoker">
|
||||
<lion-icon icon-id="lion:misc:arrowLeft" style="width: 16px; height: 16px;"></lion-icon>
|
||||
</button>
|
||||
<p slot="headline">Headline</p>
|
||||
<div slot="content" class="drawer">Hello! This is the content of the drawer</div>
|
||||
</lion-drawer>
|
||||
<div>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum convallis, lorem sit amet
|
||||
sollicitudin egestas, dui lectus sodales leo, quis luctus nulla metus vitae lacus. In at
|
||||
imperdiet augue. Mauris mauris dolor, faucibus non nulla vel, vulputate hendrerit mauris.
|
||||
Praesent dapibus leo nec libero scelerisque, ac venenatis ante tincidunt. Nulla maximus
|
||||
vestibulum orci, ac viverra nisi molestie vel. Vivamus eget elit et turpis elementum tempor
|
||||
ultricies at turpis. Ut pretium aliquet finibus. Duis ullamcorper ultrices velit id luctus.
|
||||
Phasellus in ex luctus, interdum ex vel, eleifend dolor. Cras massa odio, sodales quis
|
||||
consectetur a, blandit eu purus. Donec ut gravida libero, sed accumsan arcu.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
`lion-drawer` fires an event on `invoker` click to notify the component's current state. It is useful for analytics purposes or to perform some actions while expanding and collapsing the component.
|
||||
|
||||
- `@opened-changed`: triggers when drawer either gets opened or closed
|
||||
|
||||
```js preview-story
|
||||
export const events = ({ shadowRoot }) => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<div class="demo-custom-collapsible-state-container">
|
||||
<strong id="collapsible-state"></strong>
|
||||
</div>
|
||||
<div class="demo-container">
|
||||
<lion-drawer
|
||||
@opened-changed=${ev => {
|
||||
const collapsibleState = shadowRoot.querySelector('#collapsible-state');
|
||||
collapsibleState.innerText = `Opened: ${ev.target.opened}`;
|
||||
}}
|
||||
>
|
||||
<button slot="invoker">
|
||||
<lion-icon icon-id="lion:misc:arrowLeft" style="width: 16px; height: 16px;"></lion-icon>
|
||||
</button>
|
||||
<p slot="headline">Headline</p>
|
||||
<div slot="content" class="drawer">Hello! This is the content of the drawer</div>
|
||||
</lion-drawer>
|
||||
<div>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum convallis, lorem sit amet
|
||||
sollicitudin egestas, dui lectus sodales leo, quis luctus nulla metus vitae lacus. In at
|
||||
imperdiet augue. Mauris mauris dolor, faucibus non nulla vel, vulputate hendrerit mauris.
|
||||
Praesent dapibus leo nec libero scelerisque, ac venenatis ante tincidunt. Nulla maximus
|
||||
vestibulum orci, ac viverra nisi molestie vel. Vivamus eget elit et turpis elementum tempor
|
||||
ultricies at turpis. Ut pretium aliquet finibus. Duis ullamcorper ultrices velit id luctus.
|
||||
Phasellus in ex luctus, interdum ex vel, eleifend dolor. Cras massa odio, sodales quis
|
||||
consectetur a, blandit eu purus. Donec ut gravida libero, sed accumsan arcu.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
```
|
||||
65
package-lock.json
generated
65
package-lock.json
generated
|
|
@ -2880,6 +2880,28 @@
|
|||
"resolved": "packages/ajax",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@lion/collapsible": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@lion/collapsible/-/collapsible-0.9.1.tgz",
|
||||
"integrity": "sha512-M+62Un4HQMhTzdG885TBJL52Ji7TSNPYAg0+FQk6kb3QHdz255lWkRTp6G94w1iE1K6IsYO1g+KyibXvtEbbug==",
|
||||
"dependencies": {
|
||||
"@lion/core": "^0.24.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lion/core": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@lion/core/-/core-0.24.0.tgz",
|
||||
"integrity": "sha512-hC5Fpi5U3PY0HOVycSev1jzoE8DYHFSN42s5gt6g6RlvvRYN5Pou0wtKnDOkOYf1UfjuL+T/4r8W99UFD1r/Eg==",
|
||||
"dependencies": {
|
||||
"@open-wc/dedupe-mixin": "^1.3.0",
|
||||
"@open-wc/scoped-elements": "^2.1.1",
|
||||
"lit": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@lion/drawer": {
|
||||
"resolved": "packages/drawer",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@lion/ui": {
|
||||
"resolved": "packages/ui",
|
||||
"link": true
|
||||
|
|
@ -23199,7 +23221,7 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"packages-node/providence-analytics": {
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.10.1",
|
||||
|
|
@ -23358,7 +23380,7 @@
|
|||
},
|
||||
"packages/ajax": {
|
||||
"name": "@lion/ajax",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/button": {
|
||||
|
|
@ -23449,6 +23471,14 @@
|
|||
"@lion/overlays": "^0.33.2"
|
||||
}
|
||||
},
|
||||
"packages/drawer": {
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lion/collapsible": "^0.9.1",
|
||||
"@lion/core": "^0.24.0"
|
||||
}
|
||||
},
|
||||
"packages/fieldset": {
|
||||
"name": "@lion/fieldset",
|
||||
"version": "0.22.1",
|
||||
|
|
@ -23738,7 +23768,7 @@
|
|||
}
|
||||
},
|
||||
"packages/singleton-manager": {
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/steps": {
|
||||
|
|
@ -23803,7 +23833,7 @@
|
|||
"awesome-phonenumber": "^3.0.1",
|
||||
"ibantools": "^2.2.0",
|
||||
"lit": "^2.4.0",
|
||||
"singleton-manager": "^1.5.0"
|
||||
"singleton-manager": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"packages/validate-messages": {
|
||||
|
|
@ -25903,6 +25933,31 @@
|
|||
"@lion/ajax": {
|
||||
"version": "file:packages/ajax"
|
||||
},
|
||||
"@lion/collapsible": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@lion/collapsible/-/collapsible-0.9.1.tgz",
|
||||
"integrity": "sha512-M+62Un4HQMhTzdG885TBJL52Ji7TSNPYAg0+FQk6kb3QHdz255lWkRTp6G94w1iE1K6IsYO1g+KyibXvtEbbug==",
|
||||
"requires": {
|
||||
"@lion/core": "^0.24.0"
|
||||
}
|
||||
},
|
||||
"@lion/core": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@lion/core/-/core-0.24.0.tgz",
|
||||
"integrity": "sha512-hC5Fpi5U3PY0HOVycSev1jzoE8DYHFSN42s5gt6g6RlvvRYN5Pou0wtKnDOkOYf1UfjuL+T/4r8W99UFD1r/Eg==",
|
||||
"requires": {
|
||||
"@open-wc/dedupe-mixin": "^1.3.0",
|
||||
"@open-wc/scoped-elements": "^2.1.1",
|
||||
"lit": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"@lion/drawer": {
|
||||
"version": "file:packages/drawer",
|
||||
"requires": {
|
||||
"@lion/collapsible": "^0.9.1",
|
||||
"@lion/core": "^0.24.0"
|
||||
}
|
||||
},
|
||||
"@lion/ui": {
|
||||
"version": "file:packages/ui",
|
||||
"requires": {
|
||||
|
|
@ -25913,7 +25968,7 @@
|
|||
"awesome-phonenumber": "^3.0.1",
|
||||
"ibantools": "^2.2.0",
|
||||
"lit": "^2.4.0",
|
||||
"singleton-manager": "^1.5.0"
|
||||
"singleton-manager": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@lit/reactive-element": {
|
||||
|
|
|
|||
3
packages/drawer/docs/overview.md
Normal file
3
packages/drawer/docs/overview.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Lion Drawer Overview
|
||||
|
||||
[=> See Source <=](../../../docs/components/drawer/overview.md)
|
||||
3
packages/drawer/docs/use-cases.md
Normal file
3
packages/drawer/docs/use-cases.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Lion Drawer Use Cases
|
||||
|
||||
[=> See Source <=](../../../docs/components/drawer/use-cases.md)
|
||||
1
packages/drawer/index.js
Normal file
1
packages/drawer/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { LionDrawer } from './src/LionDrawer.js';
|
||||
3
packages/drawer/lion-drawer.js
Normal file
3
packages/drawer/lion-drawer.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { LionDrawer } from './src/LionDrawer.js';
|
||||
|
||||
customElements.define('lion-drawer', LionDrawer);
|
||||
58
packages/drawer/package.json
Normal file
58
packages/drawer/package.json
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"name": "@lion/drawer",
|
||||
"version": "0.0.0",
|
||||
"description": "Drawer that can be expanded to reveal it contents",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
"homepage": "https://github.com/ing-bank/lion/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ing-bank/lion.git",
|
||||
"directory": "packages/drawer"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./define": "./lion-drawer.js",
|
||||
"./docs/*": "./docs/*"
|
||||
},
|
||||
"main": "index.js",
|
||||
"module": "index.js",
|
||||
"files": [
|
||||
"*.d.ts",
|
||||
"*.js",
|
||||
"custom-elements.json",
|
||||
"docs",
|
||||
"src",
|
||||
"test",
|
||||
"test-helpers",
|
||||
"translations",
|
||||
"types"
|
||||
],
|
||||
"scripts": {
|
||||
"custom-elements-manifest": "custom-elements-manifest analyze --litelement --exclude \"docs/**/*\" \"test-helpers/**/*\"",
|
||||
"debug": "cd ../../ && npm run debug -- --group drawer",
|
||||
"debug:firefox": "cd ../../ && npm run debug:firefox -- --group drawer",
|
||||
"debug:webkit": "cd ../../ && npm run debug:webkit -- --group drawer",
|
||||
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../",
|
||||
"prepublishOnly": "npm run publish-docs && npm run custom-elements-manifest",
|
||||
"test": "cd ../../ && npm run test:browser -- --group drawer"
|
||||
},
|
||||
"sideEffects": [
|
||||
"lion-drawer.js",
|
||||
"./docs/styled-drawer-content.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@lion/collapsible": "^0.9.1",
|
||||
"@lion/core": "^0.24.0"
|
||||
},
|
||||
"keywords": [
|
||||
"drawer",
|
||||
"lion",
|
||||
"web-components"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"customElements": "custom-elements.json"
|
||||
}
|
||||
217
packages/drawer/src/LionDrawer.js
Normal file
217
packages/drawer/src/LionDrawer.js
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
import { html } from '@lion/core';
|
||||
import { LionCollapsible } from '@lion/collapsible';
|
||||
import { drawerStyle } from './drawerStyle.js';
|
||||
|
||||
const EVENT = {
|
||||
TRANSITION_END: 'transitionend',
|
||||
TRANSITION_START: 'transitionstart',
|
||||
};
|
||||
|
||||
export class LionDrawer extends LionCollapsible {
|
||||
static get properties() {
|
||||
return {
|
||||
transitioning: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
opened: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
reflect: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/** @private */
|
||||
this.__toggle = () => {
|
||||
this.opened = !this.opened;
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
if (!this.hasAttribute('position')) {
|
||||
this.position = 'left';
|
||||
}
|
||||
|
||||
if (this._contentNode) {
|
||||
this._contentNode.style.setProperty('display', '');
|
||||
}
|
||||
|
||||
this.__setBoundaries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update aria labels on state change.
|
||||
* @param {import('@lion/core').PropertyValues } changedProperties
|
||||
*/
|
||||
updated(changedProperties) {
|
||||
if (changedProperties.has('opened')) {
|
||||
this._openedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [drawerStyle];
|
||||
}
|
||||
|
||||
__setBoundaries() {
|
||||
const host = this.shadowRoot?.host;
|
||||
|
||||
if (this.position === 'top') {
|
||||
this.minHeight = host ? getComputedStyle(host).getPropertyValue('--min-height') : '0px';
|
||||
this.maxHeight = host ? getComputedStyle(host).getPropertyValue('--max-height') : '0px';
|
||||
this.minWidth = '0px';
|
||||
this.maxWidth = 'none';
|
||||
} else {
|
||||
this.minWidth = host ? getComputedStyle(host).getPropertyValue('--min-width') : '0px';
|
||||
this.maxWidth = host ? getComputedStyle(host).getPropertyValue('--max-width') : '0px';
|
||||
this.minHeight = 'auto';
|
||||
this.maxHeight = 'fit-content';
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const prop = this.position === 'top' ? 'width' : 'height';
|
||||
|
||||
if (this.__contentNode) {
|
||||
this.__contentNode.style.setProperty(prop, '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for position property, available values are 'top', 'left' and 'right'
|
||||
* @param {String} position
|
||||
*/
|
||||
set position(position) {
|
||||
const stale = this.position;
|
||||
this._position = position;
|
||||
this.setAttribute('position', position);
|
||||
|
||||
this.__setBoundaries();
|
||||
this.requestUpdate('position', stale);
|
||||
}
|
||||
|
||||
get position() {
|
||||
return this._position ?? 'left';
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger show animation and wait for transition to be finished.
|
||||
* @param {Object} options - element node and its options
|
||||
* @param {HTMLElement} options.contentNode
|
||||
* @override
|
||||
*/
|
||||
async _showAnimation({ contentNode }) {
|
||||
const min = this.position === 'top' ? this.minHeight : this.minWidth;
|
||||
const max = this.position === 'top' ? this.maxHeight : this.maxWidth;
|
||||
const prop = this.position === 'top' ? 'height' : 'width';
|
||||
|
||||
contentNode.style.setProperty(prop, /** @type {string} */ (min));
|
||||
await new Promise(resolve => requestAnimationFrame(() => resolve(true)));
|
||||
contentNode.style.setProperty(prop, /** @type {string} */ (max));
|
||||
await this._waitForTransition({ contentNode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger hide animation and wait for transition to be finished.
|
||||
* @param {Object} options - element node and its options
|
||||
* @param {HTMLElement} options.contentNode
|
||||
* @override
|
||||
*/
|
||||
async _hideAnimation({ contentNode }) {
|
||||
if (
|
||||
((this.position === 'left' || this.position === 'right') &&
|
||||
this._contentWidth === this.minWidth) ||
|
||||
(this.position === 'top' && this._contentHeight === this.minHeight)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const min = this.position === 'top' ? this.minHeight : this.minWidth;
|
||||
const prop = this.position === 'top' ? 'height' : 'width';
|
||||
|
||||
contentNode.style.setProperty(prop, /** @type {string} */ (min));
|
||||
await this._waitForTransition({ contentNode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the transition event is finished.
|
||||
* @param {Object} options - element node and its options
|
||||
* @param {HTMLElement} options.contentNode
|
||||
* @returns {Promise<void>} transition event
|
||||
*/
|
||||
_waitForTransition({ contentNode }) {
|
||||
return new Promise(resolve => {
|
||||
const transitionStarted = () => {
|
||||
contentNode.removeEventListener(EVENT.TRANSITION_START, transitionStarted);
|
||||
this.transitioning = true;
|
||||
};
|
||||
contentNode.addEventListener(EVENT.TRANSITION_START, transitionStarted);
|
||||
|
||||
const transitionEnded = () => {
|
||||
contentNode.removeEventListener(EVENT.TRANSITION_END, transitionEnded);
|
||||
this.transitioning = false;
|
||||
resolve();
|
||||
};
|
||||
contentNode.addEventListener(EVENT.TRANSITION_END, transitionEnded);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
get __contentNode() {
|
||||
return /** @type {HTMLElement} */ (this.shadowRoot?.querySelector('.container'));
|
||||
}
|
||||
|
||||
get _contentWidth() {
|
||||
const size = this.__contentNode?.getBoundingClientRect().width || 0;
|
||||
return `${size}px`;
|
||||
}
|
||||
|
||||
get _contentHeight() {
|
||||
const size = this.__contentNode?.getBoundingClientRect().height || 0;
|
||||
return `${size}px`;
|
||||
}
|
||||
|
||||
_openedChanged() {
|
||||
this._updateContentSize();
|
||||
if (this._invokerNode) {
|
||||
this._invokerNode.setAttribute('aria-expanded', `${this.opened}`);
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent('opened-changed'));
|
||||
}
|
||||
|
||||
async _updateContentSize() {
|
||||
if (this.__contentNode) {
|
||||
if (this.opened) {
|
||||
await this._showAnimation({ contentNode: this.__contentNode });
|
||||
} else {
|
||||
await this._hideAnimation({ contentNode: this.__contentNode });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="headline-container">
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="headline"></slot>
|
||||
</div>
|
||||
<div class="content-container">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
60
packages/drawer/src/drawerStyle.js
Normal file
60
packages/drawer/src/drawerStyle.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { css } from '@lion/core';
|
||||
|
||||
export const drawerStyle = css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
--min-width: 72px;
|
||||
--max-width: 320px;
|
||||
--min-height: auto;
|
||||
--max-height: fit-content;
|
||||
--start-width: var(--min-width);
|
||||
--start-height: 100%;
|
||||
--transition-property: width;
|
||||
}
|
||||
|
||||
:host([position='top']) {
|
||||
width: 100%;
|
||||
--min-width: 0px;
|
||||
--max-width: none;
|
||||
--min-height: 50px;
|
||||
--max-height: 200px;
|
||||
--start-width: 100%;
|
||||
--start-height: var(--min-height);
|
||||
--transition-property: height;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: var(--start-width);
|
||||
height: var(--start-height);
|
||||
min-width: var(--min-width);
|
||||
max-width: var(--max-width);
|
||||
min-height: var(--min-height);
|
||||
max-height: var(--max-height);
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: var(--transition-property) 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
}
|
||||
|
||||
.headline-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
:host([position='right']) .headline-container {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
::slotted([slot='content']) {
|
||||
width: var(--max-width);
|
||||
}
|
||||
`;
|
||||
110
packages/drawer/test/lion-drawer.test.js
Normal file
110
packages/drawer/test/lion-drawer.test.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
import '@lion/drawer/define';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionDrawer').LionDrawer} LionDrawer
|
||||
* @typedef {import('@lion/core').TemplateResult} TemplateResult
|
||||
*/
|
||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionDrawer>} */ (_fixture);
|
||||
|
||||
const template = html`
|
||||
<lion-drawer>
|
||||
<button slot="invoker">Open</button>
|
||||
<p slot="headline">Headline</p>
|
||||
<div slot="content" class="drawer">This is the content of the drawer</div>
|
||||
</lion-drawer>
|
||||
`;
|
||||
|
||||
describe('<lion-drawer>', () => {
|
||||
describe('Drawer', () => {
|
||||
it('sets position to "left" by default', async () => {
|
||||
const drawer = await fixture(template);
|
||||
expect(drawer.position).to.equal('left');
|
||||
});
|
||||
|
||||
it('has [position] attribute which serves as styling hook', async () => {
|
||||
const drawer = await fixture(template);
|
||||
expect(drawer).to.have.attribute('position').equal('left');
|
||||
});
|
||||
|
||||
it('sets the minimum and maximum width when position=left', async () => {
|
||||
const drawer = await fixture(template);
|
||||
const minWidth = getComputedStyle(drawer).getPropertyValue('--min-width');
|
||||
const maxWidth = getComputedStyle(drawer).getPropertyValue('--max-width');
|
||||
|
||||
expect(drawer.minWidth).to.equal(minWidth);
|
||||
expect(drawer.maxWidth).to.equal(maxWidth);
|
||||
});
|
||||
|
||||
it('sets the minimum and maximum width when position=right', async () => {
|
||||
const drawer = await fixture(template);
|
||||
drawer.position = 'right';
|
||||
await drawer.updateComplete;
|
||||
|
||||
const minWidth = getComputedStyle(drawer).getPropertyValue('--min-width');
|
||||
const maxWidth = getComputedStyle(drawer).getPropertyValue('--max-width');
|
||||
|
||||
expect(drawer.minWidth).to.equal(minWidth);
|
||||
expect(drawer.maxWidth).to.equal(maxWidth);
|
||||
});
|
||||
|
||||
it('sets the minimum and maximum height when position=top', async () => {
|
||||
const drawer = await fixture(template);
|
||||
drawer.position = 'top';
|
||||
await drawer.updateComplete;
|
||||
|
||||
const minHeight = getComputedStyle(drawer).getPropertyValue('--min-height');
|
||||
const maxHeight = getComputedStyle(drawer).getPropertyValue('--max-height');
|
||||
|
||||
expect(drawer.minHeight).to.equal(minHeight);
|
||||
expect(drawer.maxHeight).to.equal(maxHeight);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('[collapsed] is a11y AXE accessible', async () => {
|
||||
const drawer = await fixture(template);
|
||||
await expect(drawer).to.be.accessible();
|
||||
});
|
||||
|
||||
it('[opened] is a11y AXE accessible', async () => {
|
||||
const drawer = await fixture(template);
|
||||
drawer.opened = true;
|
||||
await expect(drawer).to.be.accessible();
|
||||
});
|
||||
|
||||
describe('Invoker', () => {
|
||||
it('links id of content items to invoker via [aria-controls]', async () => {
|
||||
const drawerElement = await fixture(template);
|
||||
const invoker = drawerElement.querySelector('[slot=invoker]');
|
||||
const content = drawerElement.querySelector('[slot=content]');
|
||||
expect(invoker?.getAttribute('aria-controls')).to.equal(content?.id);
|
||||
});
|
||||
|
||||
it('adds aria-expanded="false" to invoker when its content is not expanded', async () => {
|
||||
const drawerElement = await fixture(template);
|
||||
const invoker = drawerElement.querySelector('[slot=invoker]');
|
||||
expect(invoker).to.have.attribute('aria-expanded', 'false');
|
||||
});
|
||||
|
||||
it('adds aria-expanded="true" to invoker when its content is expanded', async () => {
|
||||
const drawerElement = await fixture(template);
|
||||
const invoker = drawerElement.querySelector('[slot=invoker]');
|
||||
drawerElement.opened = true;
|
||||
await drawerElement.updateComplete;
|
||||
expect(invoker).to.have.attribute('aria-expanded', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contents', () => {
|
||||
it('adds aria-labelledby referring to invoker id', async () => {
|
||||
const drawerElement = await fixture(template);
|
||||
const invoker = drawerElement.querySelector('[slot=invoker]');
|
||||
const content = drawerElement.querySelector('[slot=content]');
|
||||
expect(content).to.have.attribute('aria-labelledby', invoker?.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
217
packages/ui/components/drawer/src/LionDrawer.js
Normal file
217
packages/ui/components/drawer/src/LionDrawer.js
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
import { html } from 'lit';
|
||||
import { LionCollapsible } from '@lion/ui/collapsible.js';
|
||||
import { drawerStyle } from './drawerStyle.js';
|
||||
|
||||
const EVENT = {
|
||||
TRANSITION_END: 'transitionend',
|
||||
TRANSITION_START: 'transitionstart',
|
||||
};
|
||||
|
||||
export class LionDrawer extends LionCollapsible {
|
||||
static get properties() {
|
||||
return {
|
||||
transitioning: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
opened: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
reflect: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/** @private */
|
||||
this.__toggle = () => {
|
||||
this.opened = !this.opened;
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
if (!this.hasAttribute('position')) {
|
||||
this.position = 'left';
|
||||
}
|
||||
|
||||
if (this._contentNode) {
|
||||
this._contentNode.style.setProperty('display', '');
|
||||
}
|
||||
|
||||
this.__setBoundaries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update aria labels on state change.
|
||||
* @param {import('@lion/core').PropertyValues } changedProperties
|
||||
*/
|
||||
updated(changedProperties) {
|
||||
if (changedProperties.has('opened')) {
|
||||
this._openedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [drawerStyle];
|
||||
}
|
||||
|
||||
__setBoundaries() {
|
||||
const host = this.shadowRoot?.host;
|
||||
|
||||
if (this.position === 'top') {
|
||||
this.minHeight = host ? getComputedStyle(host).getPropertyValue('--min-height') : '0px';
|
||||
this.maxHeight = host ? getComputedStyle(host).getPropertyValue('--max-height') : '0px';
|
||||
this.minWidth = '0px';
|
||||
this.maxWidth = 'none';
|
||||
} else {
|
||||
this.minWidth = host ? getComputedStyle(host).getPropertyValue('--min-width') : '0px';
|
||||
this.maxWidth = host ? getComputedStyle(host).getPropertyValue('--max-width') : '0px';
|
||||
this.minHeight = 'auto';
|
||||
this.maxHeight = 'fit-content';
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const prop = this.position === 'top' ? 'width' : 'height';
|
||||
|
||||
if (this.__contentNode) {
|
||||
this.__contentNode.style.setProperty(prop, '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for position property, available values are 'top', 'left' and 'right'
|
||||
* @param {String} position
|
||||
*/
|
||||
set position(position) {
|
||||
const stale = this.position;
|
||||
this._position = position;
|
||||
this.setAttribute('position', position);
|
||||
|
||||
this.__setBoundaries();
|
||||
this.requestUpdate('position', stale);
|
||||
}
|
||||
|
||||
get position() {
|
||||
return this._position ?? 'left';
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger show animation and wait for transition to be finished.
|
||||
* @param {Object} options - element node and its options
|
||||
* @param {HTMLElement} options.contentNode
|
||||
* @override
|
||||
*/
|
||||
async _showAnimation({ contentNode }) {
|
||||
const min = this.position === 'top' ? this.minHeight : this.minWidth;
|
||||
const max = this.position === 'top' ? this.maxHeight : this.maxWidth;
|
||||
const prop = this.position === 'top' ? 'height' : 'width';
|
||||
|
||||
contentNode.style.setProperty(prop, /** @type {string} */ (min));
|
||||
await new Promise(resolve => requestAnimationFrame(() => resolve(true)));
|
||||
contentNode.style.setProperty(prop, /** @type {string} */ (max));
|
||||
await this._waitForTransition({ contentNode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger hide animation and wait for transition to be finished.
|
||||
* @param {Object} options - element node and its options
|
||||
* @param {HTMLElement} options.contentNode
|
||||
* @override
|
||||
*/
|
||||
async _hideAnimation({ contentNode }) {
|
||||
if (
|
||||
((this.position === 'left' || this.position === 'right') &&
|
||||
this._contentWidth === this.minWidth) ||
|
||||
(this.position === 'top' && this._contentHeight === this.minHeight)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const min = this.position === 'top' ? this.minHeight : this.minWidth;
|
||||
const prop = this.position === 'top' ? 'height' : 'width';
|
||||
|
||||
contentNode.style.setProperty(prop, /** @type {string} */ (min));
|
||||
await this._waitForTransition({ contentNode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the transition event is finished.
|
||||
* @param {Object} options - element node and its options
|
||||
* @param {HTMLElement} options.contentNode
|
||||
* @returns {Promise<void>} transition event
|
||||
*/
|
||||
_waitForTransition({ contentNode }) {
|
||||
return new Promise(resolve => {
|
||||
const transitionStarted = () => {
|
||||
contentNode.removeEventListener(EVENT.TRANSITION_START, transitionStarted);
|
||||
this.transitioning = true;
|
||||
};
|
||||
contentNode.addEventListener(EVENT.TRANSITION_START, transitionStarted);
|
||||
|
||||
const transitionEnded = () => {
|
||||
contentNode.removeEventListener(EVENT.TRANSITION_END, transitionEnded);
|
||||
this.transitioning = false;
|
||||
resolve();
|
||||
};
|
||||
contentNode.addEventListener(EVENT.TRANSITION_END, transitionEnded);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
get __contentNode() {
|
||||
return /** @type {HTMLElement} */ (this.shadowRoot?.querySelector('.container'));
|
||||
}
|
||||
|
||||
get _contentWidth() {
|
||||
const size = this.__contentNode?.getBoundingClientRect().width || 0;
|
||||
return `${size}px`;
|
||||
}
|
||||
|
||||
get _contentHeight() {
|
||||
const size = this.__contentNode?.getBoundingClientRect().height || 0;
|
||||
return `${size}px`;
|
||||
}
|
||||
|
||||
_openedChanged() {
|
||||
this._updateContentSize();
|
||||
if (this._invokerNode) {
|
||||
this._invokerNode.setAttribute('aria-expanded', `${this.opened}`);
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent('opened-changed'));
|
||||
}
|
||||
|
||||
async _updateContentSize() {
|
||||
if (this.__contentNode) {
|
||||
if (this.opened) {
|
||||
await this._showAnimation({ contentNode: this.__contentNode });
|
||||
} else {
|
||||
await this._hideAnimation({ contentNode: this.__contentNode });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="headline-container">
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="headline"></slot>
|
||||
</div>
|
||||
<div class="content-container">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
59
packages/ui/components/drawer/src/drawerStyle.js
Normal file
59
packages/ui/components/drawer/src/drawerStyle.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { css } from 'lit';
|
||||
|
||||
export const drawerStyle = css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
--min-width: 72px;
|
||||
--max-width: 320px;
|
||||
--min-height: auto;
|
||||
--max-height: fit-content;
|
||||
--start-width: var(--min-width);
|
||||
--start-height: 100%;
|
||||
--transition-property: width;
|
||||
}
|
||||
|
||||
:host([position='top']) {
|
||||
width: 100%;
|
||||
--min-width: 0px;
|
||||
--max-width: none;
|
||||
--min-height: 50px;
|
||||
--max-height: 200px;
|
||||
--start-width: 100%;
|
||||
--start-height: var(--min-height);
|
||||
--transition-property: height;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: var(--start-width);
|
||||
height: var(--start-height);
|
||||
min-width: var(--min-width);
|
||||
max-width: var(--max-width);
|
||||
min-height: var(--min-height);
|
||||
max-height: var(--max-height);
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: var(--transition-property) 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
}
|
||||
|
||||
.headline-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
:host([position='right']) .headline-container {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
::slotted([slot='content']) {
|
||||
width: var(--max-width);
|
||||
}
|
||||
`;
|
||||
109
packages/ui/components/drawer/test/lion-drawer.test.js
Normal file
109
packages/ui/components/drawer/test/lion-drawer.test.js
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import '@lion/ui/define/lion-drawer.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionDrawer').LionDrawer} LionDrawer
|
||||
* @typedef {import('lit').TemplateResult} TemplateResult
|
||||
*/
|
||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionDrawer>} */ (_fixture);
|
||||
|
||||
const template = html`
|
||||
<lion-drawer>
|
||||
<button slot="invoker">Open</button>
|
||||
<p slot="headline">Headline</p>
|
||||
<div slot="content" class="drawer">This is the content of the drawer</div>
|
||||
</lion-drawer>
|
||||
`;
|
||||
|
||||
describe('<lion-drawer>', () => {
|
||||
describe('Drawer', () => {
|
||||
it('sets position to "left" by default', async () => {
|
||||
const drawer = await fixture(template);
|
||||
expect(drawer.position).to.equal('left');
|
||||
});
|
||||
|
||||
it('has [position] attribute which serves as styling hook', async () => {
|
||||
const drawer = await fixture(template);
|
||||
expect(drawer).to.have.attribute('position').equal('left');
|
||||
});
|
||||
|
||||
it('sets the minimum and maximum width when position=left', async () => {
|
||||
const drawer = await fixture(template);
|
||||
const minWidth = getComputedStyle(drawer).getPropertyValue('--min-width');
|
||||
const maxWidth = getComputedStyle(drawer).getPropertyValue('--max-width');
|
||||
|
||||
expect(drawer.minWidth).to.equal(minWidth);
|
||||
expect(drawer.maxWidth).to.equal(maxWidth);
|
||||
});
|
||||
|
||||
it('sets the minimum and maximum width when position=right', async () => {
|
||||
const drawer = await fixture(template);
|
||||
drawer.position = 'right';
|
||||
await drawer.updateComplete;
|
||||
|
||||
const minWidth = getComputedStyle(drawer).getPropertyValue('--min-width');
|
||||
const maxWidth = getComputedStyle(drawer).getPropertyValue('--max-width');
|
||||
|
||||
expect(drawer.minWidth).to.equal(minWidth);
|
||||
expect(drawer.maxWidth).to.equal(maxWidth);
|
||||
});
|
||||
|
||||
it('sets the minimum and maximum height when position=top', async () => {
|
||||
const drawer = await fixture(template);
|
||||
drawer.position = 'top';
|
||||
await drawer.updateComplete;
|
||||
|
||||
const minHeight = getComputedStyle(drawer).getPropertyValue('--min-height');
|
||||
const maxHeight = getComputedStyle(drawer).getPropertyValue('--max-height');
|
||||
|
||||
expect(drawer.minHeight).to.equal(minHeight);
|
||||
expect(drawer.maxHeight).to.equal(maxHeight);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('[collapsed] is a11y AXE accessible', async () => {
|
||||
const drawer = await fixture(template);
|
||||
await expect(drawer).to.be.accessible();
|
||||
});
|
||||
|
||||
it('[opened] is a11y AXE accessible', async () => {
|
||||
const drawer = await fixture(template);
|
||||
drawer.opened = true;
|
||||
await expect(drawer).to.be.accessible();
|
||||
});
|
||||
|
||||
describe('Invoker', () => {
|
||||
it('links id of content items to invoker via [aria-controls]', async () => {
|
||||
const drawerElement = await fixture(template);
|
||||
const invoker = drawerElement.querySelector('[slot=invoker]');
|
||||
const content = drawerElement.querySelector('[slot=content]');
|
||||
expect(invoker?.getAttribute('aria-controls')).to.equal(content?.id);
|
||||
});
|
||||
|
||||
it('adds aria-expanded="false" to invoker when its content is not expanded', async () => {
|
||||
const drawerElement = await fixture(template);
|
||||
const invoker = drawerElement.querySelector('[slot=invoker]');
|
||||
expect(invoker).to.have.attribute('aria-expanded', 'false');
|
||||
});
|
||||
|
||||
it('adds aria-expanded="true" to invoker when its content is expanded', async () => {
|
||||
const drawerElement = await fixture(template);
|
||||
const invoker = drawerElement.querySelector('[slot=invoker]');
|
||||
drawerElement.opened = true;
|
||||
await drawerElement.updateComplete;
|
||||
expect(invoker).to.have.attribute('aria-expanded', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contents', () => {
|
||||
it('adds aria-labelledby referring to invoker id', async () => {
|
||||
const drawerElement = await fixture(template);
|
||||
const invoker = drawerElement.querySelector('[slot=invoker]');
|
||||
const content = drawerElement.querySelector('[slot=content]');
|
||||
expect(content).to.have.attribute('aria-labelledby', invoker?.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
3
packages/ui/exports/define/lion-drawer.js
Normal file
3
packages/ui/exports/define/lion-drawer.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { LionDrawer } from '../drawer.js';
|
||||
|
||||
customElements.define('lion-drawer', LionDrawer);
|
||||
1
packages/ui/exports/drawer.js
Normal file
1
packages/ui/exports/drawer.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { LionDrawer } from '../components/drawer/src/LionDrawer.js';
|
||||
Loading…
Reference in a new issue