diff --git a/apps/frontend/static_src/images/search.svg b/apps/frontend/static_src/images/search.svg
index 590333d5..6302a70a 100644
--- a/apps/frontend/static_src/images/search.svg
+++ b/apps/frontend/static_src/images/search.svg
@@ -1 +1 @@
-
+
diff --git a/apps/frontend/static_src/js/main.js b/apps/frontend/static_src/js/main.js
index 6382c6bb..eaa2e9e2 100644
--- a/apps/frontend/static_src/js/main.js
+++ b/apps/frontend/static_src/js/main.js
@@ -11,6 +11,11 @@ handleFeedback();
const searchInput = document.querySelector('[data-search-input]');
const searchIconButton = document.querySelector('[data-search-icon-button]');
+const searchModal = document.getElementById('search-modal');
+const resultsDiv = document.querySelector('[data-results]');
+const resultsCountContainer = document.querySelector(
+ '[data-results-count-container]',
+);
const removeExistingChildren = (parent) => {
// eslint-disable-next-line no-param-reassign
@@ -18,11 +23,6 @@ const removeExistingChildren = (parent) => {
};
const injectResultsInHTML = (results) => {
- const resultsDiv = document.querySelector('[data-results]');
- const resultsCountContainer = document.querySelector(
- '[data-results-count-container]',
- );
-
removeExistingChildren(resultsDiv);
removeExistingChildren(resultsCountContainer);
@@ -38,7 +38,7 @@ const injectResultsInHTML = (results) => {
resultsCountHeading.classList.add('autocomplete__count');
resultsCountContainer.appendChild(resultsCountHeading);
- results.forEach((result) => {
+ results.forEach((result, index) => {
const resultDiv = document.createElement('a');
const resultHeading = document.createElement('h3');
const resultDescription = document.createElement('div');
@@ -55,11 +55,22 @@ const injectResultsInHTML = (results) => {
resultDiv.classList.add('autocomplete__row');
resultHeading.classList.add('autocomplete__heading');
resultsDiv.appendChild(resultDiv);
+
+ requestAnimationFrame(() => {
+ setTimeout(() => {
+ resultDiv.classList.add('is-visible');
+ }, index *400);
+ });
});
};
const onSearchInputChange = async (event) => {
const query = event.target.value;
+
+ document.querySelector('.search__container').classList.add('is-loading');
+
+ const minDelay = new Promise(resolve => setTimeout(resolve, 200));
+
try {
const res = await fetch(
`${window.location.origin}${
@@ -75,16 +86,19 @@ const onSearchInputChange = async (event) => {
console.log(err);
// eslint-disable-next-line no-alert
window.alert(`Error: ${err}`);
+ } finally {
+ await minDelay; // wait for 300ms to pass if fetch was faster
+ document.querySelector('.search__container').classList.remove('is-loading');
}
};
searchInput.addEventListener('keyup', debounce(onSearchInputChange, 150));
-searchIconButton.addEventListener('click', () => {
- const resultsDiv = document.querySelector('[data-results]');
- const resultsCountContainer = document.querySelector(
- '[data-results-count-container]',
- );
+searchModal.addEventListener('shown.bs.modal', () => {
+ searchInput.focus();
+});
+searchModal.addEventListener('hidden.bs.modal', () => {
+ searchInput.value = '';
removeExistingChildren(resultsDiv);
removeExistingChildren(resultsCountContainer);
});
diff --git a/apps/frontend/static_src/scss/components/autocomplete.scss b/apps/frontend/static_src/scss/components/autocomplete.scss
index b076d096..25b250a3 100644
--- a/apps/frontend/static_src/scss/components/autocomplete.scss
+++ b/apps/frontend/static_src/scss/components/autocomplete.scss
@@ -5,14 +5,11 @@
&__count {
@include fs(s);
- position: absolute;
- top: $gutter;
- inset-inline-end: 0;
+ text-align: right;
color: $meta-color;
font-weight: 500;
padding: 0 $row-spacing;
margin-bottom: $gutter;
- margin-top: $gutter;
}
&__row {
@@ -20,7 +17,14 @@
padding: $gutter $row-spacing;
border-top: 1px solid light-dark($color--light-grey, $color--grey);
text-decoration: none;
- transition: background-color $transition;
+ transition: background-color $transition, opacity 0.25s ease-out, transform 0.25s ease-out;
+ opacity: 0;
+ transform: translateY(8px);
+
+ &.is-visible {
+ opacity: 1;
+ transform: translateY(0);
+ }
&:hover,
&:focus {
diff --git a/apps/frontend/static_src/scss/components/search.scss b/apps/frontend/static_src/scss/components/search.scss
index ed01a8b4..1c5b4116 100644
--- a/apps/frontend/static_src/scss/components/search.scss
+++ b/apps/frontend/static_src/scss/components/search.scss
@@ -5,6 +5,32 @@
display: flex;
flex-direction: row;
justify-content: stretch;
+ position: relative;
+ }
+
+ &__container::after {
+ content: '';
+ display: block;
+ position: absolute;
+ inset-inline-start:($gutter * 1.6);
+ inset-block-start: 50%;
+ transform: translateY(-50%);
+ width: 21px;
+ height: 21px;
+ background-image: url('../images/search.svg');
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ pointer-events: none;
+ filter: light-dark(none, brightness(0) invert(1));
+ }
+
+ &__container.is-loading::after {
+ background-image: none;
+ border: 2px solid #ccc;
+ border-top-color: #333;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
}
&__input {
@@ -12,6 +38,7 @@
border: 1px solid $color--black;
width: 100%;
padding: $gutter;
+ padding-inline-start: ($gutter * 2);
border-radius: $border-radius--m;
}
@@ -20,6 +47,10 @@
}
}
+@keyframes spin {
+ to { transform: translateY(-50%) rotate(360deg); }
+}
+
.search .modal-content {
background-color: light-dark($color--white, $color--off-black);
color: light-dark($color--off-black, $color--light-grey);