@angulogic/ng-sidebar
Version:
angular sidebar - standalone components
416 lines • 109 kB
JavaScript
import { Component, HostBinding, HostListener, Input, ViewChild, } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { AlIconComponent } from './al-icon/al-icon.component';
import { ThemeTogglerComponent } from './theme-toggler/theme-toggler.component';
import { TogglerDirective } from '../toggler.directive';
import * as i0 from "@angular/core";
import * as i1 from "../ng-sidebar.service";
import * as i2 from "@angular/common";
/**
* A dynamic and interactive sidebar component for Angular applications.
* It supports features such as menu navigation, search, favorites, and theme management.
*
* @export
* @class NgSidebarComponent
* @implements {DoCheck}
* @implements {OnInit}
* @implements {OnDestroy}
*/
export class NgSidebarComponent {
/**
* Sets the sidebar options and initializes the sidebar data.
* Ensures the default theme is applied if not provided.
*
* @param {SidebarModel} val - The sidebar configuration model.
*/
set options(val) {
this.sidebarData = this.ngSidebarService.initilazeSidebarData(val);
if (!val.options.theme) {
val.options.theme = 'light';
}
this.SIDEBAR_DATA = this.deepClone(this.sidebarData);
}
/**
* Applies the `.collapsed` class when the sidebar is collapsed.
*/
get isCollapsed() {
return !this.sidebarData.options.expand;
}
/**
* Applies the `.transition` class when the sidebar is transitioning.
*/
get isTransition() {
return !this.ngSidebarService.isResizing;
}
/**
* Applies the `.al-dark-theme` class when the dark theme is active.
*/
get isDarkTheme() {
return this.sidebarData.options.theme === 'dark';
}
/**
* Binds the sidebar's width dynamically based on its expanded state.
*/
get sidebarWidth() {
return this.sidebarData.options.expand
? `${this.sidebarData.options.width}px`
: this.sidebarData.options.viewMode === 'mobile'
? '0px'
: 'var(--collapse-width)';
}
/**
* Binds the sidebar's max width dynamically.
*/
get sidebarMaxWidth() {
return this.sidebarData.options.expand
? `${this.sidebarData.options.maxWidth}px`
: 'unset';
}
/**
* Binds the sidebar's min width dynamically.
*/
get sidebarMinWidth() {
return this.sidebarData.options.expand
? `${this.sidebarData.options.minWidth}px`
: 'unset';
}
/**
* Creates an instance of `NgSidebarComponent`.
*
* @param {NgSidebarService} ngSidebarService - The sidebar service for handling state and actions.
*/
constructor(ngSidebarService) {
this.ngSidebarService = ngSidebarService;
/**
* Stores the list of favorite menu items.
*/
this.favorites = [];
/**
* Adds the `.ng-sidebar` class to the component.
*/
this.ngSidebarClass = true;
}
/**
* Lifecycle hook that runs when the component initializes.
* It updates the list of favorite menu items.
*/
ngOnInit() {
this.updateFavorites();
}
ngAfterViewInit() {
// Listen for width changes using ResizeObserver.
if (this.sidebarRootRef) {
this.resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const width = entry.contentRect.width;
this.ngSidebarService.sidebarWidth$.next(width);
}
});
this.resizeObserver.observe(this.sidebarRootRef.nativeElement);
}
}
/**
* Lifecycle hook that detects changes and manages auto-positioning.
*/
ngDoCheck() {
if (this.sidebarData.options.autoPosition !==
this.ngSidebarService.autoPositionActive) {
this.sidebarData.options.autoPosition
? this.ngSidebarService.setAutoPosition()
: this.ngSidebarService.destroyAutoPosition();
}
}
/**
* Handles click events on the banner elements (logo or title).
* Triggers the `onClick` event if it exists in `bannerOptions`.
*
* @param {'logo' | 'title'} element - The clicked banner element.
*/
onBannerClick(element) {
this.sidebarData.bannerOptions?.onClick?.(element, this.sidebarRootRef.nativeElement);
}
/**
* Handles click events on the user profile elements (avatar or name).
* Triggers the `onClick` event if it exists in `userOptions`.
*
* @param {'avatar' | 'name'} element - The clicked user profile element.
*/
onUserClick(element) {
this.sidebarData.userOptions?.onClick?.(element, this.sidebarRootRef.nativeElement);
}
/**
* Handles search input events and filters the sidebar menu items.
* Triggers `onSearchStart` and `onSearchEnd` events if provided.
*
* @param {KeyboardEvent} event - The keyboard event triggered by the search input.
*/
async onSearch(event) {
const element = event.currentTarget;
const searchValue = element.value.trim();
let searchStartEvent = {
nativeElement: element,
searchValue: searchValue,
cancel: false,
};
if (this.sidebarData.searchOptions?.onSearchStart) {
await Promise.resolve(this.sidebarData.searchOptions.onSearchStart(searchStartEvent));
}
if (!searchStartEvent.cancel) {
let filteredResults = [];
if (searchValue.length > 0) {
filteredResults = this.ngSidebarService.searchByName(this.deepClone(this.SIDEBAR_DATA), searchValue);
if (filteredResults.length > 0) {
this.sidebarData.sidebarData = this.SIDEBAR_DATA.sidebarData.map(sidebarItem => {
const matchingItems = filteredResults.filter(item => sidebarItem.data.some(dataItem => dataItem.name === item.name));
const updateExpandedState = (item) => {
item.isExpanded = true;
if (item.children) {
item.children.forEach(updateExpandedState);
}
};
matchingItems.forEach(updateExpandedState);
return {
...sidebarItem,
data: [...new Set(matchingItems)],
};
});
this.sidebarData.sidebarData = this.sidebarData.sidebarData.filter(d => d.data.length > 0);
}
else {
this.sidebarData.sidebarData = [];
}
}
else {
this.sidebarData = this.deepClone(this.SIDEBAR_DATA);
this.ngSidebarService.sidebarData = this.sidebarData;
}
if (this.sidebarData.searchOptions?.onSearchEnd) {
let searchEndEvent = {
menuData: filteredResults,
nativeElement: this.sidebarRootRef.nativeElement,
};
this.sidebarData.searchOptions.onSearchEnd(searchEndEvent);
}
}
}
/**
* Resets the search input and restores the sidebar menu items.
* Triggers `onSearchStart` and `onSearchEnd` events if provided.
*
* @param {HTMLInputElement} searchInput - The search input element to clear.
*/
async onCancelSearch(searchInput) {
if (searchInput.value === '')
return;
searchInput.value = '';
let searchStartEvent = {
nativeElement: searchInput,
searchValue: searchInput.value,
cancel: false,
};
if (this.sidebarData.searchOptions?.onSearchStart) {
await Promise.resolve(this.sidebarData.searchOptions.onSearchStart(searchStartEvent));
}
if (!searchStartEvent.cancel) {
this.sidebarData = this.deepClone(this.SIDEBAR_DATA);
this.ngSidebarService.sidebarData = this.sidebarData;
if (this.sidebarData.searchOptions?.onSearchEnd) {
let searchEndEvent = {
menuData: this.SIDEBAR_DATA.sidebarData,
nativeElement: this.sidebarRootRef.nativeElement,
};
this.sidebarData.searchOptions.onSearchEnd(searchEndEvent);
}
}
}
/**
* Handles click events on menu items.
* If the item has children, it toggles the node.
* If the item has a route, it navigates to it.
*
* @param {MenuData} node - The clicked menu item.
* @param {MouseEvent} mouseEvent - The mouse event triggering the click.
*/
async onMenuClick(node, mouseEvent) {
let event = {
menuData: node,
cancel: false,
nativeElement: this.sidebarRootRef.nativeElement,
};
await Promise.resolve(node.onClick?.(event));
await Promise.resolve(this.sidebarData.options.onMenuNodeClick?.(event));
if (event.cancel)
return;
if (node.children && node.children.length > 0) {
this.nodeToggle(node, mouseEvent);
}
else if (node.route) {
this.ngSidebarService.router.navigate([node.route]);
}
}
/**
* Handles click events on favorite menu items.
* Triggers the `onClick` event if it exists for the menu item.
*
* @param {MenuData & { cancel: boolean }} favorite - The favorite menu item clicked.
*/
async onFavoriteClick(favorite) {
let event = {
menuData: favorite,
cancel: false,
nativeElement: this.sidebarRootRef.nativeElement,
};
await Promise.resolve(favorite.onClick?.(event));
if (event.cancel)
return;
}
/**
* Expands the sidebar when the mouse enters, if viewMode is set to "hover".
*/
onEnter() {
if (this.sidebarData.options.viewMode === 'hover' &&
!this.sidebarData.options.pinned) {
this.sidebarData.options.expand = true;
}
}
/**
* Collapses the sidebar when the mouse leaves, if viewMode is set to "hover".
*/
onLeave() {
if (this.sidebarData.options.viewMode === 'hover' &&
!this.sidebarData.options.pinned) {
this.sidebarData.options.expand = false;
}
}
/**
* Toggles the expansion state of a menu node.
* If the node is already expanded, it collapses it with an animation.
*
* @param {MenuData} node - The menu node to toggle.
* @param {MouseEvent} event - The mouse event triggering the toggle.
*/
async nodeToggle(node, event) {
let nodeTogglerClickEvent = {
menuData: node,
cancel: false,
nativeElement: this.sidebarRootRef.nativeElement,
};
await Promise.resolve(node.onToggle?.(nodeTogglerClickEvent));
if (nodeTogglerClickEvent.cancel)
return;
const nodeElement = event.currentTarget.querySelector('.node-toggler');
const parentNode = event.currentTarget;
if (node.isExpanded) {
nodeElement.classList.remove('expand');
Array.from(parentNode.parentElement.parentElement.children)
.filter(child => child.classList.contains('node'))
.forEach(child => child.classList.add('out-left'));
setTimeout(() => {
node.isExpanded = false;
}, 300);
}
else {
setTimeout(() => {
Array.from(parentNode.parentElement.parentElement.children)
.filter(child => child.classList.contains('node'))
.forEach(child => child.classList.add('in-left'));
}, 1);
node.isExpanded = true;
}
}
/**
* Adds or removes a menu item from the favorites list.
*
* @param {MenuData} node - The menu item to toggle as a favorite.
*/
onFavoriteNode(node) {
if (!this.favorites.some(fav => fav.name === node.name)) {
this.favorites.push(node);
}
else {
this.favorites = this.favorites.filter(fav => fav.name !== node.name);
}
}
/**
* Checks if a menu item is in the favorites list.
*
* @param {string} name - The name of the menu item to check.
* @returns {boolean} - True if the item is in favorites, false otherwise.
*/
isOnFav(name) {
return this.favorites.some(fav => fav.name === name);
}
/**
* Updates the favorites list by scanning the sidebar menu items.
*/
updateFavorites() {
this.favorites = [];
this.sidebarData.sidebarData.forEach((data) => {
if (data.data) {
this.collectFavorites(data.data);
}
});
}
/**
* Recursively collects favorite menu items from a list of nodes.
*
* @param {MenuData[]} nodes - The list of menu nodes to scan.
*/
collectFavorites(nodes) {
nodes.forEach(node => {
if (node.isFavorited) {
this.favorites.push(node);
}
if (node.children && node.children.length > 0) {
this.collectFavorites(node.children);
}
});
}
/**
* Toggles the pinned state of the sidebar.
* When pinned, the sidebar remains expanded regardless of hover interactions.
*/
togglePin() {
this.sidebarData.options.pinned = !this.sidebarData.options.pinned;
}
/**
* Performs a deep clone of an object to prevent reference issues.
*
* @template T
* @param {T} obj - The object to clone.
* @returns {T} - A deep copy of the input object.
*/
deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => this.deepClone(item));
}
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = this.deepClone(obj[key]);
}
}
return clonedObj;
}
ngOnDestroy() {
if (this.resizeObserver && this.sidebarRootRef) {
this.resizeObserver.unobserve(this.sidebarRootRef.nativeElement);
this.resizeObserver.disconnect();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgSidebarComponent, deps: [{ token: i1.NgSidebarService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: NgSidebarComponent, isStandalone: true, selector: "ng-sidebar", inputs: { options: "options", nodeContent: "nodeContent" }, host: { listeners: { "mouseenter": "onEnter()", "mouseleave": "onLeave()" }, properties: { "class.ng-sidebar": "this.ngSidebarClass", "class.collapsed": "this.isCollapsed", "class.transition": "this.isTransition", "class.al-dark-theme": "this.isDarkTheme", "style.width": "this.sidebarWidth", "style.maxWidth": "this.sidebarMaxWidth", "style.minWidth": "this.sidebarMinWidth" } }, viewQueries: [{ propertyName: "sidebarRootRef", first: true, predicate: ["sidebarRoot"], descendants: true }], ngImport: i0, template: "<!-- Sidebar Wrapper -->\r\n<div\r\n #sidebarRoot\r\n *ngIf=\"\r\n sidebarData.options && (sidebarData.options.expand || sidebarData.options.viewMode !== 'mobile')\r\n \">\r\n <!-- Sidebar Resizer -->\r\n <!-- Allows users to resize the sidebar when it is expanded -->\r\n <div\r\n *ngIf=\"sidebarData.options.resize && this.sidebarData.options.expand\"\r\n class=\"resizer\"\r\n (mousedown)=\"ngSidebarService.resize(sidebarData)\"\r\n [ngClass]=\"{ active: ngSidebarService.isResizing }\"></div>\r\n\r\n <!-- Sidebar Banner (Logo & Title) -->\r\n <div class=\"banner\">\r\n <div *ngIf=\"sidebarData.bannerOptions\" class=\"banner-container\">\r\n <!-- Sidebar Logo -->\r\n <al-icon\r\n *ngIf=\"sidebarData.bannerOptions.logo\"\r\n [icon]=\"sidebarData.bannerOptions.logo\"\r\n (click)=\"onBannerClick('logo')\"\r\n class=\"logo\" />\r\n\r\n <!-- Sidebar Title -->\r\n <div\r\n *ngIf=\"sidebarData.bannerOptions.title && sidebarData.options.expand\"\r\n class=\"title text-owerflow\"\r\n (click)=\"onBannerClick('title')\">\r\n {{ sidebarData.bannerOptions.title }}\r\n </div>\r\n </div>\r\n\r\n <!-- Sidebar Toggle Buttons -->\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'toggle'\"\r\n sidebarToggler\r\n class=\"toggle-icon\"\r\n [icon]=\"\r\n sidebarData.options.expand\r\n ? sidebarData.options.toggleCollapseIcon\r\n : sidebarData.options.toggleExpandIcon\r\n \" />\r\n\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'mobile'\"\r\n sidebarToggler\r\n class=\"toggle-icon mobile\"\r\n [icon]=\"sidebarData.options.closeIcon\" />\r\n\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'hover'\"\r\n (click)=\"togglePin()\"\r\n class=\"toggle-icon\"\r\n [icon]=\"\r\n sidebarData.options.pinned\r\n ? sidebarData.options.unpinIcon\r\n : sidebarData.options.pinIcon\r\n \" />\r\n\r\n <ng-content select=\"[al-sidebar-banner]\"></ng-content>\r\n </div>\r\n\r\n <!-- User Profile Section (Top) -->\r\n <ng-content select=\"[al-sidebar-top-user]\"></ng-content>\r\n <ng-container *ngIf=\"sidebarData.userOptions?.position == 'top'\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n userTemplate;\r\n context: { $implicit: sidebarData.userOptions }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Search Bar -->\r\n <div\r\n class=\"search\"\r\n *ngIf=\"sidebarData.options.search && sidebarData.options.expand\"\r\n [ngClass]=\"sidebarData.searchOptions?.cssClass\">\r\n <!-- Search Icon -->\r\n <al-icon class=\"search-icon\" icon=\"assets/icons/search.svg\" />\r\n\r\n <!-- Search Input -->\r\n <input\r\n #searchInput\r\n (keyup)=\"onSearch($event)\"\r\n [placeholder]=\"sidebarData.searchOptions?.placeholder ?? ''\"\r\n type=\"text\" />\r\n\r\n <!-- Cancel Search Button -->\r\n <al-icon\r\n *ngIf=\"searchInput.value.length > 0\"\r\n (click)=\"onCancelSearch(searchInput)\"\r\n class=\"cancel-icon\"\r\n icon=\"assets/icons/cancel.svg\"\r\n alt />\r\n\r\n <ng-content select=\"[al-sidebar-search]\"></ng-content>\r\n </div>\r\n\r\n <!-- Sidebar Menu Items -->\r\n <div class=\"menu-container\">\r\n <!-- Favorites Section -->\r\n <div\r\n *ngIf=\"favorites.length > 0 && sidebarData.options.expand\"\r\n class=\"wrapper favorites\">\r\n <div class=\"title\">{{ sidebarData.options.favoritesTitle }}</div>\r\n <ng-container\r\n *ngTemplateOutlet=\"dataTemplate; context: { $implicit: favorites }\">\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Sidebar Sections & Items -->\r\n <div\r\n [class]=\"'wrapper ' + data.cssClass\"\r\n [ngClass]=\"{\r\n collapsed:\r\n !sidebarData.options.expand &&\r\n sidebarData.options.viewMode !== 'mobile',\r\n }\"\r\n *ngFor=\"let data of sidebarData.sidebarData\">\r\n <ng-container *ngIf=\"data.visible\">\r\n <div *ngIf=\"sidebarData.options.expand\" class=\"title\">\r\n {{ data.title }}\r\n </div>\r\n\r\n <!-- Expanded Menu Items -->\r\n <ng-container *ngIf=\"sidebarData.options.expand\">\r\n <ng-container\r\n *ngTemplateOutlet=\"dataTemplate; context: { $implicit: data.data }\">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Collapsed Menu Items -->\r\n <ng-container\r\n *ngIf=\"\r\n !sidebarData.options.expand &&\r\n sidebarData.options.viewMode !== 'mobile'\r\n \">\r\n ...\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n collapseDataTemplate;\r\n context: { $implicit: data.data }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n <ng-content select=\"[al-sidebar-menu]\"></ng-content>\r\n </div>\r\n\r\n <!-- User Profile Section (Bottom) -->\r\n <ng-content select=\"[al-sidebar-bottom-user]\"></ng-content>\r\n <ng-container *ngIf=\"sidebarData.userOptions?.position == 'bottom'\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n userTemplate;\r\n context: { $implicit: sidebarData.userOptions }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Templates for Menu Items & User Profile -->\r\n <ng-template #dataTemplate let-nodes>\r\n <div class=\"node\" *ngFor=\"let node of nodes\">\r\n <div class=\"node-wrapper\">\r\n <div\r\n class=\"node-info\"\r\n (click)=\"onMenuClick(node, $event)\"\r\n [ngClass]=\"{ 'no-child': !node.children, active: node.active }\">\r\n <div class=\"node-title\">\r\n <div class=\"icon-container\">\r\n <al-icon [icon]=\"node.icon ?? 'assets/icons/circle-dot.svg'\" />\r\n </div>\r\n {{ node.name }}\r\n </div>\r\n <div class=\"node-icons\">\r\n <div *ngIf=\"node.children\">\r\n <al-icon\r\n class=\"node-toggler transition\"\r\n [ngClass]=\"{ expand: node.isExpanded }\"\r\n icon=\"assets/icons/node-toggle.svg\"></al-icon>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Favorite Icon -->\r\n <div class=\"favorite-container\" (click)=\"onFavoriteNode(node)\">\r\n <al-icon\r\n *ngIf=\"\r\n sidebarData.options.favorites &&\r\n (!node.children || (node.children && node.children?.length == 0))\r\n \"\r\n class=\"node-favorite\"\r\n [icon]=\"\r\n isOnFav(node.name)\r\n ? 'assets/icons/favorite.svg'\r\n : 'assets/icons/favorite-outline.svg'\r\n \"\r\n alt=\"Favorite\">\r\n </al-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- Expandable Child Nodes -->\r\n <ng-container\r\n *ngIf=\"node.children && node.children.length > 0 && node.isExpanded\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n dataTemplate;\r\n context: { $implicit: node.children }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- User Profile Template -->\r\n <ng-template #userTemplate let-user>\r\n <div\r\n class=\"user-container\"\r\n [ngClass]=\"{\r\n open: sidebarData.options.expand,\r\n close: !sidebarData.options.expand,\r\n }\">\r\n <div class=\"user\" [ngClass]=\"user.cssClass\">\r\n <al-icon [icon]=\"user.avatar\" />\r\n <div *ngIf=\"sidebarData.options.expand\" class=\"name\">\r\n {{ user.name }}\r\n </div>\r\n </div>\r\n <div class=\"theme-toggler\">\r\n <al-theme-toggler></al-theme-toggler>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Collapsed Sidebar Template -->\r\n <ng-template #collapseDataTemplate let-nodes>\r\n <ng-container *ngFor=\"let node of nodes\">\r\n <!-- Collapsed Menu Item Icon -->\r\n <al-icon\r\n *ngIf=\"node.icon\"\r\n class=\"collapsed-node-icon\"\r\n [icon]=\"node.icon\"></al-icon>\r\n\r\n <!-- Recursively Render Child Nodes in Collapsed State -->\r\n <ng-container\r\n *ngIf=\"node.children && node.children.length > 0 && node.isExpanded\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n collapseDataTemplate;\r\n context: { $implicit: node.children }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n </ng-template>\r\n</div>\r\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap\";::ng-deep .auto-position{margin-left:var(--sidebar-width);transition:margin 10ms ease}::ng-deep .no-select{-webkit-user-select:none;-ms-user-select:none;user-select:none}:host{--radius: 6px;--collapse-width: 100px;--sidebar-width: 250px;--bg-color: white;--text-color: #000000de;--primary: #1b84ff;--primary-2: #4394f1;--primary-3: #5c9be4;--secondary: #d6d6d6;--secondary-2: #e9e9e9;--secondary-3: #f3f3f3;--default-font-family: \"Inter\", sans-serif;--collapsed-banner-padding-right: 1rem;--resizer-color: var(--primary);--resizer-width: 5px;--resizer-height: 100%;--resizer-right: -3px;--resiver-top: 0;--toggle-border-radius: var(--radius);--toggle-border-color: var(--secondary);--toggle-bg-color: var(--bg-color);--toggle-border-width: 1px;--search-width: 100%;--search-icon-left: .75rem;--search-icon-top: 50%;--search-input-border-radius: var(--radius);--search-input-border-color: var(--secondary);--search-input-border-width: 1px;--search-input-bg-color: #f6f8fa;--search-input-color: #333333;--search-input-width: 100%;--search-input-height: 2rem;--search-input-padding: 0px 1.5rem;--search-input-margin: 0px .5rem;--search-cancel-icon-right: .75rem;--search-cancel-icon-top: 50%;--menu-padding: 0px 1rem;--menu-margin: 1rem 0px;--menu-height: 100%;--menu-wrapper-margin-bottom: 1rem;--menu-wrapper-collapsed-margin-bottom: 0px;--menu-wrapper-collapsed-color: var(--secondary);--menu-wrapper-title-font-size: 20px;--menu-wrapper-title-padding-bottom: .5rem;--menu-wrapper-node-margin-left: 1rem;--menu-wrapper-node-margin-top: .2rem;--menu-wrapper-node-wrapper-gap: 1rem;--menu-wrapper-node-wrapper-info-padding: .5rem;--menu-wrapper-node-wrapper-info-border-radius: var(--radius);--menu-wrapper-node-wrapper-active-background-color: var(--primary);--menu-wrapper-node-wrapper-active-color: var(--bg-color);--menu-wrapper-node-wrapper-after-width: 0px;--menu-wrapper-node-wrapper-after-height: 1px;--menu-wrapper-node-wrapper-after-background-color: var(--primary);--menu-wrapper-node-wrapper-after-bottom: 0;--menu-wrapper-node-wrapper-after-left: 10%;--menu-wrapper-node-wrapper-title-gap: .5rem;--menu-wrapper-node-wrapper-title-icon-width: 1rem;--menu-wrapper-node-wrapper-title-icon-height: 1rem;--menu-wrapper-node-wrapper-node-icons-gap: 1.5rem;--menu-wrapper-node-wrapper-favorite-node-margin: 0;--user-open-theme-toggler-margin-right: 1rem;--user-close-padding: 1rem;--user-gap: 1rem;--user-padding: 1rem;--user-icon-width: 2rem;--user-icon-height: 2rem;--user-icon-border-radius: 50%;--user-name-font-weight: bold;--user-name-font-size: large;--collapsed-node-icon-width: 2rem;--collapsed-node-icon-height: 2rem;--collapsed-node-icon-margin: .5rem;--collapsed-node-icon-color: var(--primary);--scrollbar-height: .4rem;--scrollbar-width: .4rem;--scrollbar-track-border-radius: var(--radius);--scrollbar-track-background-color: transparent;--scrollbar-thumb-border-radius: 5px;--scrollbar-thumb-background-color: var(--secondary);--scrollbar-thumb-hover-background-color: var(--secondary);--scrollbar-thumb-active-background-color: var(--secondary);--banner-height: 10%;--banner-gap: 1rem;--banner-padding: 1rem;--banner-padding-right: 3rem;--banner-logo-max-height: 80%;--banner-logo-width: auto;--banner-logo-height: 5rem;--banner-title-font-size: xx-large;--banner-toggle-icon-width: fit-content;--banner-toggle-icon-height: fit-content;--banner-toggle-icon-padding: .5rem;--banner-toggle-icon-right: 0;--banner-toggle-icon-top: 50%;font-family:var(--default-font-family);position:fixed;top:0;left:0;bottom:0;width:var(--sidebar-width);height:100vh;background:var(--bg-color);color:var(--text-color);z-index:1000;display:flex;flex-direction:column;border-right:1px solid var(--secondary)}:host.al-dark-theme{--bg-color: #121212;--text-color: #ffffffde;--search-bg-color: #333333;--search-color: #f6f8fa;--primary: #1b84ff;--primary-2: #3b94ff;--primary-3: #5c9be4;--secondary: #3a3a3a;--secondary-2: #444444;--secondary-3: #555555;--resizer-color: var(--primary);--search-input-bg-color: #333333;--search-input-color: #f6f8fa}:host.collapsed .menu-container{display:flex;flex-direction:column;align-items:center}:host.collapsed .user{justify-content:center}:host.collapsed .banner-container{margin:auto;padding-right:var(--collapsed-banner-padding-right)!important}:host .resizer{width:var(--resizer-width);cursor:col-resize;height:var(--resizer-height);position:absolute;right:var(--resizer-right);top:var(--resiver-top);background:transparent}:host .resizer.active{border-right:2px solid var(--resizer-color)}:host .banner{height:var(--banner-height);position:relative;display:flex}:host .banner .banner-container{height:-webkit-fill-available;display:flex;justify-content:start;gap:var(--banner-gap);padding:var(--banner-padding);padding-right:var(--banner-padding-right)}:host .banner .banner-container .logo{max-height:var(--banner-logo-max-height);width:var(--banner-logo-width);height:var(--banner-logo-height);align-self:center}:host .banner .banner-container .title{height:max-content;align-self:center;font-weight:700;font-size:var(--banner-title-font-size)}:host .banner .toggle-icon{width:var(--banner-toggle-icon-width);height:var(--banner-toggle-icon-height);padding:var(--banner-toggle-icon-padding);position:absolute;right:var(--banner-toggle-icon-right);top:var(--banner-toggle-icon-top);cursor:pointer;transform:translate(35%,-50%);background-color:var(--toggle-bg-color);border:var(--toggle-border-width) solid var(--toggle-border-color);border-radius:var(--toggle-border-radius)}:host .banner .toggle-icon.mobile{transform:translateY(-50%);border:none}:host .search{width:var(--search-width);position:relative;display:flex}:host .search .search-icon{position:absolute;left:var(--search-icon-left);top:var(--search-icon-top);transform:translateY(-50%)}:host .search input{width:var(--search-input-width);border:var(--search-input-border-width) solid var(--search-input-border-color);background-color:var(--search-input-bg-color);color:var(--search-input-color);height:var(--search-input-height);padding:var(--search-input-padding);border-radius:var(--search-input-border-radius);margin:var(--search-input-margin)}:host .search input:focus{outline:none}:host .search .cancel-icon{position:absolute;right:var(--search-cancel-icon-right);top:var(--search-cancel-icon-top);transform:translateY(-50%);cursor:pointer}:host .menu-container{padding:var(--menu-padding);margin:var(--menu-margin);height:var(--menu-height);overflow-y:scroll}:host .menu-container .wrapper{height:max-content;display:block;margin-bottom:var(--menu-wrapper-margin-bottom)}:host .menu-container .wrapper.collapsed{margin-bottom:var(--menu-wrapper-collapsed-margin-bottom);text-align:center;color:var(--menu-wrapper-collapsed-color)}:host .menu-container .wrapper .title{font-size:var(--menu-wrapper-title-font-size);font-weight:700;padding-bottom:var(--menu-wrapper-title-padding-bottom)}:host .menu-container .wrapper .node{margin-left:var(--menu-wrapper-node-margin-left);margin-top:var(--menu-wrapper-node-margin-top);position:relative;cursor:pointer}:host .menu-container .wrapper .node.in-left{animation:in-left .3s ease-in-out 0s 1 normal forwards}:host .menu-container .wrapper .node.out-left{animation:out-left .3s ease-in-out 0s 1 normal forwards}:host .menu-container .wrapper .node .node-wrapper{display:flex;align-items:center;justify-content:space-between;gap:var(--menu-wrapper-node-wrapper-gap)}:host .menu-container .wrapper .node .node-wrapper .node-info{flex:1;display:flex;justify-content:space-between;align-items:center;position:relative;padding:var(--menu-wrapper-node-wrapper-info-padding);border-radius:var(--menu-wrapper-node-wrapper-info-border-radius);transition:color .3s ease,background-color .3s ease}:host .menu-container .wrapper .node .node-wrapper .node-info.active{background-color:var(--menu-wrapper-node-wrapper-active-background-color);font-weight:700;color:var(--menu-wrapper-node-wrapper-active-color)}:host .menu-container .wrapper .node .node-wrapper .node-info:hover:not(.active){color:var(--primary)}:host .menu-container .wrapper .node .node-wrapper .node-info:hover:not(.active).no-child{background-color:var(--secondary)}:host .menu-container .wrapper .node .node-wrapper .node-info:hover:not(.active):after{width:80%}:host .menu-container .wrapper .node .node-wrapper .node-info:after{content:\"\";width:var(--menu-wrapper-node-wrapper-after-width);height:var(--menu-wrapper-node-wrapper-after-height);background-color:var(--menu-wrapper-node-wrapper-after-background-color);position:absolute;bottom:var(--menu-wrapper-node-wrapper-after-bottom);left:var(--menu-wrapper-node-wrapper-after-left);transition:all .3s ease}:host .menu-container .wrapper .node .node-wrapper .node-info .node-title{display:flex;gap:var(--menu-wrapper-node-wrapper-title-gap);align-items:center}:host .menu-container .wrapper .node .node-wrapper .node-info .node-title .icon-container{display:flex;width:var(--menu-wrapper-node-wrapper-title-icon-width);height:var(--menu-wrapper-node-wrapper-title-icon-height)}:host .menu-container .wrapper .node .node-wrapper .node-info .node-icons{display:flex;align-items:center;gap:var(--menu-wrapper-node-wrapper-node-icons-gap)}:host .menu-container .wrapper .node .node-wrapper .node-info .node-icons .node-toggler{transform:rotate(-180deg);z-index:2}:host .menu-container .wrapper .node .node-wrapper .node-info .node-icons .node-toggler.expand{transform:rotate(0)}:host .menu-container .wrapper .node .node-wrapper .favorite-container{display:flex;align-items:center}:host .menu-container .wrapper .node .node-wrapper .favorite-container .node-favorite{margin:var(--menu-wrapper-node-wrapper-favorite-node-margin)}:host .user-container.open{display:flex;align-items:center;justify-content:space-between}:host .user-container.open .theme-toggler{margin-right:var(--user-open-theme-toggler-margin-right)}:host .user-container.close{display:block;padding:var(--user-close-padding)}:host .user-container .user{display:flex;gap:var(--user-gap);padding:var(--user-padding);align-items:center}:host .user-container .user al-icon,:host .user-container .user ::ng-deep svg{width:var(--user-icon-width);height:var(--user-icon-height);border-radius:var(--user-icon-border-radius)}:host .user-container .user .name{font-weight:var(--user-name-font-weight);font-size:var(--user-name-font-size)}:host .collapsed-node-icon{margin:var(--collapsed-node-icon-margin);width:var(--collapsed-node-icon-width);height:var(--collapsed-node-icon-height);display:flex;justify-content:center;color:var(--collapsed-node-icon-color)}:host .collapsed-node-icon ::ng-deep svg{width:var(--collapsed-node-icon-width);height:var(--collapsed-node-icon-height)}:host *::-webkit-scrollbar{height:var(--scrollbar-height);width:var(--scrollbar-width)}:host *::-webkit-scrollbar-track{border-radius:var(--scrollbar-track-border-radius);background-color:var(--scrollbar-track-background-color)}:host *::-webkit-scrollbar-thumb{border-radius:var(--scrollbar-thumb-border-radius);background-color:var(--scrollbar-thumb-background-color)}:host *::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-background-color)}:host *::-webkit-scrollbar-thumb:active{background-color:var(--scrollbar-thumb-active-background-color)}:host.transition{transition:all .3s ease}.text-owerflow{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@keyframes in-left{0%{opacity:0;transform:translate(calc(var(--sidebar-width) * -1)) scale(.95)}60%{opacity:.8;transform:translate(10px) scale(1.02)}to{opacity:1;transform:translate(0) scale(1)}}@keyframes out-left{0%{opacity:1;transform:translate(0) scale(1)}40%{opacity:.6;transform:translate(-10px) scale(.98)}to{opacity:0;transform:translate(calc(var(--sidebar-width) * -1)) scale(.95)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: HttpClientModule }, { kind: "component", type: AlIconComponent, selector: "al-icon", inputs: ["icon"] }, { kind: "component", type: ThemeTogglerComponent, selector: "al-theme-toggler" }, { kind: "directive", type: TogglerDirective, selector: "[sidebarToggler]" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgSidebarComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-sidebar', standalone: true, imports: [
CommonModule,
HttpClientModule,
AlIconComponent,
ThemeTogglerComponent,
TogglerDirective
], template: "<!-- Sidebar Wrapper -->\r\n<div\r\n #sidebarRoot\r\n *ngIf=\"\r\n sidebarData.options && (sidebarData.options.expand || sidebarData.options.viewMode !== 'mobile')\r\n \">\r\n <!-- Sidebar Resizer -->\r\n <!-- Allows users to resize the sidebar when it is expanded -->\r\n <div\r\n *ngIf=\"sidebarData.options.resize && this.sidebarData.options.expand\"\r\n class=\"resizer\"\r\n (mousedown)=\"ngSidebarService.resize(sidebarData)\"\r\n [ngClass]=\"{ active: ngSidebarService.isResizing }\"></div>\r\n\r\n <!-- Sidebar Banner (Logo & Title) -->\r\n <div class=\"banner\">\r\n <div *ngIf=\"sidebarData.bannerOptions\" class=\"banner-container\">\r\n <!-- Sidebar Logo -->\r\n <al-icon\r\n *ngIf=\"sidebarData.bannerOptions.logo\"\r\n [icon]=\"sidebarData.bannerOptions.logo\"\r\n (click)=\"onBannerClick('logo')\"\r\n class=\"logo\" />\r\n\r\n <!-- Sidebar Title -->\r\n <div\r\n *ngIf=\"sidebarData.bannerOptions.title && sidebarData.options.expand\"\r\n class=\"title text-owerflow\"\r\n (click)=\"onBannerClick('title')\">\r\n {{ sidebarData.bannerOptions.title }}\r\n </div>\r\n </div>\r\n\r\n <!-- Sidebar Toggle Buttons -->\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'toggle'\"\r\n sidebarToggler\r\n class=\"toggle-icon\"\r\n [icon]=\"\r\n sidebarData.options.expand\r\n ? sidebarData.options.toggleCollapseIcon\r\n : sidebarData.options.toggleExpandIcon\r\n \" />\r\n\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'mobile'\"\r\n sidebarToggler\r\n class=\"toggle-icon mobile\"\r\n [icon]=\"sidebarData.options.closeIcon\" />\r\n\r\n <al-icon\r\n *ngIf=\"sidebarData.options.viewMode === 'hover'\"\r\n (click)=\"togglePin()\"\r\n class=\"toggle-icon\"\r\n [icon]=\"\r\n sidebarData.options.pinned\r\n ? sidebarData.options.unpinIcon\r\n : sidebarData.options.pinIcon\r\n \" />\r\n\r\n <ng-content select=\"[al-sidebar-banner]\"></ng-content>\r\n </div>\r\n\r\n <!-- User Profile Section (Top) -->\r\n <ng-content select=\"[al-sidebar-top-user]\"></ng-content>\r\n <ng-container *ngIf=\"sidebarData.userOptions?.position == 'top'\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n userTemplate;\r\n context: { $implicit: sidebarData.userOptions }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Search Bar -->\r\n <div\r\n class=\"search\"\r\n *ngIf=\"sidebarData.options.search && sidebarData.options.expand\"\r\n [ngClass]=\"sidebarData.searchOptions?.cssClass\">\r\n <!-- Search Icon -->\r\n <al-icon class=\"search-icon\" icon=\"assets/icons/search.svg\" />\r\n\r\n <!-- Search Input -->\r\n <input\r\n #searchInput\r\n (keyup)=\"onSearch($event)\"\r\n [placeholder]=\"sidebarData.searchOptions?.placeholder ?? ''\"\r\n type=\"text\" />\r\n\r\n <!-- Cancel Search Button -->\r\n <al-icon\r\n *ngIf=\"searchInput.value.length > 0\"\r\n (click)=\"onCancelSearch(searchInput)\"\r\n class=\"cancel-icon\"\r\n icon=\"assets/icons/cancel.svg\"\r\n alt />\r\n\r\n <ng-content select=\"[al-sidebar-search]\"></ng-content>\r\n </div>\r\n\r\n <!-- Sidebar Menu Items -->\r\n <div class=\"menu-container\">\r\n <!-- Favorites Section -->\r\n <div\r\n *ngIf=\"favorites.length > 0 && sidebarData.options.expand\"\r\n class=\"wrapper favorites\">\r\n <div class=\"title\">{{ sidebarData.options.favoritesTitle }}</div>\r\n <ng-container\r\n *ngTemplateOutlet=\"dataTemplate; context: { $implicit: favorites }\">\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Sidebar Sections & Items -->\r\n <div\r\n [class]=\"'wrapper ' + data.cssClass\"\r\n [ngClass]=\"{\r\n collapsed:\r\n !sidebarData.options.expand &&\r\n sidebarData.options.viewMode !== 'mobile',\r\n }\"\r\n *ngFor=\"let data of sidebarData.sidebarData\">\r\n <ng-container *ngIf=\"data.visible\">\r\n <div *ngIf=\"sidebarData.options.expand\" class=\"title\">\r\n {{ data.title }}\r\n </div>\r\n\r\n <!-- Expanded Menu Items -->\r\n <ng-container *ngIf=\"sidebarData.options.expand\">\r\n <ng-container\r\n *ngTemplateOutlet=\"dataTemplate; context: { $implicit: data.data }\">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Collapsed Menu Items -->\r\n <ng-container\r\n *ngIf=\"\r\n !sidebarData.options.expand &&\r\n sidebarData.options.viewMode !== 'mobile'\r\n \">\r\n ...\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n collapseDataTemplate;\r\n context: { $implicit: data.data }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n <ng-content select=\"[al-sidebar-menu]\"></ng-content>\r\n </div>\r\n\r\n <!-- User Profile Section (Bottom) -->\r\n <ng-content select=\"[al-sidebar-bottom-user]\"></ng-content>\r\n <ng-container *ngIf=\"sidebarData.userOptions?.position == 'bottom'\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n userTemplate;\r\n context: { $implicit: sidebarData.userOptions }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- Templates for Menu Items & User Profile -->\r\n <ng-template #dataTemplate let-nodes>\r\n <div class=\"node\" *ngFor=\"let node of nodes\">\r\n <div class=\"node-wrapper\">\r\n <div\r\n class=\"node-info\"\r\n (click)=\"onMenuClick(node, $event)\"\r\n [ngClass]=\"{ 'no-child': !node.children, active: node.active }\">\r\n <div class=\"node-title\">\r\n <div class=\"icon-container\">\r\n <al-icon [icon]=\"node.icon ?? 'assets/icons/circle-dot.svg'\" />\r\n </div>\r\n {{ node.name }}\r\n </div>\r\n <div class=\"node-icons\">\r\n <div *ngIf=\"node.children\">\r\n <al-icon\r\n class=\"node-toggler transition\"\r\n [ngClass]=\"{ expand: node.isExpanded }\"\r\n icon=\"assets/icons/node-toggle.svg\"></al-icon>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Favorite Icon -->\r\n <div class=\"favorite-container\" (click)=\"onFavoriteNode(node)\">\r\n <al-icon\r\n *ngIf=\"\r\n sidebarData.options.favorites &&\r\n (!node.children || (node.children && node.children?.length == 0))\r\n \"\r\n class=\"node-favorite\"\r\n [icon]=\"\r\n isOnFav(node.name)\r\n ? 'assets/icons/favorite.svg'\r\n : 'assets/icons/favorite-outline.svg'\r\n \"\r\n alt=\"Favorite\">\r\n </al-icon>\r\n </div>\r\n </div>\r\n\r\n <!-- Expandable Child Nodes -->\r\n <ng-container\r\n *ngIf=\"node.children && node.children.length > 0 && node.isExpanded\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n dataTemplate;\r\n context: { $implicit: node.children }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- User Profile Template -->\r\n <ng-template #userTemplate let-user>\r\n <div\r\n class=\"user-container\"\r\n [ngClass]=\"{\r\n open: sidebarData.options.expand,\r\n close: !sidebarData.options.expand,\r\n }\">\r\n <div class=\"user\" [ngClass]=\"user.cssClass\">\r\n <al-icon [icon]=\"user.avatar\" />\r\n <div *ngIf=\"sidebarData.options.expand\" class=\"name\">\r\n {{ user.name }}\r\n </div>\r\n </div>\r\n <div class=\"theme-toggler\">\r\n <al-theme-toggler></al-theme-toggler>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Collapsed Sidebar Template -->\r\n <ng-template #collapseDataTemplate let-nodes>\r\n <ng-container *ngFor=\"let node of nodes\">\r\n <!-- Collapsed Menu Item Icon -->\r\n <al-icon\r\n *ngIf=\"node.icon\"\r\n class=\"collapsed-node-icon\"\r\n [icon]=\"node.icon\"></al-icon>\r\n\r\n <!-- Recursively Render Child Nodes in Collapsed State -->\r\n <ng-container\r\n *ngIf=\"node.children && node.children.length > 0 && node.isExpanded\">\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n collapseDataTemplate;\r\n context: { $implicit: node.children }\r\n \">\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n </ng-template>\r\n</div>\r\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap\";::ng-deep .auto-position{margin-left:var(--sidebar-width);transition:margin 10ms ease}::ng-deep .no-select{-webkit-user-select:none;-ms-user-select:none;user-select:none}:host{--radius: 6px;--collapse-width: 100px;--sidebar-width: 250px;--bg-color: white;--text-color: #000000de;--primary: #1b84ff;--primary-2: #4394f1;--primary-3: #5c9be4;--secondary: #d6d6d6;--secondary-2: #e9e9e9;--secondary-3: #f3f3f3;--default-font-family: \"Inter\", sans-serif;--collapsed-banner-padding-right: 1rem;--resizer-color: var(--primary);--resizer-width: 5px;--resizer-height: 100%;--resizer-right: -3px;--resiver-top: 0;--toggle-border-radius: var(--radius);--toggle-border-color: var(--secondary);--toggle-bg-color: var(--bg-color);--toggle-border-width: 1px;--search-width: 100%;--search-icon-left: .75rem;--search-icon-top: 50%;--search-input-border-radius: var(--radius);--search-input-border-color: var(--secondary);--search-input-border-width: 1px;--search-input-bg-color: #f6f8fa;--search-input-color: #333333;--search-input-width: 100%;--search-input-height: 2rem;--search-input-padding: 0px 1.5rem;--search-input-margin: 0px .5rem;--search-cancel-icon-right: .75rem;--search-cancel-icon-top: 50%;--menu-padding: 0px 1rem;--menu-margin: 1rem 0px;--menu-height: 100%;--menu-wrapper-margin-bottom: 1rem;--menu-wrapper-collapsed-margin-bottom: 0px;--menu-wrapper-collapsed-color: var(--secondary);--menu-wrapper-title-font-size: 20px;--menu-wrapper-title-padding-bottom: .5rem;--menu-wrapper-node-margin-left: 1rem;--menu-wrapper-node-margin-top: .2rem;--menu-wrapper-node-wrapper-gap: 1rem;--menu-wrapper-node-wrapper-info-padding: .5rem;--menu-wrapper-node-wrapper-info-border-radius: var(--radius);--menu-wrapper-node-wrapper-active-background-color: var(--primary);--menu-wrapper-node-wrapper-active-color: var(--bg-color);--menu-wrapper-node-wrapper-after-width: 0px;--menu-wrapper-node-wrapper-after-height: 1px;--menu-wrapper-node-wrapper-after-background-color: var(--primary);--menu-wrapper-node-wrapper-after-bottom: 0;--menu-wrapper-node-wrapper-after-left: 10%;--menu-wrapper-node-wrapper-title-gap: .5rem;--menu-wrapper-node-wrapper-title-icon-width: 1rem;--menu-wrapper-node-wrapper-title-icon-height: 1rem;--menu-wrapper-node-wrapper-node-icons-gap: 1.5rem;--menu-wrapper-node-wrapper-favorite-node-margin: 0;--user-open-theme-toggler-margin-right: 1rem;--user-close-padding: 1rem;--user-gap: 1rem;--user-padding: 1rem;--user-icon-width: 2rem;--user-icon-height: 2rem;--user-icon-border-radius: 50%;--user-name-font-weight: bold;--user-name-font-size: large;--collapsed-node-icon-width: 2rem;--collapsed-node-icon-height: 2rem;--collapsed-node-icon-margin: .5rem;--collapsed-node-icon-color: var(--primary);--scrollbar-height: .4rem;--scrollbar-width: .4rem;--scrollbar-track-border-radius: var(--radius);--scrollbar-track-background-color: transparent;--scrollbar-thumb-border-radius: 5px;--scrollbar-thumb-background-color: var(--secondary);--scrollbar-thumb-hover-background-color: var(--secondary);--scrollbar-thumb-active-background-color: var(--secondary);--banner-height: 10%;--banner-gap: 1rem;--banner-padding: 1rem;--banner-padding-right: 3rem;--banner-logo-m