From 930def06c0b5a5d967ae91a315c5d01c1dda80a7 Mon Sep 17 00:00:00 2001 From: Andrew Haust Date: Wed, 21 Feb 2024 20:55:10 -0500 Subject: [PATCH] Bring search bar into view on desktop This commit changes how the search bar is brought into view on desktop as brought up in #1860: when using one of the keyboard shortcuts to focus the input, the entire page jumps to the top making you lose your scroll position. On mobile, technically smaller screens, this is remedied by causing the input to slide into view on scroll up, but I don't believe this is a desirable solution for desktop. The following changes are introduced: * Using one of the keyboard shortcuts will focus the search input causing it to stick to the top. * It will continue to stick to the top so long as it is focused allowing you to scroll with it open. * The slide-on-scroll effect has been changed to only fire on touch-enabled devices as opposed to just smaller screens. This is allows us to make the hexdocs window very small and still use keyboard shortcuts--Useful on laptops. Closes #1860 --- assets/css/layout.css | 24 ++++++- assets/css/search-bar.css | 24 +++++-- assets/js/helpers.js | 13 +++- assets/js/search-bar.js | 68 ++++++++++++------- .../html/templates/sidebar_template.eex | 1 - 5 files changed, 96 insertions(+), 34 deletions(-) diff --git a/assets/css/layout.css b/assets/css/layout.css index 826bfa0b0..0b63b41fa 100644 --- a/assets/css/layout.css +++ b/assets/css/layout.css @@ -122,7 +122,28 @@ body.sidebar-closed .content { left: 0; } -@media screen and (max-width: 768px) { +@media screen and (hover: hover) and (max-width: 768px) { + body.sidebar-opening .content { + left: 0; + width: 100%; + } + + body.sidebar-closed .sidebar-button { + position: absolute; + top: 14px; + } + + body.sidebar-closed .sidebar-button.fixed { + position: fixed; + padding: 16px 12px 18px 19px; + } + + body.sidebar-closed .sidebar-button.fixed-top { + position: fixed; + } +} + +@media screen and (hover: none) { .content, body.sidebar-opening .content { left: 0; @@ -137,6 +158,7 @@ body.sidebar-closed .content { body.sidebar-closed .sidebar-button { position: absolute; + top: 14px; } .sm-fixed { diff --git a/assets/css/search-bar.css b/assets/css/search-bar.css index 30db46eb5..872bf1ac7 100644 --- a/assets/css/search-bar.css +++ b/assets/css/search-bar.css @@ -1,6 +1,22 @@ .top-search { + position: relative; + top: 0; + z-index: 101; margin-top: 10px; background-color: var(--background); + padding: 0.8rem 0; +} + +@media (max-width: 480px) or ((min-width: 481px) and (max-width: 768px)) { + .top-search { + padding: 0.8rem 1.8rem 0.8rem 3rem; + margin-left: -1rem; + margin-right: -1rem; + } +} + +.top-search.sticky { + position: sticky; } .search-settings { @@ -114,13 +130,7 @@ color: var(--iconAction); } -@media (max-width: 480px) or ((min-width: 481px) and (max-width: 768px)) { - .search-bar { - margin-left: 0px; - } -} - -@media (max-width: 768px) { +@media (hover: none) { .top-search { margin-top: 0; position: absolute; diff --git a/assets/js/helpers.js b/assets/js/helpers.js index a7155d6c1..99a18eedc 100644 --- a/assets/js/helpers.js +++ b/assets/js/helpers.js @@ -161,10 +161,21 @@ export function getProjectNameAndVersion () { } /** - * Return `true` if the client's OS is MacOS + * Return `true` if the client's OS is MacOS. * * @return {Boolean} */ export function isMacOS () { return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) } + +/** + * Return `true` if the client's device is touch-enabled. + * + * @return {Boolean} + */ +export function isTouchDevice () { + return (('ontouchstart' in window) || + (navigator.maxTouchPoints > 0) || + (navigator.msMaxTouchPoints > 0)) +} diff --git a/assets/js/search-bar.js b/assets/js/search-bar.js index a987eb8ef..961f7ae7d 100644 --- a/assets/js/search-bar.js +++ b/assets/js/search-bar.js @@ -7,10 +7,11 @@ import { AUTOCOMPLETE_CONTAINER_SELECTOR, AUTOCOMPLETE_SUGGESTION_SELECTOR } from './autocomplete/autocomplete-list' -import { isMacOS, qs } from './helpers' +import { isMacOS, isTouchDevice, qs } from './helpers' const SEARCH_INPUT_SELECTOR = 'form.search-bar input' const SEARCH_CLOSE_BUTTON_SELECTOR = 'form.search-bar .search-close-button' +const TOP_SEARCH_SELECTOR = '.top-search' /** * Initializes the sidebar search box. @@ -34,6 +35,16 @@ export function setSearchInputValue (value) { */ export function focusSearchInput () { const searchInput = qs(SEARCH_INPUT_SELECTOR) + + if (!isTouchDevice()) { + qs(TOP_SEARCH_SELECTOR).classList.add('sticky') + if (window.scrollY === 0) { + qs('.sidebar-button').classList.add('fixed-top') + } else { + qs('.sidebar-button').classList.add('fixed') + } + } + searchInput.focus() } @@ -139,38 +150,47 @@ function hideAutocomplete () { } let lastScrollTop = window.scrollY -const topSearch = document.querySelector('.top-search') +const topSearch = document.querySelector(TOP_SEARCH_SELECTOR) const sidebarMenu = document.getElementById('sidebar-menu') const backgroundLayer = document.querySelector('.background-layer') const scrollThreshold = 70 // Set a threshold for scroll, adjust as needed +const searchInput = qs(SEARCH_INPUT_SELECTOR) +const sidebarButton = qs('.sidebar-button') window.addEventListener('scroll', function () { const currentScroll = window.scrollY - // Add 'fixed' class when not at the top - if (currentScroll > scrollThreshold * 2) { - topSearch.classList.add('sm-fixed') - sidebarMenu.classList.add('sm-fixed') - backgroundLayer.classList.add('sm-fixed') - } + if (isTouchDevice()) { + // Add 'fixed' class when not at the top + if (currentScroll > scrollThreshold * 2) { + topSearch.classList.add('sm-fixed') + sidebarMenu.classList.add('sm-fixed') + backgroundLayer.classList.add('sm-fixed') + } - if (currentScroll === 0) { - // Remove 'fixed' class when at the top - topSearch.classList.remove('sm-fixed') - sidebarMenu.classList.remove('sm-fixed') - backgroundLayer.classList.remove('sm-fixed') - } + if (currentScroll === 0) { + // Remove 'fixed' class when at the top + topSearch.classList.remove('sm-fixed') + sidebarMenu.classList.remove('sm-fixed') + backgroundLayer.classList.remove('sm-fixed') + } - if (currentScroll > lastScrollTop && currentScroll > scrollThreshold) { - // Scrolling down and past the threshold - topSearch.classList.add('sm-hidden') - sidebarMenu.classList.add('sm-hidden') - backgroundLayer.classList.add('sm-hidden') - } else { - // Scrolling up or at the top of the page - topSearch.classList.remove('sm-hidden') - sidebarMenu.classList.remove('sm-hidden') - backgroundLayer.classList.remove('sm-hidden') + if (currentScroll > lastScrollTop && currentScroll > scrollThreshold) { + // Scrolling down and past the threshold + topSearch.classList.add('sm-hidden') + sidebarMenu.classList.add('sm-hidden') + backgroundLayer.classList.add('sm-hidden') + } else { + // Scrolling up or at the top of the page + topSearch.classList.remove('sm-hidden') + sidebarMenu.classList.remove('sm-hidden') + backgroundLayer.classList.remove('sm-hidden') + } + } else if (currentScroll !== lastScrollTop) { + topSearch.classList.remove('sticky') + sidebarButton.classList.remove('fixed') + sidebarButton.classList.remove('fixed-top') + searchInput.blur() } lastScrollTop = currentScroll <= 0 ? 0 : currentScroll diff --git a/lib/ex_doc/formatter/html/templates/sidebar_template.eex b/lib/ex_doc/formatter/html/templates/sidebar_template.eex index 35e6f82de..21dd8e5fe 100644 --- a/lib/ex_doc/formatter/html/templates/sidebar_template.eex +++ b/lib/ex_doc/formatter/html/templates/sidebar_template.eex @@ -91,5 +91,4 @@ Settings -