diff --git a/projects/ppwcode/ng-wireframe/README.md b/projects/ppwcode/ng-wireframe/README.md index d0da10cb..f732062a 100644 --- a/projects/ppwcode/ng-wireframe/README.md +++ b/projects/ppwcode/ng-wireframe/README.md @@ -55,7 +55,8 @@ The following CSS variables are available for theming. Just add them to the `bod ### Wireframe The wireframe places an application layout with responsive left navigation and a toolbar on your web page. -You can hide the wireframe by adding a flag in the route configuration - In data add the `'showWireframe: false'` flag - If this flag is not added it will by default be true and show the wireframe +You can hide the wireframe entirely by adding the `showWireframe: false` flag to the route data. If this flag is not added, the wireframe is shown by default. +You can hide the toolbar while keeping the wireframe visible by adding the `showToolbar: false` flag to the route data. If this flag is not added, the toolbar is shown by default. #### Parameters diff --git a/projects/ppwcode/ng-wireframe/src/lib/model/wireframe-options.ts b/projects/ppwcode/ng-wireframe/src/lib/model/wireframe-options.ts new file mode 100644 index 00000000..bf6759ab --- /dev/null +++ b/projects/ppwcode/ng-wireframe/src/lib/model/wireframe-options.ts @@ -0,0 +1,18 @@ +import { InjectionToken, ValueProvider } from '@angular/core' + +export interface WireframeOptions { + showToolbar?: boolean +} + +const defaultWireframeOptions: WireframeOptions = { + showToolbar: true +} + +export const WIREFRAME_OPTIONS = new InjectionToken('WireframeOptions', { + factory: () => defaultWireframeOptions +}) + +export const provideWireframeOptions = (options?: WireframeOptions): ValueProvider => ({ + provide: WIREFRAME_OPTIONS, + useValue: options ? options : defaultWireframeOptions +}) diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html index cc971a50..29bec895 100644 --- a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html +++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.html @@ -35,7 +35,7 @@ " >
- @if (showWireframe()) { + @if (showWireframe() && showToolbar()) { } - @if (showBreadcrumb()) { + @if (resolvedShowBreadcrumb()) { diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts new file mode 100644 index 00000000..7753f21f --- /dev/null +++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.spec.ts @@ -0,0 +1,153 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core' +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { provideRouter, Route, Router } from '@angular/router' +import { provideTranslateService } from '@ngx-translate/core' +import { provideBreadcrumbOptions } from '@ppwcode/ng-router' +import { provideWireframeOptions, WireframeOptions } from '../model/wireframe-options' +import { WireframeComponent } from './wireframe.component' + +@Component({ + selector: 'ppw-visible-route', + template: '', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush +}) +class VisibleRouteComponent {} + +@Component({ + selector: 'ppw-hidden-toolbar-route', + template: '', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush +}) +class HiddenToolbarRouteComponent {} + +@Component({ + selector: 'ppw-toolbar-override-route', + template: '', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush +}) +class ToolbarOverrideRouteComponent {} + +@Component({ + selector: 'ppw-hidden-breadcrumb-route', + template: '', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush +}) +class HiddenBreadcrumbRouteComponent {} + +const routes: Route[] = [ + { + path: 'visible', + component: VisibleRouteComponent, + title: 'Visible page' + }, + { + path: 'hidden-toolbar', + component: HiddenToolbarRouteComponent, + title: 'Hidden toolbar page', + data: { + showToolbar: false, + showBreadcrumb: true + } + }, + { + path: 'toolbar-override', + component: ToolbarOverrideRouteComponent, + title: 'Toolbar override page', + data: { + showToolbar: true + } + }, + { + path: 'hidden-breadcrumb', + component: HiddenBreadcrumbRouteComponent, + title: 'Hidden breadcrumb page', + data: { + showBreadcrumb: false + } + } +] + +describe('WireframeComponent', () => { + let component: WireframeComponent + let fixture: ComponentFixture + let router: Router + + const setup = async (wireframeOptions?: WireframeOptions): Promise => { + TestBed.resetTestingModule() + + await TestBed.configureTestingModule({ + imports: [WireframeComponent], + providers: [ + provideTranslateService({}), + provideBreadcrumbOptions(), + provideRouter(routes), + ...(wireframeOptions ? [provideWireframeOptions(wireframeOptions)] : []) + ] + }).compileComponents() + + router = TestBed.inject(Router) + fixture = TestBed.createComponent(WireframeComponent) + component = fixture.componentInstance + fixture.componentRef.setInput('showBreadcrumb', true) + } + + it('should show the toolbar by default', async () => { + await setup() + await router.navigateByUrl('/visible') + fixture.detectChanges() + + expect(component.showToolbar()).toBe(true) + expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeTruthy() + }) + + it('should hide the toolbar when the activated route data disables it', async () => { + await setup() + await router.navigateByUrl('/hidden-toolbar') + fixture.detectChanges() + + expect(component.showToolbar()).toBe(false) + expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeFalsy() + }) + + it('should hide the toolbar when wireframe options disable it by default', async () => { + await setup({ showToolbar: false }) + await router.navigateByUrl('/visible') + fixture.detectChanges() + + expect(component.showToolbar()).toBe(false) + expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeFalsy() + }) + + it('should show the toolbar when route data overrides the global default', async () => { + await setup({ showToolbar: false }) + await router.navigateByUrl('/toolbar-override') + fixture.detectChanges() + + expect(component.showToolbar()).toBe(true) + expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeTruthy() + }) + + it('should keep the breadcrumb visible when the toolbar is hidden by route data', async () => { + await setup() + await router.navigateByUrl('/hidden-toolbar') + fixture.detectChanges() + + expect(component.showToolbar()).toBe(false) + expect(component.resolvedShowBreadcrumb()).toBe(true) + expect(fixture.nativeElement.querySelector('ppw-toolbar')).toBeFalsy() + expect(fixture.nativeElement.querySelector('ppw-breadcrumb')).toBeTruthy() + }) + + it('should let route data hide the breadcrumb even when the input enables it', async () => { + await setup() + await router.navigateByUrl('/hidden-breadcrumb') + fixture.detectChanges() + + expect(component.resolvedShowBreadcrumb()).toBe(false) + expect(fixture.nativeElement.querySelector('ppw-breadcrumb')).toBeFalsy() + }) +}) diff --git a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts index bee05b8b..c9c077d7 100644 --- a/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts +++ b/projects/ppwcode/ng-wireframe/src/lib/wireframe/wireframe.component.ts @@ -18,6 +18,7 @@ import { BreadcrumbComponent } from '@ppwcode/ng-router' import { filter } from 'rxjs' import { LeftSidenavComponent } from '../left-sidenav/left-sidenav.component' import { SidebarOptions } from '../model/sidebar-options' +import { WIREFRAME_OPTIONS, WireframeOptions } from '../model/wireframe-options' import { NavigationItem } from '../navigation-item/navigation-item.model' import { ToolbarComponent } from '../toolbar/toolbar.component' @@ -40,6 +41,7 @@ import { ToolbarComponent } from '../toolbar/toolbar.component' export class WireframeComponent { #observer: BreakpointObserver = inject(BreakpointObserver) #router: Router = inject(Router) + #wireframeOptions: WireframeOptions = inject(WIREFRAME_OPTIONS) #breakpointChange = toSignal( this.#observer.observe([Breakpoints.XSmall, Breakpoints.Small]).pipe(takeUntilDestroyed()) @@ -84,15 +86,15 @@ export class WireframeComponent { // Computed properties public showWireframe: Signal = computed(() => { - // This acts as a trigger for the computed property. - this.#navigationEnd() + return this.#getCurrentRouteSnapshot().data['showWireframe'] ?? true + }) - let child: ActivatedRouteSnapshot = this.#router.routerState.snapshot.root - while (child.firstChild) { - child = child.firstChild - } + public showToolbar: Signal = computed(() => { + return this.#getCurrentRouteSnapshot().data['showToolbar'] ?? this.#wireframeOptions.showToolbar ?? true + }) - return child.data['showWireframe'] ?? true + public resolvedShowBreadcrumb: Signal = computed(() => { + return this.showWireframe() && (this.#getCurrentRouteSnapshot().data['showBreadcrumb'] ?? this.showBreadcrumb()) }) public sidenavMode: Signal<'over' | 'side'> = computed(() => { @@ -136,4 +138,16 @@ export class WireframeComponent { return this.#observer.isMatched(breakpoints) }) } + + #getCurrentRouteSnapshot(): ActivatedRouteSnapshot { + // This acts as a trigger for the computed properties that depend on the active route snapshot. + this.#navigationEnd() + + let child: ActivatedRouteSnapshot = this.#router.routerState.snapshot.root + while (child.firstChild) { + child = child.firstChild + } + + return child + } } diff --git a/projects/ppwcode/ng-wireframe/src/public-api.ts b/projects/ppwcode/ng-wireframe/src/public-api.ts index 6febb0da..5babd28c 100644 --- a/projects/ppwcode/ng-wireframe/src/public-api.ts +++ b/projects/ppwcode/ng-wireframe/src/public-api.ts @@ -7,3 +7,4 @@ export * from './lib/left-sidenav/left-sidenav.component' export * from './lib/toolbar/toolbar.component' export * from './lib/wireframe/wireframe.component' export * from './lib/model/sidebar-options' +export * from './lib/model/wireframe-options' diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 4f09af3e..34bb23f5 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -42,6 +42,9 @@ export const routes: Routes = [ { path: getRouteSegment(ROUTE_MAP.globalErrorHandler), component: GlobalErrorHandlerComponent, - title: 'navigation.global_error_handler' + title: 'navigation.global_error_handler', + data: { + showToolbar: false + } } ] diff --git a/src/app/global-error-handler/global-error-handler.component.html b/src/app/global-error-handler/global-error-handler.component.html index 5b5f1351..e8abde88 100644 --- a/src/app/global-error-handler/global-error-handler.component.html +++ b/src/app/global-error-handler/global-error-handler.component.html @@ -7,6 +7,12 @@ should never occur on a production environment, but it is useful for development and testing. " > +

Usage

Add the following snippet to your app module providers.

diff --git a/src/main.ts b/src/main.ts
index f22c8fad..aa6f1b45 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -16,6 +16,7 @@ import {
 import { provideGlobalErrorHandler } from '@ppwcode/ng-common'
 import { PPW_TABLE_DEFAULT_OPTIONS } from '@ppwcode/ng-common-components'
 import { provideBreadcrumbOptions, providePaginationOptions, TranslatedPageTitleStrategy } from '@ppwcode/ng-router'
+import { provideWireframeOptions } from '@ppwcode/ng-wireframe'
 import { AppComponent } from './app/app.component'
 import { routes } from './app/app.routes'
 import { EmptyAsyncResultComponent } from './app/table/empty-async-result.component'
@@ -78,7 +79,8 @@ bootstrapApplication(AppComponent, {
                 suffix: '.json'
             })
         }),
-        provideBreadcrumbOptions()
+        provideBreadcrumbOptions(),
+        provideWireframeOptions()
     ]
 }).catch((err) => console.error(err))