mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 07:50:08 +08:00
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:
parent
5f619d43de
commit
b82d7097b7
5 changed files with 201 additions and 246 deletions
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
// });
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue