Skip to content

Commit

Permalink
feat(drawer): add dynamic width feature to drawer (#820)
Browse files Browse the repository at this point in the history
This PR adds setting width feature into drawer.

Closes #767

This had to be written with JavaScript because it was previously written
with **@ media**, and we couldn't use CSS variables inside **@ media**.
Reference source: https://stackoverflow.com/a/40723269

Also added style-to-px-converter to give width properties like '%', 'vh'

---------

Co-authored-by: Erdem Gönül <[email protected]>
  • Loading branch information
erdemgonul and Erdem Gönül authored Apr 3, 2024
1 parent ca06fb6 commit aef2ae8
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 9 deletions.
8 changes: 1 addition & 7 deletions src/components/drawer/bl-drawer.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
top: 0;
right: -100%;
bottom: 0;
width: 424px;
width: var(--bl-drawer-current-width, 424px);
padding: var(--bl-size-xl);
padding-top: max(env(safe-area-inset-top), var(--bl-size-xl));
padding-right: max(env(safe-area-inset-right), var(--bl-size-xl));
Expand Down Expand Up @@ -64,9 +64,3 @@ header h2 {
.iframe-content {
flex: 1;
}

@media only screen and (max-width: 424px) {
:host([open]) .drawer {
width: calc(100vw - 24px);
}
}
14 changes: 12 additions & 2 deletions src/components/drawer/bl-drawer.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
control: "text",
default: "",
},
width: {
control: "text",
default: "",
},
}}
/>

Expand Down Expand Up @@ -63,7 +67,8 @@ 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)}>
external-link=${ifDefined(args.externalLink)}
width=${ifDefined(args.width)}>
<div style="font: var(--bl-font-body-text-2)">
${unsafeHTML(args.content)}
</div>
Expand All @@ -72,7 +77,7 @@ export const DrawerTemplate = (args) => html`

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

# Drawer
Expand Down Expand Up @@ -123,6 +128,11 @@ A drawer presents context-specific information and/or actions without leaving th
args={{id:"drawer-5", buttonText:"with caption, embedUrl", caption: "Caption", embedUrl:"/assets/demo-iframe.html"}}>
{StoryTemplate.bind({})}
</Story>
<Story name="With custom width"
play={(event) => openDialog(event,{id:'drawer-6'})}
args={{id:"drawer-6", buttonText:"with custom width", caption: "Custom Width Drawer", content: DummyContent(), width: "800px"}}>
{StoryTemplate.bind({})}
</Story>
</Canvas>

<ArgsTable of="bl-drawer" />
35 changes: 35 additions & 0 deletions src/components/drawer/bl-drawer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,42 @@ describe("bl-drawer", () => {

expect(el?.shadowRoot?.querySelector("bl-button")).to.be.null;
});

it("should render the drawer with given specific width with width='800px'", async () => {
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer width="800px" open></bl-drawer>`);
const drawerEl = el.shadowRoot!.querySelector(".drawer")!;

const width = getComputedStyle(drawerEl).width;

expect(
width
).to.equal("800px");
});

it("should render the drawer with width: 'calc(100vw - 24px)' if viewport width is smaller than given drawer width", async () => {
// default window.innerWidth = 800px
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer width="1200px" open></bl-drawer>`);
const drawerEl = el.shadowRoot!.querySelector(".drawer")!;

const width = getComputedStyle(drawerEl).width;

expect(
width
).to.equal("776px");
});

it("should render the drawer with default width: 424px if width prop is smaller than 100px", async () => {
const el = await fixture<typeOfBlDrawer>(html`<bl-drawer width="90px" open></bl-drawer>`);
const drawerEl = el.shadowRoot!.querySelector(".drawer")!;

const width = getComputedStyle(drawerEl).width;

expect(
width
).to.equal("424px");
});
});

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>`);
Expand Down
35 changes: 35 additions & 0 deletions src/components/drawer/bl-drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { event, EventDispatcher } from "../../utilities/event";
import { styleToPixelConverter } from "../../utilities/style-to-px.converter";
import "../button/bl-button";
import style from "./bl-drawer.css";

Expand Down Expand Up @@ -42,6 +43,12 @@ export default class BlDrawer extends LitElement {
@property({ type: String, attribute: "external-link" })
externalLink?: string;

/**
* Sets the drawer width
*/
@property({ type: String, attribute: "width" })
width: string = "424px";

/**
* Fires when the drawer is opened
*/
Expand All @@ -57,16 +64,44 @@ export default class BlDrawer extends LitElement {
window?.addEventListener("bl-drawer-open", event => {
if (event.target !== this) this.closeDrawer();
});
this.resizeDrawerWidth();

window?.addEventListener("resize", () => this.resizeDrawerWidth());
window?.addEventListener("load", () => this.resizeDrawerWidth());
}

disconnectedCallback(): void {
super.disconnectedCallback();
window?.removeEventListener("resize", () => this.resizeDrawerWidth());
window?.addEventListener("load", () => this.resizeDrawerWidth());
}

updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("open")) {
this.toggleDialogHandler();
}

if (changedProperties.has("width")) {
this.resizeDrawerWidth();
}
}

private domExistenceSchedule: number;

private resizeDrawerWidth() {
const drawerWidth = styleToPixelConverter(this.width);

const newWidth = !drawerWidth || drawerWidth < 100 ? "424px" : this.width;

if (drawerWidth) {
if (window?.innerWidth < drawerWidth) {
this.style.setProperty("--bl-drawer-current-width", "calc(100vw - 24px)");
} else {
this.style.setProperty("--bl-drawer-current-width", newWidth);
}
}
}

private toggleDialogHandler() {
if (this.open) {
if (this.domExistenceSchedule) {
Expand Down
56 changes: 56 additions & 0 deletions src/utilities/style-to-px-converter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { expect } from "@open-wc/testing";
import { styleToPixelConverter } from "./style-to-px.converter";

describe("styleToPixelConverter", () => {
it("converts pixel value correctly", () => {
const result = styleToPixelConverter("100px");

expect(result).to.equal(100);
});

it("converts viewport width value correctly", () => {
Object.defineProperty(window, "innerWidth", { value: 600, writable: true });

const result = styleToPixelConverter("50vw");

expect(result).to.equal(300); // 50% of 600 (viewport width)
});

it("converts percentage value correctly", () => {
Object.defineProperty(window, "innerWidth", { value: 800, writable: true });

const result = styleToPixelConverter("25%");

expect(result).to.equal(200); // 25% of 800 (viewport width)
});

it("returns 0 for invalid input", () => {
const result = styleToPixelConverter("invalid");

expect(result).to.null;
});

it("returns null for empty input", () => {
const result = styleToPixelConverter("");

expect(result).to.null;
});

it("returns null for input without a unit", () => {
const result = styleToPixelConverter("42");

expect(result).to.null;
});

it("handles decimal values correctly", () => {
const result = styleToPixelConverter("12.5px");

expect(result).to.equal(12.5);
});

it("returns null for unsupported unit", () => {
const result = styleToPixelConverter("10em");

expect(result).to.null;
});
});
26 changes: 26 additions & 0 deletions src/utilities/style-to-px.converter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const styleToPixelConverter = (styleValue: string): number | null => {
const match = styleValue.match(/^(\d+(\.\d+)?)(.*)$/);

if (!match) return null;

const value = parseFloat(match[1]);
const unit = match[3];

let styleInPixel: number | null;

switch (unit) {
case "px":
styleInPixel = value;
break;
case "vw":
styleInPixel = (value * window.innerWidth) / 100;
break;
case "%":
styleInPixel = (value * window.innerWidth) / 100;
break;
default:
styleInPixel = null;
break;
}
return styleInPixel;
};

0 comments on commit aef2ae8

Please sign in to comment.