Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions packages/fiori/src/NavigationMenuItemTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import type NavigationMenuItem from "./NavigationMenuItem.js";
import MenuItemTemplate from "@ui5/webcomponents/dist/MenuItemTemplate.js";
import type { MenuItemHooks } from "@ui5/webcomponents/dist/MenuItemTemplate.js";
import Icon from "@ui5/webcomponents/dist/Icon.js";
import slimArrowRightIcon from "@ui5/webcomponents-icons/dist/slim-arrow-right.js";
import arrowRightIcon from "@ui5/webcomponents-icons/dist/arrow-right.js";
import type { ListItemHooks } from "@ui5/webcomponents/dist/ListItemTemplate.js";

const predefinedHooks: Partial<ListItemHooks> = {
const predefinedHooks: Partial<MenuItemHooks> = {
listItemContent,
iconBegin,
iconEnd,
};

export default function NavigationMenuItemTemplate(this: NavigationMenuItem, hooks?: Partial<ListItemHooks>) {
export default function NavigationMenuItemTemplate(this: NavigationMenuItem, hooks?: Partial<MenuItemHooks>) {
const currentHooks = { ...predefinedHooks, ...hooks, };

return <>
Expand Down
45 changes: 44 additions & 1 deletion packages/fiori/src/UserMenuItem.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { customElement, slotStrict as slot } from "@ui5/webcomponents-base/dist/decorators.js";
import { customElement, slotStrict as slot, property } from "@ui5/webcomponents-base/dist/decorators.js";
import MenuItem, { isInstanceOfMenuItem } from "@ui5/webcomponents/dist/MenuItem.js";
import MenuItemGroupCheckMode from "@ui5/webcomponents/dist/types/MenuItemGroupCheckMode.js";

import UserMenuItemTemplate from "./UserMenuItemTemplate.js";

Expand Down Expand Up @@ -44,9 +45,51 @@ class UserMenuItem extends MenuItem {
@slot({ "default": true, type: HTMLElement, invalidateOnChildChange: true })
declare items: DefaultSlot<UserMenuItem>;

/**
* When set, a second line appears below the menu item text
* showing the text of the currently selected (checked) sub-item.
*
* @default false
* @public
*/
@property({ type: Boolean })
showSelection = false;

get _menuItems() {
return this.items.filter(isInstanceOfMenuItem);
}

/**
* Overrides the base MenuItem behavior to prevent unchecking
* the currently checked item in single-select mode,
* ensuring there is always a selection.
*/
_updateCheckedState() {
if (this._checkMode === MenuItemGroupCheckMode.Single && this.checked) {
return;
}
super._updateCheckedState();
}

/**
* Returns the text of the currently checked sub-item.
* Only returns text for single-select groups.
*/
get _selectedSubItemText(): string {
if (!this.showSelection) {
return "";
}

const singleSelectGroup = this._menuItemGroups.find(
g => g.checkMode === MenuItemGroupCheckMode.Single,
);
if (!singleSelectGroup) {
return "";
}

const checkedItem = singleSelectGroup._menuItems.find(item => item.checked);
return checkedItem?.text || "";
}
}

UserMenuItem.define();
Expand Down
20 changes: 19 additions & 1 deletion packages/fiori/src/UserMenuItemTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import type UserMenuItem from "./UserMenuItem.js";
import MenuItemTemplate from "@ui5/webcomponents/dist/MenuItemTemplate.js";
import type { MenuItemHooks } from "@ui5/webcomponents/dist/MenuItemTemplate.js";

export default function UserMenuItemTemplate(this: UserMenuItem) {
return [MenuItemTemplate.call(this)];
const hooks: Partial<MenuItemHooks> = {};

if (this.showSelection) {
hooks.menuItemTextContent = userMenuItemTextContent;
}

return [MenuItemTemplate.call(this, hooks)];
}

function userMenuItemTextContent(this: UserMenuItem) {
return (
<div class="ui5-user-menu-item-text-wrapper">
{this.text && <div class="ui5-menu-item-text">{this.text}</div>}
{this._selectedSubItemText &&
<div class="ui5-user-menu-item-selection-text">{this._selectedSubItemText}</div>
}
</div>
);
}
35 changes: 35 additions & 0 deletions packages/fiori/src/themes/UserMenuItem.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,47 @@
:host {
height: 40px;
min-height: 40px;
border: none;
}

/* Ensure inner li matches host height for proper focus outline */
.ui5-li-root {
min-height: 40px;
}

:host(:last-of-type) {
margin-bottom: 0;
}

:host(:first-of-type) {
margin-top: 0;
}

/* Allow taller items when showing selection text */
:host([show-selection-text]) {
height: auto;
min-height: 40px;
}

/* Wrapper for two-line layout (text + selected sub-item) */
.ui5-user-menu-item-text-wrapper {
display: flex;
flex-direction: column;
overflow: hidden;
flex: 1;
min-width: 0;
}

/* Second line showing selected sub-item text */
.ui5-user-menu-item-selection-text {
font-size: var(--sapFontSize);
color: var(--sapContent_LabelColor);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

/* Checkmark spacing in sub-menu popover: 2rem gap between text and checkmark */
.ui5-menu-item-checked {
padding-inline-start: 2rem;
}
4 changes: 2 additions & 2 deletions packages/fiori/test/pages/UserMenu.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@
<ui5-user-menu-item icon="accelerated" text="Item 2" data-id="multi-select-item2"></ui5-user-menu-item>
</ui5-user-menu-item-group>
</ui5-user-menu-item>
<ui5-user-menu-item text="Single Select" data-id="single-select">
<ui5-user-menu-item text="Single Select" data-id="single-select" show-selection>
<ui5-user-menu-item-group check-mode="Single">
<ui5-user-menu-item icon="private" text="Item 1" data-id="single-select"></ui5-user-menu-item>
<ui5-user-menu-item icon="private" text="Item 1" data-id="single-select" checked></ui5-user-menu-item>
<ui5-user-menu-item icon="accelerated" text="Item 2" data-id="single-select-item2"></ui5-user-menu-item>
</ui5-user-menu-item-group>
</ui5-user-menu-item>
Expand Down
30 changes: 20 additions & 10 deletions packages/main/src/MenuItemTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,38 @@
import ListItemTemplate from "./ListItemTemplate.js";
import type { ListItemHooks } from "./ListItemTemplate.js";

const predefinedHooks: Partial<ListItemHooks> = {
listItemContent,
export type MenuItemHooks = ListItemHooks & {
menuItemTextContent: (this: any) => JSX.Element;
}

const predefinedHooks: Partial<MenuItemHooks> = {
iconBegin,
menuItemTextContent,
};

export default function MenuItemTemplate(this: MenuItem, hooks?: Partial<ListItemHooks>) {
export default function MenuItemTemplate(this: MenuItem, hooks?: Partial<MenuItemHooks>) {
const currentHooks = { ...predefinedHooks, ...hooks };

if (!hooks?.listItemContent) {
currentHooks.listItemContent = function(this: MenuItem) {

Check failure on line 27 in packages/main/src/MenuItemTemplate.tsx

View workflow job for this annotation

GitHub Actions / check

Missing space before function parentheses

Check warning on line 27 in packages/main/src/MenuItemTemplate.tsx

View workflow job for this annotation

GitHub Actions / check

Unexpected unnamed function
return (<>
{currentHooks.menuItemTextContent!.call(this)}

{rightContent.call(this)}
{checkmarkContent.call(this)}
</>);
};
}

return <>
{ListItemTemplate.call(this, currentHooks)}

{listItemPostContent.call(this)}
</>;
}

function listItemContent(this: MenuItem) {
return (<>
{this.text && <div class="ui5-menu-item-text">{this.text}</div>}

{rightContent.call(this)}
{checkmarkContent.call(this)}
</>);
function menuItemTextContent(this: MenuItem) {
return <>{this.text && <div class="ui5-menu-item-text">{this.text}</div>}</>;
}

function checkmarkContent(this: MenuItem) {
Expand Down
Loading