diff --git a/cypress/apps/angular-app/src/app/app.component.html b/cypress/apps/angular-app/src/app/app.component.html index 410e2479..8c7b57f9 100644 --- a/cypress/apps/angular-app/src/app/app.component.html +++ b/cypress/apps/angular-app/src/app/app.component.html @@ -14,6 +14,7 @@ + diff --git a/cypress/apps/angular-app/src/app/app.module.ts b/cypress/apps/angular-app/src/app/app.module.ts index 9e74afdd..ba217c28 100644 --- a/cypress/apps/angular-app/src/app/app.module.ts +++ b/cypress/apps/angular-app/src/app/app.module.ts @@ -1,4 +1,3 @@ -import "@webcomponents/scoped-custom-element-registry"; import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { AppComponent } from "./app.component"; @@ -19,6 +18,7 @@ import { Drawer } from "../components/drawer/drawer.component"; import { Dropdown } from "../components/dropdown/dropdown.component"; import { FileUpload } from "../components/fileupload/fileupload.component"; import { Footer } from "../components/footer/footer.component"; +import { Icon } from "../components/icon/icon.component"; import { Input } from "../components/input/input.component"; import { Mainnav } from "../components/mainnav/mainnav.component"; import { Masthead } from "../components/masthead/masthead.component"; @@ -56,6 +56,7 @@ import { Tooltip } from "../components/tooltip/tooltip.component"; FileUpload, Footer, Input, + Icon, Mainnav, Masthead, Modal, diff --git a/cypress/apps/angular-app/src/components/icon/icon.component.html b/cypress/apps/angular-app/src/components/icon/icon.component.html new file mode 100644 index 00000000..f765a588 --- /dev/null +++ b/cypress/apps/angular-app/src/components/icon/icon.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cypress/apps/angular-app/src/components/icon/icon.component.ts b/cypress/apps/angular-app/src/components/icon/icon.component.ts new file mode 100644 index 00000000..e4623e95 --- /dev/null +++ b/cypress/apps/angular-app/src/components/icon/icon.component.ts @@ -0,0 +1,7 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "icon-component", + templateUrl: "./icon.component.html" +}) +export class Icon {} diff --git a/cypress/apps/next-app/src/app/components/Breadcrumb.tsx b/cypress/apps/next-app/src/app/components/Breadcrumb.tsx index 624ab92d..b04f65cf 100644 --- a/cypress/apps/next-app/src/app/components/Breadcrumb.tsx +++ b/cypress/apps/next-app/src/app/components/Breadcrumb.tsx @@ -4,10 +4,14 @@ import SgdsBreadcrumbItem from "@govtechsg/sgds-web-component/react/breadcrumb-i export const Breadcrumb = () => { return ( - Home - Item 1 - Item 2 - Item 3 + + first + + first + first + first + first + first Last Item ) diff --git a/index.html b/index.html index 2bd9eb0e..8653a36b 100644 --- a/index.html +++ b/index.html @@ -1559,6 +1559,21 @@

Table w/ RemovableSort

Overflow

+ + + + + Option label + Option label + Google + Option label + Option label + Option label + + diff --git a/src/components/Breadcrumb/sgds-breadcrumb.ts b/src/components/Breadcrumb/sgds-breadcrumb.ts index e9554cdf..e5d9b330 100644 --- a/src/components/Breadcrumb/sgds-breadcrumb.ts +++ b/src/components/Breadcrumb/sgds-breadcrumb.ts @@ -2,7 +2,7 @@ import { property, query } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { html } from "lit/static-html.js"; import SgdsElement from "../../base/sgds-element"; -import { warnUnregisteredElements } from "../../utils/ce-registry"; +import SgdsOverflowMenu from "../../internals/OverflowMenu/sgds-overflow-menu"; import breadcrumbStyle from "./breadcrumb.css"; import type SgdsBreadcrumbItem from "./sgds-breadcrumb-item"; /** @@ -13,53 +13,39 @@ import type SgdsBreadcrumbItem from "./sgds-breadcrumb-item"; */ export class SgdsBreadcrumb extends SgdsElement { static styles = [...SgdsElement.styles, breadcrumbStyle]; + static dependencies = { + "sgds-overflow-menu": SgdsOverflowMenu + }; /** The aria-label of nav element within breadcrumb component. */ @property({ type: String }) ariaLabel = "breadcrumb"; /**@internal */ @query("slot") defaultSlot: HTMLSlotElement; - - private _checkDependencies() { - warnUnregisteredElements("sgds-dropdown"); - warnUnregisteredElements("sgds-icon-button"); - warnUnregisteredElements("sgds-icon"); - } /** * creates ` - * - * - * - * + * * * ... - * + * * ` */ private _replaceExcessItemsWithDropdown(items: SgdsBreadcrumbItem[]) { const breadcrumbItem = document.createElement("sgds-breadcrumb-item"); - const dropdown = document.createElement("sgds-dropdown"); - const overflowButton = document.createElement("sgds-icon-button"); - const icon = document.createElement("sgds-icon"); - icon.setAttribute("name", "three-dots"); - overflowButton.setAttribute("slot", "toggler"); - overflowButton.setAttribute("variant", "ghost"); - overflowButton.setAttribute("role", "button"); - overflowButton.setAttribute("aria-haspopup", "menu"); - overflowButton.appendChild(icon); - dropdown.appendChild(overflowButton); + const overflowMenu = document.createElement("sgds-overflow-menu"); + overflowMenu.setAttribute("aria-haspopup", "menu"); const mapItems = items.filter((item, index) => { if (index > 0 && index < items.length - 2) { const clonedAnchor = item.querySelector("a"); const clonedAnchorNode = clonedAnchor.cloneNode(true); const dropdownItem = document.createElement("sgds-dropdown-item"); dropdownItem.appendChild(clonedAnchorNode); - dropdown.appendChild(dropdownItem); + overflowMenu.appendChild(dropdownItem); return; } else { return item; } }); - breadcrumbItem.appendChild(dropdown); + breadcrumbItem.appendChild(overflowMenu); mapItems.splice(1, 0, breadcrumbItem); this.defaultSlot.replaceWith(...mapItems); @@ -78,7 +64,6 @@ export class SgdsBreadcrumb extends SgdsElement { }); if (items.length >= 5) { - this._checkDependencies(); this._replaceExcessItemsWithDropdown(items); } } diff --git a/src/internals/OverflowMenu/index.ts b/src/internals/OverflowMenu/index.ts new file mode 100644 index 00000000..7d5b1e4b --- /dev/null +++ b/src/internals/OverflowMenu/index.ts @@ -0,0 +1,10 @@ +import { SgdsOverflowMenu } from "./sgds-overflow-menu"; +import { register } from "../../utils/ce-registry"; + +register("sgds-overflow-menu", SgdsOverflowMenu); + +declare global { + interface HTMLElementTagNameMap { + "sgds-overflow-menu": SgdsOverflowMenu; + } +} diff --git a/src/internals/OverflowMenu/overflow-menu.css b/src/internals/OverflowMenu/overflow-menu.css new file mode 100644 index 00000000..ecd2be37 --- /dev/null +++ b/src/internals/OverflowMenu/overflow-menu.css @@ -0,0 +1,23 @@ +.overflow-btn { + width: var(--sgds-dimension-32); + height: var(--sgds-dimension-32); + background-color: var(--sgds-default-bg-transparent); + border-radius: var(--sgds-border-radius-sm); + border: 0; + padding: 0; + position: relative; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; +} +.overflow-btn:hover { + background-color: var(--sgds-default-bg-translucent); + } + +.overflow-btn:focus, +.overflow-btn:focus-visible { + outline: 0; + box-shadow: var(--sgds-box-shadow-focus); + background-color: var(--sgds-default-bg-translucent); +} \ No newline at end of file diff --git a/src/internals/OverflowMenu/sgds-overflow-menu.ts b/src/internals/OverflowMenu/sgds-overflow-menu.ts new file mode 100644 index 00000000..2cec47be --- /dev/null +++ b/src/internals/OverflowMenu/sgds-overflow-menu.ts @@ -0,0 +1,34 @@ +import { html } from "lit"; +import SgdsElement from "../../base/sgds-element"; +import overflowMenuStyles from "./overflow-menu.css"; +import { property } from "lit/decorators.js"; +import { SgdsDropdown } from "../../components/Dropdown/sgds-dropdown"; +import { SgdsDropdownItem } from "../../components/Dropdown/sgds-dropdown-item"; +import { SgdsIcon } from "../../components/Icon/sgds-icon"; +/** + * @summary An overflow menu is a UI element, often represented by three dots (⋮ or …), that opens a menu with additional actions or options. + * @slot default - The overflow menu items. Pass in sgds-dropdown-items in this slot + */ +export class SgdsOverflowMenu extends SgdsElement { + static styles = [...SgdsElement.styles, overflowMenuStyles]; + static dependencies = { + "sgds-dropdown": SgdsDropdown, + "sgds-dropdown-item": SgdsDropdownItem, + "sgds-icon": SgdsIcon + }; + /** Specifies a large or small button */ + @property({ type: String, reflect: true }) size: "sm" | "md" = "md"; + + render() { + return html` + + + + + `; + } +} + +export default SgdsOverflowMenu; diff --git a/test/breadcrumb.test.ts b/test/breadcrumb.test.ts index 7182f60e..3d7ccd09 100644 --- a/test/breadcrumb.test.ts +++ b/test/breadcrumb.test.ts @@ -63,22 +63,7 @@ describe("sgds-breadcrumb", () => { size="md" variant="primary" > - - - - - + { Contacts - + ", () => { + it("semantically matches the DOM", async () => { + const el = await fixture(html``); + assert.shadowDom.equal( + el, + ` + + + + + ` + ); + }); +});