#6797: Automatically collapse inactive sections in the sidenav

This commit is contained in:
Matt 2021-08-05 09:28:25 -07:00
parent 0ea9c65007
commit da67a35328
14 changed files with 75 additions and 5 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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 { 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
// plugins).
window.Collapse = Collapse;
window.Modal = Modal;
window.Toast = Toast;
window.Tooltip = Tooltip;

View File

@ -7,6 +7,11 @@ type Dict<T extends unknown = unknown> = Record<string, T>;
type Nullable<T> = T | null;
interface Window {
/**
* Bootstrap Collapse Instance.
*/
Collapse: typeof import('bootstrap').Collapse;
/**
* Bootstrap Modal Instance.
*/

View File

@ -1,8 +1,10 @@
import { Collapse } from 'bootstrap';
import { StateManager } from './state';
import { getElements, isElement } from './util';
type NavState = { pinned: boolean };
type BodyAttr = 'show' | 'hide' | 'hidden' | 'pinned';
type Section = [HTMLAnchorElement, InstanceType<typeof Collapse>];
class SideNav {
/**
@ -15,6 +17,16 @@ class SideNav {
*/
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) {
this.base = base;
this.state = new StateManager<NavState>(
@ -23,6 +35,7 @@ class SideNav {
);
this.init();
this.initSectionLinks();
this.initLinks();
}
@ -97,11 +110,17 @@ class SideNav {
}
}
/**
* Show the sidenav.
*/
private show(): void {
this.bodyAdd('show');
this.bodyRemove('hidden', 'hide');
}
/**
* Hide the sidenav and collapse all active nav sections.
*/
private hide(): void {
this.bodyAdd('hidden');
this.bodyRemove('pinned', 'show');
@ -131,6 +150,51 @@ class SideNav {
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
* 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 {
event.preventDefault();
if (this.bodyHas('hidden')) {

View File

@ -271,10 +271,6 @@
white-space: nowrap;
transition: $transition-100ms-ease-in-out;
// &.disabled {
// opacity: 0.8;
// }
&.active {
position: relative;
color: var(--nbx-sidebar-link-hover-bg);