Skip to content

Commit

Permalink
feat(dialog): implement critical variant (#870)
Browse files Browse the repository at this point in the history
Related to #853

Implements critical dialog functionality to prevent closing and hides
the close button for critical dialogs.

- Adds a `critical` property to `bl-dialog` to mark dialogs as critical,
which disables closing through keyboard, backdrop, and close button
interactions.
- Conditionally renders the close button in the dialog header based on
the `critical` property, effectively hiding it for critical dialogs.
- Updates event handling to prevent closing critical dialogs through
escape key presses and outside clicks.
- Includes a new story in `bl-dialog.stories.ts` to demonstrate a
critical dialog with the `critical` property set to `true`.
- Adds tests in `bl-dialog.test.ts` to verify that critical dialogs
cannot be closed through keyboard, backdrop, and close button
interactions, and to ensure the close button is hidden for critical
dialogs.
  • Loading branch information
AykutSarac committed May 16, 2024
1 parent f2deab2 commit 3373a39
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 12 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 @@ -257,3 +263,15 @@ export const DialogWithFullWidthActions: Story = {
render: FullWidthActionsTemplate,
play: dialogOpener("dl-full-width-actions")
};

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

0 comments on commit 3373a39

Please sign in to comment.