Drop base part from menu-item and menu-label (rel #207) (#884)

* [Menu-item] Drop `base` wrapper (rel #207)

Also add two states: `has-submenu` and `submenu-expanded`

* Add `checked-icon` and `submenu-icon` slots

* [Menu-label] Drop `base` part

* update changelog

---------

Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
This commit is contained in:
Lea Verou
2025-05-12 11:26:47 -04:00
committed by GitHub
parent 3c77d400f8
commit 7de6a676b8
6 changed files with 56 additions and 68 deletions

View File

@@ -33,6 +33,8 @@ During the alpha period, things might break! We take breaking changes very serio
- Added the [`.wa-cloak` utility](/docs/utilities/fouce) to prevent FOUCE
- Added the [`allDefined()` utility](/docs/usage/#all-defined) for awaiting component registration
- Simplified `<wa-breadcrumb-item>` by removing the `base` CSS part
- Simplified `<wa-menu-item>` and `<wa-menu-label>` by removing the `base` CSS part
- Added slots to `checked-icon` and `submenu-icon` in `<wa-menu-item>` so custom icons can be used
### Bug fixes

View File

@@ -5,13 +5,6 @@
display: block;
color: var(--wa-color-text-normal);
}
:host([inert]) {
display: none;
}
.menu-item {
position: relative;
display: flex;
align-items: stretch;
@@ -25,22 +18,26 @@
cursor: pointer;
}
:host([disabled]) .menu-item {
:host([inert]) {
display: none;
}
:host([disabled]) {
outline: none;
opacity: 0.5;
cursor: not-allowed;
}
.menu-item.menu-item--loading {
:host([loading]) {
outline: none;
cursor: wait;
}
.menu-item.menu-item--loading *:not(wa-spinner) {
:host([loading]) *:not(wa-spinner) {
opacity: 0.5;
}
.menu-item--loading wa-spinner {
:host([loading]) wa-spinner {
--indicator-color: currentColor;
--track-width: 0.0625rem;
position: absolute;
@@ -50,35 +47,35 @@
opacity: 1;
}
.menu-item .label {
.label {
flex: 1 1 auto;
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
}
.menu-item .prefix {
.prefix {
flex: 0 0 auto;
display: flex;
align-items: center;
}
.menu-item .prefix::slotted(*) {
.prefix::slotted(*) {
margin-inline-end: var(--wa-space-xs);
}
.menu-item .suffix {
.suffix {
flex: 0 0 auto;
display: flex;
align-items: center;
}
.menu-item .suffix::slotted(*) {
.suffix::slotted(*) {
margin-inline-start: var(--wa-space-xs);
}
/* Safe triangle */
.menu-item--submenu-expanded::after {
:host(:state(submenu-expanded))::after {
content: '';
position: fixed;
z-index: 899;
@@ -97,13 +94,13 @@
outline: none;
}
:host(:hover:not([aria-disabled='true'], :focus-visible)) .menu-item,
.menu-item--submenu-expanded {
:host(:hover:not([aria-disabled='true'], :focus-visible)),
:host(:state(submenu-expanded)) {
background-color: var(--background-color-hover);
color: var(--text-color-hover);
}
:host(:focus-visible) .menu-item {
:host(:focus-visible) {
outline: var(--wa-focus-ring);
outline-offset: calc(-1 * var(--wa-focus-ring-width));
background: var(--background-color-hover);
@@ -111,8 +108,8 @@
opacity: 1;
}
.menu-item .check,
.menu-item .chevron {
.check,
.chevron {
flex: 0 0 auto;
display: flex;
align-items: center;
@@ -122,8 +119,8 @@
visibility: hidden;
}
.menu-item--checked .check,
.menu-item--has-submenu .chevron {
:host([checked]) .check,
:host(:state(has-submenu)) .chevron {
visibility: visible;
}
@@ -139,8 +136,8 @@ wa-popup:dir(rtl)::part(popup) {
}
@media (forced-colors: active) {
:host(:hover:not([aria-disabled='true'])) .menu-item,
:host(:focus-visible) .menu-item {
:host(:hover:not([aria-disabled='true'])),
:host(:focus-visible) {
outline: dashed 1px SelectedItem;
outline-offset: -1px;
}

View File

@@ -1,7 +1,6 @@
import type { PropertyValues } from 'lit';
import { html } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import getText from '../../internal/get-text.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import { LocalizeController } from '../../utilities/localize.js';
@@ -24,8 +23,9 @@ import { SubmenuController } from './submenu-controller.js';
* @slot prefix - Used to prepend an icon or similar element to the menu item.
* @slot suffix - Used to append an icon or similar element to the menu item.
* @slot submenu - Used to denote a nested menu.
* @slot checked-icon - The icon used to indicate that this menu item is checked. Usually a `<wa-icon>`.
* @slot submenu-icon - The icon used to indicate that this menu item has a submenu. Usually a `<wa-icon>`.
*
* @csspart base - The component's base wrapper.
* @csspart checked-icon - The checked icon, which is only visible when the menu item is checked.
* @csspart prefix - The prefix container.
* @csspart label - The menu item label.
@@ -37,6 +37,9 @@ import { SubmenuController } from './submenu-controller.js';
* @cssproperty --background-color-hover - The menu item's background color on hover.
* @cssproperty --text-color-hover - The label color on hover.
* @cssproperty [--submenu-offset=-2px] - The distance submenus shift to overlap the parent menu.
*
* @cssstate has-submenu - Applied when the menu item has a submenu.
* @cssstate submenu-expanded - Applied when the menu item has a submenu and it is expanded.
*/
@customElement('wa-menu-item')
export default class WaMenuItem extends WebAwesomeElement {
@@ -129,6 +132,8 @@ export default class WaMenuItem extends WebAwesomeElement {
/** @internal - prevent the CEM from recording this event */
this.dispatchEvent(new Event('slotchange', { bubbles: true, composed: false, cancelable: false }));
}
this.toggleCustomState('has-submenu', this.isSubmenu());
}
private handleHostClick = (event: MouseEvent) => {
@@ -188,49 +193,40 @@ export default class WaMenuItem extends WebAwesomeElement {
return changed;
}
isSubmenu() {
/** Does this element have a submenu? */
private isSubmenu() {
return this.hasUpdated ? this.querySelector(`:scope > [slot="submenu"]`) !== null : this.withSubmenu;
}
render() {
const isRtl = this.hasUpdated ? this.localize.dir() === 'rtl' : this.dir === 'rtl';
const isSubmenuExpanded = this.submenuController.isExpanded();
this.toggleCustomState('submenu-expanded', isSubmenuExpanded);
this.internals.ariaHasPopup = this.isSubmenu() + '';
this.internals.ariaExpanded = isSubmenuExpanded + '';
return html`
<div
id="anchor"
part="base"
class=${classMap({
'menu-item': true,
'menu-item--checked': this.checked,
'menu-item--loading': this.loading,
'menu-item--has-submenu': this.isSubmenu(),
'menu-item--submenu-expanded': isSubmenuExpanded,
})}
?aria-haspopup="${this.isSubmenu()}"
?aria-expanded="${isSubmenuExpanded ? true : false}"
>
<span part="checked-icon" class="check">
<wa-icon name="check" library="system" variant="solid" aria-hidden="true"></wa-icon>
</span>
<slot name="checked-icon" part="checked-icon" class="check">
<wa-icon name="check" library="system" variant="solid" aria-hidden="true"></wa-icon>
</slot>
<slot name="prefix" part="prefix" class="prefix"></slot>
<slot name="prefix" part="prefix" class="prefix"></slot>
<slot part="label" class="label" @slotchange=${this.handleDefaultSlotChange}></slot>
<slot part="label" class="label" @slotchange=${this.handleDefaultSlotChange}></slot>
<slot name="suffix" part="suffix" class="suffix"></slot>
<slot name="suffix" part="suffix" class="suffix"></slot>
<span part="submenu-icon" class="chevron">
<wa-icon
name=${isRtl ? 'chevron-left' : 'chevron-right'}
library="system"
variant="solid"
aria-hidden="true"
></wa-icon>
</span>
<slot name="submenu-icon" part="submenu-icon" class="chevron">
<wa-icon
name=${isRtl ? 'chevron-left' : 'chevron-right'}
library="system"
variant="solid"
aria-hidden="true"
></wa-icon>
</slot>
${this.submenuController.renderSubmenu()} ${this.loading ? html`<wa-spinner part="spinner"></wa-spinner>` : ''}
</div>
${this.submenuController.renderSubmenu()} ${this.loading ? html`<wa-spinner part="spinner"></wa-spinner>` : ''}
`;
}
}

View File

@@ -271,7 +271,7 @@ export class SubmenuController implements ReactiveController {
<wa-popup
${ref(this.popupRef)}
placement=${isRtl ? 'left-start' : 'right-start'}
anchor="anchor"
.anchor="${this.host}"
flip
flip-fallback-strategy="best-fit"
skidding="${this.skidding}"

View File

@@ -3,12 +3,7 @@
color: var(--wa-color-text-quiet);
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-semibold);
}
.menu-label {
display: inline-block;
font: inherit;
padding: var(--wa-space-2xs) calc(var(--wa-space-2xs) + var(--wa-space-xl));
user-select: none;
-webkit-user-select: none;
user-select: none;
}

View File

@@ -10,15 +10,13 @@ import styles from './menu-label.css';
* @since 2.0
*
* @slot - The menu label's content.
*
* @csspart base - The component's base wrapper.
*/
@customElement('wa-menu-label')
export default class WaMenuLabel extends WebAwesomeElement {
static shadowStyle = styles;
render() {
return html` <slot part="base" class="menu-label"></slot> `;
return html`<slot></slot>`;
}
}