Skip to content

Commit

Permalink
feat: drawer component (#327)
Browse files Browse the repository at this point in the history
* feat: drawer component

* test(drawer): add unit tests

* fix(drawer): fix linter check error

* fix(drawer): refactor for new rules

* feat(drawer): add stories for drawer

* fix(drawer): refactor for attribute, css, stories

* fix(drawer): update attribute names in stories

* fix(drawer): remove unnecessary console log

* fix(drawer): add gap between header buttons

Co-authored-by: semih.ozker <[email protected]>
Co-authored-by: Murat Çorlu <[email protected]>
  • Loading branch information
3 people authored Dec 5, 2022
1 parent fe3eed3 commit f1d49f9
Show file tree
Hide file tree
Showing 7 changed files with 488 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .storybook/preview-body.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@
justify-content: center;
align-items: center;
}
.sb-main-fullscreen #root-inner {
height: 600px;
}
</style>
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
'pagination',
'radio',
'dialog',
'drawer',
],
],
},
Expand Down
1 change: 1 addition & 0 deletions src/baklava.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { default as BlBadge } from './components/badge/bl-badge';
export { default as BlButton } from './components/button/bl-button';
export { default as BlCheckbox } from './components/checkbox/bl-checkbox';
export { default as BlDialog } from './components/dialog/bl-dialog';
export { default as BlDrawer } from './components/drawer/bl-drawer';
export { default as BlIcon } from './components/icon/bl-icon';
export { default as BlInput } from './components/input/bl-input';
export { default as BlPagination } from './components/pagination/bl-pagination';
Expand Down
80 changes: 80 additions & 0 deletions src/components/drawer/bl-drawer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@keyframes slide-from-right{
0% {
transform: translateX(+100%);
}

100% {
transform: translateX(0);
}
}

.drawer{
position: fixed;
top:0;
right: 0;
width: 424px;
height: 100%;
background: var(--bl-color-primary-background);
box-shadow: var(--bl-size-xs) 0 var(--bl-size-2xl) rgba(0 0 0 / 50%);
animation: 0.25s slide-from-right;

/* FIXME: Use z-index variable */
z-index: 999;
}

iframe{
height: 100%;
width: 100%;
border: none;
}

.container{
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}

header {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--bl-size-2xs);
padding: var(--bl-size-xl) var(--bl-size-xl) 0 var(--bl-size-xl);
background-color: white;
}

header .header-buttons {
display: flex;
gap:24px;
margin-left: auto;
}

header h2 {
font: var(--bl-font-title-1-medium);
color: var(--bl-color-secondary);
overflow: hidden;
margin: 0;
padding: 0;
}

section {
padding: var(--bl-size-xl) var(--bl-size-xl) var(--bl-size-m) var(--bl-size-xl);
}

.content {
overflow-y: scroll;
}

.iframe-content {
height: 100%;
}

@media only screen and (max-width: 424px) {
:host([open]) .drawer {
width: calc(100vw - 24px);
}
}



121 changes: 121 additions & 0 deletions src/components/drawer/bl-drawer.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { Meta, Canvas, ArgsTable, Story, Preview, Source } from '@storybook/addon-docs';

<Meta
title="Components/Drawer"
component="bl-drawer"
parameters={{
layout: 'fullscreen',
chromatic: { viewports: [1000]},
}}
argTypes={{
open: {
control: "boolean",
default: false
},
caption: {
control: "text",
default: "",
},
externalLink: {
control: "text",
default: "",
},
embedUrl: {
control: "text",
default: "",
},
}}
/>

export const openDialog = async (event,args) => {
const insideCanvas = !event.target || event.target.parentNode.parentNode.getAttribute("id") === "root";
let selector= insideCanvas ? `#root #${args.id}`: `#docs-root #${args.id}`;
const drawer = document.querySelector(selector);
drawer.open = true;
await new Promise(resolve => setTimeout(resolve,1000));
}

export const DummyContent = () => html`
<div style="font: var(--bl-font-body-text-2)">
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>
</div>
`

export const DrawerTemplate = (args) => html`
<bl-drawer id=${ifDefined(args.id)}
caption=${ifDefined(args.caption)}
embed-url=${ifDefined(args.embedUrl)}
external-link=${ifDefined(args.externalLink)}>
${ifDefined(args.content)}
</bl-drawer>
`

export const StoryTemplate = (args) => html`
${DrawerTemplate(args)}
<bl-button @click="${(event)=>openDialog(event,args)}">${ifDefined(args.buttonText)}</bl-button>
`

# Drawer

A drawer presents context-specific information and/or actions without leaving the current page

<bl-alert variant="warning" icon caption="Note">Inline styles in examples are only for **demo purposes**. Use regular CSS classes or tag selectors to set styles.</bl-alert>

### Design Rules

* Title and external link displayed on header section in drawer. Both of them are optional.
* Close button always displayed on header section and drawer can be closed by clicking close button.
* By default, Drawer can not close by clicking somewhere outside drawer.
* Drawer appears right side on the page with full height expanded.
* Title can be multiline automatically if it does not fit one line.
* When drawer does not fit in its fixed size, it switches to mobile view.
* There is an attribute about iframe and drawer component handle iframe rendering itself.
* Only one drawer can display at the same time. When one drawer opens others will be closed.

## Usage

<Canvas>
<Story name="Default Values"
play={(event) => openDialog(event,{id:'drawer-1'})}
args={{id:"drawer-1", buttonText:"default drawer", content:DummyContent()}}>
{StoryTemplate.bind({})}
</Story>
<Story name="With caption"
play={(event)=> openDialog(event,{id:'drawer-2'})}
args={{id:"drawer-2", buttonText:"with caption", caption: "Caption", content:'example content'}}>
{StoryTemplate.bind({})}
</Story>
<Story name="With long caption"
play={(event) => openDialog(event,{id:'drawer-3'})}
args={{id:"drawer-3", buttonText:"with long caption", caption: "This drawer has a long caption and automatically handle it", content:'example content'}}>
{StoryTemplate.bind({})}
</Story>
<Story name="With caption and externalLink"
play={(event) => openDialog(event,{id:'drawer-4'})}
args={{id:"drawer-4", buttonText:"with caption and external", caption: "Caption", externalLink:"some-url", content:'example content'}}>
{StoryTemplate.bind({})}
</Story>
<Story name="With caption and embedUrl"
play={(event) => openDialog(event,{id:'drawer-5'})}
args={{id:"drawer-5", buttonText:"with caption, embedUrl", caption: "Caption", embedUrl:"some-url"}}>
{StoryTemplate.bind({})}
</Story>
</Canvas>

<ArgsTable of="bl-drawer" />
144 changes: 144 additions & 0 deletions src/components/drawer/bl-drawer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {assert, elementUpdated, expect, fixture, html, oneEvent} from '@open-wc/testing';
import BlDrawer from "./bl-drawer";
import type typeOfBlDrawer from './bl-drawer';

describe('bl-drawer',() => {
it('is defined', () => {
const el = document.createElement('bl-drawer');
assert.instanceOf(el, BlDrawer);
});

describe('render tests',()=>{
it('should render drawer component with default values', async ()=>{
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer open></bl-drawer>`);
assert.shadowDom.equal(
el,
`
<div class="drawer">
<div class="container">
<header>
<div class="header-buttons">
<bl-button
icon="close"
variant="tertiary"
size="small"
kind="neutral"
></bl-button>
</div>
</header>
<section class="content">
<slot></slot>
</section>
</div>
</div>
`
);
});
it('should render the caption, externalLink and content if provided', async ()=>{
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer caption="My Caption" external-link="some-url" open>
<div>example content</div>
</bl-drawer>`);

assert.shadowDom.equal(
el,
`
<div class="drawer">
<div class="container">
<header>
<h2 id="drawer-caption">
My Caption
</h2>
<div class="header-buttons">
<bl-button
href="some-url"
icon="external_link"
variant="tertiary"
size="small"
target="_blank"
kind="neutral"
></bl-button>
<bl-button
icon="close"
variant="tertiary"
size="small"
kind="neutral"
></bl-button>
</div>
</header>
<section class="content">
<slot></slot>
</section>
</div>
</div>
`
);
});

it('should render the caption, embedUrl if provided', async ()=>{
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer caption="My Caption" embed-url="some-url" open><div>example content</div></bl-drawer>`);

const caption = el.shadowRoot?.querySelector('#drawer-caption') as HTMLElement;
const iframeEl = el.shadowRoot?.querySelector('iframe') as HTMLElement;

expect(iframeEl).to.exist;
expect(iframeEl.attributes.getNamedItem('src')?.value).to.contain("some-url");

expect(caption).to.exist;
expect(caption.innerText).to.equal('My Caption');
});

it('should open the drawer when change open attribute as true', async () => {
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer caption="Drawer Title">
<div>Drawer Content</div>
</bl-drawer>`);

expect(el.open).to.equal(false);

el.open = true;
await elementUpdated(el);

setTimeout(() => {
expect(el.open).to.equal(true);
});
});
it('should close the drawer when change click close button', async () => {
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer open caption="Drawer Title">
<div>Drawer Content</div>
</bl-drawer>`);

const closeBtn = el?.shadowRoot?.querySelector('bl-button');

expect(closeBtn).to.exist;
expect(el.open).to.equal(true);
closeBtn?.click();

setTimeout(()=>{
expect(el.open).to.equal(false);
expect(el.offsetWidth).to.equal(0);
});
});
});
describe('event tests',()=>{
it('should fire bl-drawer-open when dialog opens',async ()=>{
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer caption="My Drawer"></bl-drawer>`)

setTimeout(async () => {
const openEvent = await oneEvent(el,'bl-drawer-open');
expect(openEvent).to.exist;
expect(openEvent.detail.isOpen).to.equal(true);
});
});
it('should fire bl-drawer-close when dialog closes',async ()=>{
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer open caption="My Drawer"></bl-drawer>`)

const closeBtn = el?.shadowRoot?.querySelector('bl-button');

setTimeout(async () => {
closeBtn?.click();
const openEvent = await oneEvent(el,'bl-drawer-close');
expect(openEvent).to.exist;
expect(openEvent.detail.isOpen).to.equal(false);
});
});
})
});
Loading

0 comments on commit f1d49f9

Please sign in to comment.