Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions packages/fiori/src/NavigationMenuItemTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,24 @@ function iconBegin(this: NavigationMenuItem) {
}

function iconEnd(this: NavigationMenuItem) {
if (this.hasSubmenu) {
return <Icon
part="icon"
name={slimArrowRightIcon}
class="ui5-menu-item-icon-end"
/>;
}

if (this.isExternalLink) {
return <Icon
class="ui5-sn-item-external-link-icon"
name={arrowRightIcon}
/>;
}
return (<>
{this.hasEndContent &&
<span class="ui5-navmenu-item-tag-container">
Comment thread
s-todorova marked this conversation as resolved.
Outdated
<slot name="endContent"></slot>
</span>
}
{this.hasSubmenu &&
<Icon
part="icon"
name={slimArrowRightIcon}
class="ui5-menu-item-icon-end"
/>
}
{!this.hasSubmenu && this.isExternalLink &&
<Icon
class="ui5-sn-item-external-link-icon"
name={arrowRightIcon}
/>
}
</>);
}
32 changes: 30 additions & 2 deletions packages/fiori/src/SideNavigationItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
SIDE_NAVIGATION_OVERFLOW_ITEM_LABEL,
SIDE_NAVIGATION_PARENT_ITEM_SELECTABLE_DESCRIPTION,
} from "./generated/i18n/i18n-defaults.js";
import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js";
import type { DefaultSlot, Slot } from "@ui5/webcomponents-base/dist/UI5Element.js";
import "@ui5/webcomponents/dist/Tag.js";

// Templates
import SideNavigationItemTemplate from "./SideNavigationItemTemplate.js";
Expand Down Expand Up @@ -81,6 +82,21 @@ class SideNavigationItem extends SideNavigationSelectableItemBase {
@slot({ type: HTMLElement, invalidateOnChildChange: true, "default": true })
items!: DefaultSlot<SideNavigationSubItem>;

/**
* Defines the tag to be displayed.
*
* **Note:** Only one `ui5-tag` is allowed. The tag should use `design="Set2"`, `hide-state-icon`,
* and `colorScheme` values 5-10 to avoid confusion with semantic colors (1-4).
*
* **Note:** It is recommended to limit tag width to 64px (4rem). If tag text exceeds this,
* use shortened forms or abbreviations (e.g., "Experimental" → "Exp").
*
* @public
* @since 2.7.0
*/
@slot({ type: HTMLElement })
tag!: Slot<HTMLElement>;

@i18n("@ui5/webcomponents-fiori")
static i18nBundle: I18nBundle;

Expand Down Expand Up @@ -183,9 +199,21 @@ class SideNavigationItem extends SideNavigationSelectableItemBase {
}

get _describedBy() {
const parts: string[] = [];

if (this.hasTag) {
parts.push(this._tagId);
}

if (!this.effectiveDisabled && this.items.length && !this.unselectable) {
return SideNavigationItem.i18nBundle.getText(SIDE_NAVIGATION_PARENT_ITEM_SELECTABLE_DESCRIPTION, this.text ?? "");
parts.push(SideNavigationItem.i18nBundle.getText(SIDE_NAVIGATION_PARENT_ITEM_SELECTABLE_DESCRIPTION, this.text ?? ""));
Comment thread
s-todorova marked this conversation as resolved.
Outdated
}

return parts.length > 0 ? parts.join(" ") : undefined;
}

get hasTag() {
return !!this.tag.length;
}

Comment thread
s-todorova marked this conversation as resolved.
get classesArray() {
Expand Down
3 changes: 3 additions & 0 deletions packages/fiori/src/SideNavigationItemTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ function ItemTemplate(this: SideNavigationItem) {
this.icon && <Icon class="ui5-sn-item-icon" name={this.icon}/>
}
<div class="ui5-sn-item-text">{this.text}</div>
{this.hasTag &&
<slot name="tag" id={this._tagId} class="ui5-sn-item-tag-slot"></slot>
Comment thread
s-todorova marked this conversation as resolved.
Outdated
}
{this.sideNavCollapsed ?
!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
Expand Down
65 changes: 57 additions & 8 deletions packages/fiori/src/SideNavigationPopoverTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,21 @@ export default function SideNavigationTemplate(this: SideNavigation) {
target={item.target}
title={item.title}
tooltip={item._tooltip}
ref={this.captureRef.bind(item)}
ref={(el: HTMLElement | null) => {
if (el && item.tag.length > 0) {
const existingTags = Array.from(el.children).filter(child => child.getAttribute("slot") === "endContent");
if (existingTags.length === 0) {
item.tag.forEach(tagEl => {
const clonedTag = tagEl.cloneNode(true) as HTMLElement;
clonedTag.slot = "endContent";
el.appendChild(clonedTag);
});
}
}
this.captureRef.bind(item)(el);
}}
>

{item.children.length > 0 && !item.unselectable &&
{(item as SideNavigationItem).items?.length > 0 && !item.unselectable &&
(<NavigationMenuItem
class="ui5-navigation-menu-item-root-parent"
accessibilityAttributes={item.accessibilityAttributes}
Expand All @@ -31,8 +42,21 @@ export default function SideNavigationTemplate(this: SideNavigation) {
target={item.target}
title={item.title}
tooltip={item._tooltip}
ref={this.captureRef.bind(item)}
></NavigationMenuItem>)
ref={(el: HTMLElement | null) => {
if (el && item.tag.length > 0) {
const existingTags = Array.from(el.children).filter(child => child.getAttribute("slot") === "endContent");
if (existingTags.length === 0) {
item.tag.forEach(tagEl => {
const clonedTag = tagEl.cloneNode(true) as HTMLElement;
clonedTag.slot = "endContent";
el.appendChild(clonedTag);
});
}
}
this.captureRef.bind(item)(el);
}}
>
</NavigationMenuItem>)
}

{(item as any).items?.map(renderMenuItem)}
Expand Down Expand Up @@ -79,7 +103,19 @@ export default function SideNavigationTemplate(this: SideNavigation) {
selected={this._popoverContents.item.selected}
unselectable={this._popoverContents.item.unselectable}
onui5-click={this.handlePopupItemClick}
ref={this.captureRef.bind(this._popoverContents.item)}
ref={(el: HTMLElement | null) => {
if (el && this._popoverContents.item.tag.length > 0) {
const existingTags = Array.from(el.children).filter(child => child.getAttribute("slot") === "tag");
if (existingTags.length === 0) {
this._popoverContents.item.tag.forEach(tagEl => {
const clonedTag = tagEl.cloneNode(true) as HTMLElement;
clonedTag.slot = "tag";
el.appendChild(clonedTag);
});
}
}
this.captureRef.bind(this._popoverContents.item)(el as SideNavigationItem | null);
}}
>
{this._popoverContents.subItems.map(item =>
<SideNavigationSubItem
Expand All @@ -93,8 +129,21 @@ export default function SideNavigationTemplate(this: SideNavigation) {
selected={item.selected}
unselectable={item.unselectable}
onui5-click={this.handlePopupItemClick}
ref={this.captureRef.bind(item)}
/>
ref={(el: HTMLElement | null) => {
if (el && item.tag.length > 0) {
const existingTags = Array.from(el.children).filter(child => child.getAttribute("slot") === "tag");
if (existingTags.length === 0) {
item.tag.forEach(tagEl => {
const clonedTag = tagEl.cloneNode(true) as HTMLElement;
clonedTag.slot = "tag";
el.appendChild(clonedTag);
});
}
}
this.captureRef.bind(item)(el as SideNavigationSubItem | null);
}}
>
</SideNavigationSubItem>
)}
</SideNavigationItem>
</SideNavigation>
Expand Down
4 changes: 4 additions & 0 deletions packages/fiori/src/SideNavigationSelectableItemBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ class SideNavigationSelectableItemBase extends SideNavigationItemBase {
return this.selected;
}

get _tagId() {
return `${this._id}-tag`;
}

_onkeydown(e: KeyboardEvent) {
const isRTL = this.effectiveDir === "rtl";

Expand Down
26 changes: 26 additions & 0 deletions packages/fiori/src/SideNavigationSubItem.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import jsxRender from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js";
import type { Slot } from "@ui5/webcomponents-base/dist/UI5Element.js";
import SideNavigationSelectableItemBase from "./SideNavigationSelectableItemBase.js";
import SideNavigationSubItemTemplate from "./SideNavigationSubItemTemplate.js";
import "@ui5/webcomponents/dist/Tag.js";

// Styles
import SideNavigationSubItemCss from "./generated/themes/SideNavigationSubItem.css.js";
Expand Down Expand Up @@ -30,6 +33,29 @@ import SideNavigationSubItemCss from "./generated/themes/SideNavigationSubItem.c
styles: SideNavigationSubItemCss,
})
class SideNavigationSubItem extends SideNavigationSelectableItemBase {
/**
* Defines the tag to be displayed.
*
* **Note:** Only one `ui5-tag` is allowed. The tag should use `design="Set2"`, `hide-state-icon`,
* and `colorScheme` values 5-10 to avoid confusion with semantic colors (1-4).
*
* **Note:** It is recommended to limit tag width to 64px (4rem). If tag text exceeds this,
* use shortened forms or abbreviations (e.g., "Experimental" → "Exp").
*
* @public
* @since 2.7.0
*/
@slot({ type: HTMLElement })
tag!: Slot<HTMLElement>;

get hasTag() {
return !!this.tag.length;
}

get _describedBy() {
return this.hasTag ? this._tagId : undefined;
}

_onkeydown(e: KeyboardEvent) {
super._onkeydown(e);
}
Expand Down
4 changes: 4 additions & 0 deletions packages/fiori/src/SideNavigationSubItemTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ export default function SideNavigationSubItemTemplate(this: SideNavigationSubIte
href={this._href}
target={this._target}
aria-haspopup={this._ariaHasPopup}
aria-describedby={this._describedBy}
>
{this.icon &&
<Icon class="ui5-sn-item-icon" name={this.icon}/>
}
<div class="ui5-sn-item-text">{this.text}</div>
{this.hasTag &&
<slot name="tag" id={this._tagId} class="ui5-sn-item-tag-slot"></slot>
}
{this.isExternalLink &&
<Icon class="ui5-sn-item-external-link-icon"
name={arrowRight}
Expand Down
10 changes: 9 additions & 1 deletion packages/fiori/src/themes/NavigationMenuItem.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,12 @@

::slotted([ui5-navigation-menu-item]:not(:last-of-type)) {
margin-block-end: var(--_ui5_side_navigation_item_bottom_margin);
}
}

.ui5-navmenu-item-tag-container {
display: inline-flex;
align-items: center;
margin-inline-start: 0.5rem;
height: 1.375rem;
min-width: 1.375rem;
}
31 changes: 31 additions & 0 deletions packages/fiori/src/themes/SideNavigationItemBase.css
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,37 @@ and there is an additional border that appears on hover. */
padding-inline-end: 0.375rem;
}

.ui5-sn-item-tag-slot {
padding-inline-start: 0.5rem;
padding-inline-end: 0.375rem;
display: inline-flex;
align-items: center;
}

.ui5-sn-item-tag-slot:has(+ .ui5-sn-item-toggle-icon),
.ui5-sn-item-tag-slot:has(+ .ui5-sn-item-external-link-icon) {
padding-inline-end: 0;
}

.ui5-sn-item-tag-slot + .ui5-sn-item-toggle-icon,
.ui5-sn-item-tag-slot + .ui5-sn-item-external-link-icon {
margin-inline-start: 0.375rem;
}

:host([side-nav-collapsed]) .ui5-sn-item-tag-slot {
display: none;
}

:host([side-nav-collapsed]) .ui5-sn-item:not(.ui5-sn-item-active):not(.ui5-sn-item-no-hover-effect):not(.ui5-sn-item-disabled):hover .ui5-sn-item-tag-slot,
:host([side-nav-collapsed]) .ui5-sn-item:not(.ui5-sn-item-active):not(.ui5-sn-item-no-hover-effect):focus .ui5-sn-item-tag-slot {
display: inline-flex;
}

:host([in-popover]) .ui5-sn-item-tag-slot {
display: inline-flex;
padding-inline-end: 0.5rem;
}

:host([side-nav-collapsed]) .ui5-sn-item-with-expander .ui5-sn-item-icon::after {
display: var(--_ui5_side_navigation_triangle_display);
content: "";
Expand Down
Loading
Loading