diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..eae3a55 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,34 @@ +[*] +charset=utf-8 +end_of_line=crlf +insert_final_newline=false +indent_style=space +indent_size=2 + +[{.es6-loader-rc,ports,firmscode,version,.babelrc,refdata,.eslintrc,.stylelintrc,jest.config,userDetails,.es6modules,nereasons,.esmrc,11742287,*.json,*.jsb3,*.jsb2,*.bowerrc}] +indent_style=space +indent_size=2 + +[*.js.map] +indent_style=space +indent_size=2 + +[*.less] +indent_style=space +indent_size=2 + +[*.scss] +indent_style=space +indent_size=2 + +[*.coffee] +indent_style=space +indent_size=2 + +[{.analysis_options,*.yml,*.yaml}] +indent_style=space +indent_size=2 + +[tslint.json] +indent_style=space +indent_size=2 diff --git a/package.json b/package.json index b45f44c..746dc7c 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "test:lib": "ng test --app lib", "test:lib:once": "ng test --app lib --watch=false", "e2e:kitchensink": "ng e2e --app kitchensink --watch=false", - "ready": "npm-run-all --parallel build:lib lint test:once e2e:kitchensink", + "ready": "npm-run-all --parallel build:kitchensink:prod build:lib lint test:once e2e:kitchensink", "test:once": "npm-run-all --serial test:lib:once test:kitchensink:once", "postinstall": "node postinstall.js" }, @@ -59,6 +59,7 @@ "zone.js": "0.8.19" }, "devDependencies": { + "@angular-devkit/core": "0.0.29", "@angular/animations": "5.1.2", "@angular/cli": "1.6.3", "@angular/common": "5.1.2", diff --git a/src/app/buttons/_buttons.scss b/src/app/buttons/_buttons.scss index 6e3ba54..253f42e 100644 --- a/src/app/buttons/_buttons.scss +++ b/src/app/buttons/_buttons.scss @@ -35,3 +35,4 @@ text-decoration: underline; } } + diff --git a/src/app/buttons/buttons.module.ts b/src/app/buttons/buttons.module.ts index f62fc83..6f1613c 100644 --- a/src/app/buttons/buttons.module.ts +++ b/src/app/buttons/buttons.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { CBPToggleSwitchComponent } from './cbp-toggle-switch/cbp-toggle-switch.component'; import {FormsModule} from '@angular/forms'; +import { CBPToggleSwitchComponent } from './cbp-toggle-switch/cbp-toggle-switch.component'; @NgModule({ imports: [ diff --git a/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.html b/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.html index eb78e33..a7a7ca1 100644 --- a/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.html +++ b/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.html @@ -1,4 +1,15 @@ - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.scss b/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.scss index 945c24f..fcee691 100644 --- a/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.scss +++ b/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.scss @@ -1,7 +1,8 @@ @import '../../variables'; // copied from https://raw.githubusercontent.com/US-CBP/cbp-theme/master/app/styles/custom/_forms.scss -.switch { +// because the current version 1.8.1 of cbp-theme does not have refactored cbp-toggle switches +.cbp-toggle-switch { padding-left: 64px; overflow: hidden; position: relative; diff --git a/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.spec.ts b/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.spec.ts index a12cbbd..fb7d35d 100644 --- a/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.spec.ts +++ b/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.spec.ts @@ -1,7 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CBPToggleSwitchComponent } from './cbp-toggle-switch.component'; -import {CBPButtonsModule} from '../buttons.module'; describe('CBPToggleSwitchComponent', () => { let component: CBPToggleSwitchComponent; @@ -9,7 +8,7 @@ describe('CBPToggleSwitchComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [CBPButtonsModule] + declarations: [ CBPToggleSwitchComponent ] }) .compileComponents(); })); diff --git a/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.ts b/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.ts index b0c2609..bc2de4c 100644 --- a/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.ts +++ b/src/app/buttons/cbp-toggle-switch/cbp-toggle-switch.component.ts @@ -1,36 +1,144 @@ -import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core'; +import { + Attribute, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, HostBinding, + Input, OnInit, + Output, + ViewEncapsulation +} from '@angular/core'; +import {CanColor, CanDisable, mixinColor, mixinDisabled, mixinTabIndex, ThemePalette} from '@angular/material'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {HasTabIndex} from '@angular/material/core/typings/common-behaviors/tabindex'; + + +export class CBPToggleSwitchChange { + checked: boolean; + source: CBPToggleSwitchComponent; + +} + + +export class CBPToggleSwitchComponentBase { + constructor(public _elementRef: ElementRef) {} +} + +export const _CBPToggleSwitchMixinBase = + mixinTabIndex(mixinColor(mixinDisabled(CBPToggleSwitchComponentBase), 'accent')); let toggleSwitchCounter = 1; +export const CBP_TOGGLE_SWITCH_CONTROL_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, + // tslint:disable-next-line:no-use-before-declare + useExisting: forwardRef(() => CBPToggleSwitchComponent), + multi: true +} @Component({ - selector: 'cbp-toggle-switch', - templateUrl: './cbp-toggle-switch.component.html', - styleUrls: ['./cbp-toggle-switch.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + moduleId: module.id, + selector: 'cbp-toggle-switch', + templateUrl: './cbp-toggle-switch.component.html', + styleUrls: ['./cbp-toggle-switch.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + providers: [CBP_TOGGLE_SWITCH_CONTROL_VALUE_ACCESSOR], + preserveWhitespaces: false // seems to trim whitespace content }) -export class CBPToggleSwitchComponent { +export class CBPToggleSwitchComponent extends _CBPToggleSwitchMixinBase + implements OnInit, CanColor, CanDisable, ControlValueAccessor, HasTabIndex { + + @Input() ariaLabel = ''; + @Input() ariaLabelledby: string | null = null; + + @Input() onValue: any = true; + @Input() offValue: any = false; + @Input() onLabel = 'ON'; + @Input() offLabel = 'OFF'; + @Input() label = ''; + @Input() disabled: boolean; + @Input() color: ThemePalette; + @Input() name: string | null = null; + + @HostBinding('class') className = 'cbp-toggle-switch'; + @HostBinding('id') id = `cbp-toggle-switch-${++toggleSwitchCounter}`; + @HostBinding('class.cbp-toggle-switch-checked') _checked: Boolean = null; + - @Input() onLabel = 'ON'; - @Input() offLabel = 'OFF'; - @Input() onValue = true; - @Input() offValue = false; - @Input() label: string = null; - @Input() required: boolean; - @Input() disabled: boolean; - @Output() changed = new EventEmitter(); - @Input() isOn: Boolean; + @Input() + get required(): boolean { return this._required; } + set required(value: boolean) { this._required = value !== null && `${value}` !== 'false' && value !== false ? true : false; } + private _required: boolean; - toggleSwitchId = `cbp-toggle-sw-${toggleSwitchCounter}`; + inputId = this.id + 'input'; - constructor() { - toggleSwitchCounter++; - } + @Output() readonly change: EventEmitter = new EventEmitter(); + constructor(elementRef: ElementRef, + private _changeDetectorRef: ChangeDetectorRef, + @Attribute('tabindex') tabIndex: string) { + super(elementRef); + this.tabIndex = Number.parseInt(tabIndex) || 0; + } - valueChange($event: any) { - this.isOn = $event.currentTarget.checked ? this.onValue : this.offValue; - this.changed.emit(this.isOn.valueOf()); - } + @Input() + get checked(): boolean { return !! this._checked ; } + set checked(value: boolean) { + if (value !== this.checked) { + this._checked = value; + this._changeDetectorRef.markForCheck(); + } + } + + _getAriaChecked(): 'true' | 'false' | 'mixed' { + return this._checked === null || this._checked === undefined ? 'mixed' : this._checked ? 'true' : 'false'; + } + + _stopPropogation($event: Event) { + $event.stopPropagation(); + } + _onClick($event: Event) { + $event.stopPropagation(); + if (!this.disabled) { + this._checked = ! this.checked; + this._emitChangeEvent(); + } + + } + + private _emitChangeEvent() { + let event = new CBPToggleSwitchChange(); + event.source = this; + event.checked = this.checked; + + this._controlValueAccessorChangeFn(this.checked ? this.onValue : this.offValue); + this.change.emit(event); + } + + // just to avoid TypeScript error - it is going to get overwritten by ControlValueAccessor impl of registerOnChange + private _controlValueAccessorChangeFn: (value: any) => void = () => {}; + + ngOnInit() { + } + + + writeValue(obj: any): void { + this.checked = obj === this.onValue; + } + + registerOnChange(fn: any): void { + // for synchronization of values from the downstream components form value + this._controlValueAccessorChangeFn = fn; + } + + /** + * Focus control not implemented yet + * @param fn + */ + registerOnTouched(): void {} + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + this._changeDetectorRef.markForCheck(); + } } + diff --git a/src/app/header/cbp-toolbar/cbp-toolbar-state.ts b/src/app/header/cbp-toolbar/cbp-toolbar-state.ts index f16de0a..22cff67 100644 --- a/src/app/header/cbp-toolbar/cbp-toolbar-state.ts +++ b/src/app/header/cbp-toolbar/cbp-toolbar-state.ts @@ -7,7 +7,7 @@ export const CBP_HEADER_STATE = new InjectionToken('cbp-toolbar export const APP_HEADER_STATE = new InjectionToken('app-toolbar-state-service'); /** - * Holds a single notification state as well as serves as a observable. + * Holds a single cbp-notification state as well as serves as a observable. */ export class CBPToolbarState { hasToolbarMenu = new BehaviorSubject(false); diff --git a/src/app/index.ts b/src/app/index.ts index 7c25d8b..474590c 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -6,3 +6,4 @@ export * from './header/index'; export * from './pipes/index'; export * from './progress/index'; export * from './user/index'; +export * from './notifications/index'; diff --git a/src/app/notifications/cbp-notification.ts b/src/app/notifications/cbp-notification.ts new file mode 100644 index 0000000..01e7998 --- /dev/null +++ b/src/app/notifications/cbp-notification.ts @@ -0,0 +1,62 @@ +import {TemplateRef} from '@angular/core'; +import {Portal} from '@angular/cdk/portal'; +import {Observable} from 'rxjs/Observable'; +import {ReplaySubject} from 'rxjs/ReplaySubject'; + +import 'rxjs/add/observable/empty'; +import 'rxjs/add/operator/delay'; + +let notificationCounter = 0; +export class CBPNotification { + type?: 'success' | 'danger' | 'warning' | 'info'; + /** + * For creating a simple test notification. + */ + message?: string; + + /** + * If you have your own markup and actionable notifications. + */ + content?: TemplateRef; + + /** + * Mainly used internally but you can pass CdkPortal i.e. your own TemplatePortal or ComponentPortal + */ + contentPortal?: Portal; + + private _state: ReplaySubject = new ReplaySubject(1); + private _id: number; + get id() { + return this._id; + } + + constructor(isClosedInitially = false) { + this._id = ++notificationCounter; + this._state.next(isClosedInitially); + } + + + /** + * Is the notification currently open ? + * @returns {Observable} + */ + isOpen(): Observable { + return this._state.asObservable(); + } + + /** + * Open the notification that is created. + */ + open(): void { + this._state.next(true); + } + + /** + * Close the notification. Meant for close and destroy. + */ + close(): void { + this._state.next(false); + } + + +} diff --git a/src/app/notifications/cbp-notification/cbp-notification.component.html b/src/app/notifications/cbp-notification/cbp-notification.component.html new file mode 100644 index 0000000..8c5d716 --- /dev/null +++ b/src/app/notifications/cbp-notification/cbp-notification.component.html @@ -0,0 +1,11 @@ +
+
{{ notification?.type }} notification + + + {{ notification.message }} + + +
+
\ No newline at end of file diff --git a/src/app/notifications/cbp-notification/cbp-notification.component.scss b/src/app/notifications/cbp-notification/cbp-notification.component.scss new file mode 100644 index 0000000..90ce717 --- /dev/null +++ b/src/app/notifications/cbp-notification/cbp-notification.component.scss @@ -0,0 +1,51 @@ +@import '../../variables'; + +.toast { + position: relative; + border: 1px solid #ddd; + border-left-width: 4px; + border-radius: 2px; + margin-bottom: 9px; + position: relative; + background: #fff; + box-shadow: 0px 1px 3px rgba(0,0,0,.1); + padding: $alert-padding*2; + + &.toast-info { + border-left-color: $brand-info; + } + &.toast-primary { + border-left-color: $brand-primary; + } + &.toast-success { + border-left-color: $brand-success; + } + &.toast-danger { + border-left-color: $brand-danger; + } + &.toast-warning { + border-left-color: $brand-warning; + } + + + + + button.close { + position: absolute; + font-size: 24px; + right: 0; + top: 0; + opacity: .6; + } +} + +.cbp-notification-contents { + display: inline-block; + margin-bottom: $alert-padding; +} +.cbp-notification-actions { + display: inline-block; + margin-top: $alert-padding; + padding-top: 0; + width: 100%; +} \ No newline at end of file diff --git a/src/app/notifications/cbp-notification/cbp-notification.component.spec.ts b/src/app/notifications/cbp-notification/cbp-notification.component.spec.ts new file mode 100644 index 0000000..28f9333 --- /dev/null +++ b/src/app/notifications/cbp-notification/cbp-notification.component.spec.ts @@ -0,0 +1,27 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CBPNotificationComponent } from './cbp-notification.component'; +import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; + +describe('CBPNotificationComponent', () => { + let component: CBPNotificationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CBPNotificationComponent ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CBPNotificationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/notifications/cbp-notification/cbp-notification.component.ts b/src/app/notifications/cbp-notification/cbp-notification.component.ts new file mode 100644 index 0000000..1c4f2d0 --- /dev/null +++ b/src/app/notifications/cbp-notification/cbp-notification.component.ts @@ -0,0 +1,97 @@ +import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation} from '@angular/core'; +import {animate, style, transition, trigger} from '@angular/animations'; +import {CBPNotification} from '../cbp-notification'; +import {Subscription} from 'rxjs/Subscription'; +import {Observable} from 'rxjs/Observable'; + +import 'rxjs/add/observable/empty'; +import 'rxjs/add/operator/delay'; + + +@Component({ + selector: 'cbp-notification', + templateUrl: './cbp-notification.component.html', + styleUrls: ['./cbp-notification.component.scss'], + encapsulation: ViewEncapsulation.None, + animations: [ + trigger( + 'animationState', [ + transition('* => enter', [ + style({transform: 'translateX(100%)', opacity: 0}), + animate('500ms', style({transform: 'translateX(0)', opacity: 1})) + ]), + transition('enter => *', [ + style({transform: 'translateX(0)', opacity: 1}), + animate('500ms', style({transform: 'translateX(100%)', opacity: 0})) + ]) + ] + ) + ] +}) +export class CBPNotificationComponent implements OnInit, OnDestroy { + @Input() type?: 'success' | 'danger' | 'warning' | 'info' = 'info'; + @Input() message?: string; + // OR + @Input() notification: CBPNotification; + + @Input() autoShow = false; + @Input() delay = 100; + @Input() show = false; + + animationState: any; + private _subscriptions: Subscription[] = []; + + + @Output() close = new EventEmitter(); + + + constructor() { + } + + get toastTypeClass() { + return `toast-${this.notification.type}`; + } + + ngOnInit() { + if (!this.notification) { + this.notification = new CBPNotification(this.autoShow); + if (!this.notification.message) { + this.notification.message = this.message; + } + if (!this.notification.type) { + this.notification.type = this.type; + } + } else if (!this.notification.open) { + throw new Error('Must be an instance of CBPNotification'); + } + + this._subscriptions.push(this.notification.isOpen().subscribe( state => { + if (state) { + this.activate(); + } else { + this.remove(); + } + })); + } + + ngOnDestroy() { + this._subscriptions.forEach(sub => sub.unsubscribe()); + } + + activate() { + this.show = true; + this.animationState = 'enter' + } + + dismiss() { + this.show = false; + this.animationState = 'leave'; + } + remove() { + this.dismiss(); + Observable.empty().delay(300).subscribe( null, null, () => { + this.close.emit(this.notification); + }); + } + +} diff --git a/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.html b/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.html new file mode 100644 index 0000000..dcd8434 --- /dev/null +++ b/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.html @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.scss b/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.scss new file mode 100644 index 0000000..59bdb4b --- /dev/null +++ b/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.scss @@ -0,0 +1,14 @@ +.cbp-notifications-overlay { + //&.static-demo { + // position: inherit; + // top: 42px; right: 0px; + // z-index: inherit; + // + //} + width: 400px; + + position: fixed; + top: 52px; + right: 0px; + z-index: 10000; +} diff --git a/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.spec.ts b/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.spec.ts new file mode 100644 index 0000000..d717784 --- /dev/null +++ b/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.spec.ts @@ -0,0 +1,31 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CBPNotificationsOverlayComponent } from './cbp-notifications-overlay.component'; +import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import {PortalModule} from '@angular/cdk/portal'; +import {CBPNotificationsService} from '../cbp-notifications.service'; + +describe('CBPNotificationsOverlayComponent', () => { + let component: CBPNotificationsOverlayComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CBPNotificationsOverlayComponent ], + imports: [PortalModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [CBPNotificationsService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CBPNotificationsOverlayComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.ts b/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.ts new file mode 100644 index 0000000..874e5d5 --- /dev/null +++ b/src/app/notifications/cbp-notifications-overlay/cbp-notifications-overlay.component.ts @@ -0,0 +1,38 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {CBPNotification} from '../cbp-notification'; +import {CBPNotificationsService} from '../cbp-notifications.service'; +import {Subscription} from 'rxjs/Subscription'; +import {TemplatePortal} from '@angular/cdk/portal'; + +@Component({ + selector: 'cbp-notifications-overlay', + templateUrl: './cbp-notifications-overlay.component.html', + styleUrls: ['./cbp-notifications-overlay.component.scss'] +}) +export class CBPNotificationsOverlayComponent implements OnInit, OnDestroy { + + notifications: CBPNotification[] = []; + private _subscriptions: Subscription[] = []; + + constructor(private notificationsService: CBPNotificationsService) { } + + ngOnInit() { + this.notificationsService.getNotifications().subscribe( notification => { + if (notification.content && !notification.contentPortal) { + notification.contentPortal = new TemplatePortal(notification.content, null!); + } + this.notifications.push(notification); + }); + } + + ngOnDestroy() { + this._subscriptions.forEach( sub => sub.unsubscribe()); + } + onClose(closed: CBPNotification) { + + console.log('closed notification $event here', this.notifications.length); + const index = this.notifications.indexOf(closed); + this.notifications.splice(index, 1); + console.log('closed notification $event here', this.notifications.length); + } +} diff --git a/src/app/notifications/cbp-notifications.service.spec.ts b/src/app/notifications/cbp-notifications.service.spec.ts new file mode 100644 index 0000000..8dc3b85 --- /dev/null +++ b/src/app/notifications/cbp-notifications.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { CBPNotificationsService } from './cbp-notifications.service'; + +describe('CBPNotificationsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [CBPNotificationsService] + }); + }); + + it('should be created', inject([CBPNotificationsService], (service: CBPNotificationsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/notifications/cbp-notifications.service.ts b/src/app/notifications/cbp-notifications.service.ts new file mode 100644 index 0000000..495e2e4 --- /dev/null +++ b/src/app/notifications/cbp-notifications.service.ts @@ -0,0 +1,42 @@ +import {Injectable} from '@angular/core'; +import {CBPNotification} from './cbp-notification'; +import {ReplaySubject} from 'rxjs/ReplaySubject'; +import {Observable} from 'rxjs/Observable'; + +import 'rxjs/add/observable/empty'; +import 'rxjs/add/operator/delay'; + + +@Injectable() +export class CBPNotificationsService { + + private _notifications$ = new ReplaySubject(3); + + constructor() { + } + + getNotifications(): Observable { + return this._notifications$.asObservable(); + } + + /** + * Adds and opens a notification. + * @param {CBPNotification} notification + */ + notify(notification: CBPNotification) { + notification.open(); + this._notifications$.next(notification); + } + + /** + * Snoozes a notification + * @param {CBPNotification} notification + * @param {number} wakeUpAfter + */ + snooze(notification: CBPNotification, wakeUpAfter = 5000) { + notification.close(); + Observable.empty().delay(wakeUpAfter).subscribe( null, null, () => { + this.notify(notification); + }); + } +} diff --git a/src/app/notifications/index.ts b/src/app/notifications/index.ts new file mode 100644 index 0000000..53d513f --- /dev/null +++ b/src/app/notifications/index.ts @@ -0,0 +1,2 @@ +export * from './notifications.module'; +export * from './cbp-notification/cbp-notification.component'; diff --git a/src/app/notifications/notifications.module.ts b/src/app/notifications/notifications.module.ts new file mode 100644 index 0000000..a64f0cc --- /dev/null +++ b/src/app/notifications/notifications.module.ts @@ -0,0 +1,18 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {CBPNotificationComponent} from './cbp-notification/cbp-notification.component'; +import {CBPNotificationsOverlayComponent} from './cbp-notifications-overlay/cbp-notifications-overlay.component'; +import {CBPNotificationsService} from './cbp-notifications.service'; +import {MatButtonModule, MatIconModule} from '@angular/material'; +import {PortalModule} from '@angular/cdk/portal'; + +@NgModule({ + imports: [ + CommonModule, MatButtonModule, PortalModule, MatIconModule + ], + declarations: [CBPNotificationComponent, CBPNotificationsOverlayComponent], + exports: [CBPNotificationComponent, CBPNotificationsOverlayComponent], + providers: [CBPNotificationsService] +}) +export class CBPNotificationsModule { +} diff --git a/src/app/test-module.ts b/src/app/test-module.ts index 30c03e0..e75119f 100644 --- a/src/app/test-module.ts +++ b/src/app/test-module.ts @@ -7,6 +7,7 @@ import {CBPAppHeaderModule} from './header/app-header/app-header.module'; import {CBPProgressModule} from './progress/progress.module'; import {CBPRootComponent} from './cbp-root/cbp-root.component'; import {CBPButtonsModule} from './buttons/buttons.module'; +import {CBPNotificationsModule} from './notifications'; @NgModule({ @@ -17,7 +18,8 @@ import {CBPButtonsModule} from './buttons/buttons.module'; CBPHeaderModule, CBPAppHeaderModule, CBPProgressModule, - CBPButtonsModule + CBPButtonsModule, + CBPNotificationsModule ], providers: [], schemas: [], diff --git a/src/kitchensink/app/demo-buttons/demo-buttons.component.html b/src/kitchensink/app/demo-buttons/demo-buttons.component.html index f502e9a..4e226a6 100644 --- a/src/kitchensink/app/demo-buttons/demo-buttons.component.html +++ b/src/kitchensink/app/demo-buttons/demo-buttons.component.html @@ -16,29 +16,47 @@

Disabled Buttons

Toggle Switches

-
- - value = {{ toggle.one }} -
-
-
- - value = {{ toggle.two }} -
-
-
- - value = {{ toggle.something }} - -
-
-
- - value = {{ toggle.disabled }} -
-
-
- - value = false -
+
+ + value = {{ toggle.one }} +
+
+
+ + value = {{ toggle.something2 }} + Toggle +
+
+
+ + value = {{ toggle.two }} +
+
+
+ + value = {{ toggle.yn }} +
+
+
+ + value = {{ toggle.three }} +
+
+
+ + value = {{ toggle.disabled }} +
+
+
+ + value = false +
+
\ No newline at end of file diff --git a/src/kitchensink/app/demo-buttons/demo-buttons.component.spec.ts b/src/kitchensink/app/demo-buttons/demo-buttons.component.spec.ts index 1fb7469..6c5224b 100644 --- a/src/kitchensink/app/demo-buttons/demo-buttons.component.spec.ts +++ b/src/kitchensink/app/demo-buttons/demo-buttons.component.spec.ts @@ -5,11 +5,12 @@ import {BrowserModule} from '@angular/platform-browser'; import {DemoButtonsComponent} from './demo-buttons.component'; import {CBPButtonsModule} from '../../../app/buttons/buttons.module'; import {FormsModule} from '@angular/forms'; +import {MatCheckboxModule} from '@angular/material'; describe('DemoButtonsComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [BrowserModule, NoopAnimationsModule, CBPButtonsModule, FormsModule], + imports: [BrowserModule, NoopAnimationsModule, CBPButtonsModule, FormsModule, MatCheckboxModule], declarations: [ DemoButtonsComponent] }).compileComponents(); diff --git a/src/kitchensink/app/demo-buttons/demo-buttons.component.ts b/src/kitchensink/app/demo-buttons/demo-buttons.component.ts index b45800b..44c6dc2 100644 --- a/src/kitchensink/app/demo-buttons/demo-buttons.component.ts +++ b/src/kitchensink/app/demo-buttons/demo-buttons.component.ts @@ -10,12 +10,19 @@ export class DemoButtonsComponent { toggle = { one: true, - two: false, + two: true, disabled: 'Y', - something: '' + something: '', + something2: '', + yn: 'Y', + three: undefined }; + changed($event: any) { + console.log('demo toggle changed - ', $event); + } constructor() { delete this.toggle.something; + delete this.toggle.something2; } } diff --git a/src/kitchensink/app/demo-buttons/demo-buttons.module.ts b/src/kitchensink/app/demo-buttons/demo-buttons.module.ts index 1a8c068..5d4d759 100644 --- a/src/kitchensink/app/demo-buttons/demo-buttons.module.ts +++ b/src/kitchensink/app/demo-buttons/demo-buttons.module.ts @@ -1,13 +1,13 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import {DemoButtonsComponent} from './demo-buttons.component'; -import {MatButtonModule} from '@angular/material'; +import {MatButtonModule, MatCheckboxModule} from '@angular/material'; import {CBPButtonsModule} from '../../../app/buttons/buttons.module'; import {FormsModule} from '@angular/forms'; @NgModule({ imports: [ - CommonModule, MatButtonModule, CBPButtonsModule, FormsModule + CommonModule, MatButtonModule, CBPButtonsModule, FormsModule, MatCheckboxModule ], exports: [DemoButtonsComponent, MatButtonModule], declarations: [DemoButtonsComponent] diff --git a/src/kitchensink/app/demo-notifications/demo-notifications.component.html b/src/kitchensink/app/demo-notifications/demo-notifications.component.html new file mode 100644 index 0000000..3c28b4c --- /dev/null +++ b/src/kitchensink/app/demo-notifications/demo-notifications.component.html @@ -0,0 +1,54 @@ +

Notifications

+ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + Jerry added a comment to one of your cases (90/098,881). + + + View comment + + + + + Case 90/842,381 is due tomorrow. + + View case + Snooze 3 sec + + + + + + Connection has been lost. Retrying in 30 seconds. + + Retry & Close + + + diff --git a/src/kitchensink/app/demo-notifications/demo-notifications.component.scss b/src/kitchensink/app/demo-notifications/demo-notifications.component.scss new file mode 100644 index 0000000..402bac1 --- /dev/null +++ b/src/kitchensink/app/demo-notifications/demo-notifications.component.scss @@ -0,0 +1,4 @@ +.demo-notifications-pre-build { + padding: 24px 0; + max-width: 400px; +} \ No newline at end of file diff --git a/src/kitchensink/app/demo-notifications/demo-notifications.component.spec.ts b/src/kitchensink/app/demo-notifications/demo-notifications.component.spec.ts new file mode 100644 index 0000000..a418aa6 --- /dev/null +++ b/src/kitchensink/app/demo-notifications/demo-notifications.component.spec.ts @@ -0,0 +1,29 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; + +import {DemoNotificationsComponent} from './demo-notifications.component'; +import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import {CBPNotificationsService} from '../../../app/notifications/cbp-notifications.service'; + +describe('DemoNotificationsComponent', () => { + let component: DemoNotificationsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [DemoNotificationsComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [CBPNotificationsService] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DemoNotificationsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/kitchensink/app/demo-notifications/demo-notifications.component.ts b/src/kitchensink/app/demo-notifications/demo-notifications.component.ts new file mode 100644 index 0000000..92fb1d1 --- /dev/null +++ b/src/kitchensink/app/demo-notifications/demo-notifications.component.ts @@ -0,0 +1,96 @@ +import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import {CBPNotification} from '../../../app/notifications/cbp-notification'; +import {CBPNotificationsService} from '../../../app/notifications/cbp-notifications.service'; +import 'rxjs/add/observable/empty'; +import 'rxjs/add/operator/delay'; +import {Observable} from 'rxjs/Observable'; +@Component({ + selector: 'demo-notifications, cbp-demo-notifications', + templateUrl: './demo-notifications.component.html', + styleUrls: ['./demo-notifications.component.scss'] +}) +export class DemoNotificationsComponent implements OnInit { + + @ViewChild('successNotification') successContentRef: TemplateRef; + @ViewChild('infoNotification') infoNotificationRef: TemplateRef; + @ViewChild('warnNotification') warnNotificationRef: TemplateRef; + @ViewChild('dangerNotification') dangerNotificationRef: TemplateRef; + + private snoozingNotification: CBPNotification; + private connectionLostNotification: CBPNotification; + dangerShow = true; + + constructor(private notificationService: CBPNotificationsService) { + } + + ngOnInit() { + } + + notifyMe(message: string) { + const notification = new CBPNotification(); + notification.type = 'success'; + notification.message = message; + this.notificationService.notify(notification); + } + + notifyInfo() { + const notification = new CBPNotification(); + notification.type = 'info'; + notification.content = this.infoNotificationRef; + this.notificationService.notify(notification); + + } + + notifyWarning() { + this.snoozingNotification = new CBPNotification(); + this.snoozingNotification.type = 'warning'; + this.snoozingNotification.content = this.warnNotificationRef; + this.notificationService.notify(this.snoozingNotification); + } + + notifyDanger() { + this.connectionLostNotification = new CBPNotification(); + this.connectionLostNotification.type = 'danger'; + this.connectionLostNotification.content = this.dangerNotificationRef; + this.notificationService.notify(this.connectionLostNotification); + } + + /** + * Actions on notifications + */ + viewComment() { + this.notifyMe('Comment Viewed'); + console.log('Comment Viewed'); + } + + /** + * Example of snoozing action. + * @param {number} snoozeFor + */ + snoozeSnoozing(snoozeFor: number) { + if (this.snoozingNotification) { + this.notificationService.snooze(this.snoozingNotification, snoozeFor); + } + } + + /** + * Various ways to interact with notification. + */ + retryAndClose() { + this.notifyMe('Connection Successful!'); + Observable.empty().delay(2000).subscribe( null, null, () => { + if (this.connectionLostNotification) { + this.connectionLostNotification.close(); + } else { + this.dangerShow = false; + } + }); + } + + /** + * Actions on notifications + */ + caseViewed() { + this.notifyMe('Case #165799-96 viewed.'); + } +} diff --git a/src/kitchensink/app/demo-notifications/demo-notifications.module.ts b/src/kitchensink/app/demo-notifications/demo-notifications.module.ts new file mode 100644 index 0000000..b55e6df --- /dev/null +++ b/src/kitchensink/app/demo-notifications/demo-notifications.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { DemoNotificationsComponent } from './demo-notifications.component'; +import {CBPNotificationsModule} from '../../../app/notifications'; +import {MatButtonModule} from '@angular/material'; + +@NgModule({ + imports: [ + CommonModule, MatButtonModule, CBPNotificationsModule + ], + declarations: [DemoNotificationsComponent], + exports: [DemoNotificationsComponent] +}) +export class DemoNotificationsModule { } diff --git a/src/kitchensink/app/demo.component.html b/src/kitchensink/app/demo.component.html index 985fa62..8f6f0f9 100644 --- a/src/kitchensink/app/demo.component.html +++ b/src/kitchensink/app/demo.component.html @@ -2,6 +2,7 @@ +
Preferences
@@ -10,6 +11,7 @@ + + + +
+
diff --git a/src/kitchensink/app/demo.component.spec.ts b/src/kitchensink/app/demo.component.spec.ts index 84979ee..0b06f6d 100644 --- a/src/kitchensink/app/demo.component.spec.ts +++ b/src/kitchensink/app/demo.component.spec.ts @@ -16,6 +16,8 @@ import {MockUserService} from '../../mock-services/user.mock.service'; import {MockApplicationsService} from '../../mock-services/applications.mock.service'; import {CBP_APPLICATIONS_SERVICE} from '../../app/applications/cbp-applications-service'; import {CBP_USER_SERVICE} from '../../app/user/user'; +import {DemoNotificationsModule} from './demo-notifications/demo-notifications.module'; +import {CBPNotificationsModule} from '../../app/notifications'; describe('DemoAppComponent', () => { beforeEach(async(() => { @@ -25,8 +27,10 @@ describe('DemoAppComponent', () => { CBPAccordionModule, CBPHeaderModule, CBPAppHeaderModule, + CBPNotificationsModule, DemoButtonsModule, - DemoAppHeaderModule + DemoAppHeaderModule, + DemoNotificationsModule ], declarations: [ DemoAppComponent, diff --git a/src/kitchensink/app/demo.module.ts b/src/kitchensink/app/demo.module.ts index 4801338..76b4ef8 100644 --- a/src/kitchensink/app/demo.module.ts +++ b/src/kitchensink/app/demo.module.ts @@ -18,6 +18,8 @@ import {MockApplicationsService} from '../../mock-services/applications.mock.ser import {CBP_USER_SERVICE} from '../../app/user/user'; import {CBP_APPLICATIONS_SERVICE} from '../../app/applications/cbp-applications-service'; import * as pkg from '../../../package.json'; +import {DemoNotificationsModule} from './demo-notifications/demo-notifications.module'; +import {CBPNotificationsModule} from '../../app/notifications'; export const KITCHENSINK_APP_VERSION = (pkg).version; @@ -34,10 +36,12 @@ export const KITCHENSINK_APP_VERSION = (pkg).version; CBPAccordionModule, CBPHeaderModule, CBPAppHeaderModule, + CBPNotificationsModule, // CBPProgressModule, // demo DemoButtonsModule, - DemoAppHeaderModule + DemoAppHeaderModule, + DemoNotificationsModule ], exports: [DemoButtonsModule, DemoAppHeaderModule], providers: [ diff --git a/src/kitchensink/index.html b/src/kitchensink/index.html index 46ed43e..527b84d 100644 --- a/src/kitchensink/index.html +++ b/src/kitchensink/index.html @@ -2,7 +2,7 @@ - Demo ngx-cbp-theme component-development + KitchenSink ngx-cbp-theme