diff --git a/package.json b/package.json index 905a75f3fc..12a8d50e88 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "postinstall": "webdriver-manager update", "webdriver-update": "bash ./node_modules/.bin/webdriver-manager update", "test": "ng test --code-coverage --single-run", - "show-coverage-win" : "start chrome ./coverage/index.html", + "coverage-win" : "start chrome ./coverage/index.html", "bump-dev": "gulp bump-version", "bump-patch": "gulp bump-version --ver patch", "bump-minor": "gulp bump-version --ver minor", diff --git a/src/app/components/components/components.component.ts b/src/app/components/components/components.component.ts index 830aa33bb3..c1dd11f60e 100644 --- a/src/app/components/components/components.component.ts +++ b/src/app/components/components/components.component.ts @@ -77,6 +77,11 @@ export class ComponentsComponent { icon: 'swap_horiz', route: 'paging', title: 'Paging', + }, { + description: 'Notification count & menu', + icon: 'notifications', + route: 'notifications', + title: 'Notifications', }, { description: 'Search and filter items', icon: 'search', diff --git a/src/app/components/components/components.module.ts b/src/app/components/components/components.module.ts index 659a36710d..4d1a1eba19 100644 --- a/src/app/components/components/components.module.ts +++ b/src/app/components/components/components.module.ts @@ -23,6 +23,7 @@ import { PagingDemoComponent } from './paging/paging.component'; import { SearchDemoComponent } from './search/search.component'; import { DynamicFormsDemoComponent } from './dynamic-forms/dynamic-forms.component'; import { MaterialComponentsComponent, DialogComponent } from './material-components/material-components.component'; +import { NotificationsDemoComponent } from './notifications/notifications.component'; import { CovalentCoreModule } from '../../../platform/core'; import { CovalentHighlightModule } from '../../../platform/highlight'; @@ -54,6 +55,7 @@ import { CovalentDynamicFormsModule } from '../../../platform/dynamic-forms'; DynamicFormsDemoComponent, MaterialComponentsComponent, DialogComponent, + NotificationsDemoComponent, ], imports: [ CovalentCoreModule.forRoot(), diff --git a/src/app/components/components/components.routes.ts b/src/app/components/components/components.routes.ts index 49c2391fcb..6d56695d6b 100644 --- a/src/app/components/components/components.routes.ts +++ b/src/app/components/components/components.routes.ts @@ -21,6 +21,7 @@ import { PagingDemoComponent } from './paging/paging.component'; import { SearchDemoComponent } from './search/search.component'; import { DynamicFormsDemoComponent } from './dynamic-forms/dynamic-forms.component'; import { MaterialComponentsComponent } from './material-components/material-components.component'; +import { NotificationsDemoComponent } from './notifications/notifications.component'; const routes: Routes = [{ children: [{ @@ -80,6 +81,9 @@ const routes: Routes = [{ }, { component: PagingDemoComponent, path: 'paging', + }, { + component: NotificationsDemoComponent, + path: 'notifications', }, { component: DynamicFormsDemoComponent, path: 'dynamic-forms', diff --git a/src/app/components/components/notifications/notifications.component.html b/src/app/components/components/notifications/notifications.component.html new file mode 100644 index 0000000000..ad79b1859e --- /dev/null +++ b/src/app/components/components/notifications/notifications.component.html @@ -0,0 +1,160 @@ + + Notifications + Notifications count & menu for toolbar + + +

Typical usage in a toolbar:

+ + + Toolbar + + + + person + + + + +
Notifications
+ + + + +
+
+ + + + + +
+
+
+ + + people + + +
+ + + person +

Jyn Erso stole the plans to the Death Star

+

10 mins ago

+ + + +
+ + + person +

Chirrut Îmwe beat down a bunch of storm troopers with a stick

+

2 days ago

+ + + +
+
+
+
+
+ + TdNotificationCountComponent + How to use this component + + +

Use ]]> element to show number of notifications.

+

Properties:

+

The ]]> component has {{notificationsAttrs.length}} properties:

+ + + +

Example:

+

HTML:

+ + + + notifications + + + + +
Notifications
+ + + + +
+
+ ]]> +
+

Typescript:

+ + + +

Setup:

+

Import the [CovalentNotificationsModule] using the forRoot() method in your NgModule:

+ + + +
+
diff --git a/src/app/components/components/notifications/notifications.component.scss b/src/app/components/components/notifications/notifications.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/components/components/notifications/notifications.component.ts b/src/app/components/components/notifications/notifications.component.ts new file mode 100644 index 0000000000..8ad3577a45 --- /dev/null +++ b/src/app/components/components/notifications/notifications.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'notifications-demo', + styleUrls: ['./notifications.component.scss'], + templateUrl: './notifications.component.html', +}) +export class NotificationsDemoComponent { + + notificationsAttrs: Object[] = [{ + description: `Number for the notification count. + Shows number if the input is a positive number or its no count state if boolean 'true'`, + name: 'notifications', + type: 'number | boolean', + }, { + description: `Sets the theme color of the notification tip. + Defaults to 'warn'`, + name: 'color', + type: '"primary" | "accent" | "warn"', + }]; + +} diff --git a/src/app/components/components/overview/overview.component.ts b/src/app/components/components/overview/overview.component.ts index fc90375a76..3f31d286d1 100644 --- a/src/app/components/components/overview/overview.component.ts +++ b/src/app/components/components/overview/overview.component.ts @@ -71,6 +71,11 @@ export class ComponentsOverviewComponent { icon: 'swap_horiz', route: 'paging', title: 'Paging', + }, { + color: 'purple-700', + icon: 'notifications', + route: 'notifications', + title: 'Notifications', }, { color: 'lime-700', icon: 'search', diff --git a/src/app/components/home/home.component.html b/src/app/components/home/home.component.html index 86d882a959..90f7b07f0e 100644 --- a/src/app/components/home/home.component.html +++ b/src/app/components/home/home.component.html @@ -2,6 +2,56 @@
Covalent + + + +
Updates
+ + + notifications +

Notifications component added

+

New component

+
+ + + format_align_center +

JSON driven dynamic forms

+

New component

+
+ + + call_to_action +

Sticky footer added to layouts

+

Component updated

+
+ + + grid_on +

Nested object support for data-table

+

Component updated

+
+ + + http +

HTTP path interceptors & transform support

+

Service updated

+
+ + + notifications +

Angular 2.4.1 & Material2 Beta1

+

Dependencies updated

+
+
+ + View Full Changelog + +
+
flash_on
diff --git a/src/platform/core/index.ts b/src/platform/core/index.ts index da94e0c256..45579b2eb1 100644 --- a/src/platform/core/index.ts +++ b/src/platform/core/index.ts @@ -76,6 +76,20 @@ export * from './loading/loading.module'; import { CovalentMediaModule } from './media/media.module'; export * from './media/media.module'; +/** + * MENU + */ + +import { CovalentMenuModule } from './menu/menu.module'; +export * from './menu/menu.module'; + +/** + * NOTIFICATIONS + */ + +import { CovalentNotificationsModule } from './notifications/notifications.module'; +export * from './notifications/notifications.module'; + /** * PAGING */ @@ -114,6 +128,8 @@ export * from './steps/steps.module'; CovalentLayoutModule.forRoot(), CovalentLoadingModule.forRoot(), CovalentMediaModule.forRoot(), + CovalentMenuModule.forRoot(), + CovalentNotificationsModule.forRoot(), CovalentPagingModule.forRoot(), CovalentSearchModule.forRoot(), CovalentStepsModule.forRoot(), @@ -134,6 +150,8 @@ export * from './steps/steps.module'; CovalentLayoutModule, CovalentLoadingModule, CovalentMediaModule, + CovalentMenuModule, + CovalentNotificationsModule, CovalentPagingModule, CovalentSearchModule, CovalentStepsModule, diff --git a/src/platform/core/menu/menu.component.html b/src/platform/core/menu/menu.component.html new file mode 100644 index 0000000000..f9e624038e --- /dev/null +++ b/src/platform/core/menu/menu.component.html @@ -0,0 +1,9 @@ +
+ + +
+ +
+ + +
\ No newline at end of file diff --git a/src/platform/core/menu/menu.component.scss b/src/platform/core/menu/menu.component.scss new file mode 100644 index 0000000000..ebabf9c45b --- /dev/null +++ b/src/platform/core/menu/menu.component.scss @@ -0,0 +1,32 @@ +$td-menu-spacing: 8px !default; +:host { + display: block; + margin-top: -$td-menu-spacing; + margin-bottom: -$td-menu-spacing; +} +:host /deep/ { + [td-menu-header] { + padding: $td-menu-spacing; + text-align: center; + } + md-list, + md-list[dense], + md-nav-list, + md-nav-list[dense], { + a[md-list-item].md-2-line .md-list-item, + md-list-item.md-2-line .md-list-item { + height: auto; + padding: $td-menu-spacing; + .md-list-text { + padding-right: 0px; + } + [md-line] + [md-line] { + margin-top: $td-menu-spacing / 2; + } + } + } +} +.td-menu-content { + max-height: calc(50vh); + overflow-y: auto; +} diff --git a/src/platform/core/menu/menu.component.ts b/src/platform/core/menu/menu.component.ts new file mode 100644 index 0000000000..2ee9380b17 --- /dev/null +++ b/src/platform/core/menu/menu.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'td-menu', + templateUrl: './menu.component.html', + styleUrls: ['./menu.component.scss'], +}) +export class TdMenuComponent { + +} diff --git a/src/platform/core/menu/menu.module.ts b/src/platform/core/menu/menu.module.ts new file mode 100644 index 0000000000..b6f342cb85 --- /dev/null +++ b/src/platform/core/menu/menu.module.ts @@ -0,0 +1,41 @@ +import { Type } from '@angular/core'; +import { NgModule, ModuleWithProviders } from '@angular/core'; + +import { CommonModule } from '@angular/common'; +import { MaterialModule } from '@angular/material'; + +import { CovalentCommonModule } from '../common/common.module'; + +import { TdMenuComponent } from './menu.component'; + +const TD_MENU: Type[] = [ + TdMenuComponent, +]; + +export { TdMenuComponent } from './menu.component'; + +@NgModule({ + imports: [ + CommonModule, + MaterialModule.forRoot(), + CovalentCommonModule.forRoot(), + ], + declarations: [ + TD_MENU, + ], + exports: [ + CommonModule, + MaterialModule, + CovalentCommonModule, + + TD_MENU, + ], +}) +export class CovalentMenuModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: CovalentMenuModule, + providers: [ ], + }; + } +} diff --git a/src/platform/core/notifications/README.md b/src/platform/core/notifications/README.md new file mode 100644 index 0000000000..80e261789b --- /dev/null +++ b/src/platform/core/notifications/README.md @@ -0,0 +1,46 @@ +# td-notification-count + +`td-notification-count` element renders a number of notifications. + +## API Summary + +Properties: + +| Name | Type | Description | +| --- | --- | --- | +| `color?` | `"primary" | "accent" | "warn"` | Sets the theme color of the notification tip. Defaults to 'warn' +| `notifications?` | `number | boolean` | Number for the notification count. Shows number if the input is a positive number or its no count state if boolean 'true' + +## Setup + +Import the [CovalentNotificationsModule] using the forRoot() method in your NgModule: + +```typescript +import { CovalentNotificationsModule } from '@covalent/core'; +@NgModule({ + imports: [ + CovalentNotificationsModule.forRoot(), // or CovalentCoreModule.forRoot() (included inside of it) + ... + ], + ... +}) +export class MyModule {} +``` + +## Usage + +Example for HTML count usage: + + ```html + + notifications + + ``` + + Example for HTML no count usage: + + ```html + + notifications + + ``` diff --git a/src/platform/core/notifications/_notification-count-theme.scss b/src/platform/core/notifications/_notification-count-theme.scss new file mode 100644 index 0000000000..582f631d20 --- /dev/null +++ b/src/platform/core/notifications/_notification-count-theme.scss @@ -0,0 +1,27 @@ +@import '~@angular/material/core/theming/theming'; +@import '~@angular/material/core/style/variables'; +@import '~@angular/material/core/style/elevation'; + +@mixin td-notification-count-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .td-notification-count { + @include md-elevation(1); + &.md-warn { + background-color: md-color($warn); + color: md-color($warn, default-contrast); + } + &.md-primary { + background-color: md-color($primary); + color: md-color($primary, default-contrast); + } + &.md-accent { + background-color: md-color($accent); + color: md-color($accent, default-contrast); + } + } +} \ No newline at end of file diff --git a/src/platform/core/notifications/notification-count.component.html b/src/platform/core/notifications/notification-count.component.html new file mode 100644 index 0000000000..35221fa723 --- /dev/null +++ b/src/platform/core/notifications/notification-count.component.html @@ -0,0 +1,8 @@ +
+ +
+
+ {{noCount ? '' : notificationsDisplay}} +
\ No newline at end of file diff --git a/src/platform/core/notifications/notification-count.component.scss b/src/platform/core/notifications/notification-count.component.scss new file mode 100644 index 0000000000..dea3ec035c --- /dev/null +++ b/src/platform/core/notifications/notification-count.component.scss @@ -0,0 +1,35 @@ +$td-notification-size: 20px !default; +$td-notification-content-size: 40px !default; + +:host { + position: relative; + display: block; + text-align: center; + width: $td-notification-content-size; + height: $td-notification-content-size; +} + +.td-notification-count { + height: $td-notification-size; + line-height: $td-notification-size + 1; + width: $td-notification-size; + position: absolute; + right: 0px; + top: 0px; + font-size: 10px; + font-weight: 600; + border-radius: 50%; + z-index: 1; + &.td-notification-no-count { + width: 8px; + height: 8px; + top: 8px; + right: 8px; + } +} + +.td-notification-content { + &, /deep/ > * { + line-height: $td-notification-content-size; + } +} \ No newline at end of file diff --git a/src/platform/core/notifications/notification-count.component.spec.ts b/src/platform/core/notifications/notification-count.component.spec.ts new file mode 100644 index 0000000000..4a730b1db4 --- /dev/null +++ b/src/platform/core/notifications/notification-count.component.spec.ts @@ -0,0 +1,149 @@ +import { + TestBed, + inject, + async, + ComponentFixture, +} from '@angular/core/testing'; +import { Component } from '@angular/core'; +import { CovalentNotificationsModule } from './notifications.module'; +import { By } from '@angular/platform-browser'; + +describe('Component: NotificationCount', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + TdNotificationCountBasicTestComponent, + TdNotificationCountContentTestComponent, + ], + imports: [ + CovalentNotificationsModule.forRoot(), + ], + }); + TestBed.compileComponents(); + })); + + it('should render component with no notification tip', + async(inject([], () => { + let fixture: ComponentFixture = TestBed.createComponent(TdNotificationCountBasicTestComponent); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.td-notification-content'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('.td-notification-count'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('.td-notification-no-count'))).toBeFalsy(); + }); + }))); + + it('should render component notification tip with no count nor number', + async(inject([], () => { + let fixture: ComponentFixture = TestBed.createComponent(TdNotificationCountBasicTestComponent); + let component: TdNotificationCountBasicTestComponent = fixture.debugElement.componentInstance; + component.notifications = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.td-notification-count'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('.td-notification-no-count'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('.td-notification-count')) + .nativeElement.textContent.trim()).toBeFalsy(); + }); + }))); + + it('should render component notification tip with count and number', + async(inject([], () => { + let fixture: ComponentFixture = TestBed.createComponent(TdNotificationCountBasicTestComponent); + let component: TdNotificationCountBasicTestComponent = fixture.debugElement.componentInstance; + component.notifications = 44; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.td-notification-count'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('.td-notification-no-count'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('.td-notification-count')) + .nativeElement.textContent.trim()).toContain(component.notifications); + }); + }))); + + it('should render component with notification tip hidden', + async(inject([], () => { + let fixture: ComponentFixture = TestBed.createComponent(TdNotificationCountBasicTestComponent); + let component: TdNotificationCountBasicTestComponent = fixture.debugElement.componentInstance; + component.notifications = 0; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.td-notification-count'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('.td-notification-no-count'))).toBeFalsy(); + }); + }))); + + it('should render component with notification tip and then hide it', + async(inject([], () => { + let fixture: ComponentFixture = TestBed.createComponent(TdNotificationCountBasicTestComponent); + let component: TdNotificationCountBasicTestComponent = fixture.debugElement.componentInstance; + component.notifications = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.td-notification-count'))).toBeTruthy(); + component.notifications = false; + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.td-notification-count'))).toBeFalsy(); + }); + }); + }))); + + it('should render component notification tip with count and 99+', + async(inject([], () => { + let fixture: ComponentFixture = TestBed.createComponent(TdNotificationCountBasicTestComponent); + let component: TdNotificationCountBasicTestComponent = fixture.debugElement.componentInstance; + component.notifications = 100; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.td-notification-count'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('.td-notification-no-count'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('.td-notification-count')) + .nativeElement.textContent.trim()).toContain('99+'); + }); + }))); + + it('should render component with content transcluded', + async(inject([], () => { + let fixture: ComponentFixture = TestBed.createComponent(TdNotificationCountContentTestComponent); + fixture.detectChanges(); + fixture.whenStable().then(() => { + + expect(fixture.debugElement.query(By.css('.td-notification-content'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('md-icon'))).toBeTruthy(); + }); + }))); + +}); + +@Component({ + selector: 'td-notification-count-basic-test', + template: ` + + + + `, +}) +class TdNotificationCountBasicTestComponent { + + color: string; + notifications: any; + +} + +@Component({ + selector: 'td-notification-count-content-test', + template: ` + + notifications + + `, +}) +class TdNotificationCountContentTestComponent {} diff --git a/src/platform/core/notifications/notification-count.component.ts b/src/platform/core/notifications/notification-count.component.ts new file mode 100644 index 0000000000..e647f0275b --- /dev/null +++ b/src/platform/core/notifications/notification-count.component.ts @@ -0,0 +1,54 @@ +import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'td-notification-count', + styleUrls: ['./notification-count.component.scss' ], + templateUrl: './notification-count.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TdNotificationCountComponent { + + private _notifications: number | boolean = 0; + + /** + * color?: "primary" | "accent" | "warn" + * Sets the theme color of the notification tip. Defaults to 'warn' + */ + @Input() color: 'primary' | 'accent' | 'warn' = 'warn'; + + /** + * notifications?: number | boolean + * Number for the notification count. Shows component only if the input is a positive number or 'true' + */ + @Input() + set notifications(notifications: number | boolean) { + this._notifications = notifications; + } + + /** + * Sets the component in its 'noCount' state if [notifications] is a boolean 'true'. + * Makes the notification tip show without a count. + */ + get noCount(): string | boolean { + return this._notifications === true; + } + + /** + * Notification display string when a count is available. + * Anything over 99 gets set as 99+ + */ + get notificationsDisplay(): string { + if (this._notifications > 99) { + return '99+'; + } + return this._notifications.toString(); + } + + /** + * Shows notification tip only when [notifications] is true or a positive integer. + */ + get show(): boolean { + return this._notifications === true || (!isNaN(this._notifications) && this._notifications > 0); + } + +} diff --git a/src/platform/core/notifications/notifications.module.ts b/src/platform/core/notifications/notifications.module.ts new file mode 100644 index 0000000000..f0b4580da4 --- /dev/null +++ b/src/platform/core/notifications/notifications.module.ts @@ -0,0 +1,41 @@ +import { Type } from '@angular/core'; +import { NgModule, ModuleWithProviders } from '@angular/core'; + +import { CommonModule } from '@angular/common'; +import { MaterialModule } from '@angular/material'; + +import { CovalentCommonModule } from '../common/common.module'; + +import { TdNotificationCountComponent } from './notification-count.component'; + +const TD_NOTIFICATIONS: Type[] = [ + TdNotificationCountComponent, +]; + +export { TdNotificationCountComponent } from './notification-count.component'; + +@NgModule({ + imports: [ + CommonModule, + MaterialModule.forRoot(), + CovalentCommonModule.forRoot(), + ], + declarations: [ + TD_NOTIFICATIONS, + ], + exports: [ + CommonModule, + MaterialModule, + CovalentCommonModule, + + TD_NOTIFICATIONS, + ], +}) +export class CovalentNotificationsModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: CovalentNotificationsModule, + providers: [ ], + }; + } +} diff --git a/src/platform/core/theming/_all-theme.scss b/src/platform/core/theming/_all-theme.scss index 142f1e4236..274411600f 100644 --- a/src/platform/core/theming/_all-theme.scss +++ b/src/platform/core/theming/_all-theme.scss @@ -7,6 +7,7 @@ @import '../loading/loading-theme'; @import '../json-formatter/json-formatter-theme'; @import '../paging/paging-bar-theme'; +@import '../notifications/notification-count-theme'; // Create a theme. @mixin covalent-theme($theme) { @@ -19,4 +20,5 @@ @include td-paging-bar-theme($theme); @include td-loading-theme($theme); @include td-data-table-theme($theme); + @include td-notification-count-theme($theme); } \ No newline at end of file