From 8cc40706645fd9a11e05307cc3d1399f4c50c4d8 Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Tue, 10 Mar 2026 09:50:29 +0200 Subject: [PATCH 1/5] 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 b23ea0cb6c172..e0e20e75a4a41 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 ca7254e3ae2d9..20eed50c7988d 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 c0ed1fb24c7c7..3d9a12c55143b 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/5] 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 c64fe8338cfb0..8717f8ad920b4 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 0000000000000..ec571681d5425 --- /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 241d9978f4c91..c5f96a8fd4675 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 e2b3ae881ca57..d9dc9a1f5d02e 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 721551a45bb9a..58b08158dbe4e 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/5] 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 3aa065281fa85..5538fd982ccac 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/5] 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 9e8b64bcf90db..73f17223b8001 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 d9dc9a1f5d02e..5ad266c2ec85e 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 1198e28c6e858..a83da647282ab 100644 --- a/packages/fiori/test/pages/ShellBarSearch.html +++ b/packages/fiori/test/pages/ShellBarSearch.html @@ -32,6 +32,28 @@ + + + + + Enterprise Portal + + + + + + + + + + + + + + + + From 8e241fe08ff78414f5569d8b23b12678c53450b8 Mon Sep 17 00:00:00 2001 From: Milen Karmidzhanov Date: Mon, 13 Jul 2026 11:57:38 +0300 Subject: [PATCH 5/5] feat(ui5-shellbar-search): add search scope adaptive width --- packages/fiori/src/Search.ts | 16 +------------- packages/fiori/src/SearchField.ts | 22 ++++++++++++++----- packages/fiori/src/SearchFieldTemplate.tsx | 2 +- .../fiori/src/i18n/messagebundle.properties | 3 +++ packages/fiori/test/pages/ShellBarSearch.html | 3 +-- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/fiori/src/Search.ts b/packages/fiori/src/Search.ts index 73f17223b8001..9e8b64bcf90db 100755 --- a/packages/fiori/src/Search.ts +++ b/packages/fiori/src/Search.ts @@ -237,12 +237,6 @@ 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; @@ -317,15 +311,7 @@ class Search extends SearchField { this._performTextSelection = false; if (!this.collapsed) { - // 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; + this.style.setProperty("--search_width", `${this.getBoundingClientRect().width}px`); } } diff --git a/packages/fiori/src/SearchField.ts b/packages/fiori/src/SearchField.ts index 8717f8ad920b4..300080f6973d9 100644 --- a/packages/fiori/src/SearchField.ts +++ b/packages/fiori/src/SearchField.ts @@ -25,6 +25,7 @@ import { SEARCH_FIELD_CLEAR_ICON, SEARCH_FIELD_SEARCH_ICON, SEARCH_FIELD_LABEL, + SEARCH_FIELD_PLACEHOLDER_WITH_SCOPE, } from "./generated/i18n/i18n-defaults.js"; const SCREEN_WIDTH_BREAKPOINT = 450; @@ -342,9 +343,7 @@ class SearchField extends UI5Element { } const scopeValue = listItem.getAttribute("data-scope-value"); - const scopeItem = this.scopes.find((scope: ISearchScope) => - scope.value === scopeValue - ); + const scopeItem = this.scopes.find((scope: ISearchScope) => scope.value === scopeValue); if (scopeItem) { this.scopeValue = scopeItem.value; @@ -372,13 +371,24 @@ class SearchField extends UI5Element { searchIcon: SearchField.i18nBundle.getText(SEARCH_FIELD_SEARCH_ICON), clearIcon: SearchField.i18nBundle.getText(SEARCH_FIELD_CLEAR_ICON), searchFieldAriaLabel: SearchField.i18nBundle.getText(SEARCH_FIELD_LABEL), + placeholderWithScope: SearchField.i18nBundle.getText(SEARCH_FIELD_PLACEHOLDER_WITH_SCOPE), }; } + get _effectivePlaceholder(): string | undefined { + // If scopes exist and no user-defined placeholder, show "Search in: {SCOPE}" + if (this.scopes?.length && !this.placeholder && this.scopeValue) { + const selectedScope = this.scopes.find((scope: ISearchScope) => scope.value === this.scopeValue); + if (selectedScope?.text) { + return String(SearchField.i18nBundle.getText(SEARCH_FIELD_PLACEHOLDER_WITH_SCOPE, String(selectedScope.text))); + } + } + return this.placeholder; + } + get _scopeIconAccessibleName(): string { - const selectedScope = this.scopes.find((scope: ISearchScope) => - scope.value === this.scopeValue - ); + const selectedScope = this.scopes.find((scope: ISearchScope) => scope.value === this.scopeValue); + return selectedScope ? `${this._translations.scope}: ${selectedScope.text}` : this._translations.scope; diff --git a/packages/fiori/src/SearchFieldTemplate.tsx b/packages/fiori/src/SearchFieldTemplate.tsx index c5f96a8fd4675..160ab6994560d 100644 --- a/packages/fiori/src/SearchFieldTemplate.tsx +++ b/packages/fiori/src/SearchFieldTemplate.tsx @@ -95,7 +95,7 @@ export default function SearchFieldTemplate(this: SearchField, options?: SearchF aria-autocomplete="both" aria-controls="ui5-search-list" value={this.value} - placeholder={this.placeholder} + placeholder={this._effectivePlaceholder} data-sap-focus-ref onInput={this._handleInput} onFocusIn={this._onfocusin} diff --git a/packages/fiori/src/i18n/messagebundle.properties b/packages/fiori/src/i18n/messagebundle.properties index f0e219e637d2c..54b0c1eff2ca5 100644 --- a/packages/fiori/src/i18n/messagebundle.properties +++ b/packages/fiori/src/i18n/messagebundle.properties @@ -503,6 +503,9 @@ SEARCH_FIELD_SCOPE_SELECT_LABEL=Select scope #XACT: ARIA label for search field SEARCH_FIELD_LABEL=Search field +#XFLD: Placeholder text for search field with scope on mobile +SEARCH_FIELD_PLACEHOLDER_WITH_SCOPE=Search in: {0} + #XACT: ARIA announcement for search field clear icon SEARCH_FIELD_CLEAR_ICON=Clear search diff --git a/packages/fiori/test/pages/ShellBarSearch.html b/packages/fiori/test/pages/ShellBarSearch.html index a83da647282ab..162597603feb2 100644 --- a/packages/fiori/test/pages/ShellBarSearch.html +++ b/packages/fiori/test/pages/ShellBarSearch.html @@ -40,8 +40,7 @@ - +