Sfoglia il codice sorgente

Completely hide sidebar in mobile mode (#7481)

* Add bars.svg icon

* Add Open/Close button for mobile mode

* Completely hide sidebar in mobile mode

* mobile state tweaks

* Rename 'open' to 'visibleOnMobile'
* Don't initialise 'collapsed' state based on screen size (old code)
* Remove main.sidebar--open (not used)

Co-authored-by: Storm Heg <storm@stormbase.digital>
Karl Hobley 3 anni fa
parent
commit
45b7788c6d

+ 63 - 24
client/src/components/Sidebar/Sidebar.scss

@@ -1,3 +1,29 @@
+@mixin sidebar-toggle() {
+    @include transition(background-color $menu-transition-duration ease);
+
+    position: absolute;
+    top: 12px;
+    left: 12px;
+    color: $color-white;
+    width: 35px;
+    height: 35px;
+    background: transparent;
+    border: 0;
+    place-items: center;
+    padding: 0;
+    border-radius: 50%;
+
+    &:hover {
+        background-color: #3a3a3a;
+        color: #ccc;
+    }
+
+    svg {
+        width: 15px;
+        height: 16px;
+    }
+}
+
 .sidebar {
     position: fixed;
     left: 0;
@@ -9,12 +35,17 @@
     background: $nav-grey-3;
     z-index: 3;
 
-    @include transition(width $menu-transition-duration ease);
+    @include transition(width $menu-transition-duration ease, left $menu-transition-duration ease);
 
     &--slim {
         width: $menu-width-slim;
     }
 
+    // The sidebar can move completely off-screen in mobile mode for extra room
+    &--hidden {
+        left: -$menu-width;
+    }
+
     &__inner {
         height: 100%;
         background: $nav-grey-3;
@@ -24,31 +55,13 @@
     }
 
     &__collapse-toggle {
-        @include transition(background-color $menu-transition-duration ease);
-        position: absolute;
-        top: 12px;
-        left: 12px;
-        color: #ccc;
-        width: 35px;
-        height: 35px;
-        background: transparent;
-        border: 0;
+        @include sidebar-toggle;
         display: grid;
-        place-items: center;
-        padding: 0;
-        border-radius: 50%;
-
-        @include media-breakpoint-up(sm) {
-            &:hover {
-                background-color: #3a3a3a;
-                color: #ccc;
-            }
-        }
+    }
 
-        svg {
-            width: 15px;
-            height: 16px;
-        }
+    // When in mobile mode, hide the collapse-toggle and show the nav-toggle (which is defined in the .sidebar-nav-toggle class below)
+    &--mobile &__collapse-toggle {
+        display: none;
     }
 
     // This element should cover all the area beneath the collapse toggle
@@ -61,6 +74,32 @@
     }
 }
 
+// This is a separate component as it needs to display in the header
+.sidebar-nav-toggle {
+    @include sidebar-toggle;
+    display: none;  // Nav toggle is for mobile only
+    z-index: 5;
+
+    &:hover {
+        background: var(--color-primary-dark);
+        color: #fff;
+    }
+
+    &--mobile {
+        display: grid;
+    }
+
+    &--open {
+        position: fixed;
+
+        &:hover {
+            background-color: #3a3a3a;
+            color: #ccc;
+        }
+    }
+}
+
+
 @import 'SidebarPanel';
 @import 'menu/MenuItem';
 @import 'menu/SubMenuItem';

+ 74 - 27
client/src/components/Sidebar/Sidebar.tsx

@@ -1,5 +1,3 @@
-/* eslint-disable react/prop-types */
-
 import * as React from 'react';
 
 import Icon from '../Icon/Icon';
@@ -41,12 +39,7 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = (
   // It records the user's general preference for a collapsed/uncollapsed menu
   // This is just a hint though, and we may still collapse the menu if the screen is too small
   // Also, we may display the full menu temporarily in collapsed mode (see 'peeking' below)
-  const [collapsed, setCollapsed] = React.useState((): boolean => {
-    if (window.innerWidth < 800 || collapsedOnLoad) {
-      return true;
-    }
-    return false;
-  });
+  const [collapsed, setCollapsed] = React.useState(collapsedOnLoad);
 
   // Call onExpandCollapse(true) if menu is initialised in collapsed state
   React.useEffect(() => {
@@ -60,10 +53,34 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = (
   // space next to the content
   const [peeking, setPeeking] = React.useState(false);
 
+  // 'visibleOnMobile' indicates whether the sidebar is currently visible on mobile
+  // On mobile, the sidebar is completely hidden by default and must be opened manually
+  const [visibleOnMobile, setVisibleOnMobile] = React.useState(false);
+
+  // Tracks whether the screen is below 800 pixels. In this state, the menu is completely hidden.
+  // State is used here in case the user changes their browser size
+  const checkWindowSizeIsMobile = () => window.innerWidth < 800;
+  const [isMobile, setIsMobile] = React.useState(checkWindowSizeIsMobile());
+  React.useEffect(() => {
+    function handleResize() {
+      if (checkWindowSizeIsMobile()) {
+        setIsMobile(true);
+      } else {
+        setIsMobile(false);
+
+        // Close the menu as this state is not used in desktop
+        setVisibleOnMobile(false);
+      }
+    }
+    window.addEventListener('resize', handleResize);
+    handleResize();
+    return () => window.removeEventListener('resize', handleResize);
+  }, []);
+
   // Whether or not to display the menu with slim layout.
   // Separate from 'collapsed' as the menu can still be displayed with an expanded
   // layout while in 'collapsed' mode if the user is 'peeking' into it (see above)
-  const slim = collapsed && !peeking;
+  const slim = collapsed && !peeking && !isMobile;
 
   // 'expandingOrCollapsing' is set to true whilst the the menu is transitioning between slim and expanded layouts
   const [expandingOrCollapsing, setExpandingOrCollapsing] = React.useState(false);
@@ -87,6 +104,19 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = (
     }
   };
 
+  const onClickOpenCloseToggle = (e: React.MouseEvent) => {
+    e.preventDefault();
+    setVisibleOnMobile(!visibleOnMobile);
+    setExpandingOrCollapsing(true);
+
+    const finishTimeout = setTimeout(() => {
+      setExpandingOrCollapsing(false);
+    }, SIDEBAR_TRANSITION_DURATION);
+    return () => {
+      clearTimeout(finishTimeout);
+    };
+  };
+
   // Switch peeking on/off when the mouse cursor hovers the sidebar or focus is on the sidebar
   const [mouseHover, setMouseHover] = React.useState(false);
   const [focused, setFocused] = React.useState(false);
@@ -135,24 +165,41 @@ export const Sidebar: React.FunctionComponent<SidebarProps> = (
   );
 
   return (
-    <aside
-      className={'sidebar' + (slim ? ' sidebar--slim' : '')}
-    >
-      <div className="sidebar__inner">
-        <button onClick={onClickCollapseToggle} className="button sidebar__collapse-toggle">
-          {collapsed ? <Icon name="angle-double-right" /> : <Icon name="angle-double-left" />}
-        </button>
-
-        <div
-          className="sidebar__peek-hover-area"
-          onMouseEnter={onMouseEnterHandler}
-          onMouseLeave={onMouseLeaveHandler}
-          onFocus={onFocusHandler}
-          onBlur={onBlurHandler}
-        >
-          {renderedModules}
+    <>
+      <aside
+        className={
+          'sidebar'
+          + (slim ? ' sidebar--slim' : '')
+          + (isMobile ? ' sidebar--mobile' : '')
+          + ((isMobile && !visibleOnMobile) ? ' sidebar--hidden' : '')
+        }
+      >
+        <div className="sidebar__inner">
+          <button onClick={onClickCollapseToggle} className="button sidebar__collapse-toggle">
+            {collapsed ? <Icon name="angle-double-right" /> : <Icon name="angle-double-left" />}
+          </button>
+
+          <div
+            className="sidebar__peek-hover-area"
+            onMouseEnter={onMouseEnterHandler}
+            onMouseLeave={onMouseLeaveHandler}
+            onFocus={onFocusHandler}
+            onBlur={onBlurHandler}
+          >
+            {renderedModules}
+          </div>
         </div>
-      </div>
-    </aside>
+      </aside>
+      <button
+        onClick={onClickOpenCloseToggle}
+        className={
+          'button sidebar-nav-toggle'
+          + (isMobile ? ' sidebar-nav-toggle--mobile' : '')
+          + (visibleOnMobile ? ' sidebar-nav-toggle--open' : '')
+        }
+      >
+        {visibleOnMobile ? <Icon name="cross" /> : <Icon name="bars" />}
+      </button>
+    </>
   );
 };

+ 3 - 2
wagtail/admin/templates/wagtailadmin/base.html

@@ -28,7 +28,7 @@
     </aside>
     {% endif %}
 
-    <main class="content-wrapper {% if slim_sidebar_enabled %}sidebar--open{% endif %}" role="main" id="main">
+    <main class="content-wrapper" role="main" id="main">
         <div class="content">
             {# Always show messages div so it can be appended to by JS #}
             <div class="messages">
@@ -53,8 +53,9 @@
                 {% endif %}
             </div>
 
+            {% if not slim_sidebar_enabled %}
             <div id="nav-toggle" class="nav-toggle icon text-replace">{% trans "Menu" %}</div>
-
+            {% endif %}
             {% block content %}{% endblock %}
         </div>
     </main>

+ 1 - 0
wagtail/admin/templates/wagtailadmin/icons/bars.svg

@@ -0,0 +1 @@
+<symbol id="icon-bars" viewBox="0 0 448 512"><path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"></path></symbol>

+ 1 - 0
wagtail/admin/wagtail_hooks.py

@@ -678,6 +678,7 @@ def register_icons(icons):
         'arrow-up-big.svg',
         'arrow-up.svg',
         'arrows-up-down.svg',
+        'bars.svg',
         'bin.svg',
         'bold.svg',
         'chain-broken.svg',