Migrate to collapsed sidebar layout

This commit is contained in:
checktheroads 2021-07-26 14:46:05 -07:00
parent 51c1f4b214
commit 2d32aeb972
9 changed files with 353 additions and 142 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -359,6 +359,8 @@ div.title-container {
nav.search { nav.search {
background-color: var(--nbx-body-bg); background-color: var(--nbx-body-bg);
// Don't overtake dropdowns
z-index: 999;
form button.dropdown-toggle { form button.dropdown-toggle {
border-color: $input-border-color; border-color: $input-border-color;
font-weight: $input-group-addon-font-weight; font-weight: $input-group-addon-font-weight;
@ -374,6 +376,157 @@ nav.search {
} }
} }
main.layout {
display: flex;
flex-wrap: nowrap;
height: 100vh;
height: -webkit-fill-available;
max-height: 100vh;
overflow-x: auto;
overflow-y: hidden;
.sidenav {
width: 4.5rem;
background-color: var(--nbx-sidebar-bg);
border-right: 1px solid $border-color;
// TODO: Figure out how to make the menu vertically scroll properly.
// overflow-x: hidden;
// overflow-y: auto;
padding-bottom: 1.5rem;
z-index: 5000;
& {
-ms-overflow-style: none; // Internet Explorer 10+
scrollbar-width: none; // Firefox
}
&::-webkit-scrollbar {
display: none; // Safari and Chrome
}
.nav-link {
font-size: $font-size-lg;
border-radius: unset;
transition: color 0s;
@include media-breakpoint-up(sm) {
font-size: $font-size-sm;
}
@include media-breakpoint-up(md) {
font-size: $font-size-base;
}
@include media-breakpoint-up(lg) {
font-size: $font-size-lg;
}
@include media-breakpoint-up(xl) {
font-size: $h4-font-size;
}
&:hover:not(.active) {
background-color: $accordion-button-active-bg;
}
&:after {
display: none;
}
}
.nav-item {
position: relative;
.nav-label {
opacity: 0;
z-index: 0;
height: 100%;
display: flex;
padding: $spacer;
position: absolute;
align-items: center;
margin-left: 4.5rem;
pointer-events: none;
justify-content: flex-start;
font-weight: $font-weight-bold;
transition: opacity 0.1s ease-in-out, transform 0.12s ease-in-out, z-index 0.12s ease-in-out;
transform: translateX(-50px);
background-color: $accordion-button-active-bg;
color: $nav-link-color;
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
[data-netbox-color-mode='dark'] &[class] {
color: shade-color($primary, 75%);
}
}
&:hover .nav-label {
transform: translateX(-1px);
z-index: 99;
opacity: 1;
box-shadow: 1rem 0 2rem rgba($black, 0.15);
}
&:hover .nav-link {
color: $nav-link-color;
[data-netbox-color-mode='dark'] &[class] {
color: shade-color($primary, 50%);
}
}
}
.sidenav-logo {
position: relative;
& .sidenav-logo-reveal {
opacity: 0;
z-index: 0;
height: 100%;
width: max-content;
display: flex;
padding: $spacer;
position: absolute;
align-items: center;
justify-content: flex-start;
font-weight: $font-weight-bold;
transition: opacity 0.1s ease-in-out, transform 0.12s ease-in-out, z-index 0.12s ease-in-out;
transform: translateX(-100%);
background-color: var(--nbx-sidebar-bg);
border-bottom-right-radius: $border-radius;
}
&:hover .sidenav-logo-reveal {
transform: translateX(-1px);
z-index: 2000;
opacity: 1;
}
}
.dropdown {
.dropdown-header {
font-weight: $font-weight-bold;
text-transform: uppercase;
color: var(--nbx-sidebar-title-color);
font-size: $font-size-sm;
}
.dropdown-item-group {
display: inline-flex;
width: 100%;
justify-content: space-between;
align-items: center;
padding-right: map.get($spacers, 3);
&.disabled {
cursor: not-allowed;
}
}
.dropdown-item {
padding-left: map.get($spacers, 4);
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
}
}
}
}
main.login-container { main.login-container {
display: flex; display: flex;
height: calc(100vh - 4rem); height: calc(100vh - 4rem);
@ -425,7 +578,6 @@ h3.accordion-item-title,
h4.accordion-item-title, h4.accordion-item-title,
h5.accordion-item-title, h5.accordion-item-title,
h6.accordion-item-title { h6.accordion-item-title {
// padding: 0 0.5rem;
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
font-weight: $font-weight-bold; font-weight: $font-weight-bold;
text-transform: uppercase; text-transform: uppercase;
@ -474,7 +626,7 @@ li.dropdown-item.dropdown-item-btns {
height: calc(100vh - 48px); height: calc(100vh - 48px);
padding-top: 0.5rem; padding-top: 0.5rem;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ overflow-y: auto; // Scrollable contents if viewport is shorter than content.
} }
.navbar-brand { .navbar-brand {
@ -501,11 +653,9 @@ div.content-container {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; width: calc(100% - 4.5rem);
overflow-x: hidden;
@include media-breakpoint-up(md) { overflow-y: auto;
margin-left: $sidebar-width;
}
div.content { div.content {
flex: 1; flex: 1;
@ -527,7 +677,7 @@ div.content-container {
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 100; /* Behind the navbar */ z-index: 100; // Behind the navbar
border-right: 1px solid $border-color; border-right: 1px solid $border-color;
background-color: var(--nbx-sidebar-bg); background-color: var(--nbx-sidebar-bg);
max-height: 100%; max-height: 100%;
@ -962,13 +1112,17 @@ html {
// Shade the home page content background-color. // Shade the home page content background-color.
&[data-netbox-path='/'] { &[data-netbox-path='/'] {
.content-container, .content-container,
.search { .search
background-color: $gray-100; // ,.sidenav-logo-reveal
{
background-color: $gray-100 !important;
} }
&[data-netbox-color-mode='dark'] { &[data-netbox-color-mode='dark'] {
.content-container, .content-container,
.search { .search
background-color: $darkest; // ,.sidenav-logo-reveal
{
background-color: $darkest !important;
} }
} }
} }

View File

@ -175,13 +175,11 @@ $accordion-bg: transparent;
$accordion-border-color: $border-color; $accordion-border-color: $border-color;
$accordion-button-color: $accordion-color; $accordion-button-color: $accordion-color;
$accordion-button-bg: $accordion-bg; $accordion-button-bg: $accordion-bg;
$accordion-body-active-bg: rgba($blue-300, 0.2); $accordion-button-active-bg: shade-color($blue-300, 10%);
$accordion-button-active-bg: rgba($blue-300, 0.25); $accordion-button-active-color: shade-color($blue-500, 10%);
$accordion-button-active-color: $gray-300;
$accordion-button-focus-border-color: $input-focus-border-color; $accordion-button-focus-border-color: $input-focus-border-color;
$accordion-icon-color: $accordion-color; $accordion-icon-color: $accordion-color;
$accordion-icon-active-color: $accordion-button-active-color; $accordion-icon-active-color: $accordion-button-active-color;
$accordion-button-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-color}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>"); $accordion-button-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-color}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>");
$accordion-button-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-active-color}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>"); $accordion-button-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-active-color}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>");

View File

@ -6,83 +6,19 @@
{% load static %} {% load static %}
{% block layout %} {% block layout %}
<div class="container-fluid px-0"> <div class="container-fluid px-0">
<main class="ms-sm-auto"> <main class="layout">
{# Sidebar #} {# Sidebar #}
<nav id="sidebar-menu" class="d-md-block sidebar collapse px-0" data-simplebar> {% include 'base/sidebar.html' %}
{# Sidebar content #}
<div class="position-sticky">
{# Logo #}
<div class="py-2">
<a class="sidebar-logo d-none d-md-flex justify-content-center" href="{% url 'home' %}">
<img src="{% static 'netbox_logo.svg' %}" alt="NetBox logo" />
</a>
</div>
<ul class="nav flex-column">
{# Search bar for collapsed menu #}
<div class="d-block d-md-none mx-1 my-3 search-container">
{% search_options %}
</div>
<div class="d-flex d-md-none mx-1 my-3 justify-content-center justify-content-md-end order-last order-md-0">
{% include 'inc/profile_button.html' %}
</div>
{# Navigation menu #}
{% nav %}
</ul>
</div>
{# Sidebar footer #}
<div class="d-flex flex-column container-fluid mt-auto justify-content-end sidebar-bottom">
<nav class="nav">
{# Documentation #}
<a type="button" class="nav-link" href="{% static 'docs/' %}" target="_blank">
<i title="Docs" class="mdi mdi-book-open-variant text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
</a>
{# REST API #}
<a type="button" class="nav-link" href="{% url 'api-root' %}" target="_blank">
<i title="REST API" class="mdi mdi-code-braces text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
</a>
{# API docs #}
<a type="button" class="nav-link" href="{% url 'api_docs' %}" target="_blank">
<i title="REST API documentation" class="mdi mdi-book text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
</a>
{# GraphQL API #}
{% if settings.GRAPHQL_ENABLED %}
<a type="button" class="nav-link" href="{% url 'graphql' %}" target="_blank">
<i title="GraphQL API" class="mdi mdi-graphql text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
</a>
{% endif %}
{# GitHub #}
<a type="button" class="nav-link" href="https://github.com/netbox-community/netbox" target="_blank">
<i title="Source Code" class="mdi mdi-github text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
</a>
{# NetDev Slack #}
<a type="button" class="nav-link" href="https://netdev.chat/" target="_blank">
<i title="Community" class="mdi mdi-slack text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
</a>
</nav>
</div>
</nav>
{# Body #} {# Body #}
<div class="content-container"> <div class="content-container">
{# Top bar #} {# Top bar #}
<nav class="navbar navbar-light sticky-top flex-md-nowrap p-3 search container-fluid"> <nav class="navbar navbar-light sticky-top flex-md-nowrap ps-6 p-3 search container-fluid">
{# Mobile Navigation #}
<div class="d-md-none w-100 d-flex justify-content-between align-items-center my-3"> <div class="d-md-none w-100 d-flex justify-content-between align-items-center my-3">
<a class="p-2 sidebar-logo d-block d-md-none" href="{% url 'home' %}"> <a class="p-2 sidebar-logo d-block d-md-none" href="{% url 'home' %}">
<img src="{% static 'netbox_logo.svg' %}" alt="NetBox logo" width="100%" /> <img src="{% static 'netbox_logo.svg' %}" alt="NetBox logo" width="100%" />
@ -99,10 +35,25 @@
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
</div> </div>
<div class="d-none d-md-flex w-100 search-container">
{# Desktop Navigation #}
<div class="d-none d-md-flex w-100 row search-container">
{# Empty spacer column to ensure search is centered. #}
<div class="col-3 d-flex flex-grow-1 ps-0"></div>
{# Search bar #}
<div class="col-6 d-flex flex-grow-1 justify-content-center">
{% search_options %} {% search_options %}
</div>
{# Proflie/login button #}
<div class="col-3 d-flex flex-grow-1 pe-0 justify-content-end">
{% include 'inc/profile_button.html' %} {% include 'inc/profile_button.html' %}
</div> </div>
</div>
</nav> </nav>
{% if settings.BANNER_TOP %} {% if settings.BANNER_TOP %}

View File

@ -0,0 +1,93 @@
<div class="dropdown border-top dropend">
<a href="#" class="nav-link dropdown-toggle py-3" data-bs-toggle="dropdown" aria-expanded="false">
<i class="mdi mdi-account"></i>
{% comment %} <span>{{ request.user|truncatechars:"30" }}</span> {% endcomment %}
</a>
<ul class="dropdown-menu shadow">
<li>
<button type="button" class="dropdown-item color-mode-toggle">
<i class="color-mode-icon mdi mdi-lightbulb"></i>&nbsp;
<span class="color-mode-text">Dark Mode</span>
</button>
</li>
<li>
{% if request.user.is_staff %}
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="mdi mdi-cog"></i> Admin
</a>
{% endif %}
</li>
<li>
<a class="dropdown-item" href="{% url 'user:profile' %}">
<i class="mdi mdi-account"></i> Profile & Settings
</a>
</li>
<li><hr class="dropdown-divider" /></li>
<li>
<a class="dropdown-item text-danger" href="{% url 'logout' %}">
<i class="mdi mdi-logout-variant"></i> Log Out
</a>
</li>
</ul>
</div>
{% comment %} {% if request.user.is_authenticated %}
<span class="dropdown ms-0 ms-md-3 profile-button">
<button
type="button"
aria-expanded="false"
data-bs-toggle="dropdown"
class="btn btn-outline-secondary dropdown-toggle"
>
<i class="mdi mdi-account"></i>
<span id="navbar_user">{{ request.user|truncatechars:"30" }}</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button type="button" class="dropdown-item color-mode-toggle">
<i class="color-mode-icon mdi mdi-lightbulb"></i>&nbsp;
<span class="color-mode-text">Dark Mode</span>
</button>
</li>
<li>
{% if request.user.is_staff %}
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="mdi mdi-cog"></i> Admin
</a>
{% endif %}
</li>
<li>
<a class="dropdown-item" href="{% url 'user:profile' %}">
<i class="mdi mdi-account"></i> Profile & Settings
</a>
</li>
<li><hr class="dropdown-divider" /></li>
<li>
<a class="dropdown-item text-danger" href="{% url 'logout' %}">
<i class="mdi mdi-logout-variant"></i> Log Out
</a>
</li>
</ul>
</span>
{% else %}
<div class="btn-group ms-0 ms-md-3">
<a
class="btn btn-primary ws-nowrap"
type="button"
href="{% url 'login' %}"
>
<i class="mdi mdi-login-variant"></i> Log In
</a>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button class="dropdown-item color-mode-toggle">
<i class="color-mode-icon mdi mdi-lightbulb"></i>&nbsp;
<span class="color-mode-text">Dark Mode</span>
</button>
</li>
</ul>
</div>
{% endif %} {% endcomment %}

View File

@ -0,0 +1,24 @@
{% load nav %}
{% load static %}
<div class="d-flex flex-column flex-shrink-0 sidenav">
{# Logo Container #}
<div class="sidenav-logo">
{# Full logo, hidden until icon is hovered. #}
<a class="sidenav-logo-reveal" href="{% url 'home' %}">
<img src="{% static 'netbox_logo.svg' %}" alt="NetBox Logo" height="39px" />
</a>
{# Logo Icon #}
<a href="/" class="sidenav-logo-icon d-block p-3 link-dark text-decoration-none">
<img src="{% static 'netbox_icon.svg' %}" />
</a>
</div>
{# Navigation Items #}
{% nav %}
</div>

View File

@ -1,43 +1,39 @@
{% load helpers %} {% load helpers %}
<div id="sidenav-accordion" class="accordion accordion-flush nav-item"> <ul class="nav nav-pills nav-flush flex-column mb-auto text-center">
{% for menu in nav_items %} {% comment %} <li class="nav-item">
<a href="/" class="nav-link active py-3" aria-current="page" title="Home" data-bs-toggle="tooltip" data-bs-placement="right">
<i class="mdi mdi-home"></i>
</a>
</li> {% endcomment %}
{# Main Collapsible Menu #} {% for menu in nav_items %}
<div class="accordion-item"> <li class="nav-item sidenav-dropdown">
<a <div class="nav-label">
href="#"
role="button"
aria-expanded="true"
data-bs-toggle="collapse"
data-bs-target="#{{ menu.label|lower }}"
class="d-flex justify-content-between align-items-center accordion-button nav-link collapsed">
<span class="fw-bold sidebar-nav-link">
<i class="{{ menu.icon_class }} me-1 opacity-50"></i>
{{ menu.label }} {{ menu.label }}
</span> </div>
<div class="dropdown dropend" title="{{ menu.label }}">
<a href="#" class="nav-link py-3 dropdown-toggle" id="menu{{ menu.label }}" data-bs-toggle="dropdown" aria-expanded="false">
<i class="{{ menu.icon_class }}"></i>
</a> </a>
<div id="{{ menu.label|lower }}" class="accordion-collapse collapse" data-bs-parent="#sidenav-accordion"> <ul class="dropdown-menu shadow" aria-labelledby="menu{{ menu.label }}">
<div class="multi-level accordion-body px-0"> <li><h4 class="dropdown-header text-dark">{{ menu.label }}</h4>{{ menu.has_link }}</li>
<hr class="dropdown-divider" />
{% for group in menu.groups %} {% for group in menu.groups %}
{# Within each main menu, there are groups of menu items #} {# Within each main menu, there are groups of menu items #}
<div class="flex-column nav">
<h6 class="accordion-item-title">{{ group.label }}</h6> {% if group.label != menu.label %}
<li><h6 class="dropdown-header">{{ group.label }}</h6></li>
{% endif %}
{% for item in group.items %} {% for item in group.items %}
{# Each Menu Item #} {# Each Menu Item #}
<div class="nav-item d-flex justify-content-between align-items-center">
{# Menu Link with Text #}
{% if request.user|has_perms:item.permissions %} {% if request.user|has_perms:item.permissions %}
<li class="dropdown-item-group">
<a class="nav-link flex-grow-1" href="{% url item.link %}"> <a class="dropdown-item" href="{% url item.link %}">{{ item.link_text }}</a>
{{ item.link_text }}
</a>
{# Menu item buttons (if any) #} {# Menu item buttons (if any) #}
{% if item.buttons %} {% if item.buttons %}
<div class="btn-group ps-1"> <div class="btn-group ps-1">
@ -50,30 +46,25 @@
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</li>
{% else %} {% else %}
{# Display a disabled link (no permission) #} {# Display a disabled link (no permission) #}
<a class="nav-link flex-grow-1 disabled"> <li class="dropdown-item-group disabled">
<a class="dropdown-item disabled" href="#" aria-disabled="true" disabled>
{{ item.link_text }} {{ item.link_text }}
</a> </a>
</li>
{% endif %} {% endif %}
</div>
{% endfor %} {% endfor %}
</div>
{# Show a divider if not the last group #} {# Show a divider if not the last group #}
{% if forloop.counter != menu.groups|length %} {% if forloop.counter != menu.groups|length %}
<hr class="dropdown-divider my-2" /> <hr class="dropdown-divider my-2" />
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul>
</div> </div>
</div> </li>
</div>
{% endfor %} {% endfor %}
</div>
</ul>

View File

@ -1,4 +1,4 @@
<form class="input-group w-100" action="{% url 'search' %}" method="get"> <form class="input-group" action="{% url 'search' %}" method="get">
<input <input
name="q" name="q"
type="text" type="text"