diff --git a/packages/fiori/cypress/specs/Search.cy.tsx b/packages/fiori/cypress/specs/Search.cy.tsx index b56b95f8647a..3afede92279d 100644 --- a/packages/fiori/cypress/specs/Search.cy.tsx +++ b/packages/fiori/cypress/specs/Search.cy.tsx @@ -4,7 +4,6 @@ import SearchItem from "../../src/SearchItem.js"; import SearchItemGroup from "../../src/SearchItemGroup.js"; import history from "@ui5/webcomponents-icons/dist/history.js"; import IllustratedMessage from "../../src/IllustratedMessage.js"; -import SearchPopupMode from "@ui5/webcomponents/dist/types/SearchPopupMode.js"; import searchIcon from "@ui5/webcomponents-icons/dist/search.js"; import SearchMessageArea from "../../src/SearchMessageArea.js"; import Button from "@ui5/webcomponents/dist/Button.js"; diff --git a/packages/fiori/cypress/specs/SearchField.cy.tsx b/packages/fiori/cypress/specs/SearchField.cy.tsx index b1398b77f721..4b70e8ee1c13 100644 --- a/packages/fiori/cypress/specs/SearchField.cy.tsx +++ b/packages/fiori/cypress/specs/SearchField.cy.tsx @@ -4,6 +4,7 @@ import { SEARCH_FIELD_SCOPE_SELECT_LABEL, SEARCH_FIELD_CLEAR_ICON, SEARCH_FIELD_SEARCH_ICON, + SEARCH_FIELD_LABEL } from "../../src/generated/i18n/i18n-defaults.js"; describe("SearchField general interaction", () => { @@ -40,6 +41,24 @@ describe("SearchField general interaction", () => { .find("input") .should("have.attr", "aria-label", attributeValue); }); + + it("accessibleName should have default value if not set", () => { + cy.mount(); + + cy.get("[ui5-search-field]") + .shadow() + .find("input") + .should("have.attr", "aria-label", SEARCH_FIELD_LABEL.defaultText); + }); + + it("accessibleDescription should propagate if set", () => { + cy.mount(); + + cy.get("[ui5-search-field]") + .shadow() + .find("input") + .should("have.attr", "aria-description", "Test"); + }); }); describe("Collapsed Search Field", () => { diff --git a/packages/fiori/cypress/specs/ShellBarSearch.cy.tsx b/packages/fiori/cypress/specs/ShellBarSearch.cy.tsx new file mode 100644 index 000000000000..e4e2fa169425 --- /dev/null +++ b/packages/fiori/cypress/specs/ShellBarSearch.cy.tsx @@ -0,0 +1,65 @@ +import ShellBarSearch from "../../src/ShellBarSearch.js"; +import { + SHELLBAR_SEARCH_COLLAPSED, + SEARCH_FIELD_SEARCH_ICON, + SHELLBAR_SEARCH_EXPANDED, +} from "../../src/generated/i18n/i18n-defaults.js"; + +describe("Behaviour", () => { + it ("Toggles collapsed property upon icon press", () => { + cy.mount(); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-icon]") + .as("searchIcon"); + + cy.get("@searchIcon") + .realClick(); + + cy.get("[ui5-shellbar-search]") + .should("have.prop", "collapsed", true); + + cy.get("@searchIcon") + .realClick(); + + cy.get("[ui5-shellbar-search]") + .should("not.have.a.property", "collapsed"); + }); + + it ("Tests icon tooltips for diffrent states", () => { + cy.mount(); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-icon]") + .as("searchIcon"); + + cy.get("@searchIcon") + .should("have.attr", "accessible-name", SHELLBAR_SEARCH_EXPANDED.defaultText); + + cy.get("@searchIcon") + .realClick(); + + cy.get("[ui5-shellbar-search]") + .should("have.prop", "collapsed", true); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-button]") + .should("have.attr", "accessible-name", SHELLBAR_SEARCH_COLLAPSED.defaultText); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-button]") + .realClick(); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("input") + .type("test"); + + cy.get("@searchIcon") + .should("have.attr", "accessible-name", SEARCH_FIELD_SEARCH_ICON.defaultText); + }); +}); \ No newline at end of file diff --git a/packages/fiori/cypress/specs/ShellBarSearch.mobile.cy.tsx b/packages/fiori/cypress/specs/ShellBarSearch.mobile.cy.tsx new file mode 100644 index 000000000000..cc1ddec6756e --- /dev/null +++ b/packages/fiori/cypress/specs/ShellBarSearch.mobile.cy.tsx @@ -0,0 +1,98 @@ +import SearchItem from "../../src/SearchItem.js"; +import ShellBarSearch from "../../src/ShellBarSearch.js"; + +describe("Mobile Behaviour", () => { + beforeEach(() => { + cy.ui5SimulateDevice(); + }); + + it("Should not close dialog upon focus out", () => { + cy.mount(); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-button]") + .realClick(); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-responsive-popover] header input") + .type("test"); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-responsive-popover] header input") + .blur(); + + cy.get("[ui5-shellbar-search]") + .should("have.prop", "open", true); + }); + + it("Should select typed ahead item when typing", () => { + cy.mount( + <> + + + + + + + + + + ); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-button]") + .realClick(); + + cy.get("[ui5-shellbar-search]") + .should("have.prop", "open", true); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-responsive-popover] header input") + .type("item 1"); + + cy.get("[ui5-search-item]:first") + .should("have.attr", "selected"); + }); + + it("Should type ahead internal input", () => { + cy.mount( + <> + + + + + + + + + + ); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-button]") + .realClick(); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-responsive-popover] header input") + .type("ite"); + + cy.get("[ui5-shellbar-search]") + .shadow() + .find("[ui5-responsive-popover] header input") + .should("have.value", "Item 1"); + }); + + it("is collapsed by default", () => { + cy.mount(); + + cy.get("[ui5-shellbar-search]") + .should("have.prop", "collapsed", true); + }); +}); \ No newline at end of file diff --git a/packages/fiori/src/Search.ts b/packages/fiori/src/Search.ts index bf2a3d103e16..0a23f31f1dd9 100644 --- a/packages/fiori/src/Search.ts +++ b/packages/fiori/src/Search.ts @@ -66,7 +66,7 @@ type SearchEventDetails = { * * ### ES6 Module Import * - * `import "@ui5/webcomponents/fiori/dist/Search.js";` + * `import "@ui5/webcomponents-fiori/dist/Search.js";` * * @constructor * @extends SearchField @@ -287,13 +287,11 @@ class Search extends SearchField { _handleMobileInput(e: CustomEvent) { this.value = (e.target as Input).value; - this._performItemSelectionOnMobile = this._shouldPerformSelectionOnMobile(e); + this._performItemSelectionOnMobile = this._shouldPerformSelectionOnMobile(e.detail.inputType); this.fireDecoratorEvent("input"); } - - _shouldPerformSelectionOnMobile(e: CustomEvent): boolean { - const eventType = e.detail.inputType; + _shouldPerformSelectionOnMobile(inputType: string): boolean { const allowedEventTypes = [ "deleteWordBackward", "deleteWordForward", @@ -310,7 +308,7 @@ class Search extends SearchField { "historyUndo", ]; - return !this.noTypeahead && !allowedEventTypes.includes(eventType || ""); + return !this.noTypeahead && !allowedEventTypes.includes(inputType || ""); } _handleTypeAhead(item: ISearchSuggestionItem) { @@ -430,7 +428,11 @@ class Search extends SearchField { _handleInput(e: InputEvent) { super._handleInput(e); - this.open = !isPhone() && ((e.currentTarget as HTMLInputElement).value.length > 0) && this._popoupHasAnyContent(); + if (isPhone()) { + return; + } + + this.open = ((e.currentTarget as HTMLInputElement).value.length > 0) && this._popoupHasAnyContent(); } _popoupHasAnyContent() { @@ -606,7 +608,7 @@ class Search extends SearchField { get nativeInput() { const domRef = this.getDomRef(); - return domRef ? domRef.querySelector(`input`) : null; + return domRef?.querySelector(`input`); } get mobileInput() { diff --git a/packages/fiori/src/SearchField.ts b/packages/fiori/src/SearchField.ts index eb80edb98c18..7d981dcb30e5 100644 --- a/packages/fiori/src/SearchField.ts +++ b/packages/fiori/src/SearchField.ts @@ -18,6 +18,7 @@ import { SEARCH_FIELD_SCOPE_SELECT_LABEL, SEARCH_FIELD_CLEAR_ICON, SEARCH_FIELD_SEARCH_ICON, + SEARCH_FIELD_LABEL, } from "./generated/i18n/i18n-defaults.js"; /** @@ -49,7 +50,7 @@ type SearchFieldScopeSelectionChangeDetails = { * * ### ES6 Module Import * - * `import "@ui5/webcomponents/fiori/dist/SearchField.js";` + * `import "@ui5/webcomponents-fiori/dist/SearchField.js";` * * @constructor * @extends UI5Element @@ -111,7 +112,7 @@ class SearchField extends UI5Element { * Defines whether the component is collapsed. * * @default false - * @public + * @private */ @property({ type: Boolean }) collapsed = false; @@ -144,12 +145,12 @@ class SearchField extends UI5Element { accessibleName?: string; /** - * Defines the tooltip of the search icon component. + * Defines the accessible ARIA description of the field. * @public * @default undefined */ @property() - searchIconTooltip?: string; + accessibleDescription?: string; /** * Defines the component scope options. @@ -248,11 +249,12 @@ class SearchField extends UI5Element { scope: SearchField.i18nBundle.getText(SEARCH_FIELD_SCOPE_SELECT_LABEL), searchIcon: SearchField.i18nBundle.getText(SEARCH_FIELD_SEARCH_ICON), clearIcon: SearchField.i18nBundle.getText(SEARCH_FIELD_CLEAR_ICON), + searchFieldAriaLabel: SearchField.i18nBundle.getText(SEARCH_FIELD_LABEL), }; } get _effectiveIconTooltip() { - return this.searchIconTooltip || this._translations.searchIcon; + return this._translations.searchIcon; } captureRef(ref: HTMLElement & { scopeOption?: UI5Element} | null) { diff --git a/packages/fiori/src/SearchFieldTemplate.tsx b/packages/fiori/src/SearchFieldTemplate.tsx index 5f0be6805098..93949fc59e89 100644 --- a/packages/fiori/src/SearchFieldTemplate.tsx +++ b/packages/fiori/src/SearchFieldTemplate.tsx @@ -7,9 +7,16 @@ import decline from "@ui5/webcomponents-icons/dist/decline.js"; import search from "@ui5/webcomponents-icons/dist/search.js"; import ButtonDesign from "@ui5/webcomponents/dist/types/ButtonDesign.js"; -export default function SearchFieldTemplate(this: SearchField) { +export type SearchFieldTemplateOptions = { + /** + * If set to true, the search field will be expanded. + */ + forceExpanded?: boolean; +}; + +export default function SearchFieldTemplate(this: SearchField, options?: SearchFieldTemplateOptions) { return ( - this.collapsed ? ( + !options?.forceExpanded && this.collapsed ? ( - ) : null } + )) : null }
diff --git a/packages/fiori/src/ShellBarSearch.ts b/packages/fiori/src/ShellBarSearch.ts new file mode 100644 index 000000000000..2730bac0c167 --- /dev/null +++ b/packages/fiori/src/ShellBarSearch.ts @@ -0,0 +1,80 @@ +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import Search from "./Search.js"; +import { isPhone } from "@ui5/webcomponents-base/dist/Device.js"; +import ShellBarSearchTemplate from "./ShellBarSearchTemplate.js"; +import { + SEARCH_FIELD_SEARCH_ICON, + SHELLBAR_SEARCH_EXPANDED, + SHELLBAR_SEARCH_COLLAPSED, +} from "./generated/i18n/i18n-defaults.js"; + +/** + * @class + * Search field for the ShellBar component. + * @constructor + * @extends Search + * @public + * @since 2.10.0 + * @experimental + */ +@customElement({ + tag: "ui5-shellbar-search", + template: ShellBarSearchTemplate, +}) +class ShellBarSearch extends Search { + _handleSearchIconPress() { + super._handleSearchIconPress(); + + if (this.collapsed) { + this.collapsed = false; + } else if (!this.value) { + this.collapsed = true; + } + } + + _onFocusOutSearch(e: FocusEvent) { + if (isPhone()) { + return; + } + + super._onFocusOutSearch(e); + } + + _handleInput(e: InputEvent) { + super._handleInput(e); + + if (isPhone()) { + this._performItemSelectionOnMobile = this._shouldPerformSelectionOnMobile(e.inputType); + } + } + + get _effectiveIconTooltip() { + if (this.collapsed) { + return ShellBarSearch.i18nBundle.getText(SHELLBAR_SEARCH_COLLAPSED); + } + + if (this.value) { + return ShellBarSearch.i18nBundle.getText(SEARCH_FIELD_SEARCH_ICON); + } + + return ShellBarSearch.i18nBundle.getText(SHELLBAR_SEARCH_EXPANDED); + } + + get nativeInput() { + const domRef = this.shadowRoot; + + return isPhone() ? domRef?.querySelector(`[ui5-responsive-popover] input`) : super.nativeInput; + } + + onBeforeRendering(): void { + super.onBeforeRendering(); + + if (isPhone()) { + this.collapsed = true; + } + } +} + +ShellBarSearch.define(); + +export default ShellBarSearch; diff --git a/packages/fiori/src/ShellBarSearchPopoverTemplate.tsx b/packages/fiori/src/ShellBarSearchPopoverTemplate.tsx new file mode 100644 index 000000000000..6f44b09abe6c --- /dev/null +++ b/packages/fiori/src/ShellBarSearchPopoverTemplate.tsx @@ -0,0 +1,24 @@ +import Button from "@ui5/webcomponents/dist/Button.js"; +import ButtonDesign from "@ui5/webcomponents/dist/types/ButtonDesign.js"; +import type ShellBarSearch from "./ShellBarSearch.js"; +import SearchPopoverTemplate from "./SearchPopoverTemplate.js"; +import SearchFieldTemplate from "./SearchFieldTemplate.js"; + +export default function ShellBarSearchPopoverTemplate(this: ShellBarSearch) { + return ( + SearchPopoverTemplate.call(this, ShellBarSearchDialogHeader) + ); +} + +function ShellBarSearchDialogHeader(this: ShellBarSearch) { + return (<> +
+ +
+ { SearchFieldTemplate.call(this, { forceExpanded: true }) } +
+ + +
+ ); +} diff --git a/packages/fiori/src/ShellBarSearchTemplate.tsx b/packages/fiori/src/ShellBarSearchTemplate.tsx new file mode 100644 index 000000000000..ef5cef85316b --- /dev/null +++ b/packages/fiori/src/ShellBarSearchTemplate.tsx @@ -0,0 +1,12 @@ +import SearchFieldTemplate from "./SearchFieldTemplate.js"; +import type ShellBarSearch from "./ShellBarSearch.js"; +import ShellBarSearchPopoverTemplate from "./ShellBarSearchPopoverTemplate.js"; + +export default function ShellBarSearchTemplate(this: ShellBarSearch) { + return ( + <> + { SearchFieldTemplate.call(this) } + { ShellBarSearchPopoverTemplate.call(this) } + + ); +} diff --git a/packages/fiori/src/bundle.esm.ts b/packages/fiori/src/bundle.esm.ts index 64bacb9209fb..d21a7580205f 100644 --- a/packages/fiori/src/bundle.esm.ts +++ b/packages/fiori/src/bundle.esm.ts @@ -33,6 +33,7 @@ import ShellBar from "./ShellBar.js"; import SearchField from "./SearchField.js"; import SearchScope from "./SearchScope.js"; import Search from "./Search.js"; +import ShellBarSearch from "./ShellBarSearch.js"; import SearchMessageArea from "./SearchMessageArea.js"; import SearchItem from "./SearchItem.js"; import SearchItemGroup from "./SearchItemGroup.js"; diff --git a/packages/fiori/src/i18n/messagebundle.properties b/packages/fiori/src/i18n/messagebundle.properties index 0aa76e9139cd..02e8e8fc5804 100644 --- a/packages/fiori/src/i18n/messagebundle.properties +++ b/packages/fiori/src/i18n/messagebundle.properties @@ -459,12 +459,21 @@ DSC_SIDE_ARIA_LABEL=Side Content #XACT: ARIA label for search field scope select SEARCH_FIELD_SCOPE_SELECT_LABEL=Select scope +#XACT: ARIA label for search field +SEARCH_FIELD_LABEL=Search field + #XACT: ARIA announcement for search field clear icon SEARCH_FIELD_CLEAR_ICON=Clear search #XACT: ARIA announcement for search field search icon SEARCH_FIELD_SEARCH_ICON=Search +#XACT: ARIA announcement for search field collapsed search button +SHELLBAR_SEARCH_COLLAPSED=Open Search + +#XACT: ARIA announcement for search field expanded search button +SHELLBAR_SEARCH_EXPANDED=Collapse Search + #XBUT: ARIA announcement for search cancel button on mobile devices SEARCH_CANCEL_BUTTON=Cancel diff --git a/packages/fiori/src/themes/SearchField.css b/packages/fiori/src/themes/SearchField.css index 14d1b154aec6..79e14da85214 100644 --- a/packages/fiori/src/themes/SearchField.css +++ b/packages/fiori/src/themes/SearchField.css @@ -1,11 +1,13 @@ /* container */ -:host { +:host, +.ui5-shellbar-search-field-wrapper { height: 2.25rem; display: flex; align-items: center; } -:host(:not([collapsed])) { +:host(:not([collapsed])), +.ui5-shellbar-search-field-wrapper { min-width: 18rem; max-width: 36rem; margin: 0; @@ -23,8 +25,14 @@ position: relative; } +.ui5-shellbar-search-field-wrapper { + flex: 1; + min-width: auto; +} + :host(:not([collapsed]):hover), -:host(:not([collapsed]):focus-within) { +:host(:not([collapsed]):focus-within), +.ui5-shellbar-search-field-wrapper:focus-within { box-shadow: var(--sapField_Hover_Shadow); background: var(--_ui5-search-wrapper-hover-background); background-color: var(--_ui5-search-wrapper-hover-background-color); diff --git a/packages/fiori/test/pages/ShellBar_evolution.html b/packages/fiori/test/pages/ShellBar_evolution.html index b15f1aa5a761..e39fc57ed826 100644 --- a/packages/fiori/test/pages/ShellBar_evolution.html +++ b/packages/fiori/test/pages/ShellBar_evolution.html @@ -134,7 +134,19 @@ PR9 - + + + + + + + + + + + + + @@ -277,20 +289,6 @@ displayMenuOpener(hiddenItems.indexOf(btnOpenBasicDynamic) > -1); }); - function handleSearchEvent(e) { - if (e.target.collapsed) { - e.target.collapsed = false; - } else { - if (!e.target.value) { - e.target.collapsed = true; - } - } - } - const searches = Array.from(document.querySelectorAll('ui5-search')); - searches.forEach((searchField) => { - searchField.addEventListener('ui5-search', handleSearchEvent); - }); - ["focus", "blur", "input"].forEach((event) => { customSearchInput.addEventListener(event, () => { shellbar_hide_search.disableAutoSearchField = diff --git a/packages/website/docs/_components_pages/fiori/ShellBar/ShellBarSearch.mdx b/packages/website/docs/_components_pages/fiori/ShellBar/ShellBarSearch.mdx new file mode 100644 index 000000000000..d7e830d54b17 --- /dev/null +++ b/packages/website/docs/_components_pages/fiori/ShellBar/ShellBarSearch.mdx @@ -0,0 +1,7 @@ +--- +slug: ../ShellBarSearch +--- + +<%COMPONENT_OVERVIEW%> + +<%COMPONENT_METADATA%> \ No newline at end of file