From 5f0dc43227618f293a56ad927af3d9c51a5acee6 Mon Sep 17 00:00:00 2001 From: Raghad Dahi Date: Thu, 7 May 2026 17:55:01 +0300 Subject: [PATCH 1/6] Focus input field after clicking on the search icon --- apps/frontend/static_src/js/main.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/frontend/static_src/js/main.js b/apps/frontend/static_src/js/main.js index 6382c6bb..6a7c3800 100644 --- a/apps/frontend/static_src/js/main.js +++ b/apps/frontend/static_src/js/main.js @@ -11,6 +11,7 @@ handleFeedback(); const searchInput = document.querySelector('[data-search-input]'); const searchIconButton = document.querySelector('[data-search-icon-button]'); +const searchModal = document.getElementById('search-modal'); const removeExistingChildren = (parent) => { // eslint-disable-next-line no-param-reassign @@ -89,6 +90,10 @@ searchIconButton.addEventListener('click', () => { removeExistingChildren(resultsCountContainer); }); +searchModal.addEventListener('shown.bs.modal', () => { + searchInput.focus(); +}); + function initComponent(ComponentClass) { const items = document.querySelectorAll(ComponentClass.selector()); items.forEach((item) => new ComponentClass(item)); From b52c1d2f593e868ff2c59d10c65f8283ecce5c29 Mon Sep 17 00:00:00 2001 From: Raghad Dahi Date: Thu, 7 May 2026 19:15:35 +0300 Subject: [PATCH 2/6] Clear search results and input on modal close --- apps/frontend/static_src/js/main.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/frontend/static_src/js/main.js b/apps/frontend/static_src/js/main.js index 6a7c3800..c5e1f300 100644 --- a/apps/frontend/static_src/js/main.js +++ b/apps/frontend/static_src/js/main.js @@ -12,6 +12,10 @@ 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 @@ -19,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); @@ -80,20 +79,16 @@ const onSearchInputChange = async (event) => { }; 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); }); -searchModal.addEventListener('shown.bs.modal', () => { - searchInput.focus(); -}); - function initComponent(ComponentClass) { const items = document.querySelectorAll(ComponentClass.selector()); items.forEach((item) => new ComponentClass(item)); From 24b6ef2f199a0a81a02ce4f4e71389cae2a7d3d2 Mon Sep 17 00:00:00 2001 From: Raghad Dahi Date: Sun, 10 May 2026 21:09:02 +0300 Subject: [PATCH 3/6] Add magnifier icon to search bar --- apps/frontend/static_src/images/search.svg | 2 +- .../static_src/scss/components/search.scss | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) 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/scss/components/search.scss b/apps/frontend/static_src/scss/components/search.scss index ed01a8b4..0f914b20 100644 --- a/apps/frontend/static_src/scss/components/search.scss +++ b/apps/frontend/static_src/scss/components/search.scss @@ -5,6 +5,24 @@ display: flex; flex-direction: row; justify-content: stretch; + position: relative; + } + + &__container::after { + content: ''; + display: block; + position: absolute; + inset-inline-start:($gutter * 1.7); + 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)); } &__input { @@ -12,6 +30,7 @@ border: 1px solid $color--black; width: 100%; padding: $gutter; + padding-inline-start: ($gutter * 2); border-radius: $border-radius--m; } From 0ba99673f21a9073122a9926d5a5fa57a788affd Mon Sep 17 00:00:00 2001 From: Raghad Dahi Date: Mon, 11 May 2026 01:41:20 +0300 Subject: [PATCH 4/6] feat:add search loading indicator --- apps/frontend/static_src/js/main.js | 8 ++++++++ .../static_src/scss/components/search.scss | 14 +++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/frontend/static_src/js/main.js b/apps/frontend/static_src/js/main.js index c5e1f300..6729106e 100644 --- a/apps/frontend/static_src/js/main.js +++ b/apps/frontend/static_src/js/main.js @@ -60,6 +60,11 @@ const injectResultsInHTML = (results) => { 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,6 +80,9 @@ 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)); diff --git a/apps/frontend/static_src/scss/components/search.scss b/apps/frontend/static_src/scss/components/search.scss index 0f914b20..1c5b4116 100644 --- a/apps/frontend/static_src/scss/components/search.scss +++ b/apps/frontend/static_src/scss/components/search.scss @@ -12,7 +12,7 @@ content: ''; display: block; position: absolute; - inset-inline-start:($gutter * 1.7); + inset-inline-start:($gutter * 1.6); inset-block-start: 50%; transform: translateY(-50%); width: 21px; @@ -25,6 +25,14 @@ 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 { @include fs(m); border: 1px solid $color--black; @@ -39,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); From f955069a6c990ad4be4ac8c628302b9712965892 Mon Sep 17 00:00:00 2001 From: Raghad Dahi Date: Mon, 11 May 2026 15:41:45 +0300 Subject: [PATCH 5/6] feat: animate reveal of the results --- apps/frontend/static_src/js/main.js | 8 +++++++- .../static_src/scss/components/autocomplete.scss | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/frontend/static_src/js/main.js b/apps/frontend/static_src/js/main.js index 6729106e..eaa2e9e2 100644 --- a/apps/frontend/static_src/js/main.js +++ b/apps/frontend/static_src/js/main.js @@ -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,6 +55,12 @@ 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); + }); }); }; diff --git a/apps/frontend/static_src/scss/components/autocomplete.scss b/apps/frontend/static_src/scss/components/autocomplete.scss index b076d096..5e479e5e 100644 --- a/apps/frontend/static_src/scss/components/autocomplete.scss +++ b/apps/frontend/static_src/scss/components/autocomplete.scss @@ -20,7 +20,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 { From fc3c9b177238724bf2ef5e04bde48d7b0f660c3e Mon Sep 17 00:00:00 2001 From: Raghad Dahi Date: Mon, 11 May 2026 17:46:03 +0300 Subject: [PATCH 6/6] Move results count to its own line --- apps/frontend/static_src/scss/components/autocomplete.scss | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/frontend/static_src/scss/components/autocomplete.scss b/apps/frontend/static_src/scss/components/autocomplete.scss index 5e479e5e..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 {