Skip to content

Commit ca372d0

Browse files
authored
feat(ui5-shellbar-search): initial implementation (#11398)
1 parent a932073 commit ca372d0

17 files changed

+372
-39
lines changed

packages/fiori/cypress/specs/Search.cy.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import SearchItem from "../../src/SearchItem.js";
44
import SearchItemGroup from "../../src/SearchItemGroup.js";
55
import history from "@ui5/webcomponents-icons/dist/history.js";
66
import IllustratedMessage from "../../src/IllustratedMessage.js";
7-
import SearchPopupMode from "@ui5/webcomponents/dist/types/SearchPopupMode.js";
87
import searchIcon from "@ui5/webcomponents-icons/dist/search.js";
98
import SearchMessageArea from "../../src/SearchMessageArea.js";
109
import Button from "@ui5/webcomponents/dist/Button.js";

packages/fiori/cypress/specs/SearchField.cy.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
SEARCH_FIELD_SCOPE_SELECT_LABEL,
55
SEARCH_FIELD_CLEAR_ICON,
66
SEARCH_FIELD_SEARCH_ICON,
7+
SEARCH_FIELD_LABEL
78
} from "../../src/generated/i18n/i18n-defaults.js";
89

910
describe("SearchField general interaction", () => {
@@ -40,6 +41,24 @@ describe("SearchField general interaction", () => {
4041
.find("input")
4142
.should("have.attr", "aria-label", attributeValue);
4243
});
44+
45+
it("accessibleName should have default value if not set", () => {
46+
cy.mount(<SearchField></SearchField>);
47+
48+
cy.get("[ui5-search-field]")
49+
.shadow()
50+
.find("input")
51+
.should("have.attr", "aria-label", SEARCH_FIELD_LABEL.defaultText);
52+
});
53+
54+
it("accessibleDescription should propagate if set", () => {
55+
cy.mount(<SearchField accessibleDescription="Test"></SearchField>);
56+
57+
cy.get("[ui5-search-field]")
58+
.shadow()
59+
.find("input")
60+
.should("have.attr", "aria-description", "Test");
61+
});
4362
});
4463

4564
describe("Collapsed Search Field", () => {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import ShellBarSearch from "../../src/ShellBarSearch.js";
2+
import {
3+
SHELLBAR_SEARCH_COLLAPSED,
4+
SEARCH_FIELD_SEARCH_ICON,
5+
SHELLBAR_SEARCH_EXPANDED,
6+
} from "../../src/generated/i18n/i18n-defaults.js";
7+
8+
describe("Behaviour", () => {
9+
it ("Toggles collapsed property upon icon press", () => {
10+
cy.mount(<ShellBarSearch />);
11+
12+
cy.get("[ui5-shellbar-search]")
13+
.shadow()
14+
.find("[ui5-icon]")
15+
.as("searchIcon");
16+
17+
cy.get("@searchIcon")
18+
.realClick();
19+
20+
cy.get("[ui5-shellbar-search]")
21+
.should("have.prop", "collapsed", true);
22+
23+
cy.get("@searchIcon")
24+
.realClick();
25+
26+
cy.get("[ui5-shellbar-search]")
27+
.should("not.have.a.property", "collapsed");
28+
});
29+
30+
it ("Tests icon tooltips for diffrent states", () => {
31+
cy.mount(<ShellBarSearch />);
32+
33+
cy.get("[ui5-shellbar-search]")
34+
.shadow()
35+
.find("[ui5-icon]")
36+
.as("searchIcon");
37+
38+
cy.get("@searchIcon")
39+
.should("have.attr", "accessible-name", SHELLBAR_SEARCH_EXPANDED.defaultText);
40+
41+
cy.get("@searchIcon")
42+
.realClick();
43+
44+
cy.get("[ui5-shellbar-search]")
45+
.should("have.prop", "collapsed", true);
46+
47+
cy.get("[ui5-shellbar-search]")
48+
.shadow()
49+
.find("[ui5-button]")
50+
.should("have.attr", "accessible-name", SHELLBAR_SEARCH_COLLAPSED.defaultText);
51+
52+
cy.get("[ui5-shellbar-search]")
53+
.shadow()
54+
.find("[ui5-button]")
55+
.realClick();
56+
57+
cy.get("[ui5-shellbar-search]")
58+
.shadow()
59+
.find("input")
60+
.type("test");
61+
62+
cy.get("@searchIcon")
63+
.should("have.attr", "accessible-name", SEARCH_FIELD_SEARCH_ICON.defaultText);
64+
});
65+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import SearchItem from "../../src/SearchItem.js";
2+
import ShellBarSearch from "../../src/ShellBarSearch.js";
3+
4+
describe("Mobile Behaviour", () => {
5+
beforeEach(() => {
6+
cy.ui5SimulateDevice();
7+
});
8+
9+
it("Should not close dialog upon focus out", () => {
10+
cy.mount(<ShellBarSearch />);
11+
12+
cy.get("[ui5-shellbar-search]")
13+
.shadow()
14+
.find("[ui5-button]")
15+
.realClick();
16+
17+
cy.get("[ui5-shellbar-search]")
18+
.shadow()
19+
.find("[ui5-responsive-popover] header input")
20+
.type("test");
21+
22+
cy.get("[ui5-shellbar-search]")
23+
.shadow()
24+
.find("[ui5-responsive-popover] header input")
25+
.blur();
26+
27+
cy.get("[ui5-shellbar-search]")
28+
.should("have.prop", "open", true);
29+
});
30+
31+
it("Should select typed ahead item when typing", () => {
32+
cy.mount(
33+
<>
34+
<ShellBarSearch showClearIcon={true}>
35+
<SearchItem text="Item 1" />
36+
<SearchItem text="Item 2" />
37+
<SearchItem text="Item 3" />
38+
<SearchItem text="Item 4" />
39+
<SearchItem text="Item 5" />
40+
<SearchItem text="Item 6" />
41+
</ShellBarSearch>
42+
</>
43+
);
44+
45+
cy.get("[ui5-shellbar-search]")
46+
.shadow()
47+
.find("[ui5-button]")
48+
.realClick();
49+
50+
cy.get("[ui5-shellbar-search]")
51+
.should("have.prop", "open", true);
52+
53+
cy.get("[ui5-shellbar-search]")
54+
.shadow()
55+
.find("[ui5-responsive-popover] header input")
56+
.type("item 1");
57+
58+
cy.get("[ui5-search-item]:first")
59+
.should("have.attr", "selected");
60+
});
61+
62+
it("Should type ahead internal input", () => {
63+
cy.mount(
64+
<>
65+
<ShellBarSearch showClearIcon={true}>
66+
<SearchItem text="Item 1" />
67+
<SearchItem text="Item 2" />
68+
<SearchItem text="Item 3" />
69+
<SearchItem text="Item 4" />
70+
<SearchItem text="Item 5" />
71+
<SearchItem text="Item 6" />
72+
</ShellBarSearch>
73+
</>
74+
);
75+
76+
cy.get("[ui5-shellbar-search]")
77+
.shadow()
78+
.find("[ui5-button]")
79+
.realClick();
80+
81+
cy.get("[ui5-shellbar-search]")
82+
.shadow()
83+
.find("[ui5-responsive-popover] header input")
84+
.type("ite");
85+
86+
cy.get("[ui5-shellbar-search]")
87+
.shadow()
88+
.find("[ui5-responsive-popover] header input")
89+
.should("have.value", "Item 1");
90+
});
91+
92+
it("is collapsed by default", () => {
93+
cy.mount(<ShellBarSearch />);
94+
95+
cy.get("[ui5-shellbar-search]")
96+
.should("have.prop", "collapsed", true);
97+
});
98+
});

packages/fiori/src/Search.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ type SearchEventDetails = {
6666
*
6767
* ### ES6 Module Import
6868
*
69-
* `import "@ui5/webcomponents/fiori/dist/Search.js";`
69+
* `import "@ui5/webcomponents-fiori/dist/Search.js";`
7070
*
7171
* @constructor
7272
* @extends SearchField
@@ -287,13 +287,11 @@ class Search extends SearchField {
287287

288288
_handleMobileInput(e: CustomEvent<InputEventDetail>) {
289289
this.value = (e.target as Input).value;
290-
this._performItemSelectionOnMobile = this._shouldPerformSelectionOnMobile(e);
290+
this._performItemSelectionOnMobile = this._shouldPerformSelectionOnMobile(e.detail.inputType);
291291

292292
this.fireDecoratorEvent("input");
293293
}
294-
295-
_shouldPerformSelectionOnMobile(e: CustomEvent<InputEventDetail>): boolean {
296-
const eventType = e.detail.inputType;
294+
_shouldPerformSelectionOnMobile(inputType: string): boolean {
297295
const allowedEventTypes = [
298296
"deleteWordBackward",
299297
"deleteWordForward",
@@ -310,7 +308,7 @@ class Search extends SearchField {
310308
"historyUndo",
311309
];
312310

313-
return !this.noTypeahead && !allowedEventTypes.includes(eventType || "");
311+
return !this.noTypeahead && !allowedEventTypes.includes(inputType || "");
314312
}
315313

316314
_handleTypeAhead(item: ISearchSuggestionItem) {
@@ -430,7 +428,11 @@ class Search extends SearchField {
430428
_handleInput(e: InputEvent) {
431429
super._handleInput(e);
432430

433-
this.open = !isPhone() && ((e.currentTarget as HTMLInputElement).value.length > 0) && this._popoupHasAnyContent();
431+
if (isPhone()) {
432+
return;
433+
}
434+
435+
this.open = ((e.currentTarget as HTMLInputElement).value.length > 0) && this._popoupHasAnyContent();
434436
}
435437

436438
_popoupHasAnyContent() {
@@ -606,7 +608,7 @@ class Search extends SearchField {
606608
get nativeInput() {
607609
const domRef = this.getDomRef();
608610

609-
return domRef ? domRef.querySelector<HTMLInputElement>(`input`) : null;
611+
return domRef?.querySelector<HTMLInputElement>(`input`);
610612
}
611613

612614
get mobileInput() {

packages/fiori/src/SearchField.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
SEARCH_FIELD_SCOPE_SELECT_LABEL,
1919
SEARCH_FIELD_CLEAR_ICON,
2020
SEARCH_FIELD_SEARCH_ICON,
21+
SEARCH_FIELD_LABEL,
2122
} from "./generated/i18n/i18n-defaults.js";
2223

2324
/**
@@ -49,7 +50,7 @@ type SearchFieldScopeSelectionChangeDetails = {
4950
*
5051
* ### ES6 Module Import
5152
*
52-
* `import "@ui5/webcomponents/fiori/dist/SearchField.js";`
53+
* `import "@ui5/webcomponents-fiori/dist/SearchField.js";`
5354
*
5455
* @constructor
5556
* @extends UI5Element
@@ -111,7 +112,7 @@ class SearchField extends UI5Element {
111112
* Defines whether the component is collapsed.
112113
*
113114
* @default false
114-
* @public
115+
* @private
115116
*/
116117
@property({ type: Boolean })
117118
collapsed = false;
@@ -144,12 +145,12 @@ class SearchField extends UI5Element {
144145
accessibleName?: string;
145146

146147
/**
147-
* Defines the tooltip of the search icon component.
148+
* Defines the accessible ARIA description of the field.
148149
* @public
149150
* @default undefined
150151
*/
151152
@property()
152-
searchIconTooltip?: string;
153+
accessibleDescription?: string;
153154

154155
/**
155156
* Defines the component scope options.
@@ -248,11 +249,12 @@ class SearchField extends UI5Element {
248249
scope: SearchField.i18nBundle.getText(SEARCH_FIELD_SCOPE_SELECT_LABEL),
249250
searchIcon: SearchField.i18nBundle.getText(SEARCH_FIELD_SEARCH_ICON),
250251
clearIcon: SearchField.i18nBundle.getText(SEARCH_FIELD_CLEAR_ICON),
252+
searchFieldAriaLabel: SearchField.i18nBundle.getText(SEARCH_FIELD_LABEL),
251253
};
252254
}
253255

254256
get _effectiveIconTooltip() {
255-
return this.searchIconTooltip || this._translations.searchIcon;
257+
return this._translations.searchIcon;
256258
}
257259

258260
captureRef(ref: HTMLElement & { scopeOption?: UI5Element} | null) {

packages/fiori/src/SearchFieldTemplate.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ import decline from "@ui5/webcomponents-icons/dist/decline.js";
77
import search from "@ui5/webcomponents-icons/dist/search.js";
88
import ButtonDesign from "@ui5/webcomponents/dist/types/ButtonDesign.js";
99

10-
export default function SearchFieldTemplate(this: SearchField) {
10+
export type SearchFieldTemplateOptions = {
11+
/**
12+
* If set to true, the search field will be expanded.
13+
*/
14+
forceExpanded?: boolean;
15+
};
16+
17+
export default function SearchFieldTemplate(this: SearchField, options?: SearchFieldTemplateOptions) {
1118
return (
12-
this.collapsed ? (
19+
!options?.forceExpanded && this.collapsed ? (
1320
<Button
1421
class="ui5-shell-search-field-button"
1522
icon={search}
@@ -46,7 +53,8 @@ export default function SearchFieldTemplate(this: SearchField) {
4653
<input
4754
class="ui5-search-field-inner-input"
4855
role="searchbox"
49-
aria-label={this.accessibleName}
56+
aria-description={this.accessibleDescription}
57+
aria-label={this.accessibleName || this._translations.searchFieldAriaLabel}
5058
value={this.value}
5159
placeholder={this.placeholder}
5260
data-sap-focus-ref

packages/fiori/src/SearchItem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
1616
*
1717
* ### ES6 Module Import
1818
*
19-
* `import "@ui5/webcomponents/fiori/dist/SearchItem.js";`
19+
* `import "@ui5/webcomponents-fiori/dist/SearchItem.js";`
2020
*
2121
* @constructor
2222
* @extends ListItemBase

packages/fiori/src/SearchPopoverTemplate.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import InputKeyHint from "@ui5/webcomponents/dist/types/InputKeyHint.js";
1212
import Button from "@ui5/webcomponents/dist/Button.js";
1313
import ButtonDesign from "@ui5/webcomponents/dist/types/ButtonDesign.js";
1414
import ListAccessibleRole from "@ui5/webcomponents/dist/types/ListAccessibleRole.js";
15+
import type { JsxTemplate } from "@ui5/webcomponents-base/dist/index.js";
1516

16-
export default function SearchPopoverTemplate(this: Search) {
17+
export default function SearchPopoverTemplate(this: Search, headerTemplate?: JsxTemplate) {
1718
return (
1819
<ResponsivePopover
1920
hideArrow={true}
@@ -34,7 +35,7 @@ export default function SearchPopoverTemplate(this: Search) {
3435
}}
3536
>
3637

37-
{isPhone() ? (
38+
{isPhone() ? (headerTemplate ? headerTemplate.call(this) : (
3839
<>
3940
<header slot="header" class="ui5-search-popup-searching-header">
4041
<Input class="ui5-search-popover-search-field" onInput={this._handleMobileInput} showClearIcon={this.showClearIcon} noTypeahead={this.noTypeahead} hint={InputKeyHint.Search} onKeyDown={this._onMobileInputKeydown}>
@@ -45,7 +46,7 @@ export default function SearchPopoverTemplate(this: Search) {
4546
<Button design={ButtonDesign.Transparent} onClick={this._handleCancel}>{this.cancelButtonText}</Button>
4647
</header>
4748
</>
48-
) : null }
49+
)) : null }
4950

5051
<main class="ui5-search-popover-content">
5152
<slot name="messageArea"></slot>

0 commit comments

Comments
 (0)