Bug 1880481 - Create a story for panel-list submenu r=reusable-components-reviewers,desktop-theme-reviewers,fxview-reviewers,emilio,sclements

This patch adds a story for `panel-list` submenus to document how to create them. It also amends the README with a small code example, and moves the submenu slot creation out of the constructor to the connectedCallback, since we can't reliably read attribute values in custom element constructors.

Differential Revision: https://phabricator.services.mozilla.com/D202012
This commit is contained in:
Hanna Jones 2024-03-06 20:09:39 +00:00
parent b71caf6061
commit 7d9d21f181
5 changed files with 87 additions and 47 deletions

View File

@ -148,10 +148,6 @@ panel-item::part(button):hover:active {
background-color: var(--fxview-element-background-active);
}
panel-list {
overflow-y: visible;
}
fxview-empty-state:not([isSelectedTab]) button[slot="primary-action"] {
margin-inline-start: 0;
}

View File

@ -6,7 +6,7 @@ children and optional `hr` elements as separators. The `panel-list` will anchor
itself to the target of the initiating event when opened with
`panelList.toggle(event)`.
Note: Nested menus are not currently supported. XUL is currently required to
Note: XUL is currently required to
support accesskey underlining (although using `moz-label` could change that).
Shortcuts are not displayed automatically in the `panel-item`.
@ -229,3 +229,23 @@ grow larger than its containing window if needed.
</html:panel-list>
</panel>
```
### Submenus
`panel-list` supports nested submenus. Submenus can be created by nesting a second `panel-list` in a `panel-item`'s `submenu` slot and specifying a `submenu` attribute on that `panel-item` that points to the nested list's ID. For example:
```html
<panel-list>
<panel-item>No submenu</panel-item>
<panel-item>No submenu</panel-item>
<panel-item submenu="example-submenu">
Has a submenu
<panel-list slot="submenu" id="example-submenu">
<panel-item>I'm a submenu item!</panel-item>
<panel-item>I'm also a submenu item!</panel-item>
</panel-list>
</panel-item>
</panel-list>
```
As of February 2024 submenus are only in use in Firefox View and support for nesting beyond one submenu may be limited.

View File

@ -26,6 +26,10 @@
box-sizing: border-box;
}
:host([has-submenu]) {
overflow-y: visible;
}
:host(:not([slot=submenu])) {
max-height: 100%;
}

View File

@ -308,7 +308,7 @@
}
addHideListeners() {
if (this.hasAttribute("stay-open") && !this.lastAnchorNode.hasSubmenu) {
if (this.hasAttribute("stay-open") && !this.lastAnchorNode?.hasSubmenu) {
// This is intended for inspection in Storybook.
return;
}
@ -631,31 +631,12 @@
this.#defaultSlot = document.createElement("slot");
this.#defaultSlot.style.display = "none";
if (this.hasSubmenu) {
this.icon = document.createElement("div");
this.icon.setAttribute("class", "submenu-icon");
this.label.setAttribute("class", "submenu-label");
this.button.setAttribute("class", "submenu-container");
this.button.appendChild(this.icon);
this.submenuSlot = document.createElement("slot");
this.submenuSlot.name = "submenu";
this.shadowRoot.append(
style,
this.button,
this.#defaultSlot,
this.submenuSlot
);
} else {
this.shadowRoot.append(
style,
this.button,
supportLinkSlot,
this.#defaultSlot
);
}
this.shadowRoot.append(
style,
this.button,
supportLinkSlot,
this.#defaultSlot
);
}
connectedCallback() {
@ -664,6 +645,10 @@
this._l10nRootConnected = true;
}
this.panel =
this.getRootNode()?.host?.closest("panel-list") ||
this.closest("panel-list");
if (!this.#initialized) {
this.#initialized = true;
// When click listeners are added to the panel-item it creates a node in
@ -683,18 +668,28 @@
});
if (this.hasSubmenu) {
this.panel.setAttribute("has-submenu", "");
this.icon = document.createElement("div");
this.icon.setAttribute("class", "submenu-icon");
this.label.setAttribute("class", "submenu-label");
this.button.setAttribute("class", "submenu-container");
this.button.appendChild(this.icon);
this.submenuSlot = document.createElement("slot");
this.submenuSlot.name = "submenu";
this.shadowRoot.append(this.submenuSlot);
this.setSubmenuContents();
}
}
this.panel =
this.getRootNode()?.host?.closest("panel-list") ||
this.closest("panel-list");
if (this.panel) {
this.panel.addEventListener("hidden", this);
this.panel.addEventListener("shown", this);
}
if (this.hasSubmenu) {
this.addEventListener("mouseenter", this);
this.addEventListener("mouseleave", this);
@ -762,7 +757,9 @@
setSubmenuContents() {
this.submenuPanel = this.submenuSlot.assignedNodes()[0];
this.shadowRoot.append(this.submenuPanel);
if (this.submenuPanel) {
this.shadowRoot.append(this.submenuPanel);
}
}
get disabled() {

View File

@ -22,6 +22,9 @@ panel-list-checked = Checked
panel-list-badged = Badged, look at me
panel-list-passwords = Passwords
panel-list-settings = Settings
submenu-item-one = Submenu Item One
submenu-item-two = Submenu Item Two
submenu-item-three = Submenu Item Three
`,
},
};
@ -36,7 +39,7 @@ function openMenu(event) {
}
}
const Template = ({ isOpen, items, wideAnchor }) =>
const Template = ({ isOpen, items, wideAnchor, hasSubMenu }) =>
html`
<style>
panel-item[icon="passwords"]::part(button) {
@ -93,22 +96,36 @@ const Template = ({ isOpen, items, wideAnchor }) =>
?open=${isOpen}
?min-width-from-anchor=${wideAnchor}
>
${items.map(i =>
i == "<hr>"
${items.map((item, index) => {
// Always showing submenu on the first item for simplicity.
let showSubMenu = hasSubMenu && index == 0;
let subMenuId = showSubMenu ? "example-sub-menu" : undefined;
return item == "<hr>"
? html` <hr /> `
: html`
<panel-item
icon=${i.icon ?? ""}
?checked=${i.checked}
?badged=${i.badged}
accesskey=${ifDefined(i.accesskey)}
data-l10n-id=${i.l10nId ?? i}
></panel-item>
`
)}
icon=${item.icon ?? ""}
?checked=${item.checked}
?badged=${item.badged}
accesskey=${ifDefined(item.accesskey)}
data-l10n-id=${item.l10nId ?? item}
submenu=${ifDefined(subMenuId)}
>
${showSubMenu ? subMenuTemplate() : ""}
</panel-item>
`;
})}
</panel-list>
`;
const subMenuTemplate = () => html`
<panel-list slot="submenu" id="example-sub-menu">
<panel-item data-l10n-id="submenu-item-one"></panel-item>
<panel-item data-l10n-id="submenu-item-two"></panel-item>
<panel-item data-l10n-id="submenu-item-three"></panel-item>
</panel-list>
`;
export const Simple = Template.bind({});
Simple.args = {
isOpen: false,
@ -145,3 +162,9 @@ Wide.args = {
...Simple.args,
wideAnchor: true,
};
export const SubMenu = Template.bind({});
SubMenu.args = {
...Simple.args,
hasSubMenu: true,
};