Browse Source

Slim sidebar: remember collapsed state

Storm Heg 3 years ago
parent
commit
952264a2ab

+ 1 - 0
client/scss/components/_main-nav.scss

@@ -307,6 +307,7 @@ body.explorer-open {
     }
 
     body.sidebar-collapsed .wrapper {
+        @include transition(padding-left $menu-transition-duration ease);
         padding-left: $menu-width-slim;
     }
 

+ 1 - 0
client/src/components/Sidebar/Sidebar.stories.tsx

@@ -237,6 +237,7 @@ function renderSidebarStory(
   return (
     <div className="wrapper">
       <Sidebar
+        collapsedOnLoad={false}
         modules={modules}
         currentPath={currentPath}
         strings={strings || STRINGS}

+ 8 - 2
client/src/components/Sidebar/Sidebar.tsx

@@ -30,17 +30,23 @@ export interface SidebarProps {
   modules: ModuleDefinition[];
   currentPath: string;
   strings: Strings;
+  collapsedOnLoad: boolean;
   navigate(url: string): Promise<void>;
   onExpandCollapse?(collapsed: boolean);
 }
 
 export const Sidebar: React.FunctionComponent<SidebarProps> = (
-  { modules, currentPath, strings, navigate, onExpandCollapse }) => {
+  { modules, currentPath, collapsedOnLoad, strings, navigate, onExpandCollapse }) => {
   // 'collapsed' is a persistent state that is controlled by the arrow icon at the top
   // 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(window.innerWidth < 800);
+  const [collapsed, setCollapsed] = React.useState((): boolean => {
+    if (window.innerWidth < 800 || collapsedOnLoad) {
+      return true;
+    }
+    return false;
+  });
 
   // Call onExpandCollapse(true) if menu is initialised in collapsed state
   React.useEffect(() => {

+ 12 - 2
client/src/components/Sidebar/index.tsx

@@ -1,8 +1,11 @@
 import * as React from 'react';
 import ReactDOM from 'react-dom';
+import Cookies from 'js-cookie';
 
 import { Sidebar } from './Sidebar';
 
+export const SIDEBAR_COLLAPSED_COOKIE_NAME = 'wagtail_sidebar_collapsed';
+
 export function initSidebar() {
   const element = document.getElementById('wagtail-sidebar');
 
@@ -20,11 +23,17 @@ export function initSidebar() {
   if (element instanceof HTMLElement && element.dataset.props) {
     const props = window.telepath.unpack(JSON.parse(element.dataset.props));
 
-    const onExpandCollapse = (collapsed: boolean) => {
-      if (collapsed) {
+    const collapsedCookie: any = Cookies.get(SIDEBAR_COLLAPSED_COOKIE_NAME);
+    // Cast to boolean
+    const collapsed = !((collapsedCookie === undefined || collapsedCookie === '0'));
+
+    const onExpandCollapse = (_collapsed: boolean) => {
+      if (_collapsed) {
         document.body.classList.add('sidebar-collapsed');
+        Cookies.set(SIDEBAR_COLLAPSED_COOKIE_NAME, 1);
       } else {
         document.body.classList.remove('sidebar-collapsed');
+        Cookies.set(SIDEBAR_COLLAPSED_COOKIE_NAME, 0);
       }
     };
 
@@ -32,6 +41,7 @@ export function initSidebar() {
       <Sidebar
         modules={props.modules}
         strings={wagtailConfig.STRINGS}
+        collapsedOnLoad={collapsed}
         currentPath={window.location.pathname}
         navigate={navigate}
         onExpandCollapse={onExpandCollapse}

+ 0 - 1
client/src/custom.d.ts

@@ -26,7 +26,6 @@ declare global {
             /* eslint-disable-next-line camelcase */
             display_name: string;
         }[];
-
         STRINGS: any;
     }
     const wagtailConfig: WagtailConfig;

+ 5 - 0
package-lock.json

@@ -20259,6 +20259,11 @@
         }
       }
     },
+    "js-cookie": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
+      "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
+    },
     "js-string-escape": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",

+ 1 - 0
package.json

@@ -107,6 +107,7 @@
     "focus-trap-react": "^8.4.2",
     "formdata-polyfill": "^3.0.20",
     "immer": "^9.0.1",
+    "js-cookie": "^2.2.1",
     "postcss-calc": "^7.0.5",
     "prop-types": "^15.6.2",
     "react": "^16.14.0",

+ 14 - 14
wagtail/admin/templates/wagtailadmin/base.html

@@ -3,32 +3,32 @@
 
 {% block furniture %}
     {% slim_sidebar_enabled as slim_sidebar_enabled %}
-    <aside id="wagtail-sidebar" class="nav-wrapper" {% if slim_sidebar_enabled %}data-props="{% menu_props %}"{% else %}data-nav-primary{% endif %}>
+    {% if slim_sidebar_enabled %}
+    <aside id="wagtail-sidebar" data-props="{% menu_props %}"></aside>
+    {% else %}
+    <aside id="wagtail-sidebar" class="nav-wrapper" data-nav-primary>
         <div class="inner">
             <a href="{% url 'wagtailadmin_home' %}" class="logo" aria-label="{% trans 'Dashboard' %}">
                 {% block branding_logo %}
-                    {% if not slim_sidebar_enabled %}
-                        {# Mobile-only logo: #}
-                        <div class="wagtail-logo-container__mobile u-hidden@sm">
-                            <img class="wagtail-logo wagtail-logo__full" src="{% versioned_static 'wagtailadmin/images/wagtail-logo.svg' %}" alt="" width="80" />
-                        </div>
+                    {# Mobile-only logo: #}
+                    <div class="wagtail-logo-container__mobile u-hidden@sm">
+                        <img class="wagtail-logo wagtail-logo__full" src="{% versioned_static 'wagtailadmin/images/wagtail-logo.svg' %}" alt="" width="80" />
+                    </div>
 
-                        {# Desktop logo (animated): #}
-                        {% include "wagtailadmin/shared/animated_logo.html" %}
-                    {% endif %}
+                    {# Desktop logo (animated): #}
+                    {% include "wagtailadmin/shared/animated_logo.html" %}
                 {% endblock %}
                 <span class="u-hidden@sm">{% trans "Dashboard" %}</span>
             </a>
 
-            {% if not slim_sidebar_enabled %}
-                {% menu_search %}
-                {% main_nav %}
-            {% endif %}
+            {% menu_search %}
+            {% main_nav %}
         </div>
         <div class="explorer__wrapper" data-explorer-menu></div>
     </aside>
+    {% endif %}
 
-    <main class="content-wrapper" role="main" id="main">
+    <main class="content-wrapper {% if slim_sidebar_enabled %}sidebar--open{% endif %}" role="main" id="main">
         <div class="content">
             {# Always show messages div so it can be appended to by JS #}
             <div class="messages">

+ 3 - 1
wagtail/admin/templates/wagtailadmin/skeleton.html

@@ -18,7 +18,9 @@
 
     {% block branding_favicon %}{% endblock %}
 </head>
-<body id="wagtail" class="{% block bodyclass %}{% endblock %} {% if messages %}has-messages{% endif %} focus-outline-on">
+{% slim_sidebar_enabled as slim_sidebar_enabled %}
+{% sidebar_collapsed as sidebar_collapsed %}
+<body id="wagtail" class="{% block bodyclass %}{% endblock %} {% if slim_sidebar_enabled and sidebar_collapsed %}sidebar-collapsed{% endif %} {% if messages %}has-messages{% endif %} focus-outline-on">
     <div data-sprite></div>
     <script>
         function loadIconSprite() {

+ 9 - 0
wagtail/admin/templatetags/wagtailadmin_tags.py

@@ -678,6 +678,15 @@ def slim_sidebar_enabled():
     return 'slim-sidebar' in getattr(settings, 'WAGTAIL_EXPERIMENTAL_FEATURES', [])
 
 
+@register.simple_tag(takes_context=True)
+def sidebar_collapsed(context):
+    request = context.get('request')
+    collapsed = request.COOKIES.get('wagtail_sidebar_collapsed', '0')
+    if collapsed == '0':
+        return False
+    return True
+
+
 @register.simple_tag(takes_context=True)
 def menu_props(context):
     request = context['request']

+ 24 - 1
wagtail/admin/tests/test_menu.py

@@ -1,4 +1,5 @@
-from django.test import RequestFactory, TestCase
+from django.test import RequestFactory, TestCase, override_settings
+from django.urls import reverse
 
 from wagtail.admin.menu import AdminOnlyMenuItem, Menu, MenuItem, SubmenuMenuItem
 from wagtail.admin.ui import sidebar
@@ -17,6 +18,28 @@ class TestMenuRendering(TestCase, WagtailTestUtils):
     def setUp(self):
         self.request = RequestFactory().get('/admin')
         self.request.user = self.create_superuser(username='admin')
+        self.user = self.login()
+
+    @override_settings(WAGTAIL_EXPERIMENTAL_FEATURES={"slim-sidebar"})
+    def test_remember_collapsed(self):
+        '''Sidebar should render with collapsed class applied.'''
+        # Sidebar should not be collapsed
+        self.client.cookies['wagtail_sidebar_collapsed'] = '0'
+        response = self.client.get(reverse('wagtailadmin_home'))
+        self.assertNotContains(response, 'sidebar-collapsed')
+
+        # Sidebar should be collapsed
+        self.client.cookies['wagtail_sidebar_collapsed'] = '1'
+        response = self.client.get(reverse('wagtailadmin_home'))
+        self.assertContains(response, 'sidebar-collapsed')
+
+    @override_settings(WAGTAIL_EXPERIMENTAL_FEATURES={})
+    def test_collapsed_only_with_feature_flag(self):
+        '''Sidebar should only remember its collapsed state with the right feature flag set.'''
+        # Sidebar should not be collapsed because the feature flag is not enabled
+        self.client.cookies['wagtail_sidebar_collapsed'] = '1'
+        response = self.client.get(reverse('wagtailadmin_home'))
+        self.assertNotContains(response, 'sidebar-collapsed')
 
     def test_simple_menu(self):
         # Note: initialise the menu before registering hooks as this is what happens in reality.