Skip to content

Commit

Permalink
Merge branch 'menu-item-loading' of github.com:mitchray/shoelace into…
Browse files Browse the repository at this point in the history
… next
  • Loading branch information
claviska committed Dec 6, 2023
2 parents b7eccb1 + 1a8403b commit 0d04376
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 3 deletions.
33 changes: 31 additions & 2 deletions docs/pages/components/menu-item.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ layout: component
<sl-menu style="max-width: 200px;">
<sl-menu-item>Option 1</sl-menu-item>
<sl-menu-item>Option 2</sl-menu-item>
<sl-menu-item>Option 3</sl-menu-item>
<sl-menu-item loading>Option 3</sl-menu-item>
<sl-divider></sl-divider>
<sl-menu-item type="checkbox" checked>Checkbox</sl-menu-item>
<sl-menu-item disabled>Disabled</sl-menu-item>
Expand Down Expand Up @@ -37,7 +37,7 @@ const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
<SlMenuItem>Option 1</SlMenuItem>
<SlMenuItem>Option 2</SlMenuItem>
<SlMenuItem>Option 3</SlMenuItem>
<SlMenuItem loading>Option 3</SlMenuItem>
<SlDivider />
<SlMenuItem type="checkbox" checked>
Checkbox
Expand All @@ -60,6 +60,35 @@ const App = () => (

## Examples

### Loading

Use the `loading` attribute to make a menu item busy. Clicks will be suppressed until the loading state is removed.

```html:preview
<sl-menu style="max-width: 200px;">
<sl-menu-item>Option 1</sl-menu-item>
<sl-menu-item loading>Option 2</sl-menu-item>
<sl-menu-item>Option 3</sl-menu-item>
</sl-menu>
```

{% raw %}

```jsx:react
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
<SlMenuItem>Option 1</SlMenuItem>
<SlMenuItem loading>Option 2</SlMenuItem>
<SlMenuItem>Option 3</SlMenuItem>
</SlMenu>
);
```

{% endraw %}

### Disabled

Add the `disabled` attribute to disable the menu item so it cannot be selected.
Expand Down
11 changes: 10 additions & 1 deletion src/components/menu-item/menu-item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import SlIcon from '../icon/icon.component.js';
import SlPopup from '../popup/popup.component.js';
import SlSpinner from '../spinner/spinner.component.js';
import styles from './menu-item.styles.js';
import type { CSSResultGroup } from 'lit';

Expand All @@ -19,6 +20,7 @@ import type { CSSResultGroup } from 'lit';
*
* @dependency sl-icon
* @dependency sl-popup
* @dependency sl-spinner
*
* @slot - The menu item's label.
* @slot prefix - Used to prepend an icon or similar element to the menu item.
Expand All @@ -30,6 +32,7 @@ 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 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.
Expand All @@ -38,7 +41,8 @@ export default class SlMenuItem extends ShoelaceElement {
static styles: CSSResultGroup = styles;
static dependencies = {
'sl-icon': SlIcon,
'sl-popup': SlPopup
'sl-popup': SlPopup,
'sl-spinner': SlSpinner
};

private cachedTextLabel: string;
Expand All @@ -55,6 +59,9 @@ export default class SlMenuItem extends ShoelaceElement {
/** 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;

Expand Down Expand Up @@ -158,6 +165,7 @@ export default class SlMenuItem extends ShoelaceElement {
'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
})}
Expand All @@ -179,6 +187,7 @@ export default class SlMenuItem extends ShoelaceElement {
</span>
${this.submenuController.renderSubmenu()}
${this.loading ? html`<sl-spinner part="spinner"></sl-spinner>` : ''}
</div>
`;
}
Expand Down
28 changes: 28 additions & 0 deletions src/components/menu-item/menu-item.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,32 @@ export default css`
outline-offset: -1px;
}
}
/*
* Loading modifier
*/
.menu-item--loading {
position: relative;
cursor: wait;
}
.menu-item--loading .menu-item__prefix,
.menu-item--loading .menu-item__label,
.menu-item--loading .menu-item__suffix,
.menu-item--loading .menu-item__check {
opacity: 0.5;
}
.menu-item--loading sl-spinner {
--indicator-color: currentColor;
--track-width: 1px;
position: absolute;
font-size: 0.75em;
height: 1em;
width: 1em;
top: calc(50% - 0.5em);
left: calc(1rem - 1px);
transform: translateX(-50%);
}
`;
8 changes: 8 additions & 0 deletions src/components/menu-item/menu-item.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('<sl-menu-item>', () => {

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');
});

Expand All @@ -48,6 +49,13 @@ describe('<sl-menu-item>', () => {
expect(el.getAttribute('aria-disabled')).to.equal('true');
});

describe('when loading', () => {
it('should have a spinner present', async () => {
const el = await fixture<SlMenuItem>(html` <sl-menu-item loading>Menu Item Label</sl-menu-item> `);
expect(el.shadowRoot!.querySelector('sl-spinner')).to.exist;
});
});

it('should return a text label when calling getTextLabel()', async () => {
const el = await fixture<SlMenuItem>(html` <sl-menu-item>Test</sl-menu-item> `);
expect(el.getTextLabel()).to.equal('Test');
Expand Down

0 comments on commit 0d04376

Please sign in to comment.