diff --git a/resources/common/components/MenuTabs.less b/resources/common/components/MenuTabs.less index 9dcdcc8..dc263cb 100644 --- a/resources/common/components/MenuTabs.less +++ b/resources/common/components/MenuTabs.less @@ -50,8 +50,8 @@ height: unit( 40 / @font-size-tabs / @font-size-browser, em ); position: relative; padding-top: 1.25em; - padding-left: 8px; - padding-right: 8px; + padding-left: @padding-horizontal-tabs; + padding-right: @padding-horizontal-tabs; font-size: @font-size-tabs; cursor: pointer; } diff --git a/resources/common/variables.less b/resources/common/variables.less index d64e26f..1942641 100644 --- a/resources/common/variables.less +++ b/resources/common/variables.less @@ -113,11 +113,15 @@ // Tabs @font-size-tabs: unit( 13 / @font-size-browser, em ); // Equals `0.8125em`. +@padding-horizontal-tabs: 8px; // Search @min-width-search-button: 28px; @width-search-button: unit( 28 / @font-size-browser / @font-size-search-input, em ); @font-size-search-input: unit( 13 / @font-size-browser, em ); // Equals `0.8125em`. +// Derived from @spacing-start-typeahead-search-figure + @spacing-end-typeahead-search-figure in WVUI +// https://gerrit.wikimedia.org/g/wvui/+/refs/changes/93/650593/10/src/components/typeahead-search/TypeaheadSearch.vue#645 +@size-search-expand: 24px; // language button @height-lang-button: unit( 32 / @font-size-browser, em ); diff --git a/resources/skins.vector.js/searchToggle.js b/resources/skins.vector.js/searchToggle.js new file mode 100644 index 0000000..015a4c0 --- /dev/null +++ b/resources/skins.vector.js/searchToggle.js @@ -0,0 +1,88 @@ +var + HEADER_SELECTOR = '.mw-header', + SEARCH_TOGGLE_SELECTOR = '.search-toggle', + SEARCH_BOX_ID = 'p-search', + SEARCH_VISIBLE_CLASS = 'vector-header-search-toggled'; + +/** + * Binds event handlers necessary for the searchBox to disappear when the user + * clicks outside the searchBox. + * + * @param {HTMLElement} searchBox + * @param {HTMLElement} header + */ +function bindSearchBoxHandler( searchBox, header ) { + /** + * @param {Event} ev + * @ignore + */ + function clickHandler( ev ) { + if ( + ev.target instanceof HTMLElement && + // Check if the click target was a suggestion link. WVUI clears the + // suggestion elements from the DOM when a suggestion is clicked so we + // can't test if the suggestion is a child of the searchBox. + !$( ev.target ).closest( '.wvui-typeahead-suggestion' ).length && + !searchBox.contains( ev.target ) + ) { + // eslint-disable-next-line mediawiki/class-doc + header.classList.remove( SEARCH_VISIBLE_CLASS ); + + document.removeEventListener( 'click', clickHandler ); + } + } + + document.addEventListener( 'click', clickHandler ); +} + +/** + * Binds event handlers necessary for the searchBox to show when the toggle is + * clicked. + * + * @param {HTMLElement} searchBox + * @param {HTMLElement} header + * @param {HTMLElement} searchToggle + */ +function bindToggleClickHandler( searchBox, header, searchToggle ) { + /** + * @param {Event} ev + * @ignore + */ + function handler( ev ) { + // The toggle is an anchor element. Prevent the browser from navigating away + // from the page when clicked. + ev.preventDefault(); + + bindSearchBoxHandler( searchBox, header ); + + // eslint-disable-next-line mediawiki/class-doc + header.classList.add( SEARCH_VISIBLE_CLASS ); + + // Defer focusing the input to another task in the event loop. At the time + // of this writing, Safari 14.0.3 has trouble changing the visibility of the + // element and focusing the input within the same task. + setTimeout( function () { + var searchInput = /** @type {HTMLInputElement|null} */ ( searchBox.querySelector( 'input[type="search"]' ) ); + + if ( searchInput ) { + searchInput.focus(); + } + } ); + } + + searchToggle.addEventListener( 'click', handler ); +} + +module.exports = function initSearchToggle() { + var + header = /** @type {HTMLElement|null} */ ( document.querySelector( HEADER_SELECTOR ) ), + searchBox = /** @type {HTMLElement|null} */ ( document.getElementById( SEARCH_BOX_ID ) ), + searchToggle = + /** @type {HTMLElement|null} */ ( document.querySelector( SEARCH_TOGGLE_SELECTOR ) ); + + if ( !( searchBox && searchToggle && header ) ) { + return; + } + + bindToggleClickHandler( searchBox, header, searchToggle ); +}; diff --git a/resources/skins.vector.js/skin.js b/resources/skins.vector.js/skin.js index bf133d1..08f01c2 100644 --- a/resources/skins.vector.js/skin.js +++ b/resources/skins.vector.js/skin.js @@ -3,6 +3,7 @@ var collapsibleTabs = require( '../skins.vector.legacy.js/collapsibleTabs.js' ), languageButton = require( './languageButton.js' ), initSearchLoader = require( './searchLoader.js' ).initSearchLoader, dropdownMenus = require( './dropdownMenus.js' ), + searchToggle = require( './searchToggle.js' ), sidebar = require( './sidebar.js' ); /** @@ -69,6 +70,7 @@ function main( window ) { dropdownMenus(); $( vector.init ); initSearchLoader( document ); + searchToggle(); languageButton(); } diff --git a/resources/skins.vector.styles/components/VueEnhancedSearchBox.less b/resources/skins.vector.styles/components/VueEnhancedSearchBox.less index 1435feb..4d2adea 100644 --- a/resources/skins.vector.styles/components/VueEnhancedSearchBox.less +++ b/resources/skins.vector.styles/components/VueEnhancedSearchBox.less @@ -1,3 +1,5 @@ +@import '../../common/variables.less'; + /** * Minimal styling for initial no-JS server-rendered * search form, which gets replaced by WVUI on focus. @@ -47,9 +49,6 @@ // Derived from @size-search-figure in WVUI // https://gerrit.wikimedia.org/r/plugins/gitiles/wvui/+/e32b54f3b8d1118b6a25cdc46b5638d6d048533e/src/themes/wikimedia-ui.less#21 @size-search-figure: unit( 36px / @font-size-browser / @font-size-base, em ); - // Derived from @spacing-start-typeahead-search-figure + @spacing-end-typeahead-search-figure in WVUI - // https://gerrit.wikimedia.org/g/wvui/+/refs/changes/93/650593/10/src/components/typeahead-search/TypeaheadSearch.vue#645 - @size-search-expand: 24px; .wvui-typeahead-search__suggestion, .wvui-typeahead-search__suggestions__footer { diff --git a/resources/skins.vector.styles/layouts/screen.less b/resources/skins.vector.styles/layouts/screen.less index 073f82b..c8ff026 100644 --- a/resources/skins.vector.styles/layouts/screen.less +++ b/resources/skins.vector.styles/layouts/screen.less @@ -190,7 +190,6 @@ body { // transition. .box-sizing( border-box ); margin-left: 0; - max-width: @max-width-search; } } @@ -457,6 +456,11 @@ body { min-width: @min-width-search-tablet; flex-basis: @min-width-search; flex-grow: 1; + + > div > #searchform, + .wvui-typeahead-search { + max-width: @max-width-search; + } } .mw-page-container { @@ -465,6 +469,27 @@ body { } } +/** + * Toggles the visibility of the search box at lower resolutions. + */ +@media ( max-width: @width-breakpoint-tablet ) { + .vector-header-search-toggled { + #mw-sidebar-button, + .mw-logo, + .search-toggle { + display: none; + } + + .vector-search-box-collapses > div { + display: block; + } + + #p-search { + margin-left: @size-search-expand + @padding-horizontal-tabs; + } + } +} + /** * Makes the sidebar full screen at lower resolutions. */ diff --git a/skin.json b/skin.json index 3255d62..6247fc3 100644 --- a/skin.json +++ b/skin.json @@ -172,7 +172,8 @@ "resources/skins.vector.legacy.js/collapsibleTabs.js", "resources/skins.vector.legacy.js/vector.js", "resources/skins.vector.js/languageButton.js", - "resources/skins.vector.js/searchLoader.js" + "resources/skins.vector.js/searchLoader.js", + "resources/skins.vector.js/searchToggle.js" ], "dependencies": [ "mediawiki.page.ready",