From fdd277aa78b22127482bff21d02560a69f864119 Mon Sep 17 00:00:00 2001 From: Ogun Babacan Date: Thu, 13 Feb 2025 13:43:43 +0300 Subject: [PATCH] feat(dropdown): add support for disabling dropdown items (#1018) Also use the appropriate custom event to ensure that the feature set works as intended. Closes #1017 --- .../dropdown/bl-dropdown.stories.mdx | 40 ++++++++++++++---- src/components/dropdown/bl-dropdown.test.ts | 42 ++++++++++++++++--- .../item/bl-dropdown-item.stories.mdx | 17 ++++++-- .../dropdown/item/bl-dropdown-item.test.ts | 9 +++- .../dropdown/item/bl-dropdown-item.ts | 11 ++++- .../split-button/bl-split-button.test.ts | 29 +++++++------ 6 files changed, 116 insertions(+), 32 deletions(-) diff --git a/src/components/dropdown/bl-dropdown.stories.mdx b/src/components/dropdown/bl-dropdown.stories.mdx index f6893da0..061b3b53 100644 --- a/src/components/dropdown/bl-dropdown.stories.mdx +++ b/src/components/dropdown/bl-dropdown.stories.mdx @@ -1,8 +1,8 @@ +import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs'; +import { userEvent } from '@storybook/testing-library'; import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { styleMap } from 'lit/directives/style-map.js'; -import { Meta, Canvas, ArgsTable, Story } from '@storybook/addon-docs'; -import { userEvent } from '@storybook/testing-library'; { const dropdown = canvasElement?.querySelector('bl-dropdown') - if(dropdown.shadowRoot) { - const button = dropdown.shadowRoot.querySelector('bl-button') + if (dropdown.shadowRoot) { + const button = dropdown.shadowRoot.querySelector("bl-button")?.shadowRoot?.querySelector("button"); await userEvent.click(button); } } @@ -78,6 +78,22 @@ export const IconDropdownTemplate = (args) => html`Action 5 ` +export const DisabledItemDropdownTemplate = (args) => html` + ${args.content || 'Action 1'} + Action 2 + + Action 3 + Action 4 + Action 5 + ` + export const Template = (args) => html` ${SingleDropdownButtonTemplate({...args})} ${SingleDropdownButtonTemplate({variant: 'secondary', ...args})} @@ -148,7 +164,7 @@ If dropdown button has an action with a long text that can not fit in a single l You can add icons to your dropdown buttons using the `icon` attribute. The icon will be displayed on the left side of the button label. - + {html` ${IconDropdownTemplate({label: 'Settings', icon: 'settings', variant: 'primary'})} ${IconDropdownTemplate({label: 'Settings', icon: 'settings', variant: 'secondary'})} @@ -162,7 +178,7 @@ You can add icons to your dropdown buttons using the `icon` attribute. The icon We have 2 types of disabled dropdown buttons: Disable version of Primary and Secondary buttons is the same. - + {SizesTemplate.bind({})} @@ -170,11 +186,21 @@ We have 2 types of disabled dropdown buttons: Disable version of Primary and Sec Whereas Tertiary buttons keep their transparent backgrounds. - + {SizesTemplate.bind({})} +## Disabling Dropdown Items + +You can disable dropdown items by setting the `disabled` attribute on the `bl-dropdown-item` element. + + + + {DisabledItemDropdownTemplate.bind({})} + + + ## Reference diff --git a/src/components/dropdown/bl-dropdown.test.ts b/src/components/dropdown/bl-dropdown.test.ts index 159a02e9..697bfac0 100644 --- a/src/components/dropdown/bl-dropdown.test.ts +++ b/src/components/dropdown/bl-dropdown.test.ts @@ -1,19 +1,19 @@ -import BlDropdown from "./bl-dropdown"; import { assert, + elementUpdated, + expect, fixture, html, oneEvent, - expect, - elementUpdated, waitUntil, } from "@open-wc/testing"; import { sendKeys } from "@web/test-runner-commands"; +import BlDropdown from "./bl-dropdown"; -import type typeOfBlDropdown from "./bl-dropdown"; import BlButton from "../button/bl-button"; import "../popover/bl-popover"; import BlPopover from "../popover/bl-popover"; +import type typeOfBlDropdown from "./bl-dropdown"; describe("bl-dropdown", () => { it("is defined", () => { @@ -112,7 +112,12 @@ describe("bl-dropdown", () => { .querySelector("bl-dropdown-item") ?.shadowRoot?.querySelector("bl-button") as HTMLElement | null; - item?.click(); + item?.dispatchEvent( + new CustomEvent("bl-click", { + bubbles: true, + composed: true, + }) + ); setTimeout(() => { expect(el.opened).to.false; @@ -167,6 +172,33 @@ describe("bl-dropdown", () => { expect(popover.visible).to.false; }); + it("should not close dropdown when disabled item is clicked", async () => { + const el = await fixture(html` + + dropdown-item + + `); + + const buttonHost = el.shadowRoot?.querySelector("bl-button"); + const button = buttonHost.shadowRoot?.querySelector(".button") as HTMLElement | null; + const popover = el.shadowRoot?.querySelector("bl-popover"); + + button?.click(); + expect(el.opened).to.true; + expect(popover.visible).to.true; + + const item = el + .querySelector("bl-dropdown-item") + ?.shadowRoot?.querySelector("bl-button") as HTMLElement | null; + + item?.click(); + + setTimeout(() => { + expect(el.opened).to.true; + expect(popover.visible).to.true; + }); + }); + describe("keyboard navigation", () => { it("should focus next action with down arrow key", async () => { //when diff --git a/src/components/dropdown/item/bl-dropdown-item.stories.mdx b/src/components/dropdown/item/bl-dropdown-item.stories.mdx index 05f0d6ca..a5343616 100644 --- a/src/components/dropdown/item/bl-dropdown-item.stories.mdx +++ b/src/components/dropdown/item/bl-dropdown-item.stories.mdx @@ -1,11 +1,11 @@ -import { html } from 'lit'; -import { ifDefined } from 'lit/directives/if-defined.js'; import { - Meta, - Canvas, ArgsTable, + Canvas, + Meta, Story, } from '@storybook/addon-docs'; +import { html } from 'lit'; +import { ifDefined } from 'lit/directives/if-defined.js'; html` Dropdown Item ` @@ -43,5 +44,13 @@ If you want to have an icon for your actions, you can use the `icon` attribute. +## Disabled +You can disable the item by setting the `disabled` attribute. + + + {Template.bind({})} + + + diff --git a/src/components/dropdown/item/bl-dropdown-item.test.ts b/src/components/dropdown/item/bl-dropdown-item.test.ts index 582a0a20..980e4af7 100644 --- a/src/components/dropdown/item/bl-dropdown-item.test.ts +++ b/src/components/dropdown/item/bl-dropdown-item.test.ts @@ -1,5 +1,5 @@ +import { assert, expect, fixture, html, oneEvent } from "@open-wc/testing"; import BlDropdownItem from "./bl-dropdown-item"; -import { assert, fixture, html, oneEvent, expect } from "@open-wc/testing"; import type typeOfBlDropdownItem from "./bl-dropdown-item"; @@ -55,7 +55,12 @@ describe("bl-dropdown-item", () => { ); const button = el.shadowRoot?.querySelector("bl-button"); - setTimeout(() => button?.click()); + setTimeout(() => button?.dispatchEvent( + new CustomEvent("bl-click", { + bubbles: true, + composed: true, + }) + )); const event = await oneEvent(el, "bl-dropdown-item-click"); expect(el).to.exist; diff --git a/src/components/dropdown/item/bl-dropdown-item.ts b/src/components/dropdown/item/bl-dropdown-item.ts index 87e4fb29..c46a3545 100644 --- a/src/components/dropdown/item/bl-dropdown-item.ts +++ b/src/components/dropdown/item/bl-dropdown-item.ts @@ -1,4 +1,4 @@ -import { LitElement, html, CSSResultGroup, TemplateResult } from "lit"; +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { event, EventDispatcher } from "../../../utilities/event"; @@ -32,6 +32,12 @@ export default class BlDropdownItem extends LitElement { @property({ type: String }) icon?: BaklavaIcon; + /** + * Sets item as disabled + */ + @property({ type: Boolean, reflect: true }) + disabled = false; + @event("bl-dropdown-item-click") private onClick: EventDispatcher; private _handleClick() { @@ -78,7 +84,8 @@ export default class BlDropdownItem extends LitElement { kind="neutral" icon="${ifDefined(this.icon)}" role="menuitem" - @click="${this._handleClick}" + ?disabled="${this.disabled}" + @bl-click="${this._handleClick}" > `; } diff --git a/src/components/split-button/bl-split-button.test.ts b/src/components/split-button/bl-split-button.test.ts index 97c2ae44..f797a95f 100644 --- a/src/components/split-button/bl-split-button.test.ts +++ b/src/components/split-button/bl-split-button.test.ts @@ -1,18 +1,18 @@ -import BlSplitButton from "./bl-split-button"; import { - assert, - fixture, - html, - oneEvent, - expect, - elementUpdated, - waitUntil, - } from "@open-wc/testing"; -import type typeOfBlSplitButton from "./bl-split-button"; + assert, + elementUpdated, + expect, + fixture, + html, + oneEvent, + waitUntil, +} from "@open-wc/testing"; import { sendKeys } from "@web/test-runner-commands"; -import BlPopover from "../popover/bl-popover"; import BlButton from "../button/bl-button"; import "../popover/bl-popover"; +import BlPopover from "../popover/bl-popover"; +import type typeOfBlSplitButton from "./bl-split-button"; +import BlSplitButton from "./bl-split-button"; describe("bl-split-button", () => { it("is defined", () => { @@ -143,7 +143,12 @@ describe("bl-split-button", () => { .querySelector("bl-dropdown-item") ?.shadowRoot?.querySelector("bl-button") as HTMLElement | null; - item?.click(); + item?.dispatchEvent( + new CustomEvent("bl-click", { + bubbles: true, + composed: true, + }) + ); setTimeout(() => { expect(el.opened).to.false;