diff --git a/packages/fiori/src/NavigationMenuItemTemplate.tsx b/packages/fiori/src/NavigationMenuItemTemplate.tsx index 8e841eeab1be..e81be9d1fffa 100644 --- a/packages/fiori/src/NavigationMenuItemTemplate.tsx +++ b/packages/fiori/src/NavigationMenuItemTemplate.tsx @@ -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 = { +const predefinedHooks: Partial = { listItemContent, iconBegin, iconEnd, }; -export default function NavigationMenuItemTemplate(this: NavigationMenuItem, hooks?: Partial) { +export default function NavigationMenuItemTemplate(this: NavigationMenuItem, hooks?: Partial) { const currentHooks = { ...predefinedHooks, ...hooks, }; return <> diff --git a/packages/fiori/src/UserMenuItem.ts b/packages/fiori/src/UserMenuItem.ts index a384c5147bb5..1daf4e206b84 100644 --- a/packages/fiori/src/UserMenuItem.ts +++ b/packages/fiori/src/UserMenuItem.ts @@ -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"; @@ -44,9 +45,51 @@ class UserMenuItem extends MenuItem { @slot({ "default": true, type: HTMLElement, invalidateOnChildChange: true }) declare items: DefaultSlot; + /** + * 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(); diff --git a/packages/fiori/src/UserMenuItemTemplate.tsx b/packages/fiori/src/UserMenuItemTemplate.tsx index 0c5b1e1d3604..ff561b1bb162 100644 --- a/packages/fiori/src/UserMenuItemTemplate.tsx +++ b/packages/fiori/src/UserMenuItemTemplate.tsx @@ -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 = {}; + + if (this.showSelection) { + hooks.menuItemTextContent = userMenuItemTextContent; + } + + return [MenuItemTemplate.call(this, hooks)]; +} + +function userMenuItemTextContent(this: UserMenuItem) { + return ( +
+ {this.text &&
{this.text}
} + {this._selectedSubItemText && +
{this._selectedSubItemText}
+ } +
+ ); } diff --git a/packages/fiori/src/themes/UserMenuItem.css b/packages/fiori/src/themes/UserMenuItem.css index f140dface58f..b552b7780f87 100644 --- a/packages/fiori/src/themes/UserMenuItem.css +++ b/packages/fiori/src/themes/UserMenuItem.css @@ -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; } \ No newline at end of file diff --git a/packages/fiori/test/pages/UserMenu.html b/packages/fiori/test/pages/UserMenu.html index 322321479a74..0b06426f9f8d 100644 --- a/packages/fiori/test/pages/UserMenu.html +++ b/packages/fiori/test/pages/UserMenu.html @@ -65,9 +65,9 @@ - + - + diff --git a/packages/main/src/MenuItemTemplate.tsx b/packages/main/src/MenuItemTemplate.tsx index 6dfa99a9f82b..5424d503978d 100644 --- a/packages/main/src/MenuItemTemplate.tsx +++ b/packages/main/src/MenuItemTemplate.tsx @@ -11,14 +11,29 @@ import Icon from "./Icon.js"; import ListItemTemplate from "./ListItemTemplate.js"; import type { ListItemHooks } from "./ListItemTemplate.js"; -const predefinedHooks: Partial = { - listItemContent, +export type MenuItemHooks = ListItemHooks & { + menuItemTextContent: (this: any) => JSX.Element; +} + +const predefinedHooks: Partial = { iconBegin, + menuItemTextContent, }; -export default function MenuItemTemplate(this: MenuItem, hooks?: Partial) { +export default function MenuItemTemplate(this: MenuItem, hooks?: Partial) { const currentHooks = { ...predefinedHooks, ...hooks }; + if (!hooks?.listItemContent) { + currentHooks.listItemContent = function(this: MenuItem) { + return (<> + {currentHooks.menuItemTextContent!.call(this)} + + {rightContent.call(this)} + {checkmarkContent.call(this)} + ); + }; + } + return <> {ListItemTemplate.call(this, currentHooks)} @@ -26,13 +41,8 @@ export default function MenuItemTemplate(this: MenuItem, hooks?: Partial; } -function listItemContent(this: MenuItem) { - return (<> - {this.text &&
{this.text}
} - - {rightContent.call(this)} - {checkmarkContent.call(this)} - ); +function menuItemTextContent(this: MenuItem) { + return <>{this.text &&
{this.text}
}; } function checkmarkContent(this: MenuItem) {