Browse Source

Fixes #4473 - Navigation sidebar does not collapse on tablet devices

Mantas Masalskis 2 years ago
parent
commit
5f68d34675

+ 10 - 0
LICENSE-ICONS-3RD-PARTY.json

@@ -154,6 +154,16 @@
         "url": "",
         "license": "MIT"
     },
+    "double-arrow-left.svg": {
+        "author": "Zammad",
+        "url": "",
+        "license": "MIT"
+    },
+    "double-arrow-right.svg": {
+        "author": "Zammad",
+        "url": "",
+        "license": "MIT"
+    },
     "download.svg": {
         "author": "Felix Niklas",
         "url": "",

+ 16 - 1
app/assets/javascripts/app/controllers/_plugin/navigation.coffee

@@ -21,6 +21,7 @@ class Navigation extends App.Controller
     'click .js-details-link': 'openExtendedSearch'
     'change .js-menu .js-switch input': 'switch'
     'click .js-onclick': 'click'
+    'click .js-navigation-toggle-button': 'toggleClicked'
 
   constructor: ->
     super
@@ -230,6 +231,9 @@ class Navigation extends App.Controller
     @searchContainer.addClass('focused')
     @selectAll(e)
 
+    $('#app').removeClass('navigation-collapsed')
+    @el.addClass('is-not-collapsed')
+
   searchPaste: (e) =>
     update = =>
       @clearDelay('emptyAndCloseDelayed')
@@ -240,7 +244,6 @@ class Navigation extends App.Controller
     @delay(update, 10, 'searchFocus')
 
   searchBlur: (e) =>
-
     # delay to be able to "click/execute" x if query is ''
     update = =>
       if @searchInput.val().trim() is ''
@@ -519,4 +522,16 @@ class Navigation extends App.Controller
       return
     @navigate('#search')
 
+  # detect collapsed by both added class and screen size
+  isCollapsed: =>
+    @el.width() == 50
+
+  toggleClicked: (e) =>
+    @preventDefaultAndStopPropagation(e)
+
+    current = @isCollapsed()
+
+    @el.toggleClass('is-not-collapsed', current)
+    $('#app').toggleClass('navigation-collapsed', !current)
+
 App.Config.set('aaa_navigation', Navigation, 'Plugins')

+ 4 - 0
app/assets/javascripts/app/lib/mixins/popover_providable.coffee

@@ -47,6 +47,10 @@ InstanceMethods =
       popover.build(buildParams)
 
   renderPopovers: (buildParams = {}) ->
+    # do not render popovers for touch
+    if window.matchMedia('(pointer: coarse)').matches
+      return
+
     if !@initializedPopovers
       @initializePopovers()
 

+ 4 - 0
app/assets/javascripts/app/views/navigation.jst.eco

@@ -30,4 +30,8 @@
   <% if !_.isEmpty(@user): %>
     <ul class="user-menu navbar-items-personal"></ul>
   <% end %>
+  <a class="menu-item menu-item-navigation-toggle-button js-navigation-toggle-button" href="#">
+    <%- @Icon "double-arrow-right" %>
+    <%- @Icon "double-arrow-left" %>
+  </a>
 </div>

+ 2 - 0
app/assets/stylesheets/svg-dimensions.css

@@ -19,6 +19,8 @@
 .icon-dashboard { width: 24px; height: 24px; }
 .icon-diagonal-cross { width: 13px; height: 13px; }
 .icon-document { width: 16px; height: 16px; }
+.icon-double-arrow-left { width: 12.2px; height: 13px; }
+.icon-double-arrow-right { width: 12.2px; height: 13px; }
 .icon-download { width: 14px; height: 13px; }
 .icon-draft-modifier { width: 9px; height: 9px; }
 .icon-draggable { width: 16px; height: 16px; }

+ 45 - 17
app/assets/stylesheets/zammad.scss

@@ -181,6 +181,10 @@ $mobileNavigationWidthOpen: 220px;
 }
 
 @mixin phone {
+  #app.navigation-collapsed & {
+    @content;
+  }
+
   @media screen and (max-width: 767px) {
     @content;
   }
@@ -2717,9 +2721,7 @@ input.has-error {
 
     -webkit-overflow-scrolling: touch;
 
-    .navigation:hover ~ &,
-    .navigation.is-hovered ~ &,
-    .navigation:active ~ & {
+    .navigation.is-not-collapsed ~ & {
       transform: translateX(
         #{$mobileNavigationWidthOpen - $mobileNavigationWidth}
       );
@@ -4131,9 +4133,7 @@ footer {
 
     min-width: 0;
 
-    .navigation:hover ~ &,
-    .navigation.is-hovered ~ &,
-    .navigation:active ~ & {
+    .navigation.is-not-collapsed ~ & {
       @include bidi-style(left, $mobileNavigationWidthOpen, right, 0);
     }
   }
@@ -4307,6 +4307,8 @@ footer {
   width: $navigationWidth;
   background: var(--background-quaternary);
   position: relative;
+  container-name: navigation;
+  container-type: inline-size;
 
   @include phone {
     position: absolute;
@@ -4317,17 +4319,13 @@ footer {
     left: 0;
     width: $mobileNavigationWidth;
     height: 100%;
-    z-index: 1;
-    overflow-x: hidden;
-    overflow-y: auto;
+    z-index: 999;
 
     &:empty {
       display: none !important;
     }
 
-    &:hover,
-    &:active,
-    &.is-hovered {
+    &.is-not-collapsed {
       width: $mobileNavigationWidthOpen;
 
       .menu-item-name {
@@ -4338,6 +4336,18 @@ footer {
   }
 }
 
+@container navigation (width = 50px) {
+  .icon-double-arrow-left {
+    display: none;
+  }
+}
+
+@container navigation (width > 50px) {
+  .icon-double-arrow-right {
+    display: none;
+  }
+}
+
 .menu {
   padding: 0;
   margin: 0;
@@ -4425,7 +4435,7 @@ footer {
 
   .zammad-switch {
     @include phone {
-      .navigation:not(:hover, :active, .is-hovered) & {
+      .navigation:not(:hover, :active, .is-not-collapsed) & {
         position: absolute;
         right: 12px;
         top: 12px;
@@ -4451,6 +4461,20 @@ footer {
       }
     }
   }
+
+  &-navigation-toggle-button {
+    justify-content: center;
+    border-top: 1px solid rgba(213, 213, 216, 0.05);
+    border-bottom: none;
+
+    @include desktop {
+      display: none;
+    }
+
+    @container navigation (width > 50px) {
+      justify-content: flex-end;
+    }
+  }
 }
 
 .menu-item-icon {
@@ -4885,7 +4909,9 @@ footer {
   }
 }
 
-.search:not(.filled, .focused) input[type='search'] {
+.navigation:not(.is-not-collapsed)
+  .search:not(.filled, .focused)
+  input[type='search'] {
   @include phone {
     padding: 0;
   }
@@ -4958,7 +4984,7 @@ input[type='search']::-webkit-search-decoration {
 .search.filled {
   .search-holder {
     @include phone {
-      .navigation:not(:hover, :active, .is-hovered) & {
+      .navigation:not(:hover, :active, .is-not-collapsed) & {
         width: 210px;
       }
     }
@@ -4972,6 +4998,7 @@ input[type='search']::-webkit-search-decoration {
 }
 
 .search .logo {
+  display: inline-block;
   position: relative;
 
   @extend .u-clickable, .zIndex-3;
@@ -5203,7 +5230,8 @@ li.add .dropdown-nose {
   min-width: 0;
   left: 10px;
   right: 15px;
-  width: auto;
+  width: 235px;
+  box-shadow: 0 0 10px hsla(0, 0%, 0%, 0.28);
 
   &.selected-clue {
     position: absolute;
@@ -10636,7 +10664,7 @@ label + .wizard-buttonList {
 
 .horizontal-filters-switch {
   display: flex;
-  justify-content: end;
+  justify-content: flex-end;
 
   & > label {
     display: flex;

+ 10 - 0
public/assets/images/icons.svg

@@ -86,6 +86,16 @@
 </symbol><symbol id="icon-document" viewBox="0 0 16 16">
     <title>document</title>
     <path d="M2 1.638A.63.63 0 0 1 2.628 1h10.744c.343 0 .628.286.628.638v12.724a.63.63 0 0 1-.628.638H2.628A.635.635 0 0 1 2 14.362V1.638ZM4.665 3C4.298 3 4 3.222 4 3.5c0 .276.298.5.665.5h4.67c.367 0 .665-.222.665-.5 0-.276-.298-.5-.665-.5h-4.67Zm-.05 3C4.274 6 4 6.222 4 6.5c0 .276.275.5.614.5h6.772c.339 0 .614-.222.614-.5 0-.276-.275-.5-.614-.5H4.614Zm0 3C4.274 9 4 9.222 4 9.5c0 .276.275.5.614.5h6.772c.339 0 .614-.222.614-.5 0-.276-.275-.5-.614-.5H4.614Zm0 3c-.34 0-.615.222-.615.5 0 .276.275.5.614.5h6.772c.339 0 .614-.222.614-.5 0-.276-.275-.5-.614-.5H4.614Z" fill-rule="evenodd"/>
+</symbol><symbol id="icon-double-arrow-left" viewBox="0 0 12.2 13">
+    <g fill-rule="evenodd" clip-rule="evenodd">
+        <path d="M7 2.2 5.6.9 0 6.5l5.6 5.6L7 10.7 2.9 6.5z"/>
+        <path d="M12.2 2.2 10.7.9 5.2 6.5l5.5 5.6 1.5-1.4-4.1-4.2z"/>
+    </g>
+</symbol><symbol id="icon-double-arrow-right" viewBox="0 0 12.2 13">
+    <g fill-rule="evenodd" clip-rule="evenodd">
+        <path d="M5.2 2.2 6.6.9l5.6 5.6-5.6 5.6-1.4-1.4 4.1-4.2z"/>
+        <path d="M0 2.2 1.4.9 7 6.5l-5.6 5.6L0 10.7l4.1-4.2z"/>
+    </g>
 </symbol><symbol id="icon-download" viewBox="0 0 14 13">
     <title>download</title>
     <path d="M4 0v6h6V0H4Zm10 6H0l7 7 7-7Z" fill-rule="evenodd"/>

+ 7 - 0
public/assets/images/icons/double-arrow-left.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12.2px" height="13px" viewBox="0 0 12.2 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="double-arrow-left">
+  <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#50E3C2" points="7,2.2 5.6,0.9 0,6.5 5.6,12.1 7,10.7 2.9,6.5" />
+  <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#50E3C2" points="12.2,2.2 10.7,0.9 5.2,6.5 10.7,12.1 12.2,10.7 8.1,6.5" />
+</g>
+</svg>

+ 7 - 0
public/assets/images/icons/double-arrow-right.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12.2px" height="13px" viewBox="0 0 12.2 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="double-arrow-right">
+  <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#50E3C2" points="5.2,2.2 6.6,0.9 12.2,6.5 6.6,12.1 5.2,10.7 9.3,6.5" />
+  <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#50E3C2" points="0,2.2 1.4,0.9 7,6.5 1.4,12.1 0,10.7 4.1,6.5" />
+</g>
+</svg>

+ 47 - 0
spec/system/navigation_spec.rb

@@ -0,0 +1,47 @@
+# Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
+
+require 'rails_helper'
+
+RSpec.describe 'Navigation', type: :system do
+  before { visit '/' }
+
+  context 'when mobile', screen_size: :mobile do
+    it 'widens navigation bar on clicking button' do
+      expect { click '.js-navigation-toggle-button' }
+        .to change { navigation_collapsed? }
+        .to(false)
+    end
+
+    it 'widens navigation bar on opening search' do
+      expect { click '#global-search' }
+        .to change { navigation_collapsed? }
+        .to(false)
+    end
+  end
+
+  context 'when tablet', screen_size: :tablet do
+    it 'collapses navigation bar on clicking button' do
+      expect { click '.js-navigation-toggle-button' }
+        .to change { navigation_collapsed? }
+        .to(true)
+    end
+  end
+
+  context 'when desktop', screen_size: :desktop do
+    it 'does not show collapse button' do
+      expect(page).to have_no_css '.js-navigation-toggle-button'
+    end
+
+    it 'shows full navigation bar' do
+      expect(navigation_collapsed?).to be_falsey
+    end
+  end
+
+  def navigation_current_width
+    evaluate_script("$('#navigation').width()")
+  end
+
+  def navigation_collapsed?
+    navigation_current_width == 50
+  end
+end