@puzzleitc/puzzle-shell
Version:
The standard design for Puzzle tools
161 lines (152 loc) • 5 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { LitElement, css, html } from "lit";
import { state } from "lit/decorators.js";
import { customElement } from "lit/decorators/custom-element.js";
import { classMap } from "lit/directives/class-map.js";
import { isNodeOrChild } from "../utils/dom";
import { navigateMenuWithKeyboard } from "../utils/menu";
import { theme } from "../utils/theme";
import "./Icon";
/**
* Menu dropdown with icon and text, will be rendered as a dropdown
* menu in the topbar on desktop, or with all items visible in the
* hamburger menu on mobile.
*
* @slot toggle - Slot for the icon and the text of the toggle button
* @slot items - Slot for dropdown menu items
*/
let MenuDropdown = class MenuDropdown extends LitElement {
constructor() {
super();
this.open = false;
this.handleEvent = this.handleEvent.bind(this);
}
connectedCallback() {
super.connectedCallback();
document.addEventListener("click", this.handleEvent);
document.addEventListener("keydown", this.handleEvent);
}
disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener("click", this.handleEvent);
document.removeEventListener("keydown", this.handleEvent);
}
handleEvent(e) {
this.handleMenuClose(e);
this.handleMenuNavigation(e);
}
handleMenuClose(e) {
if (this.open &&
((e.type === "click" &&
!isNodeOrChild(e.target, 'pzsh-menu-dropdown [slot="toggle"]')) ||
(e instanceof KeyboardEvent &&
e.type === "keydown" &&
(e.key === "Escape" || e.key === "Tab")))) {
this.toggleMenu();
}
}
handleMenuNavigation(e) {
if (this.open) {
navigateMenuWithKeyboard(() => Array.from(this.querySelector("[slot='items']")?.children || []), e);
}
}
toggleMenu(event) {
if (event) {
event.stopPropagation();
}
this.open = !this.open;
}
render() {
const dropdownIcon = this.open ? "angle-up" : "angle-down";
return html `
<button
type="button"
class="toggle"
=${this.toggleMenu}
aria-expanded=${this.open}
>
<slot name="toggle"></slot>
<pzsh-icon class="toggle-angle" name=${dropdownIcon}></pzsh-icon>
</button>
<div
class=${classMap({ "dropdown-menu": true, open: this.open })}
role="menu"
>
<slot name="items"></slot>
</div>
`;
}
};
MenuDropdown.styles = [
theme,
css `
:host {
position: relative;
margin-top: var(--pzsh-spacer);
}
.toggle {
display: flex; /* TODO: How does this work in today's browsers? */
align-items: center;
width: 100%;
border: 0;
padding: var(--pzsh-menu-item-padding-vertical)
var(--pzsh-menu-item-padding-horizontal);
color: var(--pzsh-color-gray-4);
background-color: transparent;
}
.toggle-angle {
display: none;
}
::slotted([slot="toggle"]) {
display: flex;
align-items: center;
gap: calc(var(--pzsh-spacer) / 2);
font-family: var(--pzsh-font-family);
font-size: 1rem;
}
.dropdown-menu {
margin-top: calc(-1 * var(--pzsh-menu-item-gap));
}
(min-width: ${theme.breakpoint}px) {
:host {
margin: 0;
}
.toggle {
width: auto;
padding: 0;
color: var(--pzsh-topbar-fg);
}
.toggle-angle {
display: block;
}
.dropdown-menu {
display: none;
position: absolute;
top: 100%;
right: 0;
z-index: var(--pzsh-menu-dropdown-z-index);
margin-top: calc(var(--pzsh-spacer) / 2);
border: 1px solid var(--pzsh-color-gray-3);
border-radius: 4px;
padding: var(--pzsh-spacer) 0;
background-color: var(--pzsh-menu-dropdown-item-bg);
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
}
.dropdown-menu.open {
display: block;
}
}
`,
];
__decorate([
state()
], MenuDropdown.prototype, "open", void 0);
MenuDropdown = __decorate([
customElement("pzsh-menu-dropdown")
], MenuDropdown);
export { MenuDropdown };