Improve Horizon nav sidebar
- Make menu responsive (hides on smaller screens, e,g, 1/2 laptop screen, tablet, mobile) - Add aria parameters for accessibility - Show current focus when navigating (accessibility) - Remove the blue outline when clicking links in Chrome - Makes menu less hideous Change-Id: I1cdfa079f0b371d1afddefa67d8a21e93abde9ee Implements: blueprint navigation-improvements Closes-Bug: 1315488 Closes-Bug: 1628274
This commit is contained in:
@@ -35,14 +35,15 @@ horizon.addInitFunction(horizon.selenium.init = function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
horizon.selenium.initSideBarHelpers = function() {
|
horizon.selenium.initSideBarHelpers = function() {
|
||||||
var $activeEntry = $('li.openstack-dashboard.active > ul.panel-collapse.in');
|
var $activeEntry = $('.openstack-dashboard-active > ul.panel-collapse.in');
|
||||||
var dashboardLoc = 'li.openstack-dashboard';
|
var dashboardLoc = '.openstack-dashboard';
|
||||||
var groupLoc = 'li.nav-header.panel';
|
var groupLoc = 'li.openstack-panel-group';
|
||||||
var activeCls = horizon.selenium.ACTIVE_CLS;
|
var activeCls = horizon.selenium.ACTIVE_CLS;
|
||||||
|
|
||||||
var $activeDashboard = $activeEntry.closest(dashboardLoc).toggleClass(activeCls);
|
var $activeDashboard = $activeEntry.closest(dashboardLoc).toggleClass(activeCls);
|
||||||
var $activeGroup = $activeEntry.find(
|
var $activeGroup = $activeEntry.find(
|
||||||
'li.nav-header.panel > ul.panel-collapse.in').closest(groupLoc).toggleClass(activeCls);
|
'li.openstack-panel-group > ul.panel-collapse.in'
|
||||||
|
).closest(groupLoc).toggleClass(activeCls);
|
||||||
|
|
||||||
function toggleActiveDashboard($dashboard) {
|
function toggleActiveDashboard($dashboard) {
|
||||||
if ($activeDashboard) {
|
if ($activeDashboard) {
|
||||||
|
29
horizon/static/horizon/js/horizon.sidebar.js
Normal file
29
horizon/static/horizon/js/horizon.sidebar.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
$(document).ready(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var $sidenav = $('#sidebar');
|
||||||
|
var $mask = $(document.createElement('div'))
|
||||||
|
.prop('id', 'sidebar-mask')
|
||||||
|
.appendTo($('#content_body'));
|
||||||
|
|
||||||
|
// Hamburger Happiness !!!
|
||||||
|
$(document).on('click', '#sidebar-toggle', function () {
|
||||||
|
$mask.toggleClass('on-screen');
|
||||||
|
$sidenav.toggleClass('on-screen');
|
||||||
|
});
|
||||||
|
});
|
@@ -3,11 +3,11 @@
|
|||||||
<ul id="sidebar-accordion" class="nav nav-pills nav-stacked">
|
<ul id="sidebar-accordion" class="nav nav-pills nav-stacked">
|
||||||
{% for dashboard, panel_info in components %}
|
{% for dashboard, panel_info in components %}
|
||||||
{% if user|has_permissions:dashboard %}
|
{% if user|has_permissions:dashboard %}
|
||||||
<li class="panel openstack-dashboard{% if current.slug == dashboard.slug %} active{% endif %}">
|
<li class="panel openstack-dashboard">
|
||||||
<a data-toggle="collapse"
|
<a data-toggle="collapse"
|
||||||
data-parent="#sidebar-accordion"
|
data-parent="#sidebar-accordion"
|
||||||
data-target="#sidebar-accordion-{{ dashboard.slug }}"
|
data-target="#sidebar-accordion-{{ dashboard.slug }}"
|
||||||
href="javascript:;"
|
aria-controls="sidebar-accordion-{{ dashboard.slug }}"
|
||||||
{% if current.slug != dashboard.slug %}
|
{% if current.slug != dashboard.slug %}
|
||||||
class="collapsed"
|
class="collapsed"
|
||||||
{% endif %}>
|
{% endif %}>
|
||||||
@@ -20,34 +20,30 @@
|
|||||||
{% with panels|has_permissions_on_list:user as filtered_panels %}
|
{% with panels|has_permissions_on_list:user as filtered_panels %}
|
||||||
{% if filtered_panels %}
|
{% if filtered_panels %}
|
||||||
{% if group.name %}
|
{% if group.name %}
|
||||||
<li class="nav-header panel">
|
<li class="panel openstack-panel-group">
|
||||||
<a data-toggle="collapse"
|
<a data-toggle="collapse"
|
||||||
data-parent="#sidebar-accordion-{{ dashboard.slug }}"
|
data-parent="#sidebar-accordion-{{ dashboard.slug }}"
|
||||||
data-target="#sidebar-accordion-{{ dashboard.slug }}-{{ group.slug }}"
|
data-target="#sidebar-accordion-{{ dashboard.slug }}-{{ group.slug }}"
|
||||||
href="javascript:;"
|
aria-controls="sidebar-accordion-{{ dashboard.slug }}-{{ group.slug }}"
|
||||||
{% if current.slug == dashboard.slug and current_panel_group != group.slug %}class="collapsed"
|
{% if current.slug == dashboard.slug and current_panel_group != group.slug %}class="collapsed"
|
||||||
{% elif current.slug != dashboard.slug and forloop.counter0 != 0 %}class="collapsed"{% endif %}>
|
{% elif current.slug != dashboard.slug and forloop.counter0 != 0 %}class="collapsed"{% endif %}>
|
||||||
<span class="nav-header-title">
|
{{ group.name }}
|
||||||
{{ group.name }}
|
<span class="openstack-toggle fa pull-right"></span>
|
||||||
<span class="openstack-toggle fa pull-right"></span>
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
<ul id="sidebar-accordion-{{ dashboard.slug }}-{{ group.slug }}"
|
{% endif %}
|
||||||
class="nav collapse panel-collapse
|
<div id="sidebar-accordion-{{ dashboard.slug }}-{{ group.slug }}"
|
||||||
|
class="list-group collapse
|
||||||
{% if current.slug == dashboard.slug and current_panel_group == group.slug %} in
|
{% if current.slug == dashboard.slug and current_panel_group == group.slug %} in
|
||||||
{% elif current.slug != dashboard.slug and forloop.counter0 == 0 %} in{% endif %}">
|
{% elif current.slug != dashboard.slug and forloop.counter0 == 0 %} in{% endif %}">
|
||||||
{% endif %}
|
|
||||||
{% for panel in filtered_panels %}
|
{% for panel in filtered_panels %}
|
||||||
<li class="panel openstack-panel{% if current.slug == dashboard.slug and current_panel == panel.slug %} active{% endif %}">
|
<a class="openstack-spin list-group-item openstack-panel {% if current.slug == dashboard.slug and current_panel == panel.slug %}active{% endif %}" href="{{ panel.get_absolute_url }}"
|
||||||
<a class="openstack-spin" href="{{ panel.get_absolute_url }}"
|
|
||||||
target="_self"
|
target="_self"
|
||||||
tabindex="{{ forloop.counter }}" >
|
tabindex="{{ forloop.counter }}" >
|
||||||
{{ panel.name }}
|
{{ panel.name }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% if group.name %}
|
{% if group.name %}
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{% load branding horizon i18n %}
|
{% load branding horizon i18n %}
|
||||||
|
|
||||||
<div id='sidebar'>
|
<nav id='sidebar'>
|
||||||
{% horizon_nav %}
|
{% horizon_nav %}
|
||||||
</div>
|
</nav>
|
||||||
|
@@ -13,20 +13,20 @@ $navbar-border-size: 1px !default;
|
|||||||
$navbar-true-height: $navbar-height + $navbar-border-size !default;
|
$navbar-true-height: $navbar-height + $navbar-border-size !default;
|
||||||
|
|
||||||
#main_content {
|
#main_content {
|
||||||
height: 100%;
|
|
||||||
min-width: $main-content-min-width;
|
|
||||||
padding-top: $navbar-true-height;
|
padding-top: $navbar-true-height;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
position: fixed;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content_body {
|
// Always show the side nav on larger screens
|
||||||
width: 100%;
|
@media(min-width: $screen-sm-min) {
|
||||||
padding-left: $sidebar-width;
|
#content_body {
|
||||||
|
padding-left: $sidebar-width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
margin-top: $padding-base-horizontal;
|
margin-top: $padding-base-horizontal;
|
||||||
}
|
}
|
||||||
|
@@ -1,63 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* This file defines the styling for side navigation in Horizon, which uses
|
||||||
|
* nested panels to utilise Bootstraps native JS handling for accordion menus
|
||||||
|
* in panels. However, Bootstrap does *not* natively support nested panels
|
||||||
|
* in its markup; to work around this, we remove the panel styling and inherit
|
||||||
|
* list group styling.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#sidebar-accordion {
|
||||||
|
width: $sidebar-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-mask {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
transition: all 0.3s ease 0s;
|
||||||
|
|
||||||
|
@media (max-width: $screen-sm-min) {
|
||||||
|
&.on-screen {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
min-width: $sidebar-width;
|
width: $sidebar-width;
|
||||||
z-index: 0;
|
|
||||||
|
// Make sure the side nav is always shown at larger screen sizes,
|
||||||
|
// regardless of previous state
|
||||||
|
@media (min-width: $screen-sm-min) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $screen-sm-min) {
|
||||||
|
transition: all 0.3s ease 0s;
|
||||||
|
position: fixed;
|
||||||
|
top: $navbar-height;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 3;
|
||||||
|
left: -$sidebar-width;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&.on-screen {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sets the arrow toggles for each dashboard list
|
// Sets the arrow toggles for each dashboard list
|
||||||
[data-toggle="collapse"] {
|
.openstack-toggle.fa {
|
||||||
.openstack-toggle.fa {
|
line-height: $line-height-computed;
|
||||||
line-height: $line-height-computed;
|
text-align: center;
|
||||||
width: $line-height-computed;
|
@include transition(transform 0.3s ease 0s);
|
||||||
height: $line-height-computed;
|
@extend .fa-chevron-down;
|
||||||
text-align: center;
|
}
|
||||||
@include transition(transform 0.3s ease 0s);
|
|
||||||
@extend .fa-chevron-down;
|
// Rotate the arrow toggle for closed panels
|
||||||
|
.collapsed > .openstack-toggle.fa {
|
||||||
|
@include rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove panel default styling for the side nav only
|
||||||
|
.panel {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.list-group-item {
|
||||||
|
border-radius: 0;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.collapsed {
|
// Use the list group styling for consistency. We use panels in the markup
|
||||||
.openstack-toggle.fa {
|
// for accordion, but style should be list-group.
|
||||||
@include rotate(-90deg);
|
> a {
|
||||||
|
color: $list-group-link-color;
|
||||||
|
background: $list-group-bg;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $list-group-hover-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Styles for the Dashboard Names
|
// Remove Chromes glowing blue border for focus. This should not affect
|
||||||
.openstack-dashboard {
|
// accessibility, as the tabs already have a focus effect.
|
||||||
& > a {
|
a:focus {
|
||||||
border-radius: $border-radius-base $border-radius-base 0 0;
|
outline: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Styles for the Panel Names
|
// Center align panel groups
|
||||||
|
.openstack-panel-group {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right align panels
|
||||||
.openstack-panel {
|
.openstack-panel {
|
||||||
& > a {
|
text-align: right;
|
||||||
text-align: right;
|
|
||||||
color: $text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active > a {
|
|
||||||
color: $brand-primary;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel {
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
@include box-shadow(none);
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bootstrap 3 removed nav headers, lets add them back
|
|
||||||
.nav .nav-header > a > .nav-header-title {
|
|
||||||
font-size: $font-size-base;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -7,6 +7,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta content='IE=edge' http-equiv='X-UA-Compatible' />
|
<meta content='IE=edge' http-equiv='X-UA-Compatible' />
|
||||||
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
{% include "horizon/_custom_meta.html" %}
|
{% include "horizon/_custom_meta.html" %}
|
||||||
<title>{% block title %}{% endblock %} - {% site_branding %}</title>
|
<title>{% block title %}{% endblock %} - {% site_branding %}</title>
|
||||||
{% comment %} Load CSS sheets before Javascript {% endcomment %}
|
{% comment %} Load CSS sheets before Javascript {% endcomment %}
|
||||||
|
@@ -5,13 +5,21 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!-- Brand and toggle get grouped for better mobile display -->
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
|
{% include "header/_brand.html" %}
|
||||||
|
|
||||||
|
<button id="sidebar-toggle" type="button" class="navbar-toggle pull-left">
|
||||||
|
<span class="sr-only">{% trans "Toggle navigation" %}</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
|
||||||
<span class="sr-only">{% trans "Toggle navigation" %}</span>
|
<span class="sr-only">{% trans "Toggle navigation" %}</span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
{% include "header/_brand.html" %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||||
|
@@ -50,6 +50,7 @@
|
|||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3barchart.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.d3barchart.js'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.firewalls.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.firewalls.js'></script>
|
||||||
<script src='{{ STATIC_URL }}horizon/js/horizon.volumes.js'></script>
|
<script src='{{ STATIC_URL }}horizon/js/horizon.volumes.js'></script>
|
||||||
|
<script src='{{ STATIC_URL }}horizon/js/horizon.sidebar.js'></script>
|
||||||
|
|
||||||
{% for file in HORIZON_CONFIG.js_files %}
|
{% for file in HORIZON_CONFIG.js_files %}
|
||||||
<script src='{{ STATIC_URL }}{{ file }}'></script>
|
<script src='{{ STATIC_URL }}{{ file }}'></script>
|
||||||
|
@@ -30,15 +30,15 @@ class NavigationAccordionRegion(baseregion.BaseRegion):
|
|||||||
return self._get_element(*self._project_bar_locator)
|
return self._get_element(*self._project_bar_locator)
|
||||||
|
|
||||||
_first_level_item_selected_locator = (
|
_first_level_item_selected_locator = (
|
||||||
by.By.CSS_SELECTOR, 'li.openstack-dashboard.selenium-active > a')
|
by.By.CSS_SELECTOR, '.openstack-dashboard-active.selenium-active > a')
|
||||||
_second_level_item_selected_locator = (
|
_second_level_item_selected_locator = (
|
||||||
by.By.CSS_SELECTOR, 'li.nav-header.selenium-active > a')
|
by.By.CSS_SELECTOR, 'li.openstack-panel-group.selenium-active > a')
|
||||||
|
|
||||||
_first_level_item_xpath_template = (
|
_first_level_item_xpath_template = (
|
||||||
"//li[contains(concat('', @class, ''), 'openstack-dashboard') "
|
"//li[contains(concat('', @class, ''), 'openstack-dashboard') "
|
||||||
"and contains(., '%s')]/a")
|
"and contains(., '%s')]/a")
|
||||||
_second_level_item_xpath_template = (
|
_second_level_item_xpath_template = (
|
||||||
"//li[contains(concat('', @class, ''), 'nav-header') "
|
"//li[contains(concat('', @class, ''), 'openstack-panel-group') "
|
||||||
"and contains(., '%s')]/a")
|
"and contains(., '%s')]/a")
|
||||||
_third_level_item_xpath_template = (
|
_third_level_item_xpath_template = (
|
||||||
".//li[contains(concat('', @class, ''), 'openstack-panel') and "
|
".//li[contains(concat('', @class, ''), 'openstack-panel') and "
|
||||||
|
@@ -52,6 +52,10 @@
|
|||||||
.openstack-panel > a {
|
.openstack-panel > a {
|
||||||
padding: $padding-small-horizontal $font-size-h4 $padding-small-horizontal ($font-size-h1 - $padding-small-horizontal);
|
padding: $padding-small-horizontal $font-size-h4 $padding-small-horizontal ($font-size-h1 - $padding-small-horizontal);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.openstack-toggle {
|
.openstack-toggle {
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<ul id="sidebar-drawer" class="nav nav-pills nav-stacked">
|
<ul id="sidebar-drawer" class="nav nav-pills nav-stacked">
|
||||||
{% for dashboard, panel_info in components %}
|
{% for dashboard, panel_info in components %}
|
||||||
{% if user|has_permissions:dashboard %}
|
{% if user|has_permissions:dashboard %}
|
||||||
<li class="openstack-dashboard{% if current.slug == dashboard.slug %} active{% endif %}">
|
<li class="openstack-dashboard">
|
||||||
<a data-toggle="collapse"
|
<a data-toggle="collapse"
|
||||||
data-parent="#sidebar-drawer"
|
data-parent="#sidebar-drawer"
|
||||||
data-target="#sidebar-drawer-{{ dashboard.slug }}"
|
data-target="#sidebar-drawer-{{ dashboard.slug }}"
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
{% elif current.slug != dashboard.slug and forloop.counter0 == 0 %} in{% endif %}">
|
{% elif current.slug != dashboard.slug and forloop.counter0 == 0 %} in{% endif %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for panel in filtered_panels %}
|
{% for panel in filtered_panels %}
|
||||||
<li class="openstack-panel{% if current.slug == dashboard.slug and current_panel == panel.slug %} active{% endif %}">
|
<li class="openstack-panel">
|
||||||
<a class="openstack-spin" href="{{ panel.get_absolute_url }}"
|
<a class="openstack-spin {% if current.slug == dashboard.slug and current_panel == panel.slug %}active{% endif %}" href="{{ panel.get_absolute_url }}"
|
||||||
target="_self"
|
target="_self"
|
||||||
tabindex="{{ forloop.counter }}" >
|
tabindex="{{ forloop.counter }}" >
|
||||||
{{ panel.name }}
|
{{ panel.name }}
|
||||||
|
Reference in New Issue
Block a user