diff --git a/docs/pages/components/menu-item.md b/docs/pages/components/menu-item.md index 6125d8ec7..633415003 100644 --- a/docs/pages/components/menu-item.md +++ b/docs/pages/components/menu-item.md @@ -60,35 +60,6 @@ const App = () => ( ## Examples -### Disabled - -Add the `disabled` attribute to disable the menu item so it cannot be selected. - -```html:preview - - Option 1 - Option 2 - Option 3 - -``` - -{% raw %} - -```jsx:react -import WaMenu from '@shoelace-style/shoelace/dist/react/menu'; -import WaMenuItem from '@shoelace-style/shoelace/dist/react/menu-item'; - -const App = () => ( - - Option 1 - Option 2 - Option 3 - -); -``` - -{% endraw %} - ### Prefix & Suffix Add content to the start and end of menu items using the `prefix` and `suffix` slots. @@ -151,6 +122,64 @@ const App = () => ( {% endraw %} +### Disabled + +Add the `disabled` attribute to disable the menu item so it cannot be selected. + +```html:preview + + Option 1 + Option 2 + Option 3 + +``` + +{% raw %} + +```jsx:react +import WaMenu from '@shoelace-style/shoelace/dist/react/menu'; +import WaMenuItem from '@shoelace-style/shoelace/dist/react/menu-item'; + +const App = () => ( + + Option 1 + Option 2 + Option 3 + +); +``` + +{% endraw %} + +### Loading + +Use the `loading` attribute to indicate that a menu item is busy. Like a disabled menu item, clicks will be suppressed until the loading state is removed. + +```html:preview + + Option 1 + Option 2 + Option 3 + +``` + +{% raw %} + +```jsx:react +import WaMenu from '@shoelace-style/shoelace/dist/react/menu'; +import WaMenuItem from '@shoelace-style/shoelace/dist/react/menu-item'; + +const App = () => ( + + Option 1 + Option 2 + Option 3 + +); +``` + +{% endraw %} + ### Checkbox Menu Items Set the `type` attribute to `checkbox` to create a menu item that will toggle on and off when selected. You can use the `checked` attribute to set the initial state. diff --git a/docs/pages/resources/changelog.md b/docs/pages/resources/changelog.md index cc5c39993..baff1dd55 100644 --- a/docs/pages/resources/changelog.md +++ b/docs/pages/resources/changelog.md @@ -26,6 +26,7 @@ New versions of Web Awesome are released as-needed and generally occur when a cr ## Next +- Added the `loading` attribute and the `spinner` and `spinner__base` part to `` [#1700] - Added the `hover-bridge` feature to `` to support better tooltip accessibility [#1734] - Added the `loading` attribute and the `spinner` and `spinner__base` part to `` [#1700] - Fixed files that did not have `.js` extensions. [#1770] diff --git a/src/components/menu-item/menu-item.component.ts b/src/components/menu-item/menu-item.component.ts index d1be3027b..f6c49d04c 100644 --- a/src/components/menu-item/menu-item.component.ts +++ b/src/components/menu-item/menu-item.component.ts @@ -8,6 +8,7 @@ import { watch } from '../../internal/watch.js'; import styles from './menu-item.styles.js'; import WaIcon from '../icon/icon.component.js'; import WaPopup from '../popup/popup.component.js'; +import WaSpinner from '../spinner/spinner.component.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import type { CSSResultGroup } from 'lit'; @@ -30,6 +31,8 @@ import type { CSSResultGroup } from 'lit'; * @csspart prefix - The prefix container. * @csspart label - The menu item label. * @csspart suffix - The suffix container. + * @csspart spinner - The spinner that shows when the menu item is in the loading state. + * @csspart spinner__base - The spinner's base part. * @csspart submenu-icon - The submenu icon, visible only when the menu item has a submenu (not yet implemented). * * @cssproperty [--submenu-offset=-2px] - The distance submenus shift to overlap the parent menu. @@ -38,7 +41,8 @@ export default class WaMenuItem extends WebAwesomeElement { static styles: CSSResultGroup = styles; static dependencies = { 'wa-icon': WaIcon, - 'wa-popup': WaPopup + 'wa-popup': WaPopup, + 'wa-spinner': WaSpinner }; private cachedTextLabel: string; @@ -55,6 +59,9 @@ export default class WaMenuItem extends WebAwesomeElement { /** A unique value to store in the menu item. This can be used as a way to identify menu items when selected. */ @property() value = ''; + /** Draws the menu item in a loading state. */ + @property({ type: Boolean, reflect: true }) loading = false; + /** Draws the menu item in a disabled state, preventing selection. */ @property({ type: Boolean, reflect: true }) disabled = false; @@ -158,6 +165,7 @@ export default class WaMenuItem extends WebAwesomeElement { 'menu-item--rtl': isRtl, 'menu-item--checked': this.checked, 'menu-item--disabled': this.disabled, + 'menu-item--loading': this.loading, 'menu-item--has-submenu': this.isSubmenu(), 'menu-item--submenu-expanded': isSubmenuExpanded })} @@ -183,7 +191,7 @@ export default class WaMenuItem extends WebAwesomeElement { > - ${this.submenuController.renderSubmenu()} + ${this.submenuController.renderSubmenu()} ${this.loading ? html`` : ''} `; } diff --git a/src/components/menu-item/menu-item.styles.ts b/src/components/menu-item/menu-item.styles.ts index 278e7a5ec..06c6ffed1 100644 --- a/src/components/menu-item/menu-item.styles.ts +++ b/src/components/menu-item/menu-item.styles.ts @@ -34,6 +34,25 @@ export default css` cursor: not-allowed; } + .menu-item.menu-item--loading { + outline: none; + cursor: wait; + } + + .menu-item.menu-item--loading *:not(wa-spinner) { + opacity: 0.5; + } + + .menu-item--loading wa-spinner { + --indicator-color: currentColor; + --track-width: 1px; + position: absolute; + font-size: 0.75em; + top: calc(50% - 0.5em); + left: 0.65rem; + opacity: 1; + } + .menu-item .menu-item__label { flex: 1 1 auto; display: inline-block; diff --git a/src/components/menu-item/menu-item.test.ts b/src/components/menu-item/menu-item.test.ts index e6477ea3c..a1b578b15 100644 --- a/src/components/menu-item/menu-item.test.ts +++ b/src/components/menu-item/menu-item.test.ts @@ -40,6 +40,7 @@ describe('', () => { expect(el.value).to.equal(''); expect(el.disabled).to.be.false; + expect(el.loading).to.equal(false); expect(el.getAttribute('aria-disabled')).to.equal('false'); }); @@ -48,6 +49,13 @@ describe('', () => { expect(el.getAttribute('aria-disabled')).to.equal('true'); }); + describe('when loading', () => { + it('should have a spinner present', async () => { + const el = await fixture(html` Menu Item Label `); + expect(el.shadowRoot!.querySelector('wa-spinner')).to.exist; + }); + }); + it('should return a text label when calling getTextLabel()', async () => { const el = await fixture(html` Test `); expect(el.getTextLabel()).to.equal('Test');