From 8ab0519089bc2e3dd5a97910a04e97b0665c1cf4 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Fri, 16 Sep 2022 08:18:32 +0100 Subject: [PATCH] Add search bar component (#7074) Co-authored-by: Tim Jacomb Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com> --- .../resources/hudson/PluginManager/_table.js | 2 - .../hudson/PluginManager/available.jelly | 14 +- .../hudson/PluginManager/installed.jelly | 14 +- .../hudson/PluginManager/table.jelly | 14 +- .../hudson/widgets/HistoryWidget/index.jelly | 7 +- .../resources/lib/layout/search-bar.jelly | 61 +++++ war/src/main/js/keyboard-shortcuts.js | 10 + war/src/main/js/plugin-manager-ui.js | 10 +- war/src/main/less/form/search-bar.less | 209 ++++++++++++++++++ war/src/main/less/form/search.less | 137 ------------ war/src/main/less/pages/plugin-manager.less | 152 ------------- war/src/main/less/styles.less | 2 +- .../images/symbols/search-shortcut.svg | 6 + 13 files changed, 305 insertions(+), 333 deletions(-) create mode 100644 core/src/main/resources/lib/layout/search-bar.jelly create mode 100644 war/src/main/less/form/search-bar.less delete mode 100644 war/src/main/less/form/search.less create mode 100644 war/src/main/resources/images/symbols/search-shortcut.svg diff --git a/core/src/main/resources/hudson/PluginManager/_table.js b/core/src/main/resources/hudson/PluginManager/_table.js index be0498fd6c91..4ce767a27931 100644 --- a/core/src/main/resources/hudson/PluginManager/_table.js +++ b/core/src/main/resources/hudson/PluginManager/_table.js @@ -476,8 +476,6 @@ Behaviour.specify("#filter-box", "_table", 0, function (e) { })(); Element.observe(window, "load", function () { - document.getElementById("filter-box").focus(); - const compatibleCheckbox = document.querySelector( "[data-select='compatible']" ); diff --git a/core/src/main/resources/hudson/PluginManager/available.jelly b/core/src/main/resources/hudson/PluginManager/available.jelly index 6b7d57366621..73da72de07dc 100644 --- a/core/src/main/resources/hudson/PluginManager/available.jelly +++ b/core/src/main/resources/hudson/PluginManager/available.jelly @@ -36,15 +36,11 @@ THE SOFTWARE.
- +
diff --git a/core/src/main/resources/hudson/PluginManager/installed.jelly b/core/src/main/resources/hudson/PluginManager/installed.jelly index 9db786164d43..24eb082b30f3 100644 --- a/core/src/main/resources/hudson/PluginManager/installed.jelly +++ b/core/src/main/resources/hudson/PluginManager/installed.jelly @@ -37,15 +37,11 @@ THE SOFTWARE.
- +
diff --git a/core/src/main/resources/hudson/PluginManager/table.jelly b/core/src/main/resources/hudson/PluginManager/table.jelly index eb60c29f55fa..bc3133389635 100644 --- a/core/src/main/resources/hudson/PluginManager/table.jelly +++ b/core/src/main/resources/hudson/PluginManager/table.jelly @@ -45,15 +45,11 @@ THE SOFTWARE.
- +
diff --git a/core/src/main/resources/hudson/widgets/HistoryWidget/index.jelly b/core/src/main/resources/hudson/widgets/HistoryWidget/index.jelly index 2b41fed5ef7b..f36fa46e094c 100644 --- a/core/src/main/resources/hudson/widgets/HistoryWidget/index.jelly +++ b/core/src/main/resources/hudson/widgets/HistoryWidget/index.jelly @@ -79,12 +79,7 @@ THE SOFTWARE. - +
${%No builds}
diff --git a/core/src/main/resources/lib/layout/search-bar.jelly b/core/src/main/resources/lib/layout/search-bar.jelly new file mode 100644 index 000000000000..27617dafaa04 --- /dev/null +++ b/core/src/main/resources/lib/layout/search-bar.jelly @@ -0,0 +1,61 @@ + + + + + + + + + + + + If false the default keyboard shortcut for the input is disabled. Defaults to true. + + Creates a search input + + @since TODO + + + + diff --git a/war/src/main/js/keyboard-shortcuts.js b/war/src/main/js/keyboard-shortcuts.js index f634a9976eee..3a10f4938b55 100644 --- a/war/src/main/js/keyboard-shortcuts.js +++ b/war/src/main/js/keyboard-shortcuts.js @@ -12,6 +12,16 @@ window.addEventListener("load", () => { // Returning false stops the event and prevents default browser events return false; }); + + const pageSearchBar = document.querySelectorAll(".jenkins-search__input"); + if (pageSearchBar.length === 1) { + hotkeys("/", () => { + pageSearchBar[0].focus(); + + // Returning false stops the event and prevents default browser events + return false; + }); + } }); /** diff --git a/war/src/main/js/plugin-manager-ui.js b/war/src/main/js/plugin-manager-ui.js index a69d47d4f539..fb4c67d84001 100644 --- a/war/src/main/js/plugin-manager-ui.js +++ b/war/src/main/js/plugin-manager-ui.js @@ -15,9 +15,7 @@ function applyFilter(searchQuery) { var selectedPlugins = []; var filterInput = document.getElementById("filter-box"); - filterInput.parentElement.classList.remove( - "app-plugin-manager__search--loading" - ); + filterInput.parentElement.classList.remove("jenkins-search--loading"); function clearOldResults() { if (!admin) { @@ -62,13 +60,9 @@ document.addEventListener("DOMContentLoaded", function () { var filterInput = document.getElementById("filter-box"); filterInput.addEventListener("input", function (e) { debouncedFilter(e); - filterInput.parentElement.classList.add( - "app-plugin-manager__search--loading" - ); + filterInput.parentElement.classList.add("jenkins-search--loading"); }); - filterInput.focus(); - applyFilter(filterInput.value); setTimeout(function () { diff --git a/war/src/main/less/form/search-bar.less b/war/src/main/less/form/search-bar.less new file mode 100644 index 000000000000..488dd46df72c --- /dev/null +++ b/war/src/main/less/form/search-bar.less @@ -0,0 +1,209 @@ +.jenkins-search { + --search-bar-height: 2.1875rem; + + position: relative; + max-width: 420px; + + &__input { + appearance: none; + background: var(--item-background--hover); + border: none; + outline: none; + border-radius: 0.625rem; + width: 100%; + margin: 0; + padding: 0 0.5rem 0 var(--search-bar-height); + line-height: 1; + box-shadow: 0 0 0 2px transparent, 0 0 0 12px transparent; + transition: var(--standard-transition); + font-weight: 500; + height: var(--search-bar-height); + + &::placeholder { + color: var(--text-color-secondary); + } + + // Safari adds unwanted padding - let's remove it + &::-webkit-search-decoration { + -webkit-appearance: none; + } + + &::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 45%; + min-height: 1.1rem; + aspect-ratio: 1; + margin-right: 0.2rem; + background: currentColor; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm75.31 260.69a16 16 0 11-22.62 22.62L256 278.63l-52.69 52.68a16 16 0 01-22.62-22.62L233.37 256l-52.68-52.69a16 16 0 0122.62-22.62L256 233.37l52.69-52.68a16 16 0 0122.62 22.62L278.63 256z'/%3E%3C/svg%3E"); + mask-size: contain; + mask-repeat: no-repeat; + opacity: 0; + pointer-events: none; + transform: scale(0.8); + transition: var(--standard-transition); + cursor: pointer; + + &:hover { + opacity: 0.75 !important; + } + + &:active { + opacity: 1 !important; + } + } + + &:hover { + background: var(--item-background--active); + } + + &:active, + &:focus { + outline: none; + background: var(--item-background--active); + box-shadow: 0 0 0 2px var(--focus-input-border), + 0 0 0 7px var(--focus-input-glow); + + &::-webkit-search-cancel-button { + opacity: 0.5; + pointer-events: all; + transform: scale(1); + } + } + } + + &__icon { + position: absolute; + aspect-ratio: 1 / 1; + top: 0; + left: 0; + bottom: 0; + display: grid; + pointer-events: none; + + svg { + width: 45%; + height: 45%; + max-width: 1.1rem; + max-height: 1.1rem; + grid-column-start: 1; + grid-row-start: 1; + justify-self: center; + align-self: center; + transition: var(--standard-transition); + } + + &::before, + &::after { + content: ""; + width: 0; + height: 0; + max-width: 1.1rem; + max-height: 1.1rem; + border: 0.125rem solid currentColor; + border-radius: 100%; + transition: var(--standard-transition); + grid-column-start: 1; + grid-row-start: 1; + justify-self: center; + align-self: center; + opacity: 0; + } + + &::after { + clip-path: inset(0 0 50% 50%); + transition: var(--standard-transition); + animation: loading-spinner 1s infinite linear; + + @media (prefers-reduced-motion) { + animation-duration: 2s; + } + } + } + + &__shortcut { + position: absolute; + aspect-ratio: 1 / 1; + top: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + transition: 0.25s ease; + text-align: center; + font-size: 0.8em; + font-weight: 500; + line-height: 1; + color: var(--text-color-secondary); + + &::after { + content: ""; + position: absolute; + top: calc(50% - 1em); + right: calc(50% - 1em); + width: 2em; + height: 2em; + border: 0.1rem solid var(--item-background--active); + border-radius: 0.3rem; + } + + svg { + width: 1.2em; + height: 1.2em; + } + } + + &--loading { + .jenkins-search__icon { + svg { + opacity: 0; + transform: scale(0); + } + + &::before { + opacity: 0.2; + width: 45%; + height: 45%; + } + + &::after { + opacity: 1; + width: 45%; + height: 45%; + } + } + } + + &--app-bar { + --search-bar-height: 3rem; + + max-width: 50vw; + + &::before { + content: ""; + position: absolute; + inset: 0; + border-radius: 10px; + z-index: -1; + backdrop-filter: blur(20px); + box-shadow: 0 0 var(--section-padding) var(--background); + + @supports not (backdrop-filter: blur(20px)) { + background: var(--background); + } + } + } + + &:focus-within { + .jenkins-search__icon { + fill: var(--focus-input-border); + } + + .jenkins-search__shortcut { + opacity: 0; + transform: scale(0.9); + pointer-events: none; + } + } +} diff --git a/war/src/main/less/form/search.less b/war/src/main/less/form/search.less deleted file mode 100644 index dece55b533bf..000000000000 --- a/war/src/main/less/form/search.less +++ /dev/null @@ -1,137 +0,0 @@ -.jenkins-search { - position: relative; - max-width: 420px; - - &__input { - appearance: none; - display: block; - background: var(--input-color); - border: 2px solid var(--input-border); - border-radius: var(--form-input-border-radius); - width: 100%; - margin: 0; - padding: 0 4px 0 29px; - line-height: 30px; - box-shadow: var(--form-input-glow); - transition: var(--standard-transition); - - // Safari adds unwanted padding - let's remove it - &::-webkit-search-decoration { - -webkit-appearance: none; - } - - &::-webkit-search-cancel-button { - -webkit-appearance: none; - height: 1rem; - width: 1rem; - margin-right: 2px; - background: currentColor; - mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm75.31 260.69a16 16 0 11-22.62 22.62L256 278.63l-52.69 52.68a16 16 0 01-22.62-22.62L233.37 256l-52.68-52.69a16 16 0 0122.62-22.62L256 233.37l52.69-52.68a16 16 0 0122.62 22.62L278.63 256z'/%3E%3C/svg%3E"); - mask-size: contain; - mask-repeat: no-repeat; - opacity: 0; - pointer-events: none; - transform: scale(0.8); - transition: var(--standard-transition); - cursor: pointer; - - &:hover { - opacity: 0.75 !important; - } - - &:active { - opacity: 1 !important; - } - } - - &:hover { - border-color: var(--input-border-hover); - } - - &:active, - &:focus { - outline: none; - border-color: var(--focus-input-border); - box-shadow: var(--form-input-glow--focus); - - &::-webkit-search-cancel-button { - opacity: 0.5; - pointer-events: all; - transform: scale(1); - } - } - } - - &__icon { - position: absolute; - top: 9px; - left: 9px; - width: 16px; - height: 16px; - fill: var(--input-border-hover); - transition: var(--standard-transition); - } - - &::before { - content: ""; - position: absolute; - top: 17px; - left: 17px; - width: 0; - height: 0; - color: inherit; - border: 2px solid currentColor; - border-radius: 100%; - opacity: 0; - transition: var(--standard-transition); - } - - &::after { - content: ""; - position: absolute; - top: 17px; - left: 17px; - width: 0; - height: 0; - color: inherit; - border: 2px solid currentColor; - border-radius: 100%; - clip-path: inset(0 0 50% 50%); - opacity: 0; - transition: var(--standard-transition); - animation: loading-spinner 1s infinite linear; - - @media (prefers-reduced-motion) { - animation-duration: 2s; - } - } - - &--loading { - .jenkins-search__icon { - opacity: 0; - transform: scale(0); - } - - &::before { - opacity: 0.2; - top: 9px; - left: 9px; - width: 16px; - height: 16px; - } - - &::after { - opacity: 1; - top: 9px; - left: 9px; - width: 16px; - height: 16px; - } - } - - &:focus-within { - .jenkins-search__icon { - fill: var(--focus-input-border); - } - } -} diff --git a/war/src/main/less/pages/plugin-manager.less b/war/src/main/less/pages/plugin-manager.less index 0f798de8a630..6a80235b12d2 100644 --- a/war/src/main/less/pages/plugin-manager.less +++ b/war/src/main/less/pages/plugin-manager.less @@ -127,155 +127,3 @@ margin-bottom: 0.1rem !important; } } - -.app-plugin-manager__search { - position: relative; - display: grid; - grid-template-columns: auto 1fr; - align-items: center; - width: 50vw; - z-index: 1; - padding: 0 1rem; - gap: 0.5rem 0.75rem; - - &::before, - &::after { - content: ""; - position: absolute; - inset: 0; - border-radius: 10px; - z-index: -1; - } - - &::before { - backdrop-filter: blur(20px); - box-shadow: 0 0 var(--section-padding) var(--background); - - @supports not (backdrop-filter: blur(20px)) { - background: var(--background); - } - } - - &::after { - background: var(--item-background--hover); - box-shadow: 0 0 0 10px transparent; - transition: var(--standard-transition); - opacity: 0.75; - } - - svg { - width: 1.1rem; - height: 1.1rem; - } - - &__icon { - position: relative; - display: flex; - align-items: center; - justify-content: center; - - &::before { - content: ""; - position: absolute; - inset: 1rem; - color: inherit; - border: 2px solid currentColor; - border-radius: 100%; - opacity: 0; - transition: var(--standard-transition); - } - - &::after { - content: ""; - position: absolute; - inset: 1rem; - color: inherit; - border: 2px solid currentColor; - border-radius: 100%; - clip-path: inset(0 0 50% 50%); - opacity: 0; - transition: var(--standard-transition); - animation: loading-spinner 1s infinite linear; - - @media (prefers-reduced-motion) { - animation-duration: 2s; - } - } - } - - &--loading { - .app-plugin-manager__search__icon { - svg { - opacity: 0; - transform: scale(0); - } - - &::before { - opacity: 0.2; - inset: 0; - } - - &::after { - opacity: 1; - inset: 0; - } - } - } - - input { - background: transparent; - border: none; - outline: none; - font-size: 0.9rem; - font-weight: 500; - padding: 0.75rem 0; - - &::placeholder { - color: var(--text-color-secondary); - } - - &::-webkit-search-cancel-button { - -webkit-appearance: none; - height: 1.2rem; - width: 1.2rem; - background: currentColor; - mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm75.31 260.69a16 16 0 11-22.62 22.62L256 278.63l-52.69 52.68a16 16 0 01-22.62-22.62L233.37 256l-52.68-52.69a16 16 0 0122.62-22.62L256 233.37l52.69-52.68a16 16 0 0122.62 22.62L278.63 256z'/%3E%3C/svg%3E"); - mask-size: contain; - mask-repeat: no-repeat; - opacity: 0; - pointer-events: none; - transform: scale(0.8); - transition: var(--standard-transition); - cursor: pointer; - - &:hover { - opacity: 0.75 !important; - } - - &:active { - opacity: 1 !important; - } - } - - &:focus { - &::-webkit-search-cancel-button { - opacity: 0.5; - pointer-events: all; - transform: scale(1); - } - } - } - - &:hover { - &::after { - background: var(--item-background--active); - } - } - - &:focus-within { - &::after { - background: var(--item-background--active); - box-shadow: 0 0 0 5px var(--item-box-shadow--focus); - } - } -} diff --git a/war/src/main/less/styles.less b/war/src/main/less/styles.less index fc2f46bb521f..bcaa4753e85f 100644 --- a/war/src/main/less/styles.less +++ b/war/src/main/less/styles.less @@ -12,7 +12,7 @@ @import "./form/layout"; @import "./form/radio"; @import "./form/reorderable-list"; -@import "./form/search"; +@import "./form/search-bar"; @import "./form/select"; @import "./form/toggle-switch"; @import "./form/validation"; diff --git a/war/src/main/resources/images/symbols/search-shortcut.svg b/war/src/main/resources/images/symbols/search-shortcut.svg new file mode 100644 index 000000000000..46e64a2e1338 --- /dev/null +++ b/war/src/main/resources/images/symbols/search-shortcut.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file