Overhauled mobile menus
authorAlexander Ebert <ebert@woltlab.com>
Thu, 21 Apr 2016 16:36:05 +0000 (18:36 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 21 Apr 2016 16:36:11 +0000 (18:36 +0200)
com.woltlab.wcf/templates/pageMenuMobile.tpl
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Dialog.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Menu/Abstract.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Screen.js
wcfsetup/install/files/style/layout/layout.scss
wcfsetup/install/files/style/layout/pageNavigation.scss
wcfsetup/install/files/style/ui/dialog.scss
wcfsetup/install/files/style/ui/dropdown.scss
wcfsetup/install/files/style/ui/menuMobile.scss

index 3bccaebbd57acce506e18dc26a12aecd7a0967ae..1d97e3da99324698250c98e740c12768d32c81fd 100644 (file)
@@ -1,31 +1,31 @@
 {* main menu / page options / breadcrumbs *}
 <div id="pageMainMenuMobile" class="pageMainMenuMobile menuOverlayMobile" data-page-logo="{$__wcf->getPath()}images/default-logo.png"> {* TODO: use real path *}
        <ol class="menuOverlayItemList" data-title="{lang}wcf.menu.page{/lang}">
-               <li class="menuOverlayTitle">{lang}wcf.menu.page{/lang}</li>
-               <li class="menuOverlayItem">
+               <li class="menuOverlayTitle">{lang}wcf.menu.page.navigation{/lang}</li>
+               {*<li class="menuOverlayItem">
                        <a href="#" class="menuOverlayItemLink box24">
                                <span class="icon icon24 fa-sitemap"></span>
-                               <span class="menuOverlayItemTitle">{lang}wcf.menu.page.navigation{/lang}</span>
+                               <span class="menuOverlayItemTitle"></span>
                        </a>
-                       <ol class="menuOverlayItemList">
+                       <ol class="menuOverlayItemList">*}
                                {foreach from=$__wcf->getBoxHandler()->getBoxes('mainMenu')[0]->getMenu()->getMenuItemNodeList() item=menuItemNode}
-                               <li class="menuOverlayItem">
-                                       {assign var=__outstandingItems value=$menuItemNode->getMenuItem()->getOutstandingItems()}
-                                       <a href="{$menuItemNode->getMenuItem()->getURL()}" class="menuOverlayItemLink{if $__outstandingItems} menuOverlayItemBadge{/if}{if $menuItemNode->isActiveNode()} active{/if}">
-                                               <span class="menuOverlayItemTitle">{lang}{$menuItemNode->getMenuItem()->title}{/lang}</span>
-                                               {if $__outstandingItems}
-                                                       <span class="badge badgeUpdate">{#$__outstandingItems}</span>
-                                               {/if}
-                                       </a>
-                                       
-                                       {if $menuItemNode->hasChildren()}<ol class="menuOverlayItemList">{else}</li>{/if}
+                                       <li class="menuOverlayItem">
+                                               {assign var=__outstandingItems value=$menuItemNode->getMenuItem()->getOutstandingItems()}
+                                               <a href="{$menuItemNode->getMenuItem()->getURL()}" class="menuOverlayItemLink{if $__outstandingItems} menuOverlayItemBadge{/if}{if $menuItemNode->isActiveNode()} active{/if}">
+                                                       <span class="menuOverlayItemTitle">{lang}{$menuItemNode->getMenuItem()->title}{/lang}</span>
+                                                       {if $__outstandingItems}
+                                                               <span class="badge badgeUpdate">{#$__outstandingItems}</span>
+                                                       {/if}
+                                               </a>
                                                
-                                               {if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
-                                                       {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
-                                               {/if}
-                                               {/foreach}
-                       </ol>
-               </li>
+                                               {if $menuItemNode->hasChildren()}<ol class="menuOverlayItemList">{else}</li>{/if}
+                                                       
+                                                       {if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
+                                                               {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
+                                                       {/if}
+                               {/foreach}
+                       {*</ol>
+               </li>*}
                {hascontent}
                        <li class="menuOverlayItem">
                                <a href="#" class="menuOverlayItemLink box24">
index 3ececfe6f232732934f03d595bfd9748f3116ff6..a34d32a353ef1385644b7893833b605b50cc4089 100755 (executable)
@@ -5871,7 +5871,7 @@ WCF.InlineEditor = Class.extend({
                                return;
                        }
                        
-                       $trigger.click($.proxy(self._show, self)).data('elementID', $elementID);
+                       $trigger.on(WCF_CLICK_EVENT, $.proxy(self._show, self)).data('elementID', $elementID);
                        if ($quickOption) {
                                // simulate click on target action
                                $trigger.disableSelection().data('optionName', $quickOption).dblclick($.proxy(self._click, self));
index 00adba27969e958bf934a51eaaf088a5e04b1dc3..141eacaac47dd76381d36664c116e1bd18d2c1a4 100644 (file)
@@ -2,7 +2,7 @@
  * Modal dialog handler.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
+ * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLab/WCF/Ui/Dialog
  */
@@ -31,7 +31,7 @@ define(
        /**
         * @exports     WoltLab/WCF/Ui/Dialog
         */
-       var UiDialog = {
+       return {
                /**
                 * Sets up global container and internal variables.
                 */
@@ -547,6 +547,4 @@ define(
                        return {};
                }
        };
-       
-       return UiDialog;
 });
index d9c7850f7fd9030fc92ed78ee55f4728d041a584..6a88b2ff33144fb4966afc146c9e1744545c3579 100644 (file)
@@ -6,9 +6,11 @@
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLab/WCF/Ui/Page/Menu/Abstract
  */
-define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen'], function(Environment, EventHandler, ObjectMap, DomTraverse, UiScreen) {
+define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util', 'Ui/Screen'], function(Environment, EventHandler, ObjectMap, DomTraverse, DomUtil, UiScreen) {
        "use strict";
        
+       var _pageContainer = elById('pageContainer');
+       
        /**
         * @param       {string}        eventIdentifier         event namespace
         * @param       {string}        elementId               menu element id
@@ -25,10 +27,13 @@ define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen']
                 * @param       {string}        buttonSelector          CSS selector for toggle button
                 */
                init: function(eventIdentifier, elementId, buttonSelector) {
+                       this._activeList = [];
+                       this._depth = 0;
                        this._enabled = true;
                        this._eventIdentifier = eventIdentifier;
                        this._items = new ObjectMap();
                        this._menu = elById(elementId);
+                       this._removeActiveList = false;
                        
                        var callbackOpen = this.open.bind(this);
                        var button = elBySel(buttonSelector);
@@ -52,6 +57,25 @@ define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen']
                                        }
                                }
                        }).bind(this));
+                       
+                       this._menu.children[0].addEventListener('transitionend', (function() {
+                               this._menu.classList.add('allowScroll');
+                               
+                               if (this._removeActiveList) {
+                                       this._removeActiveList = false;
+                                       
+                                       var list = this._activeList.pop();
+                                       if (list) {
+                                               list.classList.remove('activeList');
+                                       }
+                               }
+                       }).bind(this));
+                       
+                       var backdrop = elCreate('div');
+                       backdrop.className = 'menuOverlayMobileBackdrop';
+                       backdrop.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
+                       
+                       DomUtil.insertAfter(backdrop, this._menu);
                },
                
                /**
@@ -68,10 +92,13 @@ define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen']
                                event.preventDefault();
                        }
                        
-                       this._menu.classList.add('enableAnimation');
                        this._menu.classList.add('open');
+                       this._menu.classList.add('allowScroll');
+                       this._menu.children[0].classList.add('activeList');
                        
                        UiScreen.scrollDisable();
+                       
+                       _pageContainer.classList.add('menuOverlay-' + this._menu.id);
                },
                
                /**
@@ -83,16 +110,14 @@ define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen']
                        if (event instanceof Event) {
                                event.preventDefault();
                        }
-                       else if (event === true) {
-                               this._menu.classList.remove('enableAnimation');
-                       }
                        
                        if (this._menu.classList.contains('open')) {
                                this._menu.classList.remove('open');
                                
                                UiScreen.scrollEnable();
+                               
+                               _pageContainer.classList.remove('menuOverlay-' + this._menu.id);
                        }
-                       
                },
                
                /**
@@ -176,7 +201,7 @@ define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen']
                        });
                        
                        if (itemTitle === '') {
-                               itemTitle = DomTraverse.childByClass(item, 'menuOverlayItemTitle').textContent
+                               itemTitle = DomTraverse.childByClass(item, 'menuOverlayItemTitle').textContent;
                                elData(itemList, 'title', itemTitle);
                        }
                        
@@ -279,9 +304,13 @@ define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen']
                                event.preventDefault();
                        }
                        
+                       this._menu.classList.remove('allowScroll');
+                       this._removeActiveList = true;
+                       
                        var data = this._items.get(item);
-                       data.itemList.classList.remove('active');
                        data.parentItemList.classList.remove('hidden');
+                       
+                       this._updateDepth(false);
                },
                
                /**
@@ -313,8 +342,20 @@ define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen']
                                }
                        }
                        
-                       data.itemList.classList.add('active');
+                       this._menu.classList.remove('allowScroll');
+                       
+                       data.itemList.classList.add('activeList');
                        data.parentItemList.classList.add('hidden');
+                       
+                       this._activeList.push(data.itemList);
+                       
+                       this._updateDepth(true);
+               },
+               
+               _updateDepth: function(increase) {
+                       this._depth += (increase) ? 1 : -1;
+                       
+                       this._menu.children[0].style.setProperty('transform', 'translateX(' + (this._depth * -100) + '%)', '')
                }
        };
        
index dd9dcbe654c5a20ffe0316f110b3eb1b22d35b55..369fd78f171ef0ebfdf86346553bf67e8dc45c8a 100644 (file)
@@ -9,7 +9,6 @@
 define(['Core', 'Dictionary'], function(Core, Dictionary) {
        "use strict";
        
-       var _bodyOverflow = '';
        var _mql = new Dictionary();
        var _scrollDisableCounter = 0;
        
@@ -95,9 +94,7 @@ define(['Core', 'Dictionary'], function(Core, Dictionary) {
                 */
                scrollDisable: function() {
                        if (_scrollDisableCounter === 0) {
-                               _bodyOverflow = document.body.style.getPropertyValue('overflow');
-                               
-                               document.body.style.setProperty('overflow', 'hidden', '');
+                               document.documentElement.classList.add('disableScrolling');
                        }
                        
                        _scrollDisableCounter++;
@@ -111,12 +108,7 @@ define(['Core', 'Dictionary'], function(Core, Dictionary) {
                                _scrollDisableCounter--;
                                
                                if (_scrollDisableCounter === 0) {
-                                       if (_bodyOverflow) {
-                                               document.body.style.setProperty('overflow', _bodyOverflow, '');
-                                       }
-                                       else {
-                                               document.body.style.removeProperty('overflow');
-                                       }
+                                       document.documentElement.classList.remove('disableScrolling');
                                }
                        }
                },
index cacb670efc525442d4d511228d68071c487c841a..d15394eef24c4e96d8b411e2adb4028ad69bd670 100644 (file)
@@ -1,3 +1,15 @@
+html.disableScrolling {
+       overflow: hidden !important;
+       
+       body {
+               overflow: hidden !important;
+               
+               @include screen-md-down {
+                       position: fixed !important;
+               }
+       }
+}
+
 html, body {
        height: 100%;
        
@@ -8,6 +20,7 @@ html, body {
 body {
        font-family: $wcfFontFamily;
        position: relative;
+       width: 100%;
        word-wrap: break-word;
 }
 
index e4c6719716c49dfc1bd352133adebb4d1fa95683..d6639ba1ea96f8c34ca56dbd47f99cee934ecc5a 100644 (file)
@@ -1,4 +1,4 @@
-@include screen-md-up {
+@include screen-lg {
        .pageNavigation {
                background-color: $wcfNavigationBackground;
                color: $wcfNavigationText;
@@ -45,7 +45,7 @@
        }
 }
 
-@include screen-sm-down {
+@include screen-md-down {
        .pageNavigation {
                display: none;
        }
index 5f646d18bfcc8b6460e6c2ede0aab75bfddcbb92..6b775e07c4b84ec5305726f7093e221b1100d0de 100644 (file)
@@ -11,7 +11,7 @@
        will-change: opacity;
        z-index: 399;
        
-       @include screen-md-up {
+       @include screen-sm-up {
                padding: 100px 0;
        }
        
 .dialogContainer {
        z-index: 200;
        
-       @include screen-sm-down {
+       @include screen-xs {
                left: 0 !important;
                position: fixed;
                right: 0 !important;
                top: 0 !important;
        }
        
-       @include screen-md-up {
+       @include screen-sm-up {
                animation: wcfDialogOut .3s;
                animation-fill-mode: forwards;
                box-shadow: 0 1px 15px 0 rgba(0, 0, 0, .3);
index 6a530a0adf322b6f71a710498360e6c01c38304c..43d0134dd7347d3e4a7d2bbb996cec8238cd3737 100644 (file)
                max-height: 300px;
                overflow: auto;
        }
-}
-
-.boxFlag > .box24, .boxFlag.box24 {
-       display: flex !important;
-       min-height: 20px;
        
-       &.dropdownToggle {
-               display: inline-flex !important;
-       }
-}
-
-@include screen-md-up {
-       .dropdownMenu.pageHeaderSearchDropdown {
-               transform: translateY(-10px);
-       }
-}
-
-@include screen-sm-down {
-       .dropdownMenu {
+       @include screen-xs {
                left: 0 !important;
                right: 0 !important;
-               
+       }
+       
+       @include screen-md-down {
                li {
                        overflow: hidden;
                        
                        }
                }
        }
+       
+       @include screen-md-up {
+               .dropdownMenu.pageHeaderSearchDropdown {
+                       transform: translateY(-10px);
+               }
+       }
+}
+
+.boxFlag > .box24, .boxFlag.box24 {
+       display: flex !important;
+       min-height: 20px;
+       
+       &.dropdownToggle {
+               display: inline-flex !important;
+       }
 }
index 635a27492ec0acfa5269bc6fe8d8bd6e23561a5a..bdaf463fd217b2c38d68935de0c256e538407f43 100644 (file)
-/* animations for overlay appearing from the left */
-@keyframes wcfMenuOverlayLeft {
-       0%   { visibility: hidden;  transform: translateX(-100%); }
-       100% { visibility: visible; transform: translateX(0);     }
-}
-
-@keyframes wcfMenuOverlayLeftOut {
-       0%   { visibility: visible; transform: translateX(0);     }
-       99%  { visibility: visible; transform: translateX(-100%); }
-       100% { visibility: hidden;  transform: translateX(-100%); }
-}
-
-/* animations for overlay appearing from the right */
-@keyframes wcfMenuOverlayRight {
-       0%   { visibility: hidden;  transform: translateX(100%); }
-       100% { visibility: visible; transform: translateX(0);    }
-}
-
-@keyframes wcfMenuOverlayRightOut {
-       0%   { visibility: visible; transform: translateX(0);    }
-       99%  { visibility: visible; transform: translateX(100%); }
-       100% { visibility: hidden;  transform: translateX(100%); }
+@include screen-sm-up {
+       .pageContainer {
+               &.menuOverlay-pageMainMenuMobile {
+                       transform: translateX(350px);
+                       
+                       .mainMenu {
+                               visibility: hidden;
+                       }
+               }
+               
+               &.menuOverlay-pageUserMenuMobile {
+                       transform: translateX(-350px);
+                       
+                       .userPanel {
+                               visibility: hidden;
+                       }
+               }
+       }
 }
 
 /* menu container */
 .menuOverlayMobile {
+       background-color: rgb(52, 73, 94);
        bottom: 0;
-       left: 0;
-       max-width: 100vw;
+       display: none;
        overflow: hidden;
-       position: fixed;
-       right: 0;
+       position: absolute;
        top: 0;
-       visibility: hidden;
        z-index: 320;
        
-       &.enableAnimation {
-               animation: wcfMenuOverlayLeftOut .3s;
-               animation-fill-mode: forwards;
+       &.open {
+               display: block;
                
-               /* different animation for user menu */
-               &.pageUserMenuMobile {
-                       animation-name: wcfMenuOverlayRightOut;
+               @include screen-sm-up {
+                       + .menuOverlayMobileBackdrop {
+                               display: block;
+                       }
                }
        }
        
-       &.open {
-               animation: wcfMenuOverlayLeft .3s;
-               animation-fill-mode: forwards;
+       > .menuOverlayItemList {
+               // we use `transform: translateX()` for performance reasons
+               transition: transform .24s cubic-bezier(.25, .46, .45, .94);
                
-               /* different animation for user menu */
-               &.pageUserMenuMobile {
-                       animation-name: wcfMenuOverlayRight;
+               /* work-around to avoid setting explicit visibility */
+               visibility: visible;
+       }
+       
+       &.allowScroll {
+               .menuOverlayItemList:not(.hidden) {
+                       overflow: auto;
+               }
+       }
+       
+       &:not(.allowScroll) {
+               // block UI while switching between menus
+               &::before {
+                       bottom: 0;
+                       content: "";
+                       left: 0;
+                       position: absolute;
+                       right: 0;
+                       top: 0;
+                       z-index: 500;
                }
        }
        
-       /* work-around to avoid setting explicit visibility */
-       > .menuOverlayItemList:not(.hidden) {
-               visibility: inherit;
+       @include screen-xs {
+               left: 0;
+               max-width: 100vw;
+               right: 0;
+               
+               .menuOverlayItemList {
+                       right: 0;
+               }
+       }
+       
+       @include screen-sm-up {
+               width: 350px;
+               
+               &.pageMainMenuMobile {
+                       left: 0;
+                       
+                       & + .menuOverlayMobileBackdrop {
+                               box-shadow: inset 5px 0 10px -5px rgba(0, 0, 0, .6);
+                               left: 350px;
+                       }
+                       
+                       .menuOverlayItemList {
+                               left: 0;
+                       }
+               }
+               
+               &.pageUserMenuMobile {
+                       right: 0;
+                       
+                       & + .menuOverlayMobileBackdrop {
+                               box-shadow: inset -5px 0 10px -5px rgba(0, 0, 0, .6);
+                               right: 350px;
+                       }
+                       
+                       .menuOverlayItemList {
+                               right: 0;
+                       }
+               }
        }
 }
 
+.menuOverlayMobileBackdrop {
+       background-color: rgba(0, 0, 0, .4);
+       bottom: 0;
+       display: none;
+       left: 0;
+       position: fixed;
+       right: 0;
+       top: 0;
+       z-index: 500;
+}
+
 .menuOverlayItemWrapper {
        display: flex;
        justify-content: flex-end;
 
 .menuOverlayItemList {
        background-color: rgb(52, 73, 94);
-       box-shadow: -5px 0 10px 0 rgba(0, 0, 0, .2);
+       height: 100%;
        list-style-type: none;
        margin: 0;
-       padding: 10px 0;
-       position: fixed;
+       padding: 5px 0;
+       position: absolute;
        top: 0;
        left: -1px;
-       right: 0;
        bottom: 0;
        width: calc(100% + 1px);
        z-index: 450;
-       transition: margin-left .3s cubic-bezier(.25, .46, .45, .94);
-       transition-timing-function: linear;
-       will-change: margin-left;
        
-       /* chaining `.hidden` and `.active` below is required because each active
-          item list receives `.active` but it could still be `.hidden` due to
-          a child list being active */
-       &.hidden,
-       &.hidden.active {
-               margin-left: -25%;
+       @include screen-sm-up {
+               width: 350px;
+       }
+       
+       &:not(.activeList) {
+               display: none;
        }
 }
 
        
        /* nested item list */
        > .menuOverlayItemList {
-               margin-left: 110%;
+               margin-left: 100%;
                z-index: 500;
-               
-               &.active {
-                       margin-left: 0;
-                       overflow: scroll;
-               }
        }
 }