Add Base Modal Component

- Refactor exiting modal component
- Add component with base modal layout
-- Support adding modal body and footer using content projection
-- Supports declaring close button
- Add karma/jasmine tests
This commit is contained in:
Clemente Raposo 2021-01-11 16:21:59 +00:00 committed by Dillon-Brown
parent 5f619d43de
commit b82d7097b7
5 changed files with 201 additions and 246 deletions

View file

@ -1,165 +1,16 @@
<!-- Start of modal component section -->
<ng-template #modal let-modal>
<!-- Start of modal header section -->
<div class="modal-header">
<div class="modal-title">{{ modalTitle }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"
(click)="modal.dismiss('Cross click')">
<scrm-image class="sicon" image="close_modal"></scrm-image>
</button>
<div [class]="klass">
<div class="modal-header {{headerKlass}}">
<div class="modal-title">
<scrm-label *ngIf="titleKey" [labelKey]="titleKey"></scrm-label>
</div>
<scrm-close-button *ngIf="closable" [config]="close"></scrm-close-button>
</div>
<!-- End of modal header section -->
<!-- Start of modal body section -->
<div class="modal-body">
<h5 class="modal-field-header">Overview</h5>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<div class="select">
<select>
<option>Aw yeah.</option>
<option>You should pick me instead.</option>
<option>I think I'm the better option!</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<label class="checkbox-container create-popup-checkbox">
<input type="checkbox">
<span class="checkmark"></span>
</label>
</div>
<div class="col-lg-6">
<div class="relate-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-arrow">
<scrm-image class="sicon" image="cursor"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="calendar-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-calendar">
<scrm-image class="sicon" image="calendar"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
</div>
</div>
<div class="row">
<div class="col-lg-6">
<label class="modal-label">Label</label>
<div class="select">
<select>
<option>Aw yeah.</option>
<option>You should pick me instead.</option>
<option>I think I'm the better option!</option>
</select>
</div>
</div>
<div class="col-lg-6">
<label class="modal-label">Label</label>
<label class="checkbox-container create-popup-checkbox">
<input type="checkbox">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="relate-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-arrow">
<scrm-image class="sicon" image="cursor"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
<div class="col-lg-6">
<div class="calendar-input-group">
<label class="modal-label">Label</label>
<input class="modal-input" type="text">
<button type="button" class="create-popup-button create-popup-calendar">
<scrm-image class="sicon" image="calendar"></scrm-image>
</button>
<button type="button" class="create-popup-button create-popup-cross">
<scrm-image class="sicon" image="cross"></scrm-image>
</button>
</div>
</div>
</div>
<div class="modal-body {{bodyKlass}}">
<ng-content select="[modal-body]"></ng-content>
</div>
<!-- End of modal body section -->
<!-- Start of modal footer section -->
<div class="modal-footer">
<div class="row w-100">
<div class="col-lg-6 pr-0 mr-0">
<div class="modal-options">
<label class="modal-checkbox-container">
<input type="checkbox">
<span class="checkmark"></span>
</label>
<p class="modal-redirect-text">
Redirect on SAVE
<scrm-image class="sicon info-icon" image="info"></scrm-image>
</p>
</div>
</div>
<div class="col-lg-6">
<div class="modal-buttons">
<button type="button" class="btn modal-button-cancel" data-dismiss="modal"
(click)="modal.dismiss('Cross click')">
Cancel
</button>
<button type="button" class="btn modal-button-save">
Save and New
</button>
<button type="button" class="btn modal-button-save">
Save
</button>
</div>
</div>
</div>
<div class="modal-footer {{footerKlass}}">
<ng-content select="[modal-footer]"></ng-content>
</div>
<!-- End of modal footer section -->
</ng-template>
<scrm-button [config]="newButtonConfig" (click)="open(modal)"></scrm-button>
<!-- End of modal component section -->
</div>

View file

@ -1,36 +1,177 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {Component} from '@angular/core';
import {LanguageStore} from '@store/language/language.store';
import {languageStoreMock} from '@store/language/language.store.spec.mock';
import {SystemConfigStore} from '@store/system-config/system-config.store';
import {systemConfigStoreMock} from '@store/system-config/system-config.store.spec.mock';
import {ButtonInterface} from '@app-common/components/button/button.model';
import {ModalModule} from '@components/modal/components/modal/modal.module';
import {ModalUiComponent} from './modal.component';
import {ThemeImagesStore} from '@store/theme-images/theme-images.store';
import {of} from 'rxjs';
import {themeImagesMockData} from '@store/theme-images/theme-images.store.spec.mock';
import {take} from 'rxjs/operators';
@Component({
selector: 'modal-test-host-component',
template: `
<scrm-modal [close]="closeButton"
[closable]="true"
[titleKey]="titleKey"
bodyKlass="test-body-modal"
headerKlass="test-header-modal"
footerKlass="test-footer-modal"
klass="test-modal">
describe('ModalUiComponent', () => {
let component: ModalUiComponent;
let fixture: ComponentFixture<ModalUiComponent>;
<div modal-body>
<span class="modal-body-content">TEST BODY</span>
</div>
<div modal-footer>
<span class="modal-footer-content">TEST FOOTER</span>
</div>
</scrm-modal>
`
})
class ModalTestHostComponent {
titleKey = 'LBL_NEW';
closeClicked = 0;
closeButton = {
onClick: (): void => {
this.closeClicked++;
}
} as ButtonInterface;
}
describe('BaseModal', () => {
let testHostComponent: ModalTestHostComponent;
let testHostFixture: ComponentFixture<ModalTestHostComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ModalUiComponent],
providers: [
{
provide: ThemeImagesStore, useValue: {
images$: of(themeImagesMockData).pipe(take(1))
}
},
declarations: [
ModalTestHostComponent,
],
})
.compileComponents();
imports: [
ModalModule
],
providers: [
{provide: LanguageStore, useValue: languageStoreMock},
{provide: SystemConfigStore, useValue: systemConfigStoreMock}
],
}).compileComponents();
testHostFixture = TestBed.createComponent(ModalTestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ModalUiComponent);
component = fixture.componentInstance;
fixture.detectChanges();
it('should create', () => {
expect(testHostComponent).toBeTruthy();
});
it('should have custom class', () => {
expect(testHostComponent).toBeTruthy();
const wrapper = testHostFixture.nativeElement.getElementsByClassName('test-modal');
expect(wrapper).toBeTruthy();
expect(wrapper.length).toEqual(1);
});
it('should have custom modal header class', () => {
expect(testHostComponent).toBeTruthy();
const header = testHostFixture.nativeElement.getElementsByClassName('modal-header');
expect(header).toBeTruthy();
expect(header.length).toEqual(1);
expect(header.item(0).className).toContain('test-header-modal');
});
it('should have title', () => {
expect(testHostComponent).toBeTruthy();
const header = testHostFixture.nativeElement.getElementsByClassName('modal-header');
expect(header).toBeTruthy();
expect(header.length).toEqual(1);
const title = header.item(0).getElementsByClassName('modal-title');
expect(title).toBeTruthy();
expect(title.length).toEqual(1);
expect(title.item(0).textContent).toContain('New');
});
it('should have clickable close button', () => {
expect(testHostComponent).toBeTruthy();
const header = testHostFixture.nativeElement.getElementsByClassName('modal-header');
expect(header).toBeTruthy();
expect(header.length).toEqual(1);
const close = header.item(0).getElementsByTagName('scrm-close-button');
expect(close).toBeTruthy();
expect(close.length).toEqual(1);
const button = close.item(0).getElementsByTagName('button');
expect(button).toBeTruthy();
expect(button.length).toEqual(1);
button.item(0).click();
expect(testHostComponent.closeClicked).toEqual(1);
});
it('should have custom body class', () => {
expect(testHostComponent).toBeTruthy();
const body = testHostFixture.nativeElement.getElementsByClassName('modal-body');
expect(body).toBeTruthy();
expect(body.length).toEqual(1);
expect(body.item(0).className).toContain('test-body-modal');
});
it('should have body', () => {
expect(testHostComponent).toBeTruthy();
const body = testHostFixture.nativeElement.getElementsByClassName('modal-body');
expect(body).toBeTruthy();
expect(body.length).toEqual(1);
const projectedContent = body.item(0).getElementsByClassName('modal-body-content');
expect(projectedContent).toBeTruthy();
expect(projectedContent.length).toEqual(1);
expect(projectedContent.item(0).textContent).toContain('TEST BODY');
});
it('should have custom footer class', () => {
expect(testHostComponent).toBeTruthy();
const footer = testHostFixture.nativeElement.getElementsByClassName('modal-footer');
expect(footer).toBeTruthy();
expect(footer.length).toEqual(1);
expect(footer.item(0).className).toContain('test-footer-modal');
});
it('should have footer', () => {
expect(testHostComponent).toBeTruthy();
const footer = testHostFixture.nativeElement.getElementsByClassName('modal-footer');
expect(footer).toBeTruthy();
expect(footer.length).toEqual(1);
const projectedContent = footer.item(0).getElementsByClassName('modal-footer-content');
expect(projectedContent).toBeTruthy();
expect(projectedContent.length).toEqual(1);
expect(projectedContent.item(0).textContent).toContain('TEST FOOTER');
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View file

@ -1,57 +1,21 @@
import {Component, OnInit} from '@angular/core';
import {ModalDismissReasons, NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {animate, transition, trigger,} from '@angular/animations';
import {Component, Input} from '@angular/core';
import {ButtonInterface} from '@app-common/components/button/button.model';
@Component({
selector: 'scrm-modal-ui',
selector: 'scrm-modal',
templateUrl: './modal.component.html',
animations: [
trigger('modalFade', [
transition('void <=> *', [
animate('800ms')
]),
]),
]
styleUrls: [],
})
export class ModalComponent {
export class ModalUiComponent implements OnInit {
closeResult: string;
modalTitle: string = 'New Account';
createModal: boolean = true;
constructor(private modalService: NgbModal) {
}
open(modal) {
this.modalService.open(modal, {
ariaLabelledBy: 'modal-basic-title',
centered: true,
size: 'lg'
}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
newButtonConfig = {
text: 'NEW',
buttonClass: 'action-button'
};
ngOnInit() {
}
@Input() klass = '';
@Input() headerKlass = '';
@Input() bodyKlass = '';
@Input() footerKlass = '';
@Input() titleKey = '';
@Input() closable = false;
@Input() close: ButtonInterface = {
klass: ['btn', 'btn-outline-light', 'btn-sm']
} as ButtonInterface;
}

View file

@ -2,22 +2,21 @@ import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {AppManagerModule} from '@base/app-manager/app-manager.module';
import {ModalUiComponent} from './modal.component';
import {ButtonModule} from '@components/button/button.module';
import {ModalComponent} from './modal.component';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {ImageModule} from '@components/image/image.module';
import {CloseButtonModule} from '@components/close-button/close-button.module';
import {LabelModule} from '@components/label/label.module';
@NgModule({
declarations: [ModalUiComponent],
exports: [ModalUiComponent],
declarations: [ModalComponent],
exports: [ModalComponent],
imports: [
CommonModule,
AppManagerModule.forChild(ModalUiComponent),
ButtonModule,
AppManagerModule.forChild(ModalComponent),
AngularSvgIconModule,
ImageModule
CloseButtonModule,
LabelModule
]
})
export class ModalUiModule {
export class ModalModule {
}

View file

@ -6,7 +6,7 @@ import {AppManagerModule} from '@base/app-manager/app-manager.module';
import {ActionMenuComponent} from './action-menu.component';
import {ButtonModule} from '@components/button/button.module';
import {ModalUiModule} from '@components/modal/components/modal/modal.module';
import {ModalModule} from '@components/modal/components/modal/modal.module';
import {ButtonGroupModule} from '@components/button-group/button-group.module';
@ -16,7 +16,7 @@ import {ButtonGroupModule} from '@components/button-group/button-group.module';
imports: [
CommonModule,
AppManagerModule.forChild(ActionMenuComponent),
ModalUiModule,
ModalModule,
ButtonModule,
AngularSvgIconModule,
ButtonGroupModule