diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index b1035bad..4fc387de 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -19,6 +19,7 @@ @import "partials/_record"; @import "partials/_search"; @import "partials/_shared"; +@import "partials/_source_tabs"; @import "partials/_results"; @import "partials/_loading_spinner"; @import "partials/_suggestion-panel"; \ No newline at end of file diff --git a/app/assets/stylesheets/partials/_search.scss b/app/assets/stylesheets/partials/_search.scss index 7897fc4e..105bfd9b 100644 --- a/app/assets/stylesheets/partials/_search.scss +++ b/app/assets/stylesheets/partials/_search.scss @@ -214,52 +214,6 @@ color: #fff; } -/* =========== */ -/* New Tab Bar */ -/* =========== */ - -#tabs { - margin-top: 0; - - ul { - list-style-type: none; - padding: 0; - margin: 0; - display: flex; - gap: 4px; - } - - a { - padding: 12px 20px 16px; - background-color: transparent; - display: inline-block; - border: 2px solid transparent; - border-bottom: 0; - - @include searchUnderlinedLinks; - - &:hover { - border-color: $color-gray-700; - background-color: $color-gray-900; - color: $color-text-oncolor; - } - - &.active { - background-color: $color-white; - color: $color-text-primary; - text-decoration: none; - - &:hover { - color: $color-text-primary; - border-color: transparent; - } - } - - - } - -} - /* ============== */ /* Search Actions */ /* ============== */ diff --git a/app/assets/stylesheets/partials/_source_tabs.scss b/app/assets/stylesheets/partials/_source_tabs.scss new file mode 100644 index 00000000..bcd049d7 --- /dev/null +++ b/app/assets/stylesheets/partials/_source_tabs.scss @@ -0,0 +1,103 @@ +/* ============================ */ +/* Source Tab Bar in USE Search */ +/* ============================ */ + +#tabs { + margin-top: 0; + position: relative; + + // Graceful degradation when JS is not enabled + &:not(.has-js) { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + // Display styles for the main tab bar + ul.primary { + list-style-type: none; + padding: 0; + margin: 0; + display: flex; + gap: 4px; + } + + li { + + &.--hidden { + display: none; + } + + } + + a, button { + padding: 12px 20px 16px; + background-color: transparent; + display: inline-block; + border: 2px solid transparent; + border-bottom: 0; + white-space: nowrap; + + @include searchUnderlinedLinks; + + &:hover { + border-color: $color-gray-700; + background-color: $color-gray-900; + color: $color-text-oncolor; + } + + &.active { + background-color: $color-white; + color: $color-text-primary; + text-decoration: none; + + &:hover { + color: $color-text-primary; + border-color: transparent; + } + } + + } + + // Swap the icon to chevron-up when menu is open + button[aria-expanded="true"] { + + i::before { + content: '\f077'; + } + + } + + // Display styles for the dropdown menu under the "More" button + ul.-secondary { + width: 100%; + display: none; + position: absolute; + top: 100%; + right: 0; + list-style-type: none; + padding: 0; + margin: 0; + z-index: 999; + + li { + width: 100%; + + a { + background-color: $color-gray-950; + width: 100%; + + &:hover { + border-color: $color-gray-950; + background-color: $color-gray-900; + } + } + + } + + } + + &.--show-secondary .-secondary { + display: block; + } + +} \ No newline at end of file diff --git a/app/javascript/application.js b/app/javascript/application.js index b3e67a5a..ad1461bf 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -2,6 +2,7 @@ import "@hotwired/turbo-rails" import "controllers" import "loading_spinner" +import "source_tabs" // Show the progress bar after 200 milliseconds, not the default 500 Turbo.config.drive.progressBarDelay = 200; \ No newline at end of file diff --git a/app/javascript/loading_spinner.js b/app/javascript/loading_spinner.js index 2626047e..0d2655fb 100644 --- a/app/javascript/loading_spinner.js +++ b/app/javascript/loading_spinner.js @@ -68,6 +68,10 @@ document.addEventListener('click', function(event) { // Handle tab clicks if (clickedElement.closest('.tab-navigation')) { + + // If the element is NOT a link, don't show the spinner + if (clickedElement.nodeName !== "A") { return; } + const clickedParams = new URLSearchParams(clickedElement.search); const newTab = clickedParams.get('tab'); diff --git a/app/javascript/source_tabs.js b/app/javascript/source_tabs.js new file mode 100644 index 00000000..83e0ba72 --- /dev/null +++ b/app/javascript/source_tabs.js @@ -0,0 +1,89 @@ +// =========================================================================== +// RESPONSIVE TAB BAR LOGIC WITH GRACEFUL DEGRADATION +// Source: https://css-tricks.com/container-adapting-tabs-with-more-button/ +// =========================================================================== + +// Store references to relevant selectors +const container = document.querySelector('#tabs') +const primary = container.querySelector('.primary') +const primaryItems = container.querySelectorAll('.primary > li:not(.-more)') + +// Add a class to turn off graceful degradation style +container.classList.add('has-js') + +// insert "more" button and duplicate the original tab bar items +primary.insertAdjacentHTML('beforeend', ` +
  • + + +
  • +`) +const secondary = container.querySelector('.-secondary') +const secondaryItems = secondary.querySelectorAll('li') +const allItems = container.querySelectorAll('li') +const moreLi = primary.querySelector('.-more') +const moreBtn = moreLi.querySelector('button') + +// When the more button is clicked, toggle classes to indicate the secondary menu is open +moreBtn.addEventListener('click', (e) => { + e.preventDefault() + container.classList.toggle('--show-secondary') + moreBtn.setAttribute('aria-expanded', container.classList.contains('--show-secondary')) +}) + +// adapt tabs +const doAdapt = () => { + + // reveal all items for the calculation + allItems.forEach((item) => { + item.classList.remove('--hidden') + }) + + // hide items that won't fit in the Primary tab bar + let stopWidth = moreBtn.offsetWidth + let hiddenItems = [] + const primaryWidth = primary.offsetWidth + primaryItems.forEach((item, i) => { + if(primaryWidth >= stopWidth + item.offsetWidth) { + stopWidth += item.offsetWidth + } else { + item.classList.add('--hidden') + hiddenItems.push(i) + } + }) + + // toggle the visibility of More button and items in Secondary menu + if(!hiddenItems.length) { + moreLi.classList.add('--hidden') + container.classList.remove('--show-secondary') + moreBtn.setAttribute('aria-expanded', false) + } + else { + secondaryItems.forEach((item, i) => { + if(!hiddenItems.includes(i)) { + item.classList.add('--hidden') + } + }) + } +} + +// Adapt the tabs to fit the viewport +doAdapt() // immediately on load +window.addEventListener('resize', doAdapt) // on window resize + +// hide Secondary menu on the outside click +document.addEventListener('click', (e) => { + let el = e.target + while(el) { + if(el === moreBtn) { + return; + } + el = el.parentNode + } + container.classList.remove('--show-secondary') + moreBtn.setAttribute('aria-expanded', false) +}) \ No newline at end of file diff --git a/app/views/search/_source_tabs.html.erb b/app/views/search/_source_tabs.html.erb index a9482de4..52033a61 100644 --- a/app/views/search/_source_tabs.html.erb +++ b/app/views/search/_source_tabs.html.erb @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/config/importmap.rb b/config/importmap.rb index 672f444c..cbd365c1 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -2,6 +2,7 @@ pin "application", preload: true pin "loading_spinner", preload: true +pin "source_tabs", preload: true pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true