Skip to content

Commit

Permalink
chore(dialog): fix conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
ozkersemih committed May 20, 2024
2 parents 0564c75 + 3373a39 commit 0e0a97f
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 22 deletions.
6 changes: 6 additions & 0 deletions src/components/dialog/bl-dialog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ For long content that does not fit on the page, the dialog action area remains s

<Canvas of={DialogStories.DialogWithStickyFooter} />

## Critical Dialog

Critical dialogs are used to inform users about critical information or actions. They are unable to be dismissed by clicking the backdrop or pressing Esc.

<Canvas of={DialogStories.CriticalDialog} />

## Dialog Sizing

The dialog doesn't have any default size, it will be fluidly sized regarding its content. You can give your own width and height style to your content.
Expand Down
20 changes: 19 additions & 1 deletion src/components/dialog/bl-dialog.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ interface DialogArgs {
focusPrimary?: boolean;
focusSecondary?: boolean;
focusTertiary?: boolean;
critical?: boolean;
closeAction?: string;
}

type Story = StoryObj<DialogArgs>;
Expand All @@ -68,7 +70,8 @@ const BasicTemplate = (args: DialogArgs) => html`
class="${ifDefined(args.className)}"
caption="${ifDefined(args.caption)}"
?open="${args.open}"
?polyfilled="${args.polyfilled}">
?polyfilled="${args.polyfilled}"
?critical="${args.critical}">
${
unsafeHTML(args.content)
}${
Expand All @@ -79,6 +82,9 @@ const BasicTemplate = (args: DialogArgs) => html`
<bl-button slot="secondary-action" variant="secondary" ?autofocus=${args.focusSecondary} size="large">${args.secondaryAction}</bl-button>` : ""}${
args.tertiaryAction ? html`
<bl-button slot="tertiary-action" variant="tertiary" ?autofocus=${args.focusTertiary} size="large">${args.tertiaryAction}</bl-button>` : ""}
${
args.closeAction ? html`
<bl-button slot="primary-action" variant="primary" ?autofocus=${args.focusSecondary} size="large" @click=${(e: CustomEvent) => (e.target as HTMLElement).closest("bl-dialog")?.toggleAttribute("open")}>${args.closeAction}</bl-button>` : ""}
</bl-dialog>
`;

Expand Down Expand Up @@ -293,3 +299,15 @@ export const DialogWithTabGroup: Story = {
render: TabGroupTemplate,
play: dialogOpener("dl-tab-group")
};

export const CriticalDialog: Story = {
args: {
id: "dl-critical",
caption: "Critical Action Required",
content: "<p>This action is irreversible. Please confirm to proceed.</p>",
closeAction: "Confirm",
critical: true,
},
render: FullWidthActionsTemplate,
play: dialogOpener("dl-critical")
};
55 changes: 54 additions & 1 deletion src/components/dialog/bl-dialog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ describe("bl-dialog", () => {

it("should remove shadow from footer when hitting bottom", async () => {
window.innerWidth = 400;

const el = await fixture<HTMLElement>(html`<bl-dialog open caption="My title">
<p>
Contrary to popular belief, Lorem Ipsum is not simply random text., comes from a line in
Expand Down Expand Up @@ -477,4 +477,57 @@ describe("bl-dialog", () => {
});
});
});

describe("critical dialog tests", () => {
it("should not close the critical dialog when the close button is clicked", async () => {
const el = await fixture<typeOfBlDialog>(html`<bl-dialog critical open caption="Critical Dialog">
<div>Content that cannot be dismissed without taking action.</div>
</bl-dialog>`);

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

expect(el.critical).to.be.true;

closeBtn?.click();
await elementUpdated(el);

expect(el.open).to.be.true;
});

it("should not close the critical dialog when the escape key is pressed", async () => {
const el = await fixture<typeOfBlDialog>(html`<bl-dialog critical open caption="Critical Dialog">
<div>Content that cannot be dismissed without taking action.</div>
</bl-dialog>`);

expect(el.critical).to.be.true;

await sendKeys({ press: "Escape" });
await elementUpdated(el);

expect(el.open).to.be.true;
});

it("should not close the critical dialog when clicked outside", async () => {
const el = await fixture<typeOfBlDialog>(html`<bl-dialog critical open caption="Critical Dialog">
<div>Content that cannot be dismissed without taking action.</div>
</bl-dialog>`);

expect(el.critical).to.be.true;

await sendMouse({ type: "click", position: [0, 0] });
await elementUpdated(el);

expect(el.open).to.be.true;
});

it("should hide the close button for critical dialog", async () => {
const el = await fixture<typeOfBlDialog>(html`<bl-dialog critical open caption="Critical Dialog">
<div>Content that cannot be dismissed without taking action.</div>
</bl-dialog>`);

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

expect(closeBtn).to.be.null;
});
});
});
28 changes: 18 additions & 10 deletions src/components/dialog/bl-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export default class BlDialog extends LitElement {
@property({ type: String })
caption?: string;

/**
* Determines if the dialog is critical, which disables closing through keyboard, backdrop, and close button interactions.
*/
@property({ type: Boolean, reflect: true })
critical = false;

/**
* Determines if dialog currently uses polyfilled version instead of native HTML Dialog. By
* default, it uses native Dialog if the browser supports it, otherwise polyfills. You can force
Expand Down Expand Up @@ -130,6 +136,8 @@ export default class BlDialog extends LitElement {
}

private clickOutsideHandler = (event: MouseEvent) => {
if (this.critical) return;

const eventPath = event.composedPath() as HTMLElement[];

if (!eventPath.includes(this.container)) {
Expand All @@ -138,7 +146,7 @@ export default class BlDialog extends LitElement {
};

private onKeydown = (event: KeyboardEvent): void => {
if (event.code === "Escape" && this.open) {
if (event.code === "Escape" && this.open && !this.critical) {
event.preventDefault();
this.closeDialog("keyboard");
}
Expand Down Expand Up @@ -168,22 +176,22 @@ export default class BlDialog extends LitElement {

private renderContainer() {
const title = this.caption ? html`<h2 id="dialog-caption">${this.caption}</h2>` : "";
const closeButton = !this.critical
? html`<bl-button
@click="${() => this.closeDialog("close-button")}"
icon="close"
variant="tertiary"
kind="neutral"
></bl-button>`
: null;

const classes = {
"container": true,
"has-footer": this._hasFooter,
};

return html` <div class="${classMap(classes)}">
<header>
${title}
<bl-button
@click="${() => this.closeDialog("close-button")}"
icon="close"
variant="tertiary"
kind="neutral"
></bl-button>
</header>
<header>${title} ${closeButton}</header>
<section class="content"><slot></slot></section>
${this.renderFooter()}
</div>`;
Expand Down
14 changes: 8 additions & 6 deletions src/components/popover/bl-popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Middleware,
MiddlewareState,
} from "@floating-ui/dom";
import { getTarget } from "../../utilities/elements";
import { event, EventDispatcher } from "../../utilities/event";
import style from "./bl-popover.css";

Expand Down Expand Up @@ -176,15 +177,16 @@ export default class BlPopover extends LitElement {
}

set target(value: string | Element) {
if (typeof value === "string") {
this._target = document.getElementById(value) as Element;
} else if (value instanceof Element) {
this._target = value;
} else {
const target = getTarget(value);

if (!target) {
console.warn(
"BlPopover target only accepts an Element instance or a string id of a DOM element."
);
return;
}

this._target = target;
}

/**
Expand Down Expand Up @@ -222,7 +224,7 @@ export default class BlPopover extends LitElement {
const { parentElement } = event.target as HTMLElement;
const isNestedPopover = this.contains(parentElement);

if (!isNestedPopover) {
if (!isNestedPopover && (event.target as HTMLElement).tagName === this.tagName) {
this.hide();
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/components/tooltip/bl-tooltip.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export const PlacementTemplate = (args) => html`
You can use this section to cancel your order.
</bl-tooltip>`

export const TargetAttrTemplate = (args) => html`
<bl-tooltip placement="${ifDefined(args.placement)}" target="trigger-btn">
Target Attribute
</bl-tooltip>
<bl-button id="trigger-btn">With Target</bl-button>
`;

# Tooltip

<bl-badge icon="document">ADR</bl-badge>
Expand Down Expand Up @@ -153,6 +160,14 @@ For example, if there is not enough room on the top, the tooltip is shown on the
</Story>
</Canvas>

## Target Attribute

By using the target attribute, we can add a tooltip to the element.

<Canvas>
<Story name="Usage With Target Attribute" play={tooltipOpener}>{TargetAttrTemplate.bind({})}</Story>
</Canvas>

## Reference

<ArgsTable of="bl-tooltip" />
50 changes: 50 additions & 0 deletions src/components/tooltip/bl-tooltip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,54 @@ describe("bl-tooltip", () => {
expect(ev).to.exist;
expect(el.visible).to.be.false;
});

it("should work with target attribute", async () => {
// given
const el = await fixture(html`
<div><bl-tooltip target="btn">Tooltip Text</bl-tooltip><button type="button" id="btn">Test</button></div>`);

const tooltipEl = el.querySelector("bl-tooltip")!;
const trigger = el.querySelector<HTMLButtonElement>("#btn")!;

// when
const { x, y } = getMiddleOfElement(trigger);

setTimeout(() => sendMouse({ type: "move", position: [x, y] }));

// then
const ev = await oneEvent(tooltipEl, "bl-tooltip-show");

expect(ev).to.exist;
expect(ev.detail).to.be.equal("");
});

it("should remove previous target elements", async () => {
// given
const el = await fixture(html`
<div><bl-tooltip target="btn">Tooltip Text</bl-tooltip><button type="button" id="btn">Test</button><button type="button" id="new-btn">Test</button></div>`);

const tooltipEl = el.querySelector("bl-tooltip")!;
const triggerPrev = el.querySelector<HTMLButtonElement>("#btn")!;

// when
tooltipEl.target = "new-btn";
const { x, y } = getMiddleOfElement(triggerPrev);

setTimeout(() => sendMouse({ type: "move", position: [x, y] }));

// then
const ev = await new Promise(resolve => {
function listener(ev: Event) {
resolve(ev);
tooltipEl.removeEventListener("bl-tooltip-show", listener);
}
tooltipEl.addEventListener("bl-tooltip-show", listener);
setTimeout(() => {
resolve(null);
tooltipEl.removeEventListener("bl-tooltip-show", listener);
}, 200);
});

expect(ev).to.be.eq(null);
});
});
62 changes: 58 additions & 4 deletions src/components/tooltip/bl-tooltip.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { CSSResultGroup, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { ReferenceElement } from "@floating-ui/core";
import { getTarget } from "../../utilities/elements";
import { event, EventDispatcher } from "../../utilities/event";
import "../popover/bl-popover";
import BlPopover, { Placement } from "../popover/bl-popover";
Expand Down Expand Up @@ -39,11 +40,64 @@ export default class BlTooltip extends LitElement {
*/
@event("bl-tooltip-hide") private onHide: EventDispatcher<string>;

@property() target: string | Element;

protected update(changedProperties: PropertyValues) {
if (changedProperties.has("target")) {
const prev = changedProperties.get("target");

if (prev) {
this._removeEvents(prev);
}

this._addEvents();
}

super.update(changedProperties);
}

private _addEvents() {
const target = getTarget(this.target);

if (target) {
target.addEventListener("focus", this.show, { capture: true });
target.addEventListener("mouseover", this.show);
target.addEventListener("blur", this.hide, { capture: true });
target.addEventListener("mouseleave", this.hide);
}
}

private _removeEvents(value: string | Element) {
const target = getTarget(value);

if (target) {
target.removeEventListener("focus", this.show, { capture: true });
target.removeEventListener("mouseover", this.show);
target.removeEventListener("blur", this.hide, { capture: true });
target.removeEventListener("mouseleave", this.hide);
}
}

connectedCallback() {
super.connectedCallback();

this.show = this.show.bind(this);
this.hide = this.hide.bind(this);

this._addEvents();
}

disconnectedCallback() {
super.disconnectedCallback();

this._removeEvents(this.target);
}

/**
* Shows tooltip
*/
show() {
this._popover.target = this.trigger;
this._popover.target = this.target ?? this.trigger;
this._popover.show();
this.onShow("");
}
Expand Down Expand Up @@ -78,9 +132,9 @@ export default class BlTooltip extends LitElement {

render(): TemplateResult {
return html`
${this.triggerTemplate()}
${this.target ? "" : this.triggerTemplate()}
<bl-popover
.target="${this.trigger}"
.target="${this.target ?? this.trigger}"
placement="${ifDefined(this.placement)}"
@bl-popover-hide="${() => this.onHide("")}"
>
Expand Down
Loading

0 comments on commit 0e0a97f

Please sign in to comment.