From 72a62b965251f62b75c6c091d01f5ca3e652501c Mon Sep 17 00:00:00 2001 From: Rafal Galka Date: Thu, 17 Oct 2019 12:00:33 +0200 Subject: [PATCH 1/2] feat(sidebar): `minimized` state @Input/@Output - When `minimized` input property is set from outside, sidebar will properly switch it's state, eg: `[minimized]="sidebar.minimized$ | async"` - It's possible to listen to `minimized` value changes, eg. to persist in localStorage `[(minimized)]="minimized"` or `[minimized]="minimized$ | async" (minimizedChange)="minimized$.next($event)"` --- .../app-sidebar-minimizer.component.ts | 29 ++++------ .../src/lib/sidebar/app-sidebar.component.ts | 54 ++++++++++++++----- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/projects/coreui/angular/src/lib/sidebar/app-sidebar-minimizer.component.ts b/projects/coreui/angular/src/lib/sidebar/app-sidebar-minimizer.component.ts index 751d950a..42188810 100644 --- a/projects/coreui/angular/src/lib/sidebar/app-sidebar-minimizer.component.ts +++ b/projects/coreui/angular/src/lib/sidebar/app-sidebar-minimizer.component.ts @@ -1,33 +1,24 @@ -import {Component, ElementRef, HostBinding, HostListener, Inject, OnInit, Renderer2} from '@angular/core'; -import {DOCUMENT} from '@angular/common'; +import { Component, HostBinding, HostListener, Optional } from '@angular/core'; +import { AppSidebarComponent } from './app-sidebar.component'; @Component({ selector: 'app-sidebar-minimizer, cui-sidebar-minimizer', - template: ``, + template: `` }) -export class AppSidebarMinimizerComponent implements OnInit { +export class AppSidebarMinimizerComponent { @HostBinding('attr.role') role = 'button'; + @HostBinding('class.sidebar-minimizer') _minimizer = true; @HostListener('click', ['$event']) toggleOpen($event: any) { $event.preventDefault(); - const body = this.document.body; - body.classList.contains('sidebar-minimized') ? - this.renderer.removeClass(body, 'sidebar-minimized') : - this.renderer.addClass(body, 'sidebar-minimized'); - body.classList.contains('brand-minimized') ? - this.renderer.removeClass(body, 'brand-minimized') : - this.renderer.addClass(body, 'brand-minimized'); + this.sidebar.toggleMinimized(); } - constructor( - @Inject(DOCUMENT) private document: any, - private renderer: Renderer2, - private hostElement: ElementRef - ) { - renderer.addClass(hostElement.nativeElement, 'sidebar-minimizer'); + constructor(@Optional() private sidebar: AppSidebarComponent) { + if (!sidebar) { + throw Error(`AppSidebarMinimizer must be placed within a AppSidebar component.`); + } } - - ngOnInit() {} } diff --git a/projects/coreui/angular/src/lib/sidebar/app-sidebar.component.ts b/projects/coreui/angular/src/lib/sidebar/app-sidebar.component.ts index 8c0b7b0b..e2be302b 100644 --- a/projects/coreui/angular/src/lib/sidebar/app-sidebar.component.ts +++ b/projects/coreui/angular/src/lib/sidebar/app-sidebar.component.ts @@ -1,6 +1,5 @@ -import {Component, Input, Inject, OnInit, OnDestroy, Renderer2, ElementRef} from '@angular/core'; import { DOCUMENT } from '@angular/common'; - +import { Component, EventEmitter, HostBinding, Inject, Input, OnDestroy, OnInit, Output, Renderer2 } from '@angular/core'; import { sidebarCssClasses } from '../shared'; @Component({ @@ -11,32 +10,51 @@ export class AppSidebarComponent implements OnInit, OnDestroy { @Input() compact: boolean; @Input() display: any; @Input() fixed: boolean; - @Input() minimized: boolean; @Input() offCanvas: boolean; + @Input() + get minimized() { + return this._minimized; + } + set minimized(value: boolean) { + // only update / emit events when the value changes + if (this._minimized !== value) { + this._minimized = value; + this._updateMinimized(value); + this.minimizedChange.emit(value); + } + } + private _minimized = false; + + /** + * Emits whenever the minimized state of the sidebar changes. + * Primarily used to facilitate two-way binding. + */ + @Output() minimizedChange = new EventEmitter(); + + @HostBinding('class.sidebar') _sidebar = true; + constructor( @Inject(DOCUMENT) private document: any, - private renderer: Renderer2, - private hostElement: ElementRef + private renderer: Renderer2 ) { - renderer.addClass(hostElement.nativeElement, 'sidebar'); } ngOnInit(): void { this.displayBreakpoint(this.display); this.isCompact(this.compact); this.isFixed(this.fixed); - this.isMinimized(this.minimized); this.isOffCanvas(this.offCanvas); } ngOnDestroy(): void { - this.renderer.removeClass(this.document.body, 'sidebar-fixed' ); + this.minimizedChange.complete(); + this.renderer.removeClass(this.document.body, 'sidebar-fixed'); } isCompact(compact: boolean = this.compact): void { if (compact) { - this.renderer.addClass(this.document.body, 'sidebar-compact' ); + this.renderer.addClass(this.document.body, 'sidebar-compact'); } } @@ -46,10 +64,8 @@ export class AppSidebarComponent implements OnInit, OnDestroy { } } - isMinimized(minimized: boolean = this.minimized): void { - if (minimized) { - this.renderer.addClass(this.document.body, 'sidebar-minimized'); - } + toggleMinimized(): void { + this.minimized = !this._minimized; } isOffCanvas(offCanvas: boolean = this.offCanvas): void { @@ -64,4 +80,16 @@ export class AppSidebarComponent implements OnInit, OnDestroy { this.renderer.addClass(this.document.body, cssClass); } } + + private _updateMinimized(minimized: boolean): void { + const body = this.document.body; + + if (minimized) { + this.renderer.addClass(body, 'sidebar-minimized'); + this.renderer.addClass(body, 'brand-minimized'); + } else { + this.renderer.removeClass(body, 'sidebar-minimized'); + this.renderer.removeClass(body, 'brand-minimized'); + } + } } From f3bf67707c48bf15972e5498bb3a99db25043c72 Mon Sep 17 00:00:00 2001 From: Rafal Galka Date: Thu, 17 Oct 2019 12:52:53 +0200 Subject: [PATCH 2/2] test(sidebar): `minimized` state --- .../lib/sidebar/app-sidebar.component.spec.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 projects/coreui/angular/src/lib/sidebar/app-sidebar.component.spec.ts diff --git a/projects/coreui/angular/src/lib/sidebar/app-sidebar.component.spec.ts b/projects/coreui/angular/src/lib/sidebar/app-sidebar.component.spec.ts new file mode 100644 index 00000000..23ce079d --- /dev/null +++ b/projects/coreui/angular/src/lib/sidebar/app-sidebar.component.spec.ts @@ -0,0 +1,50 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { AppSidebarComponent } from './app-sidebar.component'; + +describe('AppSidebarComponent', () => { + let component: AppSidebarComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AppSidebarComponent ], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AppSidebarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('has sidebar class', () => { + expect(fixture.nativeElement.classList.contains('sidebar')).toBeTruthy(); + }); + + describe('minimized', () => { + it('updates document.body classes', () => { + component.minimized = true; + expect(document.body.classList.contains('sidebar-minimized')).toBeTruthy(); + expect(document.body.classList.contains('brand-minimized')).toBeTruthy(); + + component.minimized = false; + expect(document.body.classList.contains('sidebar-minimized')).toBeFalsy(); + expect(document.body.classList.contains('brand-minimized')).toBeFalsy(); + }); + + it('emits only when value changes', async(() => { + spyOn(component.minimizedChange, 'emit'); + + component.minimized = true; + component.minimized = true; + component.minimized = false; + + expect(component.minimizedChange.emit).toHaveBeenCalledTimes(2); + })); + }); +});