Enhance the left sidebar with dropdown toggles for expandable menu items

This commit is contained in:
aditya.chandel
2025-07-23 23:35:49 -06:00
committed by Aditya Chandel
parent b52e771488
commit f3f59155ed
4 changed files with 69 additions and 66 deletions

View File

@@ -6,9 +6,6 @@
@if (!item.separator) {
<li app-menuitem [item]="item" [index]="i" [root]="true"></li>
}
@if (item.separator) {
<li class="menu-separator"></li>
}
}
}
</ul>
@@ -19,9 +16,6 @@
@if (!item.separator) {
<li app-menuitem [item]="item" [index]="i" [root]="true"></li>
}
@if (item.separator) {
<li class="menu-separator"></li>
}
}
}
</ul>
@@ -32,9 +26,6 @@
@if (!item.separator) {
<li app-menuitem [item]="item" [index]="i" [root]="true"></li>
}
@if (item.separator) {
<li class="menu-separator"></li>
}
}
}
</ul>

View File

@@ -11,7 +11,6 @@ import {LibraryShelfMenuService} from '../../../book/service/library-shelf-menu.
import {AppVersion, VersionService} from '../../../core/service/version.service';
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
import {VersionChangelogDialogComponent} from './version-changelog-dialog/version-changelog-dialog.component';
import {AppSettingsService} from '../../../core/service/app-settings.service';
import {UserService} from '../../../settings/user-management/user.service';
@Component({
@@ -41,6 +40,8 @@ export class AppMenuComponent implements OnInit {
shelfSortField: 'name' | 'id' = 'name';
shelfSortOrder: 'asc' | 'desc' = 'asc';
ngOnInit(): void {
this.versionService.getVersion().subscribe((data) => {
this.versionInfo = data;
@@ -64,7 +65,6 @@ export class AppMenuComponent implements OnInit {
map((bookState) => [
{
label: 'Home',
separator: false,
items: [
{
label: 'Dashboard',
@@ -89,11 +89,10 @@ export class AppMenuComponent implements OnInit {
map((state) => {
const libraries = state.libraries ?? [];
const sortedLibraries = this.sortArray(libraries, this.librarySortField, this.librarySortOrder);
return [
{
label: 'Library',
separator: false,
hasDropDown: true,
items: sortedLibraries.map((library) => ({
menu: this.libraryShelfMenuService.initializeLibraryMenuItems(library),
label: library.name,
@@ -132,7 +131,7 @@ export class AppMenuComponent implements OnInit {
return [
{
label: 'Shelves',
separator: false,
hasDropDown: true,
items: [unshelvedItem, ...shelfItems],
},
];

View File

@@ -1,43 +1,43 @@
<ng-container>
@if (root && item.visible !== false) {
<div class="layout-menuitem-root-text">{{ item.label }}</div>
}
@if ((!item.routerLink || item.items) && item.visible !== false) {
<a [attr.href]="item.url"
(click)="itemClick($event)"
[ngClass]="item.class" [attr.target]="item.target" tabindex="0" pRipple>
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text">
{{ item.label }}
</span>
@if (item.items) {
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
}
</a>
}
<div class="flex flex-row items-center justify-between">
@if ((item.routerLink && !item.items) && item.visible !== false) {
<a (click)="itemClick($event)"
[ngClass]="item.class"
[routerLink]="item.routerLink" routerLinkActive="active-route"
[routerLinkActiveOptions]="item.routerLinkActiveOptions || { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' }"
[fragment]="item.fragment" [queryParamsHandling]="item.queryParamsHandling"
[preserveFragment]="item.preserveFragment"
[skipLocationChange]="item.skipLocationChange" [replaceUrl]="item.replaceUrl" [state]="item.state"
[queryParams]="item.queryParams"
[attr.target]="item.target" tabindex="0" pRipple>
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text w-full truncate">
{{ item.label }}
</span>
@if (item.items) {
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
}
</a>
@if (root) {
@if (item.hasDropDown) {
<div class="flex items-center justify-between layout-menuitem-root-text cursor-pointer pt-4" (click)="toggleExpand(key)">
<div>{{ item.label }}</div>
<i [ngClass]="{
'pi': true,
'pi-angle-up': isExpanded(key),
'pi-angle-down': !isExpanded(key),
'text-surface-500': true,
'hover:text-surface-200': true,
'pr-3': true
}"></i>
</div>
} @else {
<div class="flex items-center justify-between layout-menuitem-root-text">
<div>{{ item.label }}</div>
</div>
}
@if ((item.routerLink && !item.items) && item.visible !== false) {
}
<div>
<div class="flex flex-row items-center justify-between hover:bg-surface-800 rounded-md">
@if ((item.routerLink && !item.items)) {
<a
(click)="itemClick($event)"
[ngClass]="item.class"
[routerLink]="item.routerLink" routerLinkActive="active-route"
[routerLinkActiveOptions]="item.routerLinkActiveOptions || { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' }"
[fragment]="item.fragment" [queryParamsHandling]="item.queryParamsHandling"
[preserveFragment]="item.preserveFragment"
[skipLocationChange]="item.skipLocationChange" [replaceUrl]="item.replaceUrl" [state]="item.state"
[queryParams]="item.queryParams"
[attr.target]="item.target" tabindex="0" pRipple>
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text w-full truncate">
{{ item.label }}
</span>
</a>
}
<span>
@if (item.type === 'Library' || item.type === 'Shelf') {
@if ((item.type !== 'Library' || (admin || canManipulateLibrary)) && item.label !== 'Unshelved') {
@@ -56,20 +56,23 @@
}
<p-menu #entitymenu [model]="item.menu" [popup]="true" appendTo="body"></p-menu>
}
@if (item.type === 'Library' && (!admin && !canManipulateLibrary) || item.type === 'All Books' || item.label === 'Unshelved') {
<p class="text-[var(--primary-color)] pr-3">
{{ (item.bookCount$ | async)?.toString() || '0' }}
</p>
}
</span>
}
</div>
</div>
@if (item.items && item.visible !== false) {
<ul [@children]="submenuAnimation">
@for (child of item.items; track child; let i = $index) {
<li app-menuitem [item]="child" [index]="i" [parentKey]="key" [class]="child.badgeClass"></li>
<div [@children]="isExpanded(key) ? 'expanded' : 'collapsed'" style="overflow: hidden;">
@if (item.items) {
<ul>
@for (child of item.items; track child; let i = $index) {
<li app-menuitem [item]="child" [index]="i" [parentKey]="key" [class]="child.badgeClass"></li>
}
</ul>
}
</ul>
}
</div>
</div>
</ng-container>

View File

@@ -4,11 +4,10 @@ import {animate, state, style, transition, trigger} from '@angular/animations';
import {Subscription} from 'rxjs';
import {filter} from 'rxjs/operators';
import {MenuService} from './service/app.menu.service';
import { AsyncPipe, NgClass } from '@angular/common';
import {AsyncPipe, NgClass} from '@angular/common';
import {Ripple} from 'primeng/ripple';
import {Button} from 'primeng/button';
import {Menu} from 'primeng/menu';
import {MenuItem} from 'primeng/api';
import {UserService} from '../../../settings/user-management/user.service';
@Component({
@@ -52,6 +51,20 @@ export class AppMenuitemComponent implements OnInit, OnDestroy {
menuSourceSubscription: Subscription;
menuResetSubscription: Subscription;
expandedItems = new Set<string>();
toggleExpand(key: string) {
if (this.expandedItems.has(key)) {
this.expandedItems.delete(key);
} else {
this.expandedItems.add(key);
}
}
isExpanded(key: string): boolean {
return this.expandedItems.has(key);
}
constructor(public router: Router, private menuService: MenuService, private userService: UserService) {
this.userService.userState$.subscribe(userData => {
if (userData) {
@@ -85,6 +98,7 @@ export class AppMenuitemComponent implements OnInit, OnDestroy {
ngOnInit() {
this.key = this.parentKey ? this.parentKey + '-' + this.index : String(this.index);
this.expandedItems.add(this.key);
if (this.item.routerLink) {
this.updateActiveStateFromRoute();
}
@@ -116,10 +130,6 @@ export class AppMenuitemComponent implements OnInit, OnDestroy {
this.menuService.onMenuStateChange({key: this.key});
}
get submenuAnimation() {
return this.root ? 'expanded' : (this.active ? 'expanded' : 'collapsed');
}
@HostBinding('class.active-menuitem')
get activeClass() {
return this.active && !this.root;