Skip to content

Commit 793d5ac

Browse files
committed
feat: search spinner and lang
1 parent 00c5e26 commit 793d5ac

File tree

3 files changed

+114
-3
lines changed

3 files changed

+114
-3
lines changed

js/features/createSearch/createSearchInterface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ export type SearchOptions = {
99
apiUrl?: string;
1010
apiSearchParam?: string;
1111
noResultsText?: string;
12+
resetButtonLabel?: string;
13+
placeholder?: string;
1214
}

js/features/createSearch/search/searchUi.ts

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,27 @@ export class SearchUi implements SearchUiInterface {
77
private searchContainer: HTMLElement|undefined = undefined;
88
private list: HTMLElement|undefined = undefined;
99
private input: HTMLInputElement|undefined = undefined;
10+
private resetButton: HTMLElement|undefined = undefined;
11+
private spinner: HTMLElement|undefined = undefined;
1012
private itemClickListeners: ListItemClickListener[] = [];
1113
private currentValue: string = '';
1214

1315
constructor(private searchOptions: SearchOptions, private searchApi: SearchApiInterface) {}
1416

1517
private listenForInput(): void {
1618
const debouncedSearch = this.debounce((value: string) => {
19+
this.showOrHideSpinner(true);
1720
this.searchApi.search(value)
1821
.then((data: PlaceObject[]) => {
22+
this.showOrHideSpinner(false);
1923
this.setSearchListItems(data);
2024
});
2125
}, 500);
2226

2327
this.getInput()?.addEventListener('input', (e: Event) => {
2428
const input = e.target as HTMLInputElement;
2529
this.currentValue = input.value;
30+
this.showOrHideReset();
2631
debouncedSearch(input.value);
2732
});
2833
}
@@ -43,7 +48,7 @@ export class SearchUi implements SearchUiInterface {
4348
} else {
4449
listContainer.innerHTML = '';
4550
}
46-
51+
console.log(items);
4752
items.forEach((item: PlaceObject) => {
4853
const listItem = this.createListItem(item);
4954

@@ -76,16 +81,21 @@ export class SearchUi implements SearchUiInterface {
7681
container.className = `openstreetmap__search ${this.searchOptions.className || ''}`;
7782

7883
container.innerHTML = `
79-
<input type="text" placeholder="Search location..." />
84+
<div class="openstreetmap__search-container">
85+
<input type="text" placeholder="${this.searchOptions.placeholder ?? 'Search location...'}" />
86+
<span class="openstreetmap__search-spinner" data-js-search-spinner="true"></span>
87+
<span class="openstreetmap__search-icon" data-js-search-reset="true" role="button" aria-label="${this.searchOptions.resetButtonLabel ?? 'Reset search'}">&#10005;</span>
88+
</div>
8089
<ul></ul>
8190
`;
8291

8392
const controlContainer = (map.getAddable() as LeafletMap).getContainer()?.querySelector('.leaflet-control-container');
8493
controlContainer?.appendChild(container);
8594
this.searchContainer = container;
86-
this.stopDblClickZoom();
8795

96+
this.stopDblClickZoom();
8897
this.listenForInput();
98+
this.listenForResetButton();
8999

90100
return this;
91101
}
@@ -102,11 +112,67 @@ export class SearchUi implements SearchUiInterface {
102112
this.getContainer()?.addEventListener('dblclick', (e: Event) => {
103113
e.stopPropagation();
104114
});
115+
}
116+
117+
private listenForResetButton(): void {
118+
this.getResetButton()?.addEventListener('click', (e: Event) => {
119+
e.preventDefault();
120+
e.stopPropagation();
121+
console.log('Reset search');
122+
if (this.getList()) {
123+
this.getList()!.innerHTML = '';
124+
}
125+
126+
if (this.getInput()) {
127+
this.getInput()!.value = '';
128+
this.getInput()!.focus();
129+
}
130+
});
131+
}
132+
133+
private showOrHideSpinner(isSearching: boolean): void {
134+
if (!this.getSpinner()) {
135+
return;
136+
}
137+
138+
if (isSearching && this.currentValue.length > 0) {
139+
this.getSpinner()!.style.display = 'inline-block';
140+
} else {
141+
this.getSpinner()!.style.display = 'none';
142+
}
143+
}
144+
145+
private showOrHideReset(): void {
146+
if (!this.getResetButton()) {
147+
return;
148+
}
149+
150+
if (this.currentValue.length > 0) {
151+
this.getResetButton()!.style.display = 'block';
152+
} else {
153+
this.getResetButton()!.style.display = 'none';
154+
}
105155
}
106156

107157
public getContainer(): HTMLElement|undefined {
108158
return this.searchContainer;
109159
}
160+
161+
private getSpinner(): HTMLElement|undefined {
162+
if (!this.spinner) {
163+
this.spinner = this.getContainer()?.querySelector('[data-js-search-spinner]') ?? undefined;
164+
}
165+
166+
return this.spinner;
167+
}
168+
169+
private getResetButton(): HTMLElement|undefined {
170+
if (!this.resetButton) {
171+
this.resetButton = this.getContainer()?.querySelector('[data-js-search-reset]') ?? undefined;
172+
}
173+
174+
return this.resetButton;
175+
}
110176

111177
private getInput(): HTMLInputElement|undefined {
112178
if (!this.input) {

sass/main.scss

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,53 @@
2020
padding: 0 .5rem;
2121
}
2222

23+
.openstreetmap__search-container {
24+
position: relative;
25+
}
26+
27+
.openstreetmap__search-icon {
28+
cursor: pointer;
29+
position: absolute;
30+
top: 50%;
31+
right: 0;
32+
transform: translate(.5rem, -50%);
33+
padding: .5rem;
34+
transition: font-size 0.2s ease-in-out;
35+
display: none;
36+
37+
&:hover {
38+
font-size: 110%;
39+
}
40+
}
41+
42+
.openstreetmap__search-spinner {
43+
width: 12px;
44+
height: 12px;
45+
border: 2px solid #000;
46+
border-bottom-color: transparent;
47+
border-radius: 50%;
48+
display: none;
49+
box-sizing: border-box;
50+
animation: rotation 1s linear infinite;
51+
position: absolute;
52+
right: 1rem;
53+
top: calc(50% - 6px);
54+
}
55+
56+
@keyframes rotation {
57+
0% {
58+
transform: rotate(0deg);
59+
}
60+
100% {
61+
transform: rotate(360deg);
62+
}
63+
}
64+
2365
input {
2466
width: 100%;
2567
border: none;
2668
height: 2rem;
69+
padding-right: 2rem;
2770

2871
&:focus-visible {
2972
outline: unset;

0 commit comments

Comments
 (0)