mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
#6797: Automatically collapse inactive sections in the sidenav
This commit is contained in:
parent
0ea9c65007
commit
da67a35328
BIN
netbox/project-static/dist/config.js
vendored
BIN
netbox/project-static/dist/config.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/config.js.map
vendored
BIN
netbox/project-static/dist/config.js.map
vendored
Binary file not shown.
BIN
netbox/project-static/dist/jobs.js
vendored
BIN
netbox/project-static/dist/jobs.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/jobs.js.map
vendored
BIN
netbox/project-static/dist/jobs.js.map
vendored
Binary file not shown.
BIN
netbox/project-static/dist/lldp.js
vendored
BIN
netbox/project-static/dist/lldp.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/lldp.js.map
vendored
BIN
netbox/project-static/dist/lldp.js.map
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
BIN
netbox/project-static/dist/status.js
vendored
BIN
netbox/project-static/dist/status.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/status.js.map
vendored
BIN
netbox/project-static/dist/status.js.map
vendored
Binary file not shown.
@ -1,4 +1,4 @@
|
|||||||
import { Modal, Tab, Toast, Tooltip } from 'bootstrap';
|
import { Collapse, Modal, Tab, Toast, Tooltip } from 'bootstrap';
|
||||||
import Masonry from 'masonry-layout';
|
import Masonry from 'masonry-layout';
|
||||||
import { getElements } from './util';
|
import { getElements } from './util';
|
||||||
|
|
||||||
@ -6,6 +6,7 @@ type ToastLevel = 'danger' | 'warning' | 'success' | 'info';
|
|||||||
|
|
||||||
// Add common Bootstrap components to `window`, so they may be consumed globally (primarily for
|
// Add common Bootstrap components to `window`, so they may be consumed globally (primarily for
|
||||||
// plugins).
|
// plugins).
|
||||||
|
window.Collapse = Collapse;
|
||||||
window.Modal = Modal;
|
window.Modal = Modal;
|
||||||
window.Toast = Toast;
|
window.Toast = Toast;
|
||||||
window.Tooltip = Tooltip;
|
window.Tooltip = Tooltip;
|
||||||
|
5
netbox/project-static/src/global.d.ts
vendored
5
netbox/project-static/src/global.d.ts
vendored
@ -7,6 +7,11 @@ type Dict<T extends unknown = unknown> = Record<string, T>;
|
|||||||
type Nullable<T> = T | null;
|
type Nullable<T> = T | null;
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
|
/**
|
||||||
|
* Bootstrap Collapse Instance.
|
||||||
|
*/
|
||||||
|
Collapse: typeof import('bootstrap').Collapse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstrap Modal Instance.
|
* Bootstrap Modal Instance.
|
||||||
*/
|
*/
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import { Collapse } from 'bootstrap';
|
||||||
import { StateManager } from './state';
|
import { StateManager } from './state';
|
||||||
import { getElements, isElement } from './util';
|
import { getElements, isElement } from './util';
|
||||||
|
|
||||||
type NavState = { pinned: boolean };
|
type NavState = { pinned: boolean };
|
||||||
type BodyAttr = 'show' | 'hide' | 'hidden' | 'pinned';
|
type BodyAttr = 'show' | 'hide' | 'hidden' | 'pinned';
|
||||||
|
type Section = [HTMLAnchorElement, InstanceType<typeof Collapse>];
|
||||||
|
|
||||||
class SideNav {
|
class SideNav {
|
||||||
/**
|
/**
|
||||||
@ -15,6 +17,16 @@ class SideNav {
|
|||||||
*/
|
*/
|
||||||
private state: StateManager<NavState>;
|
private state: StateManager<NavState>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently active parent nav-link controlling a section.
|
||||||
|
*/
|
||||||
|
private activeLink: Nullable<HTMLAnchorElement> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All collapsible sections and their controlling nav-links.
|
||||||
|
*/
|
||||||
|
private sections: Section[] = [];
|
||||||
|
|
||||||
constructor(base: HTMLDivElement) {
|
constructor(base: HTMLDivElement) {
|
||||||
this.base = base;
|
this.base = base;
|
||||||
this.state = new StateManager<NavState>(
|
this.state = new StateManager<NavState>(
|
||||||
@ -23,6 +35,7 @@ class SideNav {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
|
this.initSectionLinks();
|
||||||
this.initLinks();
|
this.initLinks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,11 +110,17 @@ class SideNav {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the sidenav.
|
||||||
|
*/
|
||||||
private show(): void {
|
private show(): void {
|
||||||
this.bodyAdd('show');
|
this.bodyAdd('show');
|
||||||
this.bodyRemove('hidden', 'hide');
|
this.bodyRemove('hidden', 'hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the sidenav and collapse all active nav sections.
|
||||||
|
*/
|
||||||
private hide(): void {
|
private hide(): void {
|
||||||
this.bodyAdd('hidden');
|
this.bodyAdd('hidden');
|
||||||
this.bodyRemove('pinned', 'show');
|
this.bodyRemove('pinned', 'show');
|
||||||
@ -131,6 +150,51 @@ class SideNav {
|
|||||||
this.state.set('pinned', false);
|
this.state.set('pinned', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a section's controlling nav-link is clicked, update this instance's `activeLink`
|
||||||
|
* attribute and close all other sections.
|
||||||
|
*/
|
||||||
|
private handleSectionClick(event: Event): void {
|
||||||
|
event.preventDefault();
|
||||||
|
const element = event.target as HTMLAnchorElement;
|
||||||
|
this.activeLink = element;
|
||||||
|
this.closeInactiveSections();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all sections that are not associated with the currently active link (`activeLink`).
|
||||||
|
*/
|
||||||
|
private closeInactiveSections(): void {
|
||||||
|
for (const [link, collapse] of this.sections) {
|
||||||
|
if (link !== this.activeLink) {
|
||||||
|
link.classList.add('collapsed');
|
||||||
|
link.setAttribute('aria-expanded', 'false');
|
||||||
|
collapse.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize `bootstrap.Collapse` instances on all section collapse elements and add event
|
||||||
|
* listeners to the controlling nav-links.
|
||||||
|
*/
|
||||||
|
private initSectionLinks(): void {
|
||||||
|
for (const section of getElements<HTMLAnchorElement>(
|
||||||
|
'.navbar-nav .nav-item .nav-link[data-bs-toggle]',
|
||||||
|
)) {
|
||||||
|
if (section.parentElement !== null) {
|
||||||
|
const collapse = section.parentElement.querySelector<HTMLDivElement>('.collapse');
|
||||||
|
if (collapse !== null) {
|
||||||
|
const collapseInstance = new Collapse(collapse, {
|
||||||
|
toggle: false, // Don't automatically open the collapse element on invocation.
|
||||||
|
});
|
||||||
|
this.sections.push([section, collapseInstance]);
|
||||||
|
section.addEventListener('click', event => this.handleSectionClick(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starting from the bottom-most active link in the element tree, work backwards to determine the
|
* Starting from the bottom-most active link in the element tree, work backwards to determine the
|
||||||
* link's containing `.collapse` element and the `.collapse` element's containing `.nav-link`
|
* link's containing `.collapse` element and the `.collapse` element's containing `.nav-link`
|
||||||
@ -232,6 +296,10 @@ class SideNav {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle sidenav visibility state for small screens. On small screens, there is no pinned state,
|
||||||
|
* only open/closed.
|
||||||
|
*/
|
||||||
private onMobileToggle(event: Event): void {
|
private onMobileToggle(event: Event): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.bodyHas('hidden')) {
|
if (this.bodyHas('hidden')) {
|
||||||
|
@ -271,10 +271,6 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transition: $transition-100ms-ease-in-out;
|
transition: $transition-100ms-ease-in-out;
|
||||||
|
|
||||||
// &.disabled {
|
|
||||||
// opacity: 0.8;
|
|
||||||
// }
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--nbx-sidebar-link-hover-bg);
|
color: var(--nbx-sidebar-link-hover-bg);
|
||||||
|
Loading…
Reference in New Issue
Block a user