From 8cc40706645fd9a11e05307cc3d1399f4c50c4d8 Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Tue, 10 Mar 2026 09:50:29 +0200 Subject: [PATCH 1/4] feat(ui5-search): add search scope adaptive width --- packages/fiori/src/themes/SearchField.css | 21 +++++++++++++++------ packages/fiori/test/pages/Search.html | 13 +++++++++++++ packages/main/src/SelectTemplate.tsx | 1 + 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/fiori/src/themes/SearchField.css b/packages/fiori/src/themes/SearchField.css index b23ea0cb6c17..e0e20e75a4a4 100644 --- a/packages/fiori/src/themes/SearchField.css +++ b/packages/fiori/src/themes/SearchField.css @@ -76,15 +76,24 @@ /* scope select */ [ui5-select] { outline: none; - margin: var(--_ui5_search_input_scope_margin); - max-width: 10rem; + margin: 0 0.25rem; + width: fit-content; + min-width: auto; + max-width: 18rem; + flex: 0 1 auto; border-radius: var(--_ui5_search_input_border_radius); border: var(--_ui5-search-border); box-shadow: none; - background: unset; - background-color: var(--_ui5-search-elements-background); - height: var(--_ui5-search-select-height); - --_ui5_select_label_color: var(--sapShell_TextColor); + background: none; + height: 1.75rem; + --_ui5_select_label_color: var(--sapField_TextColor); +} + +.ui5-search-field-content [ui5-select]::part(label) { + padding-inline-end: 0.25rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } [ui5-select]:hover { diff --git a/packages/fiori/test/pages/Search.html b/packages/fiori/test/pages/Search.html index ca7254e3ae2d..20eed50c7988 100644 --- a/packages/fiori/test/pages/Search.html +++ b/packages/fiori/test/pages/Search.html @@ -221,6 +221,17 @@ The examples shows scoped search with scoped suggestions. Change scope to filter suggestions. +
+ Search with long Scoped Suggestions + + + + + + + This example demonstrates scope adaptive width behavior. +
+
Search with Suggestions - Filters and noTypeAhead @@ -312,6 +323,8 @@ { name: 'Manage Products', scope: 'apps' }, { name: 'Mobile Phones', scope: 'products' }, { name: 'Tablet', scope: 'products' }, + { name: 'Long Scope', scope: 'long' }, + { name: 'Very long scope', scope: 'long' }, ]; function createScopeItems(scope) { diff --git a/packages/main/src/SelectTemplate.tsx b/packages/main/src/SelectTemplate.tsx index c0ed1fb24c7c..3d9a12c55143 100644 --- a/packages/main/src/SelectTemplate.tsx +++ b/packages/main/src/SelectTemplate.tsx @@ -24,6 +24,7 @@ export default function SelectTemplate(this: Select) {
Date: Mon, 6 Jul 2026 14:30:00 +0300 Subject: [PATCH 2/4] feat(ui5-shellbar-search): update scope selector visual design POC --- packages/fiori/src/SearchField.ts | 95 +++++++++++++++++++ .../src/SearchFieldScopePopoverTemplate.tsx | 41 ++++++++ packages/fiori/src/SearchFieldTemplate.tsx | 60 ++++++++---- packages/fiori/src/themes/SearchField.css | 49 +++++++++- packages/fiori/test/pages/Search.html | 16 ++++ 5 files changed, 243 insertions(+), 18 deletions(-) create mode 100644 packages/fiori/src/SearchFieldScopePopoverTemplate.tsx diff --git a/packages/fiori/src/SearchField.ts b/packages/fiori/src/SearchField.ts index c64fe8338cfb..8717f8ad920b 100644 --- a/packages/fiori/src/SearchField.ts +++ b/packages/fiori/src/SearchField.ts @@ -10,6 +10,10 @@ import SearchFieldCss from "./generated/themes/SearchField.css.js"; import type Button from "@ui5/webcomponents/dist/Button.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import type { IOption, SelectChangeEventDetail } from "@ui5/webcomponents/dist/Select.js"; +import { isPhone } from "@ui5/webcomponents-base/dist/Device.js"; +import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; +import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; +import type { ListItemClickEventDetail } from "@ui5/webcomponents/dist/List.js"; import { isEnter, @@ -23,6 +27,8 @@ import { SEARCH_FIELD_LABEL, } from "./generated/i18n/i18n-defaults.js"; +const SCREEN_WIDTH_BREAKPOINT = 450; + /** * Interface for components that may be slotted inside a `ui5-search` * @public @@ -207,11 +213,53 @@ class SearchField extends UI5Element { @property({ type: Boolean }) _effectiveShowClearIcon = false; + /** + * Indicates whether the component is rendering on a small screen (mobile). + * @private + */ + @property({ type: Boolean }) + _isMobileView = false; + + + /** + * Indicates whether the scope selection popover is open on mobile. + * @private + */ + @property({ type: Boolean }) + _scopePopoverOpen = false; + + _scopeIconButton?: HTMLElement; + @i18n("@ui5/webcomponents-fiori") static i18nBundle: I18nBundle; + onEnterDOM() { + ResizeHandler.register(this, this._handleResize.bind(this) as ResizeObserverCallback); + this._isMobileView = this._isSmallScreen(); + } + + onExitDOM() { + ResizeHandler.deregister(this, this._handleResize.bind(this) as ResizeObserverCallback); + } + onBeforeRendering() { this._effectiveShowClearIcon = (this.showClearIcon && !!this.value); + this._isMobileView = this._isSmallScreen(); + } + + private _isSmallScreen(): boolean { + return isPhone() || window.innerWidth < SCREEN_WIDTH_BREAKPOINT; + } + + private _handleResize() { + const newMobileView = this._isSmallScreen(); + if (this._isMobileView !== newMobileView) { + this._isMobileView = newMobileView; + // Close popover when switching modes to prevent state issues + if (this._scopePopoverOpen) { + this._scopePopoverOpen = false; + } + } } _onkeydown(e:KeyboardEvent) { @@ -276,6 +324,38 @@ class SearchField extends UI5Element { }); } + _handleScopeIconPress() { + if (!this.scopes?.length) { + return; + } + this._scopePopoverOpen = !this._scopePopoverOpen; + } + + _handleScopePopoverClose() { + this._scopePopoverOpen = false; + } + + _handleScopeItemClick(e: CustomEvent) { + const listItem = e.detail.item; + if (!listItem) { + return; + } + + const scopeValue = listItem.getAttribute("data-scope-value"); + const scopeItem = this.scopes.find((scope: ISearchScope) => + scope.value === scopeValue + ); + + if (scopeItem) { + this.scopeValue = scopeItem.value; + this.fireDecoratorEvent("scope-change", { + scope: scopeItem, + }); + } + + this._scopePopoverOpen = false; + } + get _isSearchIcon() { return this.value.length && this.focusedInnerInput; } @@ -295,6 +375,15 @@ class SearchField extends UI5Element { }; } + get _scopeIconAccessibleName(): string { + const selectedScope = this.scopes.find((scope: ISearchScope) => + scope.value === this.scopeValue + ); + return selectedScope + ? `${this._translations.scope}: ${selectedScope.text}` + : this._translations.scope; + } + get _effectiveIconTooltip() { return this._translations.searchIcon; } @@ -304,6 +393,12 @@ class SearchField extends UI5Element { ref.scopeOption = this; } } + + captureScopeIconRef(ref: HTMLElement | null) { + if (ref) { + this._scopeIconButton = ref; + } + } } SearchField.define(); diff --git a/packages/fiori/src/SearchFieldScopePopoverTemplate.tsx b/packages/fiori/src/SearchFieldScopePopoverTemplate.tsx new file mode 100644 index 000000000000..ec571681d542 --- /dev/null +++ b/packages/fiori/src/SearchFieldScopePopoverTemplate.tsx @@ -0,0 +1,41 @@ +import ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js"; +import List from "@ui5/webcomponents/dist/List.js"; +import ListItemStandard from "@ui5/webcomponents/dist/ListItemStandard.js"; +import ListSeparator from "@ui5/webcomponents/dist/types/ListSeparator.js"; +import PopoverPlacement from "@ui5/webcomponents/dist/types/PopoverPlacement.js"; +import PopoverHorizontalAlign from "@ui5/webcomponents/dist/types/PopoverHorizontalAlign.js"; +import type SearchField from "./SearchField.js"; + +export default function SearchFieldScopePopoverTemplate(this: SearchField) { + if (!this._isMobileView || !this.scopes?.length || !this._scopePopoverOpen) { + return null; + } + + return ( + + + {this.scopes.map(scopeOption => ( + + {scopeOption.text} + + ))} + + + ); +} diff --git a/packages/fiori/src/SearchFieldTemplate.tsx b/packages/fiori/src/SearchFieldTemplate.tsx index 241d9978f4c9..c5f96a8fd467 100644 --- a/packages/fiori/src/SearchFieldTemplate.tsx +++ b/packages/fiori/src/SearchFieldTemplate.tsx @@ -5,8 +5,10 @@ import Select from "@ui5/webcomponents/dist/Select.js"; import type SearchField from "./SearchField.js"; import decline from "@ui5/webcomponents-icons/dist/decline.js"; import search from "@ui5/webcomponents-icons/dist/search.js"; +import slimArrowDown from "@ui5/webcomponents-icons/dist/slim-arrow-down.js"; import ButtonDesign from "@ui5/webcomponents/dist/types/ButtonDesign.js"; import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js"; +import SearchFieldScopePopoverTemplate from "./SearchFieldScopePopoverTemplate.js"; export type SearchFieldTemplateOptions = { /** @@ -35,23 +37,46 @@ export default function SearchFieldTemplate(this: SearchField, options?: SearchF
{this.scopes?.length ? ( <> - -
+ {!this._isMobileView ? ( + // Desktop/Tablet: Show full Select component + <> + +
+ + ) : ( + // Mobile/Phone: Show icon button only + <> +
+ {SearchFieldScopePopoverTemplate.call(this)} ) ); diff --git a/packages/fiori/src/themes/SearchField.css b/packages/fiori/src/themes/SearchField.css index e2b3ae881ca5..d9dc9a1f5d02 100644 --- a/packages/fiori/src/themes/SearchField.css +++ b/packages/fiori/src/themes/SearchField.css @@ -301,4 +301,51 @@ .ui5-search-field-inner-input::selection { background: var(--sapSelectedColor); color: var(--sapContent_ContrastTextColor); -} \ No newline at end of file +} + +/* Mobile scope selector button */ +.ui5-search-field-scope-button { + min-width: 2rem; + width: 2rem; + height: var(--_ui5-search-select-height); + margin: 0 0.25rem; + border-radius: var(--_ui5_search_input_border_radius); + border: var(--_ui5-search-border); + box-shadow: none; + background: var(--_ui5-search-elements-background); + color: var(--sapShell_InteractiveTextColor); +} + +.ui5-search-field-scope-button::part(button)::before, +.ui5-search-field-scope-button::part(button)::after { + display: none; +} + +.ui5-search-field-scope-button:hover { + box-shadow: var(--_ui5-search_input_scope_hover_shadow); + background-color: var(--sapShell_Hover_Background); +} + +.ui5-search-field-scope-button:active, +.ui5-search-field-scope-button:focus-within { + box-shadow: var(--_ui5-search_input_scope_active_shadow); + background: var(--sapShell_Active_Background); + color: var(--sapShell_Active_TextColor); +} + +/* Scope popover on mobile */ +.ui5-search-field-scope-popover { + min-width: 12rem; +} + +.ui5-search-field-scope-popover::part(content) { + padding: 0; +} + +.ui5-search-field-scope-popover [ui5-list] { + border: none; +} + +.ui5-search-field-scope-popover [ui5-li-standard][selected] { + background-color: var(--sapList_SelectionBackgroundColor); +} diff --git a/packages/fiori/test/pages/Search.html b/packages/fiori/test/pages/Search.html index 721551a45bb9..58b08158dbe4 100644 --- a/packages/fiori/test/pages/Search.html +++ b/packages/fiori/test/pages/Search.html @@ -586,5 +586,21 @@ } }); + +
+ Search with Long Scope Labels - Testing scope dropdown with extensive text + + + + + + + + + + This example demonstrates how the scope dropdown handles long text labels and proper truncation/wrapping behavior. + +
+ From f0a357915e29674464127b09d7be44a41184b906 Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Thu, 9 Jul 2026 13:29:23 +0300 Subject: [PATCH 3/4] feat(ui5-shellbar-search): add search scope adaptive width --- .../fiori/test/pages/ShellBar_Features.html | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/fiori/test/pages/ShellBar_Features.html b/packages/fiori/test/pages/ShellBar_Features.html index 3aa065281fa8..5538fd982cca 100644 --- a/packages/fiori/test/pages/ShellBar_Features.html +++ b/packages/fiori/test/pages/ShellBar_Features.html @@ -513,6 +513,40 @@

ShellBar Feature Toggle

newEl.setAttribute('placeholder', 'Search...'); newEl.setAttribute('collapsed', ''); + // Add scope options + const scope1 = document.createElement('ui5-search-scope'); + scope1.setAttribute('text', 'All'); + scope1.setAttribute('value', 'all'); + scope1.setAttribute('slot', 'scopes'); + + const scope2 = document.createElement('ui5-search-scope'); + scope2.setAttribute('text', 'Enterprise Resource Planning'); + scope2.setAttribute('value', 'erp'); + scope2.setAttribute('slot', 'scopes'); + + const scope3 = document.createElement('ui5-search-scope'); + scope3.setAttribute('text', 'Customer Relationship Management and Marketing Automation'); + scope3.setAttribute('value', 'crm'); + scope3.setAttribute('slot', 'scopes'); + + const scope4 = document.createElement('ui5-search-scope'); + scope4.setAttribute('text', 'Supply Chain Management, Logistics, and Warehouse Operations'); + scope4.setAttribute('value', 'scm'); + scope4.setAttribute('slot', 'scopes'); + + const scope5 = document.createElement('ui5-search-scope'); + scope5.setAttribute('text', 'HR'); + scope5.setAttribute('value', 'hr'); + scope5.setAttribute('slot', 'scopes'); + + newEl.appendChild(scope1); + newEl.appendChild(scope2); + newEl.appendChild(scope3); + newEl.appendChild(scope4); + newEl.appendChild(scope5); + + newEl.setAttribute('scope-value', 'all'); + replaceSearchField('searchField', newEl); } else { // Create ui5-input element From 7ba10d99b704772aa331fbcdd8579b1f8436e3af Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Fri, 10 Jul 2026 18:20:41 +0300 Subject: [PATCH 4/4] feat(ui5-shellbar-search): add search scope adaptive width --- packages/fiori/src/Search.ts | 16 +++++++++++++- packages/fiori/src/themes/SearchField.css | 1 + packages/fiori/test/pages/ShellBarSearch.html | 22 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/fiori/src/Search.ts b/packages/fiori/src/Search.ts index 9e8b64bcf90d..73f17223b800 100755 --- a/packages/fiori/src/Search.ts +++ b/packages/fiori/src/Search.ts @@ -237,6 +237,12 @@ class Search extends SearchField { */ _deleteHandler: (e: CustomEvent) => void; + /** + * Stores the initial search width to prevent recalculation on scope changes. + * @private + */ + _initialSearchWidth?: number; + @i18n("@ui5/webcomponents-fiori") static i18nBundle: I18nBundle; @@ -311,7 +317,15 @@ class Search extends SearchField { this._performTextSelection = false; if (!this.collapsed) { - this.style.setProperty("--search_width", `${this.getBoundingClientRect().width}px`); + // Capture width only once when first expanded + if (!this._initialSearchWidth) { + this._initialSearchWidth = this.getBoundingClientRect().width; + } + // Always set from stored value to prevent recalculation + this.style.setProperty("--search_width", `${this._initialSearchWidth}px`); + } else { + // Reset when collapsed so it can recalculate on next expansion + this._initialSearchWidth = undefined; } } diff --git a/packages/fiori/src/themes/SearchField.css b/packages/fiori/src/themes/SearchField.css index d9dc9a1f5d02..5ad266c2ec85 100644 --- a/packages/fiori/src/themes/SearchField.css +++ b/packages/fiori/src/themes/SearchField.css @@ -8,6 +8,7 @@ :host(:not([collapsed])), .ui5-shellbar-search-field-wrapper { + width: var(--search_width); min-width: 18rem; max-width: 36rem; margin: 0; diff --git a/packages/fiori/test/pages/ShellBarSearch.html b/packages/fiori/test/pages/ShellBarSearch.html index 1198e28c6e85..a83da647282a 100644 --- a/packages/fiori/test/pages/ShellBarSearch.html +++ b/packages/fiori/test/pages/ShellBarSearch.html @@ -32,6 +32,28 @@ + + + + + Enterprise Portal + + + + + + + + + + + + + + + +