diff --git a/src/components/dropdownAction/dropdown-action-item.css b/src/components/dropdownAction/dropdown-action-item.css new file mode 100644 index 0000000..6c100ef --- /dev/null +++ b/src/components/dropdownAction/dropdown-action-item.css @@ -0,0 +1,16 @@ +.action { + width: 100%; + padding-block: 7px; + padding-inline: 8px; + background: none; + border: 0; + color: var(--color-semantic-text-regular); + font-size: 12px; + text-align: left; + line-height: 1; +} + +.action:hover, +.action:focus { + background: var(--color-semantic-surface-regular-3); +} diff --git a/src/components/dropdownAction/dropdown-action.css b/src/components/dropdownAction/dropdown-action.css new file mode 100644 index 0000000..2179289 --- /dev/null +++ b/src/components/dropdownAction/dropdown-action.css @@ -0,0 +1,16 @@ +.base { + position: relative; +} + +.contents { + position: absolute; + top: 100%; + left: 0; + min-width: 80px; + margin-block-start: 8px; + padding-block: 8px; + background: var(--color-semantic-surface-regular-1); + border: 1px solid var(--color-semantic-border-regular); + border-radius: 5px; + box-shadow: 0px 3px 12px 0px var(--color-semantic-elevation-regular); +} diff --git a/src/components/dropdownAction/sp-dropdown-action-button.ts b/src/components/dropdownAction/sp-dropdown-action-button.ts new file mode 100644 index 0000000..696f611 --- /dev/null +++ b/src/components/dropdownAction/sp-dropdown-action-button.ts @@ -0,0 +1,40 @@ +import { UbButton } from "@ub-design/components-web-components/"; +// @ts-ignore +import foundationStyle from "../foundation.css?inline" assert { type: "css" }; +// @ts-ignore +import buttonStyle from "../button/button.css?inline" assert { type: "css" }; +import "../icon/sp-icon"; + +const styles = new CSSStyleSheet(); +styles.replaceSync(`${foundationStyle} ${buttonStyle}`); + +export class SpDropdownActionButton extends UbButton { + constructor() { + super(); + + if (this.shadowRoot) { + this.shadowRoot.adoptedStyleSheets = [ + ...this.shadowRoot!.adoptedStyleSheets, + styles, + ]; + } + + this.#insertIconElement(); + } + + #insertIconElement() { + const iconElement = document.createElement("sp-icon"); + iconElement.type = "arrow_down"; + iconElement.size = "small"; + this.buttonElement.insertBefore(iconElement, this.textElement.nextSibling); + } +} + +declare global { + interface HTMLElementTagNameMap { + "sp-dropdown-action-button": SpDropdownActionButton; + } +} + +customElements.get("sp-dropdown-action-button") || + customElements.define("sp-dropdown-action-button", SpDropdownActionButton); diff --git a/src/components/dropdownAction/sp-dropdown-action-item.ts b/src/components/dropdownAction/sp-dropdown-action-item.ts new file mode 100644 index 0000000..e5f3758 --- /dev/null +++ b/src/components/dropdownAction/sp-dropdown-action-item.ts @@ -0,0 +1,55 @@ +// @ts-ignore +import foundationStyle from "../foundation.css?inline" assert { type: "css" }; +// @ts-ignore +import dropdownActionItemStyle from "./dropdown-action-item.css?inline" assert { type: "css" }; + +const styles = new CSSStyleSheet(); +styles.replaceSync(`${foundationStyle} ${dropdownActionItemStyle}`); + +export class SpDropdownActionItem extends HTMLElement { + #baseElement = document.createElement("div"); + #buttonElement = document.createElement("button"); + + set text(value: string) { + this.#buttonElement.innerText = value; + } + + static get observedAttributes() { + return ["text"]; + } + + constructor() { + super(); + + const shadowRoot = this.attachShadow({ mode: "open" }); + shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, styles]; + } + + connectedCallback() { + this.#baseElement.classList.add("base"); + this.#baseElement.role = "menuitem"; + + this.#buttonElement.classList.add("action"); + + this.#baseElement.appendChild(this.#buttonElement); + this.shadowRoot?.appendChild(this.#baseElement); + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (oldValue === newValue) return; + switch (name) { + case "text": + this.text = newValue; + break; + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "sp-dropdown-action-item": SpDropdownActionItem; + } +} + +customElements.get("sp-dropdown-action-item") || + customElements.define("sp-dropdown-action-item", SpDropdownActionItem); diff --git a/src/components/dropdownAction/sp-dropdown-action.ts b/src/components/dropdownAction/sp-dropdown-action.ts new file mode 100644 index 0000000..2ccc109 --- /dev/null +++ b/src/components/dropdownAction/sp-dropdown-action.ts @@ -0,0 +1,70 @@ +// @ts-ignore +import foundationStyle from "../foundation.css?inline" assert { type: "css" }; +// @ts-ignore +import dropdownActionStyle from "./dropdown-action.css?inline" assert { type: "css" }; +import "./sp-dropdown-action-button"; +import "./sp-dropdown-action-item"; + +const styles = new CSSStyleSheet(); +styles.replaceSync(`${foundationStyle} ${dropdownActionStyle}`); + +export class SpDropdownAction extends HTMLElement { + #baseElement = document.createElement("div"); + #buttonElement = document.createElement("sp-dropdown-action-button"); + #contentsElement = document.createElement("div"); + + set label(value: string) { + this.#buttonElement.text = value; + } + + static get observedAttributes() { + return ["label"]; + } + + constructor() { + super(); + + const shadowRoot = this.attachShadow({ mode: "open" }); + shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, styles]; + } + + connectedCallback() { + this.#buttonElement.addEventListener( + "click", + this.#toggleButton.bind(this), + ); + + this.#baseElement.appendChild(this.#buttonElement); + + this.#contentsElement.classList.add("contents"); + this.#contentsElement.role = "menu"; + this.#contentsElement.appendChild(document.createElement("slot")); + + this.#baseElement.appendChild(this.#contentsElement); + this.#baseElement.classList.add("base"); + + this.shadowRoot?.appendChild(this.#baseElement); + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (oldValue === newValue) return; + switch (name) { + case "label": + this.label = newValue; + break; + } + } + + #toggleButton() { + this.#buttonElement.toggleAttribute("selected"); + } +} + +declare global { + interface HTMLElementTagNameMap { + "sp-dropdown-action": SpDropdownAction; + } +} + +customElements.get("sp-dropdown-action") || + customElements.define("sp-dropdown-action", SpDropdownAction); diff --git a/stories/dropdownAction/sp-dropdown-action.story.ts b/stories/dropdownAction/sp-dropdown-action.story.ts new file mode 100644 index 0000000..ee630b1 --- /dev/null +++ b/stories/dropdownAction/sp-dropdown-action.story.ts @@ -0,0 +1,28 @@ +import "../../src/components/dropdownAction/sp-dropdown-action"; +import type { Meta, StoryObj } from "@storybook/web-components"; +import "@sp-design/token/lib/speeda-tokens.css"; +import { html } from "lit"; + +const meta: Meta = { + component: "sp-dropdown-action", + argTypes: {}, + args: {}, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = {}; + +export const WithItems: Story = { + render: () => html` + + + + + + + `, +};