Skip to content

chore(ui5-side-navigation): unify item rendering and attributes with shared base template #11900

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9bc7c21
chore(ui5-side-navigation): simplify rendering templates
TeodorTaushanov Jul 10, 2025
8e63b79
chore: fix lint error
TeodorTaushanov Jul 11, 2025
6a64855
Merge remote-tracking branch 'origin/main' into sidenav_templates
TeodorTaushanov Jul 11, 2025
620ee2e
chore: simplify code
TeodorTaushanov Jul 11, 2025
2c8f7f0
chore: simplify code
TeodorTaushanov Jul 11, 2025
94a94b0
fix: fixed aria-haspopup logic
TeodorTaushanov Jul 11, 2025
e9716d4
Merge remote-tracking branch 'origin/main' into sidenav_templates
TeodorTaushanov Jul 11, 2025
0f7d28a
Merge remote-tracking branch 'origin/main' into sidenav_templates
TeodorTaushanov Jul 14, 2025
bc8eb06
chore: simplifying code
TeodorTaushanov Jul 14, 2025
ecddfb5
chore: simplifying code
TeodorTaushanov Jul 14, 2025
739cdd0
Merge remote-tracking branch 'origin/main' into sidenav_templates
TeodorTaushanov Jul 15, 2025
d27c1e4
chore: simplifying code
TeodorTaushanov Jul 15, 2025
12ada28
chore: add base template
TeodorTaushanov Jul 15, 2025
a9563ca
Merge remote-tracking branch 'origin/main' into sidenav_templates
TeodorTaushanov Jul 15, 2025
423babc
Merge remote-tracking branch 'origin/main' into sidenav_templates
TeodorTaushanov Jul 16, 2025
cf21e73
fix: fix tests
TeodorTaushanov Jul 16, 2025
9d862fe
chore: simplify template attributes
TeodorTaushanov Jul 16, 2025
84aa2c8
chore: fix lint error
TeodorTaushanov Jul 16, 2025
8682944
Merge remote-tracking branch 'origin/main' into sidenav_templates
TeodorTaushanov Jul 17, 2025
0bdf6b6
chore: simplify code
TeodorTaushanov Jul 17, 2025
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
14 changes: 14 additions & 0 deletions packages/fiori/src/SideNavigationGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ class SideNavigationGroup extends SideNavigationItemBase {
}
}

get templateAttributes() {
return {
classes: "ui5-sn-item ui5-sn-item-group",
role: "treeitem",
onKeyDown: this._onkeydown.bind(this),
onClick: this._onclick.bind(this),
onFocusIn: this._onfocusin.bind(this),
tabIndex: this.effectiveTabIndex,
ariaExpanded: this._expanded,
title: this._tooltip,
ariaOwns: this._groupId,
};
}

get isSideNavigationGroup() {
return true;
}
Expand Down
37 changes: 17 additions & 20 deletions packages/fiori/src/SideNavigationGroupTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Icon from "@ui5/webcomponents/dist/Icon.js";
import navRightArrow from "@ui5/webcomponents-icons/dist/navigation-right-arrow.js";
import navDownArrow from "@ui5/webcomponents-icons/dist/navigation-down-arrow.js";
import type SideNavigationGroup from "./SideNavigationGroup.js";
import SideNavigationItemBaseTemplate from "./SideNavigationItemBaseTemplate.js";

export default function SideNavigationGroupTemplate(this: SideNavigationGroup) {
if (this.sideNavCollapsed) {
Expand All @@ -22,26 +23,7 @@ function TreeItemTemplate(this: SideNavigationGroup) {
role="none"
>
<div class="ui5-sn-item-separator"></div>
<div class={`ui5-sn-item ui5-sn-item-group ${this._classes}`}
role="treeitem"
data-sap-focus-ref
onKeyDown={this._onkeydown}
onClick={this._onclick}
onFocusIn={this._onfocusin}
tabIndex={this.effectiveTabIndex ? parseInt(this.effectiveTabIndex) : undefined}
aria-expanded={this._expanded}
title={this._tooltip}
aria-owns={this._groupId}
>
<div class="ui5-sn-item-text">{this.text}</div>
{!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
name={this.expanded ? navDownArrow : navRightArrow}
accessibleName={this._arrowTooltip}
showTooltip={true}
/>
}
</div>
{SideNavigationItemBaseTemplate.call(this, "div", itemContent, this.templateAttributes)}
{!!this.items.length &&
<ul id={this._groupId}
class="ui5-sn-item-ul"
Expand All @@ -54,4 +36,19 @@ function TreeItemTemplate(this: SideNavigationGroup) {
<div class="ui5-sn-item-separator"></div>
</li>
);

function itemContent(this: SideNavigationGroup) {
return (
<>
<div class="ui5-sn-item-text">{this.text}</div>
{!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
name={this.expanded ? navDownArrow : navRightArrow}
accessibleName={this._arrowTooltip}
showTooltip={true}
/>
}
</>
);
}
}
27 changes: 22 additions & 5 deletions packages/fiori/src/SideNavigationItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,27 +145,31 @@ class SideNavigationItem extends SideNavigationSelectableItemBase {
return "tree";
}

if (this.accessibilityAttributes?.hasPopup) {
return this.accessibilityAttributes.hasPopup;
}

return undefined;
}

get _ariaChecked() {
if (this.isOverflow || this.unselectable) {
if (this.isOverflow || this.unselectable || !this.sideNavCollapsed) {
return undefined;
}

return this.selected;
}

get _groupId() {
if (!this.items.length) {
if (!this.items.length || this.sideNavCollapsed) {
return undefined;
}

return `${this._id}-group`;
}

get _expanded() {
if (!this.items.length) {
if (!this.items.length || this.sideNavCollapsed) {
return undefined;
}

Expand Down Expand Up @@ -313,15 +317,28 @@ class SideNavigationItem extends SideNavigationSelectableItemBase {
this.getDomRef()!.classList.add("ui5-sn-item-no-hover-effect");
}

get isSideNavigationItem() {
return true;
get templateAttributes() {
return Object.assign(super.templateAttributes, {
classes: "ui5-sn-item ui5-sn-item-level1",
onFocusOut: this._onfocusout.bind(this),
onMouseEnter: this._onmouseenter.bind(this),
onMouseLeave: this._onmouseleave.bind(this),
ariaChecked: this._ariaChecked,
ariaOwns: this._groupId,
ariaLabel: this._ariaLabel,
ariaExpanded: this._expanded,
});
}

_toggle() {
if (this.items.length && !this.effectiveDisabled) {
this.expanded = !this.expanded;
}
}

get isSideNavigationItem() {
return true;
}
}

SideNavigationItem.define();
Expand Down
2 changes: 1 addition & 1 deletion packages/fiori/src/SideNavigationItemBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class SideNavigationItemBase extends UI5Element implements ITabbable {
}

get effectiveTabIndex() {
return this.forcedTabIndex || undefined;
return this.forcedTabIndex !== undefined ? parseInt(this.forcedTabIndex) : undefined;
}

get sideNavigation() {
Expand Down
34 changes: 34 additions & 0 deletions packages/fiori/src/SideNavigationItemBaseTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type SideNavigationItemBase from "./SideNavigationItemBase.js";

export default function SideNavigationItemBaseTemplate(this: SideNavigationItemBase, tag?: string, itemContent?: () => void, injectedProps? : any) {
const EffectiveTag = tag || "div";

return (
<EffectiveTag id={this._id}
data-sap-focus-ref
class={`${injectedProps?.classes} ${this._classes}`}
role={injectedProps?.role}
onKeyDown={injectedProps?.onKeyDown}
onKeyUp={injectedProps?.onKeyUp}
onClick={injectedProps?.onClick}
onFocusIn={injectedProps?.onFocusIn}
onFocusOut={injectedProps?.onFocusOut}
onMouseEnter={injectedProps?.onMouseEnter}
onMouseLeave={injectedProps?.onMouseLeave}
tabIndex={injectedProps?.tabIndex}
aria-haspopup={injectedProps?.ariaHasPopup}
aria-checked={injectedProps?.ariaChecked}
aria-owns={injectedProps?.ariaOwns}
title={injectedProps?.title}
aria-label={injectedProps?.ariaLabel}
aria-expanded={injectedProps?.ariaExpanded}
aria-current={injectedProps?.ariaCurrent}
aria-selected={injectedProps?.ariaSelected}
href={injectedProps?.href}
target={injectedProps?.target}
aria-disabled={injectedProps?.ariaDisabled}
>
{ itemContent?.call(this) }
</EffectiveTag>
);
}
190 changes: 46 additions & 144 deletions packages/fiori/src/SideNavigationItemTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,157 +3,27 @@ import navRightArrow from "@ui5/webcomponents-icons/dist/navigation-right-arrow.
import navDownArrow from "@ui5/webcomponents-icons/dist/navigation-down-arrow.js";
import arrowRight from "@ui5/webcomponents-icons/dist/arrow-right.js";
import type SideNavigationItem from "./SideNavigationItem.js";
import SideNavigationItemBaseTemplate from "./SideNavigationItemBaseTemplate.js";

export default function SideNavigationItemTemplate(this: SideNavigationItem) {
if (this.sideNavCollapsed) {
return MenuItemTemplate.call(this);
return ItemTemplate.call(this);
}
return TreeItemTemplate.call(this);
}

function MenuItemTemplate(this: SideNavigationItem) {
return (<>
{this._href ?
<a id={this._id}
class={`ui5-sn-item ui5-sn-item-level1 ${this._classes}`}
role={this.ariaRole}
data-sap-focus-ref
onKeyDown={this._onkeydown}
onKeyUp={this._onkeyup}
onClick={this._onclick}
onFocusIn={this._onfocusin}
onFocusOut={this._onfocusout}
onMouseEnter={this._onmouseenter}
onMouseLeave={this._onmouseleave}
tabIndex={this.effectiveTabIndex !== undefined ? parseInt(this.effectiveTabIndex) : undefined}
aria-haspopup={this._ariaHasPopup}
aria-checked={this._ariaChecked}
title={this._tooltip}
href={this._href}
target={this._target}
>
<Icon class="ui5-sn-item-icon" name={this.icon}/>
<div class="ui5-sn-item-text">{this.text}</div>
{!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
name={navRightArrow}
/>
}
{this.isExternalLink &&
<Icon class="ui5-sn-item-external-link-icon"
name={arrowRight}
/>
}
</a>
:
<div id={this._id}
class={`ui5-sn-item ui5-sn-item-level1 ${this._classes}`}
role={this.ariaRole}
data-sap-focus-ref
onKeyDown={this._onkeydown}
onKeyUp={this._onkeyup}
onClick={this._onclick}
onFocusIn={this._onfocusin}
onFocusOut={this._onfocusout}
onMouseEnter={this._onmouseenter}
onMouseLeave={this._onmouseleave}
tabIndex={this.effectiveTabIndex !== undefined ? parseInt(this.effectiveTabIndex) : undefined}
aria-haspopup={this._ariaHasPopup}
aria-checked={this._ariaChecked}
title={this._tooltip}
aria-label={this._ariaLabel}
>
<Icon class="ui5-sn-item-icon" name={this.icon}/>
<div class="ui5-sn-item-text">{this.text}</div>
{!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
name={navRightArrow}
/>
}
{this.isExternalLink &&
<Icon class="ui5-sn-item-external-link-icon"
name={arrowRight}
/>
}
</div>
}
</>);
return (
<li id={this._id} class="ui5-sn-list-li" role="none">
{ItemTemplate.call(this)}
</li>
);
}

function TreeItemTemplate(this: SideNavigationItem) {
function ItemTemplate(this: SideNavigationItem) {
const EffectiveTag = this._effectiveTag;

return (
<li id={this._id} class="ui5-sn-list-li" role="none">
{this._href ?
<a class={`ui5-sn-item ui5-sn-item-level1 ${this._classes}`}
role={this.ariaRole}
data-sap-focus-ref
onKeyDown={this._onkeydown}
onKeyUp={this._onkeyup}
onClick={this._onclick}
onFocusIn={this._onfocusin}
tabIndex={this.effectiveTabIndex !== undefined ? parseInt(this.effectiveTabIndex) : undefined}
aria-expanded={this._expanded}
aria-current={this._ariaCurrent}
aria-selected={this.selected}
title={this._tooltip}
aria-owns={this._groupId}
href={this._href}
target={this._target}
>
{this.icon &&
<Icon class="ui5-sn-item-icon" name={this.icon}/>
}
<div class="ui5-sn-item-text">{this.text}</div>
{this.isExternalLink &&
<Icon class="ui5-sn-item-external-link-icon"
name={arrowRight}
/>
}
{!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
name={this.expanded ? navDownArrow : navRightArrow}
accessibleName={this._arrowTooltip}
showTooltip={true}
onClick={this._onToggleClick}
/>
}
</a>
:
<div class={`ui5-sn-item ui5-sn-item-level1 ${this._classes}`}
role={this.ariaRole}
data-sap-focus-ref
onKeyDown={this._onkeydown}
onKeyUp={this._onkeyup}
onClick={this._onclick}
onFocusIn={this._onfocusin}
tabIndex={this.effectiveTabIndex !== undefined ? parseInt(this.effectiveTabIndex) : undefined}
aria-expanded={this._expanded}
aria-current={this._ariaCurrent}
aria-selected={this.selected}
aria-haspopup={this.accessibilityAttributes?.hasPopup}
title={this._tooltip}
aria-owns={this._groupId}
>
{this.icon &&
<Icon class="ui5-sn-item-icon" name={this.icon}/>
}
<div class="ui5-sn-item-text">{this.text}</div>
{this.isExternalLink &&
<Icon class="ui5-sn-item-external-link-icon"
name={arrowRight}
/>
}
{!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
name={this.expanded ? navDownArrow : navRightArrow}
accessibleName={this._arrowTooltip}
showTooltip={true}
onClick={this._onToggleClick}
/>
}
</div>
}
{!!this.items.length &&
<>
{SideNavigationItemBaseTemplate.call(this, EffectiveTag, itemContent, this.templateAttributes)}
{!this.sideNavCollapsed && !!this.items.length &&
<ul id={this._groupId}
class="ui5-sn-item-ul"
aria-label={this.text}
Expand All @@ -162,6 +32,38 @@ function TreeItemTemplate(this: SideNavigationItem) {
<slot></slot>
</ul>
}
</li>
</>
);

function itemContent(this: SideNavigationItem) {
return (
<>
{this.sideNavCollapsed ?
<Icon class="ui5-sn-item-icon" name={this.icon}/>
:
this.icon && <Icon class="ui5-sn-item-icon" name={this.icon}/>
}
<div class="ui5-sn-item-text">{this.text}</div>
{this.sideNavCollapsed ?
!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
name={navRightArrow}
/>
:
!!this.items.length &&
<Icon class="ui5-sn-item-toggle-icon"
name={this.expanded ? navDownArrow : navRightArrow}
accessibleName={this._arrowTooltip}
showTooltip={true}
onClick={this._onToggleClick}
/>
}
{this.isExternalLink &&
<Icon class="ui5-sn-item-external-link-icon"
name={arrowRight}
/>
}
</>
);
}
}
Loading
Loading