Skip to content

Commit 1825bfa

Browse files
authored
Merge pull request #138 from umbraco/feature/uui-menu-item-as-anchor-tag
Support href and render a anchor tag
2 parents 7f5a2ac + 321b1a9 commit 1825bfa

File tree

5 files changed

+280
-49
lines changed

5 files changed

+280
-49
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/uui-menu-item/lib/uui-menu-item.element.ts

Lines changed: 97 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { LitElement, css, html } from 'lit';
22
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
33
import { property, state } from 'lit/decorators.js';
4+
import { ifDefined } from 'lit/directives/if-defined.js';
45
import {
56
ActiveMixin,
67
LabelMixin,
78
SelectableMixin,
9+
SelectOnlyMixin,
810
} from '@umbraco-ui/uui-base/lib/mixins';
911
import { UUIMenuItemEvent } from './UUIMenuItemEvent';
1012

@@ -21,8 +23,8 @@ import { UUIMenuItemEvent } from './UUIMenuItemEvent';
2123
*
2224
*/
2325
@defineElement('uui-menu-item')
24-
export class UUIMenuItemElement extends SelectableMixin(
25-
ActiveMixin(LabelMixin('label', LitElement))
26+
export class UUIMenuItemElement extends SelectOnlyMixin(
27+
SelectableMixin(ActiveMixin(LabelMixin('label', LitElement)))
2628
) {
2729
static styles = [
2830
css`
@@ -31,6 +33,8 @@ export class UUIMenuItemElement extends SelectableMixin(
3133
background-color: var(--uui-interface-surface);
3234
/** consider transparent. */
3335
--uui-menu-item-child-indent: calc(var(--uui-menu-item-indent, 0) + 1);
36+
37+
user-select: none;
3438
}
3539
3640
#menu-item {
@@ -46,19 +50,20 @@ export class UUIMenuItemElement extends SelectableMixin(
4650
}
4751
4852
button {
49-
display: block;
53+
display: inline-flex;
54+
align-items: center;
55+
5056
font-family: inherit;
57+
font-size: inherit;
5158
5259
padding: 0;
5360
text-align: left;
54-
box-shadow: none;
5561
border: none;
5662
color: inherit;
5763
background-color: transparent;
5864
cursor: pointer;
59-
z-index: 1;
60-
/* padding: 0 var(--uui-size-base-unit) 0 var(--uui-size-base-unit); */
6165
min-height: var(--uui-size-12);
66+
z-index: 1;
6267
}
6368
/* button:hover {
6469
color: var(--uui-interface-contrast-hover);
@@ -70,6 +75,16 @@ export class UUIMenuItemElement extends SelectableMixin(
7075
white-space: nowrap;
7176
overflow: hidden;
7277
text-overflow: ellipsis;
78+
79+
display: inline-flex;
80+
align-items: center;
81+
text-decoration: none;
82+
color: currentColor;
83+
min-height: var(--uui-size-12);
84+
z-index: 1;
85+
}
86+
span#label-button {
87+
pointer-events: none; /* avoid hovering state on this. */
7388
}
7489
7590
#caret-button + #label-button {
@@ -101,9 +116,6 @@ export class UUIMenuItemElement extends SelectableMixin(
101116
transition: opacity 120ms;
102117
grid-column-start: 3;
103118
}
104-
#menu-item:hover #actions-container {
105-
opacity: 1;
106-
}
107119
108120
#loader {
109121
position: absolute;
@@ -112,13 +124,12 @@ export class UUIMenuItemElement extends SelectableMixin(
112124
}
113125
114126
#icon {
127+
display: inline-flex;
115128
font-size: 16px;
116-
margin-bottom: var(--uui-size-1);
117129
margin-right: var(--uui-size-2);
118-
display: inline-block;
119130
}
120131
121-
:host([disabled]) #label-button {
132+
:host([disabled]) {
122133
color: var(--uui-interface-surface-contrast-disabled);
123134
}
124135
:host([disabled]) #label-button-background {
@@ -128,10 +139,10 @@ export class UUIMenuItemElement extends SelectableMixin(
128139
background-color: var(--uui-interface-surface-disabled);
129140
}
130141
131-
:host([active]) button {
142+
:host([active]) {
132143
color: var(--uui-interface-active-contrast);
133144
}
134-
:host([active]) button:hover {
145+
:host([active]) #label-button:hover {
135146
color: var(--uui-interface-active-contrast-hover);
136147
}
137148
:host([active]) #label-button-background {
@@ -145,10 +156,10 @@ export class UUIMenuItemElement extends SelectableMixin(
145156
background-color: var(--uui-interface-active-disabled);
146157
}
147158
148-
:host([selected]) button {
159+
:host([selected]) {
149160
color: var(--uui-interface-select-contrast);
150161
}
151-
:host([selected]) button:hover {
162+
:host([selected]) #label-button:hover {
152163
color: var(--uui-interface-select-contrast-hover);
153164
}
154165
:host([selected]) #label-button-background {
@@ -218,6 +229,37 @@ export class UUIMenuItemElement extends SelectableMixin(
218229
@property({ type: Boolean, attribute: 'loading' })
219230
public loading = false;
220231

232+
/**
233+
* Set an href, this will turns the label into a anchor tag.
234+
* @type {string}
235+
* @attr
236+
* @default undefined
237+
*/
238+
@property({ type: String })
239+
public href?: string;
240+
241+
/**
242+
* Set an anchor tag target, only used when using href.
243+
* @type {string}
244+
* @attr
245+
* @default undefined
246+
*/
247+
@property({ type: String })
248+
public target?: '_blank' | '_parent' | '_self' | '_top';
249+
250+
@state()
251+
private iconSlotHasContent = false;
252+
253+
connectedCallback() {
254+
super.connectedCallback();
255+
if (!this.hasAttribute('role')) this.setAttribute('role', 'menu');
256+
}
257+
258+
private iconSlotChanged(e: any): void {
259+
this.iconSlotHasContent =
260+
(e.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;
261+
}
262+
221263
private onCaretClicked() {
222264
this.showChildren = !this.showChildren;
223265
const eventName: string = this.showChildren
@@ -232,12 +274,41 @@ export class UUIMenuItemElement extends SelectableMixin(
232274
this.dispatchEvent(event);
233275
}
234276

235-
@state()
236-
private iconSlotHasContent = false;
277+
private _renderLabelInside() {
278+
return html` <slot
279+
name="icon"
280+
id="icon"
281+
style=${this.iconSlotHasContent ? '' : 'display: none;'}
282+
@slotchange=${this.iconSlotChanged}></slot>
283+
${this.renderLabel()}`;
284+
}
237285

238-
private iconSlotChanged(e: any): void {
239-
this.iconSlotHasContent =
240-
(e.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0;
286+
private _renderLabelAsAnchor() {
287+
if (this.disabled) {
288+
return html` <span id="label-button">
289+
${this._renderLabelInside()}
290+
</span>`;
291+
}
292+
return html` <a
293+
id="label-button"
294+
href=${this.href}
295+
target=${ifDefined(this.target || undefined)}
296+
rel=${ifDefined(this.target === '_blank' ? 'noopener' : undefined)}
297+
@click=${this.onLabelClicked}
298+
?disabled=${this.disabled}
299+
aria-label="${this.label}">
300+
${this._renderLabelInside()}
301+
</a>`;
302+
}
303+
304+
private _renderLabelAsButton() {
305+
return html` <button
306+
id="label-button"
307+
@click=${this.onLabelClicked}
308+
?disabled=${this.disabled}
309+
aria-label="${this.label}">
310+
${this._renderLabelInside()}
311+
</button>`;
241312
}
242313

243314
render() {
@@ -248,20 +319,12 @@ export class UUIMenuItemElement extends SelectableMixin(
248319
<uui-symbol-expand ?open=${this.showChildren}></uui-symbol-expand>
249320
</button>`
250321
: ''}
251-
<button
252-
id="label-button"
253-
@click=${this.onLabelClicked}
254-
?disabled=${this.disabled}
255-
aria-label="${this.label}">
256-
<slot
257-
name="icon"
258-
id="icon"
259-
style=${this.iconSlotHasContent ? '' : 'display: none;'}
260-
@slotchange=${this.iconSlotChanged}></slot>
261-
${this.renderLabel()}
262-
</button>
322+
${this.href ? this._renderLabelAsAnchor() : this._renderLabelAsButton()}
323+
263324
<div id="label-button-background"></div>
264-
<slot id="actions-container" name="actions"></slot>
325+
${this.selectOnly === false
326+
? html`<slot id="actions-container" name="actions"></slot>`
327+
: ''}
265328
${this.loading
266329
? html`<uui-loader-bar id="loader"></uui-loader-bar>`
267330
: ''}

0 commit comments

Comments
 (0)