From a8d2da1d13552bd5826318e053c250f3204a45ed Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 24 Nov 2016 16:33:00 +0100 Subject: [PATCH] Added overflow handling for main menu --- .../js/WoltLabSuite/Core/BootstrapFrontend.js | 6 +- .../WoltLabSuite/Core/Ui/Page/Header/Menu.js | 183 ++++++++++++++++++ .../files/style/layout/pageHeader.scss | 40 +++- 3 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Header/Menu.js diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js b/wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js index 696fb98e60..76cf96115a 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js @@ -9,11 +9,11 @@ define( [ 'Ajax', 'WoltLabSuite/Core/Bootstrap', 'WoltLabSuite/Core/Controller/Style/Changer', - 'WoltLabSuite/Core/Controller/Popover', 'WoltLabSuite/Core/Ui/User/Ignore' + 'WoltLabSuite/Core/Controller/Popover', 'WoltLabSuite/Core/Ui/User/Ignore', 'WoltLabSuite/Core/Ui/Page/Header/Menu' ], function( Ajax, Bootstrap, ControllerStyleChanger, - ControllerPopover, UiUserIgnore + ControllerPopover, UiUserIgnore, UiPageHeaderMenu ) { "use strict"; @@ -32,6 +32,8 @@ define( setup: function(options) { Bootstrap.setup(); + UiPageHeaderMenu.init(); + if (options.styleChanger) { ControllerStyleChanger.setup(); } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Header/Menu.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Header/Menu.js new file mode 100644 index 0000000000..938dd22cb5 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Header/Menu.js @@ -0,0 +1,183 @@ +/** + * Handles main menu overflow. + * + * @author Alexander Ebert + * @copyright 2001-2016 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Page/Header/Menu + */ +define(['Ui/Screen'], function(UiScreen) { + "use strict"; + + var _enabled = false; + + // elements + var _buttonShowNext, _buttonShowPrevious, _firstElement, _menu; + + // internal states + var _marginLeft = 0, _invisibleLeft = [], _invisibleRight = []; + + /** + * @exports WoltLabSuite/Core/Ui/Page/Header/Menu + */ + return { + /** + * Initializes the main menu overflow handling. + */ + init: function () { + _menu = elBySel('.mainMenu .boxMenu'); + _firstElement = (_menu && _menu.childElementCount) ? _menu.children[0] : null; + if (_firstElement === null) { + throw new Error("Unable to find the menu."); + } + + UiScreen.on('screen-lg', { + enable: this._enable.bind(this), + disable: this._disable.bind(this), + setup: this._setup.bind(this) + }); + }, + + /** + * Enables the overflow handler. + * + * @protected + */ + _enable: function () { + _enabled = true; + + this._rebuildVisibility(); + }, + + /** + * Disables the overflow handler. + * + * @protected + */ + _disable: function () { + _enabled = false; + }, + + /** + * Displays the next three menu items. + * + * @param {Event} event event object + * @protected + */ + _showNext: function(event) { + event.preventDefault(); + + if (_invisibleRight.length) { + var showItem = _invisibleRight.slice(0, 3).pop(); + this._setMarginLeft(_menu.clientWidth - (showItem.offsetLeft + showItem.clientWidth)); + + if (_menu.lastElementChild === showItem) { + _buttonShowNext.classList.remove('active'); + } + + _buttonShowPrevious.classList.add('active'); + } + }, + + /** + * Displays the previous three menu items. + * + * @param {Event} event event object + * @protected + */ + _showPrevious: function (event) { + event.preventDefault(); + + if (_invisibleLeft.length) { + var showItem = _invisibleLeft.slice(-3)[0]; + this._setMarginLeft(showItem.offsetLeft * -1); + + if (_menu.firstElementChild === showItem) { + _buttonShowPrevious.classList.remove('active'); + } + + _buttonShowNext.classList.add('active'); + } + }, + + /** + * Sets the first item's margin-left value that is + * used to move the menu contents around. + * + * @param {int} offset changes to the margin-left value in pixel + * @protected + */ + _setMarginLeft: function (offset) { + _marginLeft = Math.min(_marginLeft + offset, 0); + + _firstElement.style.setProperty('margin-left', _marginLeft + 'px', ''); + }, + + /** + * Toggles button overlays and rebuilds the list + * of invisible items from left to right. + * + * @protected + */ + _rebuildVisibility: function () { + if (!_enabled) return; + + _invisibleLeft = []; + _invisibleRight = []; + + var menuWidth = _menu.clientWidth; + if (_menu.scrollWidth > menuWidth || _marginLeft < 0) { + var child; + for (var i = 0, length = _menu.childElementCount; i < length; i++) { + child = _menu.children[i]; + + var offsetLeft = child.offsetLeft; + if (offsetLeft < 0) { + _invisibleLeft.push(child); + } + else if (offsetLeft + child.clientWidth > menuWidth) { + _invisibleRight.push(child); + } + } + } + + _buttonShowPrevious.classList[(_invisibleLeft.length ? 'add' : 'remove')]('active'); + _buttonShowNext.classList[(_invisibleRight.length ? 'add' : 'remove')]('active'); + }, + + /** + * Builds the UI and binds the event listeners. + * + * @protected + */ + _setup: function () { + _buttonShowNext = elCreate('a'); + _buttonShowNext.className = 'mainMenuShowNext'; + _buttonShowNext.href = '#'; + _buttonShowNext.innerHTML = ''; + _buttonShowNext.addEventListener(WCF_CLICK_EVENT, this._showNext.bind(this)); + + _menu.parentNode.appendChild(_buttonShowNext); + + _buttonShowPrevious = elCreate('a'); + _buttonShowPrevious.className = 'mainMenuShowPrevious'; + _buttonShowPrevious.href = '#'; + _buttonShowPrevious.innerHTML = ''; + _buttonShowPrevious.addEventListener(WCF_CLICK_EVENT, this._showPrevious.bind(this)); + + _menu.parentNode.insertBefore(_buttonShowPrevious, _menu.parentNode.firstChild); + + var rebuildVisibility = this._rebuildVisibility.bind(this); + _firstElement.addEventListener('transitionend', rebuildVisibility); + + window.addEventListener('resize', function () { + _firstElement.style.setProperty('margin-left', '0px', ''); + _marginLeft = 0; + + rebuildVisibility(); + }); + + this._enable(); + } + }; +}); diff --git a/wcfsetup/install/files/style/layout/pageHeader.scss b/wcfsetup/install/files/style/layout/pageHeader.scss index b07b5bc394..fa207377e4 100644 --- a/wcfsetup/install/files/style/layout/pageHeader.scss +++ b/wcfsetup/install/files/style/layout/pageHeader.scss @@ -79,14 +79,20 @@ /* MAIN MENU */ .mainMenu { flex: 1 1 auto; + margin-right: 20px; + position: relative; .boxMenu { display: flex; - flex-wrap: wrap; + overflow: hidden; > li { flex: 0 0 auto; + &:first-child { + transition: margin-left .24s ease-in-out; + } + > a { background: $wcfHeaderMenuLinkBackground; align-items: center; @@ -118,8 +124,6 @@ } > .boxMenuHasChildren { - position: relative; - &:hover .boxMenuDepth1 { visibility: visible; } @@ -185,6 +189,36 @@ } } } + + .mainMenuShowPrevious, + .mainMenuShowNext { + align-items: center; + display: flex; + height: 100%; + opacity: 0; + position: absolute; + top: 0; + transition: opacity .24s linear, visibility 0s linear .24s; + visibility: hidden; + width: 50px; + + &.active { + opacity: 1; + transition-delay: 0s; + visibility: visible; + } + } + + .mainMenuShowPrevious { + background: linear-gradient(to left, transparent 0%, $wcfHeaderMenuBackground 75%); + left: 0; + } + + .mainMenuShowNext { + background: linear-gradient(to right, transparent 0%, $wcfHeaderMenuBackground 75%); + justify-content: flex-end; + right: 0; + } } /* user panel */ -- 2.20.1