From e3664cc847c9d76a8d176c282f0e40bdd16ff43a Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 2 Oct 2017 16:07:24 -0500 Subject: [PATCH 01/15] feat(badge): add badge component --- src/demo-app/badge/badge-demo.html | 84 ++++++++++++++++++++++++++++ src/demo-app/badge/badge-demo.scss | 0 src/demo-app/badge/badge-demo.ts | 11 ++++ src/demo-app/demo-app/demo-app.ts | 1 + src/demo-app/demo-app/demo-module.ts | 2 + src/demo-app/demo-app/routes.ts | 2 + src/demo-app/demo-material-module.ts | 2 + src/demo-app/system-config.ts | 1 + src/lib/badge/_badge-theme.scss | 32 +++++++++++ src/lib/badge/badge-module.ts | 22 ++++++++ src/lib/badge/badge.html | 5 ++ src/lib/badge/badge.md | 35 ++++++++++++ src/lib/badge/badge.scss | 66 ++++++++++++++++++++++ src/lib/badge/badge.spec.ts | 82 +++++++++++++++++++++++++++ src/lib/badge/badge.ts | 61 ++++++++++++++++++++ src/lib/badge/index.ts | 9 +++ src/lib/badge/public_api.ts | 10 ++++ src/lib/badge/tsconfig-build.json | 14 +++++ src/lib/core/theming/_all-theme.scss | 2 + src/lib/public_api.ts | 1 + test/karma-test-shim.js | 1 + 21 files changed, 443 insertions(+) create mode 100644 src/demo-app/badge/badge-demo.html create mode 100644 src/demo-app/badge/badge-demo.scss create mode 100644 src/demo-app/badge/badge-demo.ts create mode 100644 src/lib/badge/_badge-theme.scss create mode 100644 src/lib/badge/badge-module.ts create mode 100644 src/lib/badge/badge.html create mode 100644 src/lib/badge/badge.md create mode 100644 src/lib/badge/badge.scss create mode 100644 src/lib/badge/badge.spec.ts create mode 100644 src/lib/badge/badge.ts create mode 100644 src/lib/badge/index.ts create mode 100644 src/lib/badge/public_api.ts create mode 100644 src/lib/badge/tsconfig-build.json diff --git a/src/demo-app/badge/badge-demo.html b/src/demo-app/badge/badge-demo.html new file mode 100644 index 000000000000..e3303ebac472 --- /dev/null +++ b/src/demo-app/badge/badge-demo.html @@ -0,0 +1,84 @@ +
+ +

Icons

+ + home + + + + home + + +
+
+
+ +

Buttons

+ + + + + + + + +
+
+
+ +

Text

+ + Email + + + + Email + + +
+
+
+ +

Text Directions

+ + Email + + + + Email + + + + Email + + + + Email + + +
+
+
+ +

Icon Directions

+ + home + + + + home + + + + home + + + + home + + +
\ No newline at end of file diff --git a/src/demo-app/badge/badge-demo.scss b/src/demo-app/badge/badge-demo.scss new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/demo-app/badge/badge-demo.ts b/src/demo-app/badge/badge-demo.ts new file mode 100644 index 000000000000..d3f0a91be35d --- /dev/null +++ b/src/demo-app/badge/badge-demo.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'badge-demo', + templateUrl: 'badge-demo.html', + styleUrls: ['badge-demo.css'], +}) +export class BadgeDemo { + badgeContent = '1'; +} diff --git a/src/demo-app/demo-app/demo-app.ts b/src/demo-app/demo-app/demo-app.ts index f95c0a7a731f..b675220d81d1 100644 --- a/src/demo-app/demo-app/demo-app.ts +++ b/src/demo-app/demo-app/demo-app.ts @@ -43,6 +43,7 @@ export class DemoApp { dark = false; navItems = [ {name: 'Autocomplete', route: '/autocomplete'}, + {name: 'Badge', route: '/badge'}, {name: 'Button Toggle', route: '/button-toggle'}, {name: 'Button', route: '/button'}, {name: 'Card', route: '/card'}, diff --git a/src/demo-app/demo-app/demo-module.ts b/src/demo-app/demo-app/demo-module.ts index ed2841168317..6d9bfc2c90b9 100644 --- a/src/demo-app/demo-app/demo-module.ts +++ b/src/demo-app/demo-app/demo-module.ts @@ -47,6 +47,7 @@ import {TooltipDemo} from '../tooltip/tooltip-demo'; import {TypographyDemo} from '../typography/typography-demo'; import {DemoApp, Home} from './demo-app'; import {DEMO_APP_ROUTES} from './routes'; +import {BadgeDemo} from '../badge/badge-demo'; @NgModule({ imports: [ @@ -59,6 +60,7 @@ import {DEMO_APP_ROUTES} from './routes'; ], declarations: [ AutocompleteDemo, + BadgeDemo, BaselineDemo, ButtonDemo, ButtonToggleDemo, diff --git a/src/demo-app/demo-app/routes.ts b/src/demo-app/demo-app/routes.ts index db33beea133b..01d9b8c1a3c1 100644 --- a/src/demo-app/demo-app/routes.ts +++ b/src/demo-app/demo-app/routes.ts @@ -41,11 +41,13 @@ import {ToolbarDemo} from '../toolbar/toolbar-demo'; import {TooltipDemo} from '../tooltip/tooltip-demo'; import {TypographyDemo} from '../typography/typography-demo'; import {DemoApp, Home} from './demo-app'; +import {BadgeDemo} from '../badge/badge-demo'; export const DEMO_APP_ROUTES: Routes = [ {path: '', component: DemoApp, children: [ {path: '', component: Home}, {path: 'autocomplete', component: AutocompleteDemo}, + {path: 'badge', component: BadgeDemo}, {path: 'baseline', component: BaselineDemo}, {path: 'button', component: ButtonDemo}, {path: 'button-toggle', component: ButtonToggleDemo}, diff --git a/src/demo-app/demo-material-module.ts b/src/demo-app/demo-material-module.ts index 4da1bc2e0575..585c4e646deb 100644 --- a/src/demo-app/demo-material-module.ts +++ b/src/demo-app/demo-material-module.ts @@ -1,6 +1,7 @@ import {NgModule} from '@angular/core'; import { MatAutocompleteModule, + MatBadgeModule, MatButtonModule, MatButtonToggleModule, MatCardModule, @@ -46,6 +47,7 @@ import {PortalModule} from '@angular/cdk/portal'; @NgModule({ exports: [ MatAutocompleteModule, + MatBadgeModule, MatButtonModule, MatButtonToggleModule, MatCardModule, diff --git a/src/demo-app/system-config.ts b/src/demo-app/system-config.ts index f10e687d711e..10925f14505b 100644 --- a/src/demo-app/system-config.ts +++ b/src/demo-app/system-config.ts @@ -76,6 +76,7 @@ System.config({ '@angular/material/tabs': 'dist/packages/material/tabs/index.js', '@angular/material/toolbar': 'dist/packages/material/toolbar/index.js', '@angular/material/tooltip': 'dist/packages/material/tooltip/index.js', + '@angular/material/badge': 'dist/packages/material/badge/index.js', }, packages: { // Thirdparty barrels. diff --git a/src/lib/badge/_badge-theme.scss b/src/lib/badge/_badge-theme.scss new file mode 100644 index 000000000000..1fe5e4a6502b --- /dev/null +++ b/src/lib/badge/_badge-theme.scss @@ -0,0 +1,32 @@ +@import '../core/theming/palette'; +@import '../core/theming/theming'; +@import '../core/typography/typography-utils'; + +@mixin _mat-badge-color($palette) { + background: mat-color($palette); + color: mat-color($palette, default-contrast); +} + +@mixin mat-badge-theme($theme) { + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $primary: map-get($theme, primary); + + .mat-badge { + .mat-badge-content { + @include _mat-badge-color($primary); + } + + &.mat-accent { + .mat-badge-content { + @include _mat-toolbar-color($accent); + } + } + + &.mat-warn { + .mat-badge-content { + @include _mat-toolbar-color($warn); + } + } + } +} diff --git a/src/lib/badge/badge-module.ts b/src/lib/badge/badge-module.ts new file mode 100644 index 000000000000..23640902f8c4 --- /dev/null +++ b/src/lib/badge/badge-module.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {MatCommonModule} from '@angular/material/core'; +import {MatBadge} from './badge'; + + +@NgModule({ + imports: [MatCommonModule], + exports: [ + MatBadge, + MatCommonModule, + ], + declarations: [MatBadge], +}) +export class MatBadgeModule {} diff --git a/src/lib/badge/badge.html b/src/lib/badge/badge.html new file mode 100644 index 000000000000..5c615843d3a7 --- /dev/null +++ b/src/lib/badge/badge.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/lib/badge/badge.md b/src/lib/badge/badge.md new file mode 100644 index 000000000000..3f94d13405fe --- /dev/null +++ b/src/lib/badge/badge.md @@ -0,0 +1,35 @@ +Badges are small status descriptors for UI elements. A badge consists of a small circle, +typically containing a number or other short set of characters, that appears in proximity to another object. + +### Customizing badge position +By default, the badge will be placed `above after`. The direction can be changed by defining +the attribute `direction` follow by `above|below` and `before|after`. + +```html + + home + +``` + +The overlap of the badge in relation to its inner contents can also be defined +using the `overlap` tag. Typically, you want the badge to overlap an icon and not +a text phrase. By default it will overlap. + +```html + + Email + +``` + +### Theming +Badges can be colored in terms of the current theme using the `color` property to set the +background color to `primary`, `accent`, or `warn`. + +```html + + home + +``` + +### Accessibility +Badges should be given a meaningful label via `aria-label` or `aria-labelledby` attributes. \ No newline at end of file diff --git a/src/lib/badge/badge.scss b/src/lib/badge/badge.scss new file mode 100644 index 000000000000..cd022b9b8df6 --- /dev/null +++ b/src/lib/badge/badge.scss @@ -0,0 +1,66 @@ +$mat-badge-size: 22px !default; + +.mat-badge { + position: relative; + display: inline-block; + + &.mat-badge-above { + .mat-badge-content { + top: -$mat-badge-size / 2; + } + } + + &.mat-badge-below { + .mat-badge-content { + bottom: -$mat-badge-size / 2; + } + } + + &.mat-badge-before { + margin-left: $mat-badge-size; + .mat-badge-content { + left: -$mat-badge-size; + } + } + + &.mat-badge-after { + margin-right: $mat-badge-size; + .mat-badge-content { + right: -$mat-badge-size; + } + } + + &.mat-badge-overlap { + &.mat-badge-before { + margin-left: $mat-badge-size / 2; + .mat-badge-content { + left: -$mat-badge-size / 2; + } + } + + &.mat-badge-after { + margin-right: $mat-badge-size / 2; + .mat-badge-content { + right: -$mat-badge-size / 2; + } + } + } + + .mat-badge-content { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-content: center; + align-items: center; + position: absolute; + font-weight: 600; + font-size: 12px; + width: $mat-badge-size; + height: $mat-badge-size; + border-radius: 50%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } +} diff --git a/src/lib/badge/badge.spec.ts b/src/lib/badge/badge.spec.ts new file mode 100644 index 000000000000..416ee6cc85d5 --- /dev/null +++ b/src/lib/badge/badge.spec.ts @@ -0,0 +1,82 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, DebugElement} from '@angular/core'; +import {By} from '@angular/platform-browser'; +import {MatBadge, MatBadgeModule} from './index'; + + +fdescribe('MatBadge', () => { + + let fixture: ComponentFixture; + let badgeDebugElement: DebugElement; + let badgeContentDebugElement: DebugElement; + let testComponent: TestApp; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MatBadgeModule], + declarations: [TestApp] + }); + + TestBed.compileComponents(); + + fixture = TestBed.createComponent(TestApp); + testComponent = fixture.debugElement.componentInstance; + badgeDebugElement = fixture.debugElement.query(By.directive(MatBadge)); + badgeContentDebugElement = fixture.debugElement.query(By.css('.mat-badge-content')); + fixture.detectChanges(); + })); + + it('should update the badge based on content attribute', () => { + expect(badgeContentDebugElement.nativeElement.innerHTML).toContain('1'); + + testComponent.badgeContent = '22'; + fixture.detectChanges(); + expect(badgeContentDebugElement.nativeElement.innerHTML).toContain('22'); + }); + + it('should apply class based on color attribute', () => { + testComponent.badgeColor = 'primary'; + fixture.detectChanges(); + expect(badgeDebugElement.nativeElement.classList.contains('mat-primary')).toBe(true); + + testComponent.badgeColor = 'accent'; + fixture.detectChanges(); + expect(badgeDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true); + + testComponent.badgeColor = 'warn'; + fixture.detectChanges(); + expect(badgeDebugElement.nativeElement.classList.contains('mat-warn')).toBe(true); + + testComponent.badgeColor = null; + fixture.detectChanges(); + + expect(badgeDebugElement.nativeElement.classList).not.toContain('mat-accent'); + }); + + it('should update the badget position on direction change', () => { + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-above')).toBe(true); + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-after')).toBe(true); + + testComponent.badgeDirection = 'below before'; + fixture.detectChanges(); + + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-below')).toBe(true); + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-before')).toBe(true); + }); + +}); + +/** Test component that contains an MatBadge. */ +@Component({ + selector: 'test-app', + template: ` + + home + + ` +}) +class TestApp { + badgeColor; + badgeContent = '1'; + badgeDirection = 'above after'; +} diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts new file mode 100644 index 000000000000..243f824091c7 --- /dev/null +++ b/src/lib/badge/badge.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + + import {Component, Input, ViewEncapsulation, Renderer2, ElementRef} from '@angular/core'; + import {coerceBooleanProperty} from '@angular/cdk/coercion'; + import {CanColor, mixinColor} from '@angular/material/core'; + + /** @docs-private */ +export class MatBadgeBase { + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} +} +export const _MatBadgeMixinBase = mixinColor(MatBadgeBase); + +@Component({ + selector: 'mat-badge', + templateUrl: './badge.html', + styleUrls: ['./badge.css'], + inputs: ['color'], + host: { + 'class': 'mat-badge', + '[class.mat-badge-overlap]': 'overlap', + '[class.mat-badge-above]': '_isAbove', + '[class.mat-badge-below]': '!_isAbove', + '[class.mat-badge-before]': '_isBefore', + '[class.mat-badge-after]': '!_isBefore', + }, + encapsulation: ViewEncapsulation.None, + preserveWhitespaces: false, +}) +export class MatBadge extends _MatBadgeMixinBase implements CanColor { + + /** Whether the badge should overlap its contents or not */ + @Input() + set overlap(val: boolean) { + this._overlap = coerceBooleanProperty(val); + } + get overlap(): boolean { + return this._overlap; + } + private _overlap: boolean = true; + + /** Content of the badget */ + @Input() content: string = ''; + + /** Direction the badge should reside; 'before|after above|below' */ + @Input() direction: string = 'above after'; + + get _isAbove(): boolean { return this.direction.indexOf('above') > -1; } + + get _isBefore(): boolean { return this.direction.indexOf('before') > -1; } + + constructor(_renderer: Renderer2, _elementRef: ElementRef) { + super(_renderer, _elementRef); + } + +} diff --git a/src/lib/badge/index.ts b/src/lib/badge/index.ts new file mode 100644 index 000000000000..f93e7c31d564 --- /dev/null +++ b/src/lib/badge/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './public_api'; diff --git a/src/lib/badge/public_api.ts b/src/lib/badge/public_api.ts new file mode 100644 index 000000000000..faff71446c0b --- /dev/null +++ b/src/lib/badge/public_api.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './badge-module'; +export * from './badge'; diff --git a/src/lib/badge/tsconfig-build.json b/src/lib/badge/tsconfig-build.json new file mode 100644 index 000000000000..a12a61d45dfd --- /dev/null +++ b/src/lib/badge/tsconfig-build.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig-build", + "files": [ + "public_api.ts", + "../typings.d.ts" + ], + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "strictMetadataEmit": true, + "flatModuleOutFile": "index.js", + "flatModuleId": "@angular/material/badge", + "skipTemplateCodegen": true + } +} diff --git a/src/lib/core/theming/_all-theme.scss b/src/lib/core/theming/_all-theme.scss index a2ed3f9459ba..0e7a93762545 100644 --- a/src/lib/core/theming/_all-theme.scss +++ b/src/lib/core/theming/_all-theme.scss @@ -1,6 +1,7 @@ // Import all the theming functionality. @import '../core'; @import '../../autocomplete/autocomplete-theme'; +@import '../../badge/badge-theme'; @import '../../button/button-theme'; @import '../../button-toggle/button-toggle-theme'; @import '../../card/card-theme'; @@ -35,6 +36,7 @@ @mixin angular-material-theme($theme) { @include mat-core-theme($theme); @include mat-autocomplete-theme($theme); + @include mat-badge-theme($theme); @include mat-button-theme($theme); @include mat-button-toggle-theme($theme); @include mat-card-theme($theme); diff --git a/src/lib/public_api.ts b/src/lib/public_api.ts index 526918c24de5..899b6382f643 100644 --- a/src/lib/public_api.ts +++ b/src/lib/public_api.ts @@ -8,6 +8,7 @@ export * from './version'; export * from '@angular/material/autocomplete'; +export * from '@angular/material/badge'; export * from '@angular/material/button'; export * from '@angular/material/button-toggle'; export * from '@angular/material/card'; diff --git a/test/karma-test-shim.js b/test/karma-test-shim.js index 2712689e5335..89f1b1088ac1 100644 --- a/test/karma-test-shim.js +++ b/test/karma-test-shim.js @@ -67,6 +67,7 @@ System.config({ '@angular/cdk/testing': 'dist/packages/cdk/testing/index.js', '@angular/material/autocomplete': 'dist/packages/material/autocomplete/index.js', + '@angular/material/badge': 'dist/packages/material/badge/index.js', '@angular/material/button': 'dist/packages/material/button/index.js', '@angular/material/button-toggle': 'dist/packages/material/button-toggle/index.js', '@angular/material/card': 'dist/packages/material/card/index.js', From 4a454fe618e8f881d6b3956229a83159b32e1519 Mon Sep 17 00:00:00 2001 From: Austin Date: Tue, 3 Oct 2017 16:30:32 -0500 Subject: [PATCH 02/15] chore(nit): address PR feedback --- src/demo-app/badge/badge-demo.html | 8 ++-- src/lib/badge/_badge-theme.scss | 10 +++++ src/lib/badge/badge.scss | 2 - src/lib/badge/badge.ts | 42 ++++++++++++++++---- src/lib/core/typography/_all-typography.scss | 1 + 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/demo-app/badge/badge-demo.html b/src/demo-app/badge/badge-demo.html index e3303ebac472..59afabdaf9c0 100644 --- a/src/demo-app/badge/badge-demo.html +++ b/src/demo-app/badge/badge-demo.html @@ -52,11 +52,11 @@

Text Directions

Email - + Email - + Email @@ -73,11 +73,11 @@

Icon Directions

home
- + home - + home diff --git a/src/lib/badge/_badge-theme.scss b/src/lib/badge/_badge-theme.scss index 1fe5e4a6502b..1617c458f48d 100644 --- a/src/lib/badge/_badge-theme.scss +++ b/src/lib/badge/_badge-theme.scss @@ -2,6 +2,9 @@ @import '../core/theming/theming'; @import '../core/typography/typography-utils'; +$mat-badge-font-size: 12px; +$mat-badge-font-weight: 600; + @mixin _mat-badge-color($palette) { background: mat-color($palette); color: mat-color($palette, default-contrast); @@ -30,3 +33,10 @@ } } } + +@mixin mat-badge-typography($config) { + .mat-badge-content { + font-weight: $mat-badge-font-weight; + font-size: $mat-badge-font-size; + } +} diff --git a/src/lib/badge/badge.scss b/src/lib/badge/badge.scss index cd022b9b8df6..9ccc89f9ab5c 100644 --- a/src/lib/badge/badge.scss +++ b/src/lib/badge/badge.scss @@ -54,8 +54,6 @@ $mat-badge-size: 22px !default; align-content: center; align-items: center; position: absolute; - font-weight: 600; - font-size: 12px; width: $mat-badge-size; height: $mat-badge-size; border-radius: 50%; diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index 243f824091c7..dd9dae188653 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -6,7 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ - import {Component, Input, ViewEncapsulation, Renderer2, ElementRef} from '@angular/core'; + import { + Component, Input, ViewEncapsulation, Renderer2, ElementRef, ChangeDetectionStrategy +} from '@angular/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {CanColor, mixinColor} from '@angular/material/core'; @@ -25,12 +27,13 @@ export const _MatBadgeMixinBase = mixinColor(MatBadgeBase); 'class': 'mat-badge', '[class.mat-badge-overlap]': 'overlap', '[class.mat-badge-above]': '_isAbove', - '[class.mat-badge-below]': '!_isAbove', + '[class.mat-badge-below]': '_isBelow', '[class.mat-badge-before]': '_isBefore', - '[class.mat-badge-after]': '!_isBefore', + '[class.mat-badge-after]': '_isAfter', }, encapsulation: ViewEncapsulation.None, preserveWhitespaces: false, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MatBadge extends _MatBadgeMixinBase implements CanColor { @@ -44,15 +47,38 @@ export class MatBadge extends _MatBadgeMixinBase implements CanColor { } private _overlap: boolean = true; - /** Content of the badget */ + /** Content of the badge */ @Input() content: string = ''; - /** Direction the badge should reside; 'before|after above|below' */ - @Input() direction: string = 'above after'; + /** Direction the badge should reside; 'above|below before|after' */ + @Input() + set direction(val: string) { + this._direction = val; + if (val.indexOf('above') > -1) { + this._isAbove = true; + this._isBelow = false; + } else if (val.indexOf('below') > -1) { + this._isAbove = false; + this._isBelow = true; + } - get _isAbove(): boolean { return this.direction.indexOf('above') > -1; } + if (val.indexOf('before') > -1) { + this._isBefore = true; + this._isAfter = false; + } else if (val.indexOf('after') > -1) { + this._isAfter = true; + this._isBefore = false; + } + } + get direction(): string { + return this._direction; + } + private _direction: string = 'above after'; - get _isBefore(): boolean { return this.direction.indexOf('before') > -1; } + _isAbove: boolean = true; + _isBelow: boolean = false; + _isBefore: boolean = false; + _isAfter: boolean = true; constructor(_renderer: Renderer2, _elementRef: ElementRef) { super(_renderer, _elementRef); diff --git a/src/lib/core/typography/_all-typography.scss b/src/lib/core/typography/_all-typography.scss index a0766383a781..36d8a113e6a3 100644 --- a/src/lib/core/typography/_all-typography.scss +++ b/src/lib/core/typography/_all-typography.scss @@ -38,6 +38,7 @@ $config: mat-typography-config(); } + @include mat-badge-typography($config); @include mat-base-typography($config); @include mat-autocomplete-typography($config); @include mat-button-typography($config); From d886d982fdc6a572ea2499cb55f0190dfc95f067 Mon Sep 17 00:00:00 2001 From: Austin Date: Wed, 4 Oct 2017 09:22:12 -0500 Subject: [PATCH 03/15] chore(nit): address PR feedback --- src/lib/badge/badge.ts | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index dd9dae188653..2f004280a06c 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -27,8 +27,8 @@ export const _MatBadgeMixinBase = mixinColor(MatBadgeBase); 'class': 'mat-badge', '[class.mat-badge-overlap]': 'overlap', '[class.mat-badge-above]': '_isAbove', - '[class.mat-badge-below]': '_isBelow', - '[class.mat-badge-before]': '_isBefore', + '[class.mat-badge-below]': '!_isAbove', + '[class.mat-badge-before]': '!_isAfter', '[class.mat-badge-after]': '_isAfter', }, encapsulation: ViewEncapsulation.None, @@ -54,21 +54,8 @@ export class MatBadge extends _MatBadgeMixinBase implements CanColor { @Input() set direction(val: string) { this._direction = val; - if (val.indexOf('above') > -1) { - this._isAbove = true; - this._isBelow = false; - } else if (val.indexOf('below') > -1) { - this._isAbove = false; - this._isBelow = true; - } - - if (val.indexOf('before') > -1) { - this._isBefore = true; - this._isAfter = false; - } else if (val.indexOf('after') > -1) { - this._isAfter = true; - this._isBefore = false; - } + this._isAbove = val.indexOf('below') === -1; + this._isAfter = val.indexOf('before') === -1; } get direction(): string { return this._direction; From c0ecf6203031091a338e9d7d50cc2a67ce7ed470 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 5 Oct 2017 12:45:12 -0500 Subject: [PATCH 04/15] feat(badges): rewrite w/ new api --- src/demo-app/badge/badge-demo.html | 108 +++++++------- src/demo-app/badge/badge-demo.ts | 14 ++ src/lib/badge/_badge-theme.scss | 157 ++++++++++++++++++-- src/lib/badge/badge-module.ts | 10 +- src/lib/badge/badge.html | 5 - src/lib/badge/badge.md | 43 ++++-- src/lib/badge/badge.scss | 64 -------- src/lib/badge/badge.spec.ts | 178 +++++++++++++++++----- src/lib/badge/badge.ts | 227 ++++++++++++++++++++++++----- 9 files changed, 580 insertions(+), 226 deletions(-) delete mode 100644 src/lib/badge/badge.html delete mode 100644 src/lib/badge/badge.scss diff --git a/src/demo-app/badge/badge-demo.html b/src/demo-app/badge/badge-demo.html index 59afabdaf9c0..63c571b33353 100644 --- a/src/demo-app/badge/badge-demo.html +++ b/src/demo-app/badge/badge-demo.html @@ -1,84 +1,84 @@
-

Icons

- - home - +

Text

+ + Hello + - - home - + + Hello + + + + Hello + + + + Hello + + +


Buttons

- - - - - - - + -
-
-
+ -

Text

- - Email - + + + - - Email - +


-

Text Directions

- - Email - +

Icons

+ + home + - - Email - + + home + - - Email - + + home + - - Email - + + home +


-

Icon Directions

- - home - +

Font Icons

+ Home is where you live - - home - +
+
+
- - home - +

SVG Icons

+ Home is where I live - - home - +
\ No newline at end of file diff --git a/src/demo-app/badge/badge-demo.ts b/src/demo-app/badge/badge-demo.ts index d3f0a91be35d..fdb87a857bf6 100644 --- a/src/demo-app/badge/badge-demo.ts +++ b/src/demo-app/badge/badge-demo.ts @@ -1,4 +1,6 @@ import {Component} from '@angular/core'; +import {DomSanitizer} from '@angular/platform-browser'; +import {MatIconRegistry} from '@angular/material'; @Component({ moduleId: module.id, @@ -7,5 +9,17 @@ import {Component} from '@angular/core'; styleUrls: ['badge-demo.css'], }) export class BadgeDemo { + badgeContent = '1'; + svgIcon = 'cat'; + + constructor(_iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) { + _iconRegistry.addSvgIcon('home', + sanitizer.bypassSecurityTrustResourceUrl( + 'https://upload.wikimedia.org/wikipedia/commons/3/34/Home-icon.svg')); + + _iconRegistry.addSvgIcon('cat', + sanitizer.bypassSecurityTrustResourceUrl( + 'https://upload.wikimedia.org/wikipedia/commons/3/34/Home-icon.svg')); + } } diff --git a/src/lib/badge/_badge-theme.scss b/src/lib/badge/_badge-theme.scss index 1617c458f48d..cb19a70eb27a 100644 --- a/src/lib/badge/_badge-theme.scss +++ b/src/lib/badge/_badge-theme.scss @@ -4,10 +4,112 @@ $mat-badge-font-size: 12px; $mat-badge-font-weight: 600; +$mat-badge-size: 22px !default; -@mixin _mat-badge-color($palette) { - background: mat-color($palette); - color: mat-color($palette, default-contrast); +.mat-badge { + position: relative; +} + +.mat-badge:after, +.mat-badge-icon, +.mat-badge-content, +.mat-badge-svg-icon { + position: absolute; + text-align: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-content: center; + align-items: center; +} + +.mat-badge:after { + width: $mat-badge-size; + height: $mat-badge-size; + border-radius: 50%; + content: ''; + z-index: 1; +} + +.mat-badge-content, +.mat-badge .mat-badge-icon { + z-index: 2; + font-size: 16px; + width: $mat-badge-size; + height: $mat-badge-size; +} + +.mat-badge-svg-icon { + z-index: 2; + width: 12px; + height: 12px; + z-index: 2; + transform: translate(-5px, 5px); +} + +.mat-badge-above { + .mat-badge-svg-icon, + .mat-badge-icon, + .mat-badge-content, + &:after { + top: -$mat-badge-size / 2; + } +} + +.mat-badge-below { + .mat-badge-svg-icon, + .mat-badge-icon, + .mat-badge-content, + &:after { + bottom: -$mat-badge-size / 2; + } +} + +.mat-badge-before { + margin-left: $mat-badge-size; + + .mat-badge-svg-icon, + .mat-badge-icon, + .mat-badge-content, + &:after { + left: -$mat-badge-size; + } +} + +.mat-badge-after { + margin-right: $mat-badge-size; + + .mat-badge-svg-icon, + .mat-badge-icon, + .mat-badge-content, + &:after { + right: -$mat-badge-size; + } +} + +.mat-badge-overlap { + &.mat-badge-before { + margin-left: $mat-badge-size / 2; + + .mat-badge-svg-icon, + .mat-badge-icon, + .mat-badge-content, + &:after { + left: -$mat-badge-size / 2; + } + } + + &.mat-badge-after { + margin-right: $mat-badge-size / 2; + + .mat-badge-svg-icon, + .mat-badge-icon, + .mat-badge-content, + &:after { + right: -$mat-badge-size / 2; + } + } } @mixin mat-badge-theme($theme) { @@ -16,27 +118,60 @@ $mat-badge-font-weight: 600; $primary: map-get($theme, primary); .mat-badge { - .mat-badge-content { - @include _mat-badge-color($primary); + .mat-badge-svg-icon { + fill: mat-color($primary, default-contrast); + } + + .mat-badge-content, + .mat-badge-icon { + color: mat-color($primary, default-contrast); + } + + &:after { + background: mat-color($primary); } - &.mat-accent { - .mat-badge-content { - @include _mat-toolbar-color($accent); + &.mat-badge-accent { + .mat-badge-svg-icon { + fill: mat-color($accent, default-contrast); + } + + .mat-badge-content, + .mat-badge-icon { + color: mat-color($accent, default-contrast); + } + + &:after { + background: mat-color($accent); } } - &.mat-warn { - .mat-badge-content { - @include _mat-toolbar-color($warn); + &.mat-badge-warn { + .mat-badge-svg-icon { + fill: mat-color($warn, default-contrast); + } + + .mat-badge-content, + .mat-badge-icon { + color: mat-color($warn, default-contrast); + } + + &:after { + background: mat-color($warn); } } } } @mixin mat-badge-typography($config) { + .mat-badge-svg-icon, + .mat-badge-icon, .mat-badge-content { font-weight: $mat-badge-font-weight; font-size: $mat-badge-font-size; } + + .mat-badge-content { + font-family: mat-font-family($config); + } } diff --git a/src/lib/badge/badge-module.ts b/src/lib/badge/badge-module.ts index 23640902f8c4..2aac7842a7e7 100644 --- a/src/lib/badge/badge-module.ts +++ b/src/lib/badge/badge-module.ts @@ -8,15 +8,21 @@ import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; -import {MatBadge} from './badge'; +import {MatBadge, MatSvgIconBadge, MatIconBadge} from './badge'; @NgModule({ imports: [MatCommonModule], exports: [ MatBadge, + MatIconBadge, + MatSvgIconBadge, MatCommonModule, ], - declarations: [MatBadge], + declarations: [ + MatBadge, + MatIconBadge, + MatSvgIconBadge, + ], }) export class MatBadgeModule {} diff --git a/src/lib/badge/badge.html b/src/lib/badge/badge.html deleted file mode 100644 index 5c615843d3a7..000000000000 --- a/src/lib/badge/badge.html +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/src/lib/badge/badge.md b/src/lib/badge/badge.md index 3f94d13405fe..f4f0cd084648 100644 --- a/src/lib/badge/badge.md +++ b/src/lib/badge/badge.md @@ -1,34 +1,53 @@ Badges are small status descriptors for UI elements. A badge consists of a small circle, typically containing a number or other short set of characters, that appears in proximity to another object. -### Customizing badge position +### Icons +Badges can contain text or font icons and SVGs registered with the `MdIconRegistry` + +#### Font Icons +Badges using font icons are delcared with the `matIconBadge` tag and its content being +the name of the icon you wish to use. + +```html +Home is where I live. +``` + +You can also pass a custom font set using the `matBadgeFontSet` tag. + +#### SVG Icons +Badges using SVG icons are declared with the `matSvgIconBadge` and its content being +the name of the icon you wish to use. + +```html +Home is where I live. +``` + +### Badge position By default, the badge will be placed `above after`. The direction can be changed by defining -the attribute `direction` follow by `above|below` and `before|after`. +the attribute `matBadgePosition` follow by `above|below` and `before|after`. ```html - - home - +home ``` The overlap of the badge in relation to its inner contents can also be defined -using the `overlap` tag. Typically, you want the badge to overlap an icon and not +using the `matBadgeOverlap` tag. Typically, you want the badge to overlap an icon and not a text phrase. By default it will overlap. ```html - +

Email - +

``` ### Theming -Badges can be colored in terms of the current theme using the `color` property to set the +Badges can be colored in terms of the current theme using the `matBadgeColor` property to set the background color to `primary`, `accent`, or `warn`. ```html - - home - + + home + ``` ### Accessibility diff --git a/src/lib/badge/badge.scss b/src/lib/badge/badge.scss deleted file mode 100644 index 9ccc89f9ab5c..000000000000 --- a/src/lib/badge/badge.scss +++ /dev/null @@ -1,64 +0,0 @@ -$mat-badge-size: 22px !default; - -.mat-badge { - position: relative; - display: inline-block; - - &.mat-badge-above { - .mat-badge-content { - top: -$mat-badge-size / 2; - } - } - - &.mat-badge-below { - .mat-badge-content { - bottom: -$mat-badge-size / 2; - } - } - - &.mat-badge-before { - margin-left: $mat-badge-size; - .mat-badge-content { - left: -$mat-badge-size; - } - } - - &.mat-badge-after { - margin-right: $mat-badge-size; - .mat-badge-content { - right: -$mat-badge-size; - } - } - - &.mat-badge-overlap { - &.mat-badge-before { - margin-left: $mat-badge-size / 2; - .mat-badge-content { - left: -$mat-badge-size / 2; - } - } - - &.mat-badge-after { - margin-right: $mat-badge-size / 2; - .mat-badge-content { - right: -$mat-badge-size / 2; - } - } - } - - .mat-badge-content { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - align-content: center; - align-items: center; - position: absolute; - width: $mat-badge-size; - height: $mat-badge-size; - border-radius: 50%; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } -} diff --git a/src/lib/badge/badge.spec.ts b/src/lib/badge/badge.spec.ts index 416ee6cc85d5..8deff3094aeb 100644 --- a/src/lib/badge/badge.spec.ts +++ b/src/lib/badge/badge.spec.ts @@ -1,67 +1,157 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing'; +import {HttpModule, XHRBackend} from '@angular/http'; +import {MockBackend} from '@angular/http/testing'; import {Component, DebugElement} from '@angular/core'; import {By} from '@angular/platform-browser'; -import {MatBadge, MatBadgeModule} from './index'; - - -fdescribe('MatBadge', () => { +import {MatBadge, MatIconBadge, MatSvgIconBadge, MatBadgeModule} from './index'; +import {MatIconRegistry} from '../icon/icon-registry'; +import {SafeResourceUrl, DomSanitizer} from '@angular/platform-browser'; +import {getFakeSvgHttpResponse} from '../icon/fake-svgs'; +describe('MatBadge', () => { let fixture: ComponentFixture; - let badgeDebugElement: DebugElement; - let badgeContentDebugElement: DebugElement; let testComponent: TestApp; + let iconRegistry: MatIconRegistry; + let sanitizer: DomSanitizer; + let httpRequestUrls: string[]; + + function trust(iconUrl: string): SafeResourceUrl { + return sanitizer.bypassSecurityTrustResourceUrl(iconUrl); + } beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [MatBadgeModule], - declarations: [TestApp] + imports: [MatBadgeModule, HttpModule], + declarations: [TestApp], + providers: [ + MatIconRegistry, + MockBackend, + {provide: XHRBackend, useExisting: MockBackend}, + ] }); TestBed.compileComponents(); + })); + + let deps = [MatIconRegistry, MockBackend, DomSanitizer]; + beforeEach(inject(deps, (mir: MatIconRegistry, mockBackend: MockBackend, ds: DomSanitizer) => { + iconRegistry = mir; + sanitizer = ds; + httpRequestUrls = []; + + mockBackend.connections.subscribe((connection: any) => { + const url = connection.request.url; + httpRequestUrls.push(url); + connection.mockRespond(getFakeSvgHttpResponse(url)); + }); + + iconRegistry.addSvgIcon('fluffy', trust('cat.svg')); + iconRegistry.addSvgIcon('fido', trust('dog.svg')); fixture = TestBed.createComponent(TestApp); testComponent = fixture.debugElement.componentInstance; - badgeDebugElement = fixture.debugElement.query(By.directive(MatBadge)); - badgeContentDebugElement = fixture.debugElement.query(By.css('.mat-badge-content')); fixture.detectChanges(); })); - it('should update the badge based on content attribute', () => { - expect(badgeContentDebugElement.nativeElement.innerHTML).toContain('1'); + describe('MatBadge Text', () => { + let badgeDebugElement: DebugElement; - testComponent.badgeContent = '22'; - fixture.detectChanges(); - expect(badgeContentDebugElement.nativeElement.innerHTML).toContain('22'); + beforeEach(async(() => { + badgeDebugElement = fixture.debugElement.query(By.directive(MatBadge)); + fixture.detectChanges(); + })); + + it('should update the badge based on attribute', () => { + let badgeContentDebugElement = + badgeDebugElement.nativeElement.querySelector('.mat-badge-content'); + + expect(badgeContentDebugElement.innerHTML).toContain('1'); + + testComponent.badgeContent = '22'; + fixture.detectChanges(); + + badgeContentDebugElement = + badgeDebugElement.nativeElement.querySelector('.mat-badge-content'); + expect(badgeContentDebugElement.innerHTML).toContain('22'); + }); + + it('should apply class based on color attribute', () => { + testComponent.badgeColor = 'primary'; + fixture.detectChanges(); + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-primary')).toBe(true); + + testComponent.badgeColor = 'accent'; + fixture.detectChanges(); + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-accent')).toBe(true); + + testComponent.badgeColor = 'warn'; + fixture.detectChanges(); + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-warn')).toBe(true); + + testComponent.badgeColor = null; + fixture.detectChanges(); + + expect(badgeDebugElement.nativeElement.classList).not.toContain('mat-badge-accent'); + }); + + it('should update the badget position on direction change', () => { + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-above')).toBe(true); + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-after')).toBe(true); + + testComponent.badgeDirection = 'below before'; + fixture.detectChanges(); + + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-below')).toBe(true); + expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-before')).toBe(true); + }); }); - it('should apply class based on color attribute', () => { - testComponent.badgeColor = 'primary'; - fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList.contains('mat-primary')).toBe(true); + describe('MatBadge Font Icon', () => { + let badgeDebugElement: DebugElement; - testComponent.badgeColor = 'accent'; - fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true); + beforeEach(async(() => { + badgeDebugElement = fixture.debugElement.query(By.directive(MatIconBadge)); + fixture.detectChanges(); + })); - testComponent.badgeColor = 'warn'; - fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList.contains('mat-warn')).toBe(true); + it('should update the badge icon based on attribute', () => { + let badgeContentDebugElement = + badgeDebugElement.nativeElement.querySelector('.mat-badge-icon'); - testComponent.badgeColor = null; - fixture.detectChanges(); + expect(badgeContentDebugElement.innerHTML).toContain('home'); + + testComponent.badgeIcon = 'phone'; + fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList).not.toContain('mat-accent'); + badgeContentDebugElement = + badgeDebugElement.nativeElement.querySelector('.mat-badge-icon'); + + expect(badgeContentDebugElement.innerHTML).toContain('phone'); + }); }); - it('should update the badget position on direction change', () => { - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-above')).toBe(true); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-after')).toBe(true); + describe('MatBadge SVG Icon', () => { + let badgeDebugElement: DebugElement; - testComponent.badgeDirection = 'below before'; - fixture.detectChanges(); + beforeEach(() => { + badgeDebugElement = fixture.debugElement.query(By.directive(MatSvgIconBadge)); + fixture.detectChanges(); + }); + + it('should update the badge svg based on attribute', async() => { + let badgeContentDebugElement = + badgeDebugElement.nativeElement.querySelector('.mat-badge-svg-icon'); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-below')).toBe(true); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-before')).toBe(true); + expect(badgeContentDebugElement.innerHTML).toContain(''); + + testComponent.badgeSvg = 'fido'; + fixture.detectChanges(); + + badgeContentDebugElement = + badgeDebugElement.nativeElement.querySelector('.mat-badge-svg-icon'); + + expect(badgeContentDebugElement.innerHTML).toContain(''); + }); }); }); @@ -70,13 +160,23 @@ fdescribe('MatBadge', () => { @Component({ selector: 'test-app', template: ` - - home - + + home + + + Hello + + + Hello + ` }) class TestApp { badgeColor; badgeContent = '1'; + badgeIcon = 'home'; + badgeSvg = 'fluffy'; badgeDirection = 'above after'; } diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index 2f004280a06c..52b002b0e85b 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -6,69 +6,218 @@ * found in the LICENSE file at https://angular.io/license */ - import { - Component, Input, ViewEncapsulation, Renderer2, ElementRef, ChangeDetectionStrategy -} from '@angular/core'; - import {coerceBooleanProperty} from '@angular/cdk/coercion'; - import {CanColor, mixinColor} from '@angular/material/core'; +import {Directive, Input, Renderer2, ElementRef} from '@angular/core'; +import {MatIconRegistry} from '@angular/material/icon'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {ThemePalette} from '@angular/material/core'; + +let nextId = 0; /** @docs-private */ export class MatBadgeBase { - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} -} -export const _MatBadgeMixinBase = mixinColor(MatBadgeBase); -@Component({ - selector: 'mat-badge', - templateUrl: './badge.html', - styleUrls: ['./badge.css'], - inputs: ['color'], - host: { - 'class': 'mat-badge', - '[class.mat-badge-overlap]': 'overlap', - '[class.mat-badge-above]': '_isAbove', - '[class.mat-badge-below]': '!_isAbove', - '[class.mat-badge-before]': '!_isAfter', - '[class.mat-badge-after]': '_isAfter', - }, - encapsulation: ViewEncapsulation.None, - preserveWhitespaces: false, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MatBadge extends _MatBadgeMixinBase implements CanColor { + /** Theme for the badge */ + get matBadgeColor(): ThemePalette { return this._color; } + set matBadgeColor(value: ThemePalette) { + const colorPalette = value; + + if (colorPalette !== this._color) { + if (this._color) { + this._renderer.removeClass(this._elementRef.nativeElement, `mat-badge-${this._color}`); + } + if (colorPalette) { + this._renderer.addClass(this._elementRef.nativeElement, `mat-badge-${colorPalette}`); + } + + this._color = colorPalette; + } + } + private _color: ThemePalette = 'primary'; /** Whether the badge should overlap its contents or not */ - @Input() - set overlap(val: boolean) { + set matBadgeOverlap(val: boolean) { this._overlap = coerceBooleanProperty(val); } - get overlap(): boolean { + get matBadgeOverlap(): boolean { return this._overlap; } private _overlap: boolean = true; - /** Content of the badge */ - @Input() content: string = ''; - - /** Direction the badge should reside; 'above|below before|after' */ - @Input() - set direction(val: string) { - this._direction = val; + /** Position the badge should reside; 'above|below before|after' */ + set matBadgePosition(val: string) { + this._position = val; this._isAbove = val.indexOf('below') === -1; this._isAfter = val.indexOf('before') === -1; } - get direction(): string { - return this._direction; + get matBadgePosition(): string { + return this._position; } - private _direction: string = 'above after'; + private _position: string = 'above after'; + _id: number = nextId++; _isAbove: boolean = true; _isBelow: boolean = false; _isBefore: boolean = false; _isAfter: boolean = true; + constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} + + /** Clears the previous badge content */ + _clearContents(): void { + const contents = document.getElementById(`badge-content-${this._id}`); + if (contents) { + this._renderer.removeChild(this._elementRef.nativeElement, contents); + } + } +} + +/** Directive to display a text badge. */ +@Directive({ + selector: '[matBadge]', + inputs: ['matBadgeColor', 'matBadgeOverlap', 'matBadgePosition'], + host: { + 'class': 'mat-badge', + '[class.mat-badge-overlap]': '_overlap', + '[class.mat-badge-above]': '_isAbove', + '[class.mat-badge-below]': '!_isAbove', + '[class.mat-badge-before]': '!_isAfter', + '[class.mat-badge-after]': '_isAfter', + }, +}) +export class MatBadge extends MatBadgeBase { + + @Input('matBadge') + set content(val: string) { + this._content = val; + this._clearContents(); + this._setContent(); + } + get content(): string { return this._content; } + private _content: string; + constructor(_renderer: Renderer2, _elementRef: ElementRef) { super(_renderer, _elementRef); } + /** Injects a span element into the DOM with the content. */ + private _setContent(): void { + let pane = document.createElement('span'); + pane.setAttribute('id', `badge-content-${this._id}`); + pane.classList.add('mat-badge-content'); + pane.innerText = this.content; + this._elementRef.nativeElement.appendChild(pane); + } +} + +/** Directive to display a font icon badge. */ +@Directive({ + selector: '[matIconBadge]', + inputs: ['matBadgeColor', 'matBadgeOverlap', 'matBadgePosition'], + host: { + 'class': 'mat-badge', + '[class.mat-badge-overlap]': '_overlap', + '[class.mat-badge-above]': '_isAbove', + '[class.mat-badge-below]': '!_isAbove', + '[class.mat-badge-before]': '!_isAfter', + '[class.mat-badge-after]': '_isAfter', + }, +}) +export class MatIconBadge extends MatBadgeBase { + + @Input('matIconBadge') + set icon(val: string) { + this._icon = val; + this._clearContents(); + this._setFontIcon(); + } + get icon(): string { return this._icon; } + private _icon: string; + + /** Font set to use for the icon */ + @Input() matBadgeFontSet: string; + + constructor( + private _iconRegistry: MatIconRegistry, + _elementRef: ElementRef, + _renderer: Renderer2) { + super(_renderer, _elementRef); + } + + /** Gets the font icon set to use. */ + private _getFontSet(): string { + return this.matBadgeFontSet ? + this._iconRegistry.classNameForFontAlias(this.matBadgeFontSet) : + this._iconRegistry.getDefaultFontSetClass(); + } + + /** + * Gets the font icon from the icon registery and injects it into the document. + */ + private _setFontIcon(): void { + let pane = document.createElement('span'); + pane.setAttribute('id', `badge-content-${this._id}`); + pane.classList.add(this._getFontSet()); + pane.classList.add('mat-badge-icon'); + pane.innerText = this.icon; + this._elementRef.nativeElement.appendChild(pane); + } +} + +/** Directive to display a SVG icon badge. */ +@Directive({ + selector: '[matSvgIconBadge]', + inputs: ['matBadgeColor', 'matBadgeOverlap', 'matBadgePosition'], + host: { + 'class': 'mat-badge', + '[class.mat-badge-overlap]': '_overlap', + '[class.mat-badge-above]': '_isAbove', + '[class.mat-badge-below]': '!_isAbove', + '[class.mat-badge-before]': '!_isAfter', + '[class.mat-badge-after]': '_isAfter', + }, +}) +export class MatSvgIconBadge extends MatBadgeBase { + + @Input('matSvgIconBadge') + set icon(val: string) { + this._icon = val; + this._clearContents(); + this._setSvgIcon(); + } + get icon(): string { return this._icon; } + private _icon: string; + + constructor( + private _iconRegistry: MatIconRegistry, + _elementRef: ElementRef, + _renderer: Renderer2) { + super(_renderer, _elementRef); + } + + /** + * Splits an svgIcon binding value into its icon set and icon name components. + */ + private _splitIconName(iconName: string): [string, string] { + if (!iconName) { + return ['', '']; + } + const parts = iconName.split(':'); + switch (parts.length) { + case 1: return ['', parts[0]]; // Use default namespace. + case 2: return <[string, string]>parts; + default: throw Error(`Invalid icon name: "${iconName}"`); + } + } + + /** + * Gets the icon from the icon registery and injects it into the document. + */ + private _setSvgIcon(): void { + const [namespace, iconName] = this._splitIconName(this.icon); + this._iconRegistry.getNamedSvgIcon(iconName, namespace).subscribe((svg) => { + svg.setAttribute('id', `badge-content-${this._id}`); + svg.classList.add('mat-badge-svg-icon'); + this._elementRef.nativeElement.appendChild(svg); + }); + } } From 1640a5ce71ec771c73e29d86b9efcdaaa843ded8 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 5 Oct 2017 12:55:52 -0500 Subject: [PATCH 05/15] feat(badge): update a few css classes --- src/lib/badge/_badge-theme.scss | 26 +++++++++++++------------- src/lib/badge/badge.spec.ts | 4 ++-- src/lib/badge/badge.ts | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/lib/badge/_badge-theme.scss b/src/lib/badge/_badge-theme.scss index cb19a70eb27a..f01246b2f14d 100644 --- a/src/lib/badge/_badge-theme.scss +++ b/src/lib/badge/_badge-theme.scss @@ -12,7 +12,7 @@ $mat-badge-size: 22px !default; .mat-badge:after, .mat-badge-icon, -.mat-badge-content, +.mat-badge-text, .mat-badge-svg-icon { position: absolute; text-align: center; @@ -32,7 +32,7 @@ $mat-badge-size: 22px !default; z-index: 1; } -.mat-badge-content, +.mat-badge-text, .mat-badge .mat-badge-icon { z-index: 2; font-size: 16px; @@ -51,7 +51,7 @@ $mat-badge-size: 22px !default; .mat-badge-above { .mat-badge-svg-icon, .mat-badge-icon, - .mat-badge-content, + .mat-badge-text, &:after { top: -$mat-badge-size / 2; } @@ -60,7 +60,7 @@ $mat-badge-size: 22px !default; .mat-badge-below { .mat-badge-svg-icon, .mat-badge-icon, - .mat-badge-content, + .mat-badge-text, &:after { bottom: -$mat-badge-size / 2; } @@ -71,7 +71,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, - .mat-badge-content, + .mat-badge-text, &:after { left: -$mat-badge-size; } @@ -82,7 +82,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, - .mat-badge-content, + .mat-badge-text, &:after { right: -$mat-badge-size; } @@ -94,7 +94,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, - .mat-badge-content, + .mat-badge-text, &:after { left: -$mat-badge-size / 2; } @@ -105,7 +105,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, - .mat-badge-content, + .mat-badge-text, &:after { right: -$mat-badge-size / 2; } @@ -122,7 +122,7 @@ $mat-badge-size: 22px !default; fill: mat-color($primary, default-contrast); } - .mat-badge-content, + .mat-badge-text, .mat-badge-icon { color: mat-color($primary, default-contrast); } @@ -136,7 +136,7 @@ $mat-badge-size: 22px !default; fill: mat-color($accent, default-contrast); } - .mat-badge-content, + .mat-badge-text, .mat-badge-icon { color: mat-color($accent, default-contrast); } @@ -151,7 +151,7 @@ $mat-badge-size: 22px !default; fill: mat-color($warn, default-contrast); } - .mat-badge-content, + .mat-badge-text, .mat-badge-icon { color: mat-color($warn, default-contrast); } @@ -166,12 +166,12 @@ $mat-badge-size: 22px !default; @mixin mat-badge-typography($config) { .mat-badge-svg-icon, .mat-badge-icon, - .mat-badge-content { + .mat-badge-text { font-weight: $mat-badge-font-weight; font-size: $mat-badge-font-size; } - .mat-badge-content { + .mat-badge-text { font-family: mat-font-family($config); } } diff --git a/src/lib/badge/badge.spec.ts b/src/lib/badge/badge.spec.ts index 8deff3094aeb..eeed4471a136 100644 --- a/src/lib/badge/badge.spec.ts +++ b/src/lib/badge/badge.spec.ts @@ -63,7 +63,7 @@ describe('MatBadge', () => { it('should update the badge based on attribute', () => { let badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-content'); + badgeDebugElement.nativeElement.querySelector('.mat-badge-text'); expect(badgeContentDebugElement.innerHTML).toContain('1'); @@ -71,7 +71,7 @@ describe('MatBadge', () => { fixture.detectChanges(); badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-content'); + badgeDebugElement.nativeElement.querySelector('.mat-badge-text'); expect(badgeContentDebugElement.innerHTML).toContain('22'); }); diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index 52b002b0e85b..91219a1c7d3b 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -103,7 +103,7 @@ export class MatBadge extends MatBadgeBase { private _setContent(): void { let pane = document.createElement('span'); pane.setAttribute('id', `badge-content-${this._id}`); - pane.classList.add('mat-badge-content'); + pane.classList.add('mat-badge-text'); pane.innerText = this.content; this._elementRef.nativeElement.appendChild(pane); } From a7077d9833a4214b202c0c9e34319a0dda9187e7 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 5 Oct 2017 15:52:55 -0500 Subject: [PATCH 06/15] chore(nit): fix nit feedback --- src/lib/badge/badge.md | 4 ++-- src/lib/badge/badge.ts | 24 +++++++++---------- .../badge/{public_api.ts => public-api.ts} | 0 src/lib/badge/tsconfig-build.json | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) rename src/lib/badge/{public_api.ts => public-api.ts} (100%) diff --git a/src/lib/badge/badge.md b/src/lib/badge/badge.md index f4f0cd084648..4a35d56cdfc3 100644 --- a/src/lib/badge/badge.md +++ b/src/lib/badge/badge.md @@ -2,10 +2,10 @@ Badges are small status descriptors for UI elements. A badge consists of a small typically containing a number or other short set of characters, that appears in proximity to another object. ### Icons -Badges can contain text or font icons and SVGs registered with the `MdIconRegistry` +Badges can contain text or font icons and SVGs registered with the `MdIconRegistry`. #### Font Icons -Badges using font icons are delcared with the `matIconBadge` tag and its content being +Badges using font icons are declared with the `matIconBadge` tag and its content being the name of the icon you wish to use. ```html diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index 91219a1c7d3b..120f52f4f2e1 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -56,8 +56,6 @@ export class MatBadgeBase { _id: number = nextId++; _isAbove: boolean = true; - _isBelow: boolean = false; - _isBefore: boolean = false; _isAfter: boolean = true; constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} @@ -101,11 +99,11 @@ export class MatBadge extends MatBadgeBase { /** Injects a span element into the DOM with the content. */ private _setContent(): void { - let pane = document.createElement('span'); - pane.setAttribute('id', `badge-content-${this._id}`); - pane.classList.add('mat-badge-text'); - pane.innerText = this.content; - this._elementRef.nativeElement.appendChild(pane); + const content = document.createElement('span'); + content.setAttribute('id', `badge-content-${this._id}`); + content.classList.add('mat-badge-text'); + content.innerText = this.content; + this._elementRef.nativeElement.appendChild(content); } } @@ -154,12 +152,12 @@ export class MatIconBadge extends MatBadgeBase { * Gets the font icon from the icon registery and injects it into the document. */ private _setFontIcon(): void { - let pane = document.createElement('span'); - pane.setAttribute('id', `badge-content-${this._id}`); - pane.classList.add(this._getFontSet()); - pane.classList.add('mat-badge-icon'); - pane.innerText = this.icon; - this._elementRef.nativeElement.appendChild(pane); + const icon = document.createElement('span'); + icon.setAttribute('id', `badge-content-${this._id}`); + icon.classList.add(this._getFontSet()); + icon.classList.add('mat-badge-icon'); + icon.innerText = this.icon; + this._elementRef.nativeElement.appendChild(icon); } } diff --git a/src/lib/badge/public_api.ts b/src/lib/badge/public-api.ts similarity index 100% rename from src/lib/badge/public_api.ts rename to src/lib/badge/public-api.ts diff --git a/src/lib/badge/tsconfig-build.json b/src/lib/badge/tsconfig-build.json index a12a61d45dfd..9e813e4e6317 100644 --- a/src/lib/badge/tsconfig-build.json +++ b/src/lib/badge/tsconfig-build.json @@ -1,7 +1,7 @@ { "extends": "../tsconfig-build", "files": [ - "public_api.ts", + "public-api.ts", "../typings.d.ts" ], "angularCompilerOptions": { From d1adb9420f19c30e8ec822e34bf303d9d868c646 Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 9 Oct 2017 09:08:40 -0500 Subject: [PATCH 07/15] chore(nit): update per PR feedback --- src/demo-app/badge/badge-demo.html | 154 ++++++++++++++--------------- src/demo-app/badge/badge-demo.scss | 3 + src/lib/badge/_badge-theme.scss | 34 ++++--- 3 files changed, 97 insertions(+), 94 deletions(-) diff --git a/src/demo-app/badge/badge-demo.html b/src/demo-app/badge/badge-demo.html index 63c571b33353..711f2977420f 100644 --- a/src/demo-app/badge/badge-demo.html +++ b/src/demo-app/badge/badge-demo.html @@ -1,84 +1,78 @@
-

Text

- - Hello - - - - Hello - - - - Hello - - - - Hello - - - - -
-
-
- -

Buttons

- - - - - - - - - - -
-
-
- -

Icons

- - home - - - - home - - - - home - - - - home - - -
-
-
- -

Font Icons

- Home is where you live - -
-
-
- -

SVG Icons

- Home is where I live - - +
+

Text

+ + Hello + + + + Hello + + + + Hello + + + + Hello + + + +
+ +
+

Buttons

+ + + + + + + + + +
+ +
+

Icons

+ + home + + + + home + + + + home + + + + home + +
+ + +
+

Font Icons

+ Home is where you live +
+ +
+

SVG Icons

+ Home is where I live + +
\ No newline at end of file diff --git a/src/demo-app/badge/badge-demo.scss b/src/demo-app/badge/badge-demo.scss index e69de29bb2d1..f94a80a1fb67 100644 --- a/src/demo-app/badge/badge-demo.scss +++ b/src/demo-app/badge/badge-demo.scss @@ -0,0 +1,3 @@ +.badge-examples { + margin-bottom: 25px; +} diff --git a/src/lib/badge/_badge-theme.scss b/src/lib/badge/_badge-theme.scss index f01246b2f14d..b7627ad5319a 100644 --- a/src/lib/badge/_badge-theme.scss +++ b/src/lib/badge/_badge-theme.scss @@ -5,12 +5,13 @@ $mat-badge-font-size: 12px; $mat-badge-font-weight: 600; $mat-badge-size: 22px !default; +$mat-badge-svg-size: 12px; .mat-badge { position: relative; } -.mat-badge:after, +.mat-badge::after, .mat-badge-icon, .mat-badge-text, .mat-badge-svg-icon { @@ -24,7 +25,7 @@ $mat-badge-size: 22px !default; align-items: center; } -.mat-badge:after { +.mat-badge::after { width: $mat-badge-size; height: $mat-badge-size; border-radius: 50%; @@ -35,15 +36,14 @@ $mat-badge-size: 22px !default; .mat-badge-text, .mat-badge .mat-badge-icon { z-index: 2; - font-size: 16px; width: $mat-badge-size; height: $mat-badge-size; } .mat-badge-svg-icon { z-index: 2; - width: 12px; - height: 12px; + width: $mat-badge-svg-size; + height: $mat-badge-svg-size; z-index: 2; transform: translate(-5px, 5px); } @@ -52,7 +52,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, .mat-badge-text, - &:after { + &::after { top: -$mat-badge-size / 2; } } @@ -61,7 +61,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, .mat-badge-text, - &:after { + &::after { bottom: -$mat-badge-size / 2; } } @@ -72,7 +72,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, .mat-badge-text, - &:after { + &::after { left: -$mat-badge-size; } } @@ -83,7 +83,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, .mat-badge-text, - &:after { + &::after { right: -$mat-badge-size; } } @@ -95,7 +95,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, .mat-badge-text, - &:after { + &::after { left: -$mat-badge-size / 2; } } @@ -106,7 +106,7 @@ $mat-badge-size: 22px !default; .mat-badge-svg-icon, .mat-badge-icon, .mat-badge-text, - &:after { + &::after { right: -$mat-badge-size / 2; } } @@ -127,7 +127,7 @@ $mat-badge-size: 22px !default; color: mat-color($primary, default-contrast); } - &:after { + &::after { background: mat-color($primary); } @@ -141,7 +141,7 @@ $mat-badge-size: 22px !default; color: mat-color($accent, default-contrast); } - &:after { + &::after { background: mat-color($accent); } } @@ -156,7 +156,7 @@ $mat-badge-size: 22px !default; color: mat-color($warn, default-contrast); } - &:after { + &::after { background: mat-color($warn); } } @@ -174,4 +174,10 @@ $mat-badge-size: 22px !default; .mat-badge-text { font-family: mat-font-family($config); } + + + .mat-badge-text, + .mat-badge .mat-badge-icon { + font-size: 16px; + } } From d3e4e5074075765f0c0e162da4fe5e12107f9441 Mon Sep 17 00:00:00 2001 From: Austin Date: Wed, 11 Oct 2017 09:25:01 -0500 Subject: [PATCH 08/15] chore(nit): update per feedback --- src/lib/badge/badge-module.ts | 1 - src/lib/badge/index.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/badge/badge-module.ts b/src/lib/badge/badge-module.ts index 2aac7842a7e7..5711eb07b8da 100644 --- a/src/lib/badge/badge-module.ts +++ b/src/lib/badge/badge-module.ts @@ -17,7 +17,6 @@ import {MatBadge, MatSvgIconBadge, MatIconBadge} from './badge'; MatBadge, MatIconBadge, MatSvgIconBadge, - MatCommonModule, ], declarations: [ MatBadge, diff --git a/src/lib/badge/index.ts b/src/lib/badge/index.ts index f93e7c31d564..d6e71197ad19 100644 --- a/src/lib/badge/index.ts +++ b/src/lib/badge/index.ts @@ -6,4 +6,4 @@ * found in the LICENSE file at https://angular.io/license */ -export * from './public_api'; +export * from './public-api'; From ec10e441f807aa043b292874651456f308c7fde7 Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 23 Oct 2017 09:18:10 -0500 Subject: [PATCH 09/15] chore(nit): updates post review --- src/demo-app/badge/badge-demo.html | 31 ++-- src/lib/badge/_badge-theme.scss | 208 +++++++++++---------------- src/lib/badge/badge-module.ts | 8 +- src/lib/badge/badge.spec.ts | 116 ++------------- src/lib/badge/badge.ts | 219 ++++++++++------------------- src/lib/badge/index.ts | 2 +- src/lib/badge/public-api.ts | 2 +- 7 files changed, 194 insertions(+), 392 deletions(-) diff --git a/src/demo-app/badge/badge-demo.html b/src/demo-app/badge/badge-demo.html index 711f2977420f..653f765cbb14 100644 --- a/src/demo-app/badge/badge-demo.html +++ b/src/demo-app/badge/badge-demo.html @@ -6,6 +6,10 @@

Text

Hello + + Hello + + Hello @@ -18,6 +22,14 @@

Text

Hello + + Aria + + + + Hidden + + @@ -46,7 +58,7 @@

Buttons

Icons

- + home @@ -63,16 +75,15 @@

Icons

+
+

Size

+ + home + + + home + -
-

Font Icons

- Home is where you live -
- -
-

SVG Icons

- Home is where I live -
\ No newline at end of file diff --git a/src/lib/badge/_badge-theme.scss b/src/lib/badge/_badge-theme.scss index b7627ad5319a..6ade7a802c9e 100644 --- a/src/lib/badge/_badge-theme.scss +++ b/src/lib/badge/_badge-theme.scss @@ -5,109 +5,59 @@ $mat-badge-font-size: 12px; $mat-badge-font-weight: 600; $mat-badge-size: 22px !default; -$mat-badge-svg-size: 12px; - -.mat-badge { - position: relative; -} - -.mat-badge::after, -.mat-badge-icon, -.mat-badge-text, -.mat-badge-svg-icon { - position: absolute; - text-align: center; - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - align-content: center; - align-items: center; -} - -.mat-badge::after { - width: $mat-badge-size; - height: $mat-badge-size; - border-radius: 50%; - content: ''; - z-index: 1; -} - -.mat-badge-text, -.mat-badge .mat-badge-icon { - z-index: 2; - width: $mat-badge-size; - height: $mat-badge-size; -} - -.mat-badge-svg-icon { - z-index: 2; - width: $mat-badge-svg-size; - height: $mat-badge-svg-size; - z-index: 2; - transform: translate(-5px, 5px); -} +$mat-badge-small-size: $mat-badge-size - 6; +$mat-badge-large-size: $mat-badge-size + 6; + +@mixin mat-badge-size($size) { + .mat-badge-content { + width: $size; + height: $size; + line-height: $size; + } -.mat-badge-above { - .mat-badge-svg-icon, - .mat-badge-icon, - .mat-badge-text, - &::after { - top: -$mat-badge-size / 2; + &.mat-badge-above { + .mat-badge-content { + top: -$size / 2; + } } -} -.mat-badge-below { - .mat-badge-svg-icon, - .mat-badge-icon, - .mat-badge-text, - &::after { - bottom: -$mat-badge-size / 2; + &.mat-badge-below { + .mat-badge-content { + bottom: -$size / 2; + } } -} -.mat-badge-before { - margin-left: $mat-badge-size; + &.mat-badge-before { + margin-left: $size; - .mat-badge-svg-icon, - .mat-badge-icon, - .mat-badge-text, - &::after { - left: -$mat-badge-size; + .mat-badge-content { + left: -$size; + } } -} -.mat-badge-after { - margin-right: $mat-badge-size; + &.mat-badge-after { + margin-right: $size; - .mat-badge-svg-icon, - .mat-badge-icon, - .mat-badge-text, - &::after { - right: -$mat-badge-size; + .mat-badge-content { + right: -$size; + } } -} -.mat-badge-overlap { - &.mat-badge-before { - margin-left: $mat-badge-size / 2; + &.mat-badge-overlap { + &.mat-badge-before { + margin-left: $size / 2; - .mat-badge-svg-icon, - .mat-badge-icon, - .mat-badge-text, - &::after { - left: -$mat-badge-size / 2; + .mat-badge-content { + left: -$size / 2; + } } - } - &.mat-badge-after { - margin-right: $mat-badge-size / 2; + &.mat-badge-after { + margin-right: $size / 2; - .mat-badge-svg-icon, - .mat-badge-icon, - .mat-badge-text, - &::after { - right: -$mat-badge-size / 2; + .mat-badge-content { + right: -$size / 2; + } } } } @@ -118,45 +68,21 @@ $mat-badge-svg-size: 12px; $primary: map-get($theme, primary); .mat-badge { - .mat-badge-svg-icon { - fill: mat-color($primary, default-contrast); - } - - .mat-badge-text, - .mat-badge-icon { + .mat-badge-content { color: mat-color($primary, default-contrast); - } - - &::after { background: mat-color($primary); } &.mat-badge-accent { - .mat-badge-svg-icon { - fill: mat-color($accent, default-contrast); - } - - .mat-badge-text, - .mat-badge-icon { - color: mat-color($accent, default-contrast); - } - - &::after { + .mat-badge-content { background: mat-color($accent); + color: mat-color($accent, default-contrast); } } &.mat-badge-warn { - .mat-badge-svg-icon { - fill: mat-color($warn, default-contrast); - } - - .mat-badge-text, - .mat-badge-icon { + .mat-badge-content { color: mat-color($warn, default-contrast); - } - - &::after { background: mat-color($warn); } } @@ -164,20 +90,54 @@ $mat-badge-svg-size: 12px; } @mixin mat-badge-typography($config) { - .mat-badge-svg-icon, - .mat-badge-icon, - .mat-badge-text { + .mat-badge-content { font-weight: $mat-badge-font-weight; font-size: $mat-badge-font-size; + font-family: mat-font-family($config); } - .mat-badge-text { - font-family: mat-font-family($config); + .mat-badge-small .mat-badge-content { + font-size: $mat-badge-font-size / 2; } + .mat-badge-large .mat-badge-content { + font-size: $mat-badge-font-size * 2; + } +} + +.mat-badge { + position: relative; - .mat-badge-text, - .mat-badge .mat-badge-icon { - font-size: 16px; + &.mat-badge-hidden { + .mat-badge-content { + visibility: hidden; + } } } + +.mat-badge-content { + position: absolute; + text-align: center; + display: inline-block; + border-radius: 50%; + transition: all .2s ease-in-out; + transform: scale(.6); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + pointer-events: none; +} + +.mat-badge-active.mat-badge-content { + transform: scale(1); +} + +.mat-badge-small { + @include mat-badge-size($mat-badge-small-size); +} +.mat-badge-medium { + @include mat-badge-size($mat-badge-size); +} +.mat-badge-large { + @include mat-badge-size($mat-badge-large-size); +} \ No newline at end of file diff --git a/src/lib/badge/badge-module.ts b/src/lib/badge/badge-module.ts index 5711eb07b8da..45672b7e6a02 100644 --- a/src/lib/badge/badge-module.ts +++ b/src/lib/badge/badge-module.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -8,20 +8,16 @@ import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; -import {MatBadge, MatSvgIconBadge, MatIconBadge} from './badge'; +import {MatBadge} from './badge'; @NgModule({ imports: [MatCommonModule], exports: [ MatBadge, - MatIconBadge, - MatSvgIconBadge, ], declarations: [ MatBadge, - MatIconBadge, - MatSvgIconBadge, ], }) export class MatBadgeModule {} diff --git a/src/lib/badge/badge.spec.ts b/src/lib/badge/badge.spec.ts index eeed4471a136..4195289adf53 100644 --- a/src/lib/badge/badge.spec.ts +++ b/src/lib/badge/badge.spec.ts @@ -1,69 +1,35 @@ -import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing'; -import {HttpModule, XHRBackend} from '@angular/http'; -import {MockBackend} from '@angular/http/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {Component, DebugElement} from '@angular/core'; import {By} from '@angular/platform-browser'; -import {MatBadge, MatIconBadge, MatSvgIconBadge, MatBadgeModule} from './index'; -import {MatIconRegistry} from '../icon/icon-registry'; -import {SafeResourceUrl, DomSanitizer} from '@angular/platform-browser'; -import {getFakeSvgHttpResponse} from '../icon/fake-svgs'; +import {MatBadge, MatBadgeModule} from './index'; -describe('MatBadge', () => { +fdescribe('MatBadge', () => { let fixture: ComponentFixture; let testComponent: TestApp; - let iconRegistry: MatIconRegistry; - let sanitizer: DomSanitizer; - let httpRequestUrls: string[]; - function trust(iconUrl: string): SafeResourceUrl { - return sanitizer.bypassSecurityTrustResourceUrl(iconUrl); - } - - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ - imports: [MatBadgeModule, HttpModule], + imports: [MatBadgeModule], declarations: [TestApp], - providers: [ - MatIconRegistry, - MockBackend, - {provide: XHRBackend, useExisting: MockBackend}, - ] }); TestBed.compileComponents(); - })); - - let deps = [MatIconRegistry, MockBackend, DomSanitizer]; - beforeEach(inject(deps, (mir: MatIconRegistry, mockBackend: MockBackend, ds: DomSanitizer) => { - iconRegistry = mir; - sanitizer = ds; - httpRequestUrls = []; - - mockBackend.connections.subscribe((connection: any) => { - const url = connection.request.url; - httpRequestUrls.push(url); - connection.mockRespond(getFakeSvgHttpResponse(url)); - }); - - iconRegistry.addSvgIcon('fluffy', trust('cat.svg')); - iconRegistry.addSvgIcon('fido', trust('dog.svg')); - fixture = TestBed.createComponent(TestApp); testComponent = fixture.debugElement.componentInstance; fixture.detectChanges(); - })); + }); describe('MatBadge Text', () => { let badgeDebugElement: DebugElement; - beforeEach(async(() => { + beforeEach(() => { badgeDebugElement = fixture.debugElement.query(By.directive(MatBadge)); fixture.detectChanges(); - })); + }); it('should update the badge based on attribute', () => { let badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-text'); + badgeDebugElement.nativeElement.querySelector('.mat-badge-content'); expect(badgeContentDebugElement.innerHTML).toContain('1'); @@ -71,7 +37,7 @@ describe('MatBadge', () => { fixture.detectChanges(); badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-text'); + badgeDebugElement.nativeElement.querySelector('.mat-badge-content'); expect(badgeContentDebugElement.innerHTML).toContain('22'); }); @@ -106,77 +72,21 @@ describe('MatBadge', () => { }); }); - describe('MatBadge Font Icon', () => { - let badgeDebugElement: DebugElement; - - beforeEach(async(() => { - badgeDebugElement = fixture.debugElement.query(By.directive(MatIconBadge)); - fixture.detectChanges(); - })); - - it('should update the badge icon based on attribute', () => { - let badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-icon'); - - expect(badgeContentDebugElement.innerHTML).toContain('home'); - - testComponent.badgeIcon = 'phone'; - fixture.detectChanges(); - - badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-icon'); - - expect(badgeContentDebugElement.innerHTML).toContain('phone'); - }); - }); - - describe('MatBadge SVG Icon', () => { - let badgeDebugElement: DebugElement; - - beforeEach(() => { - badgeDebugElement = fixture.debugElement.query(By.directive(MatSvgIconBadge)); - fixture.detectChanges(); - }); - - it('should update the badge svg based on attribute', async() => { - let badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-svg-icon'); - - expect(badgeContentDebugElement.innerHTML).toContain(''); - - testComponent.badgeSvg = 'fido'; - fixture.detectChanges(); - - badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-svg-icon'); - - expect(badgeContentDebugElement.innerHTML).toContain(''); - }); - }); - }); /** Test component that contains an MatBadge. */ @Component({ selector: 'test-app', template: ` - + home - - - Hello - - - Hello ` }) class TestApp { badgeColor; badgeContent = '1'; - badgeIcon = 'home'; - badgeSvg = 'fluffy'; badgeDirection = 'above after'; } diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index 120f52f4f2e1..0208efd6fc06 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -1,26 +1,41 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {Directive, Input, Renderer2, ElementRef} from '@angular/core'; -import {MatIconRegistry} from '@angular/material/icon'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {ThemePalette} from '@angular/material/core'; let nextId = 0; - /** @docs-private */ -export class MatBadgeBase { +/** Directive to display a text badge. */ +@Directive({ + selector: '[matBadge]', + inputs: ['matBadgeColor', 'matBadgeOverlap', 'matBadgePosition'], + host: { + 'class': 'mat-badge', + '[class.mat-badge-overlap]': '_overlap', + '[class.mat-badge-above]': '_isAbove', + '[class.mat-badge-below]': '!_isAbove', + '[class.mat-badge-before]': '!_isAfter', + '[class.mat-badge-after]': '_isAfter', + '[class.mat-badge-small]': 'matBadgeSize === "small"', + '[class.mat-badge-medium]': 'matBadgeSize === "medium"', + '[class.mat-badge-large]': 'matBadgeSize === "large"', + '[class.mat-badge-hidden]': 'matBadgeHidden', + }, +}) +export class MatBadge { /** Theme for the badge */ + @Input() get matBadgeColor(): ThemePalette { return this._color; } set matBadgeColor(value: ThemePalette) { const colorPalette = value; - if (colorPalette !== this._color) { if (this._color) { this._renderer.removeClass(this._elementRef.nativeElement, `mat-badge-${this._color}`); @@ -35,6 +50,7 @@ export class MatBadgeBase { private _color: ThemePalette = 'primary'; /** Whether the badge should overlap its contents or not */ + @Input() set matBadgeOverlap(val: boolean) { this._overlap = coerceBooleanProperty(val); } @@ -44,6 +60,7 @@ export class MatBadgeBase { private _overlap: boolean = true; /** Position the badge should reside; 'above|below before|after' */ + @Input() set matBadgePosition(val: string) { this._position = val; this._isAbove = val.indexOf('below') === -1; @@ -54,168 +71,76 @@ export class MatBadgeBase { } private _position: string = 'above after'; - _id: number = nextId++; - _isAbove: boolean = true; - _isAfter: boolean = true; - - constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {} - - /** Clears the previous badge content */ - _clearContents(): void { - const contents = document.getElementById(`badge-content-${this._id}`); - if (contents) { - this._renderer.removeChild(this._elementRef.nativeElement, contents); - } - } -} - -/** Directive to display a text badge. */ -@Directive({ - selector: '[matBadge]', - inputs: ['matBadgeColor', 'matBadgeOverlap', 'matBadgePosition'], - host: { - 'class': 'mat-badge', - '[class.mat-badge-overlap]': '_overlap', - '[class.mat-badge-above]': '_isAbove', - '[class.mat-badge-below]': '!_isAbove', - '[class.mat-badge-before]': '!_isAfter', - '[class.mat-badge-after]': '_isAfter', - }, -}) -export class MatBadge extends MatBadgeBase { - @Input('matBadge') set content(val: string) { this._content = val; - this._clearContents(); this._setContent(); } get content(): string { return this._content; } private _content: string; - constructor(_renderer: Renderer2, _elementRef: ElementRef) { - super(_renderer, _elementRef); + /** Aria description */ + @Input() + set matBadgeDescription(val: string) { + this._description = val; + this._setLabel(); } + get matBadgeDescription(): string { return this._description; } + private _description: string; - /** Injects a span element into the DOM with the content. */ - private _setContent(): void { - const content = document.createElement('span'); - content.setAttribute('id', `badge-content-${this._id}`); - content.classList.add('mat-badge-text'); - content.innerText = this.content; - this._elementRef.nativeElement.appendChild(content); - } -} + /** Size of the badge */ + @Input() matBadgeSize: string = 'medium'; -/** Directive to display a font icon badge. */ -@Directive({ - selector: '[matIconBadge]', - inputs: ['matBadgeColor', 'matBadgeOverlap', 'matBadgePosition'], - host: { - 'class': 'mat-badge', - '[class.mat-badge-overlap]': '_overlap', - '[class.mat-badge-above]': '_isAbove', - '[class.mat-badge-below]': '!_isAbove', - '[class.mat-badge-before]': '!_isAfter', - '[class.mat-badge-after]': '_isAfter', - }, -}) -export class MatIconBadge extends MatBadgeBase { - - @Input('matIconBadge') - set icon(val: string) { - this._icon = val; - this._clearContents(); - this._setFontIcon(); + @Input() + set matBadgeHidden(val: boolean) { + this._hidden = coerceBooleanProperty(val); } - get icon(): string { return this._icon; } - private _icon: string; - - /** Font set to use for the icon */ - @Input() matBadgeFontSet: string; - - constructor( - private _iconRegistry: MatIconRegistry, - _elementRef: ElementRef, - _renderer: Renderer2) { - super(_renderer, _elementRef); + get matBadgeHidden(): boolean { + return this._hidden; } + private _hidden: boolean; - /** Gets the font icon set to use. */ - private _getFontSet(): string { - return this.matBadgeFontSet ? - this._iconRegistry.classNameForFontAlias(this.matBadgeFontSet) : - this._iconRegistry.getDefaultFontSetClass(); - } + _id: number = nextId++; + _isAbove: boolean = true; + _isAfter: boolean = true; - /** - * Gets the font icon from the icon registery and injects it into the document. - */ - private _setFontIcon(): void { - const icon = document.createElement('span'); - icon.setAttribute('id', `badge-content-${this._id}`); - icon.classList.add(this._getFontSet()); - icon.classList.add('mat-badge-icon'); - icon.innerText = this.icon; - this._elementRef.nativeElement.appendChild(icon); - } -} + constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {} -/** Directive to display a SVG icon badge. */ -@Directive({ - selector: '[matSvgIconBadge]', - inputs: ['matBadgeColor', 'matBadgeOverlap', 'matBadgePosition'], - host: { - 'class': 'mat-badge', - '[class.mat-badge-overlap]': '_overlap', - '[class.mat-badge-above]': '_isAbove', - '[class.mat-badge-below]': '!_isAbove', - '[class.mat-badge-before]': '!_isAfter', - '[class.mat-badge-after]': '_isAfter', - }, -}) -export class MatSvgIconBadge extends MatBadgeBase { + /** Injects a span element into the DOM with the content. */ + private _setContent(): HTMLSpanElement { + let content = document.getElementById(`badge-content-${this._id}`); - @Input('matSvgIconBadge') - set icon(val: string) { - this._icon = val; - this._clearContents(); - this._setSvgIcon(); - } - get icon(): string { return this._icon; } - private _icon: string; - - constructor( - private _iconRegistry: MatIconRegistry, - _elementRef: ElementRef, - _renderer: Renderer2) { - super(_renderer, _elementRef); - } + if (!content) { + content = document.createElement('span'); + content.setAttribute('id', `badge-content-${this._id}`); + content.classList.add('mat-badge-content'); + content.textContent = this.content; - /** - * Splits an svgIcon binding value into its icon set and icon name components. - */ - private _splitIconName(iconName: string): [string, string] { - if (!iconName) { - return ['', '']; - } - const parts = iconName.split(':'); - switch (parts.length) { - case 1: return ['', parts[0]]; // Use default namespace. - case 2: return <[string, string]>parts; - default: throw Error(`Invalid icon name: "${iconName}"`); + if (this.matBadgeDescription) { + content.setAttribute('aria-label', this.matBadgeDescription); + } + + this._elementRef.nativeElement.appendChild(content); + + // animate in after insertion + setTimeout(() => { + // ensure content available + if (content !== null) { + content.classList.add('mat-badge-active'); + } + }, 100); + } else if (content.textContent !== this.content) { + content.textContent = this.content; } + + return content; } - /** - * Gets the icon from the icon registery and injects it into the document. - */ - private _setSvgIcon(): void { - const [namespace, iconName] = this._splitIconName(this.icon); - this._iconRegistry.getNamedSvgIcon(iconName, namespace).subscribe((svg) => { - svg.setAttribute('id', `badge-content-${this._id}`); - svg.classList.add('mat-badge-svg-icon'); - this._elementRef.nativeElement.appendChild(svg); - }); + /** Sets the aria-label property on the element */ + private _setLabel(): void { + // ensure content available before setting label + const content = this._setContent(); + content.setAttribute('aria-label', this.matBadgeDescription); } + } diff --git a/src/lib/badge/index.ts b/src/lib/badge/index.ts index d6e71197ad19..676ca90f1ffa 100644 --- a/src/lib/badge/index.ts +++ b/src/lib/badge/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license diff --git a/src/lib/badge/public-api.ts b/src/lib/badge/public-api.ts index faff71446c0b..91b69772f78c 100644 --- a/src/lib/badge/public-api.ts +++ b/src/lib/badge/public-api.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license From 2f8c6aa48413c4f32180ddd216b99a21c48465c4 Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 23 Oct 2017 15:57:05 -0500 Subject: [PATCH 10/15] chore(nit): updates per feedback --- src/lib/badge/_badge-theme.scss | 51 +++++++++++----- src/lib/badge/badge.md | 21 ------- src/lib/badge/badge.spec.ts | 38 ++++++------ src/lib/badge/badge.ts | 101 ++++++++++++++++++-------------- 4 files changed, 114 insertions(+), 97 deletions(-) diff --git a/src/lib/badge/_badge-theme.scss b/src/lib/badge/_badge-theme.scss index 6ade7a802c9e..9fe1603e4d05 100644 --- a/src/lib/badge/_badge-theme.scss +++ b/src/lib/badge/_badge-theme.scss @@ -4,11 +4,12 @@ $mat-badge-font-size: 12px; $mat-badge-font-weight: 600; -$mat-badge-size: 22px !default; -$mat-badge-small-size: $mat-badge-size - 6; -$mat-badge-large-size: $mat-badge-size + 6; +$mat-badge-default-size: 22px !default; +$mat-badge-small-size: $mat-badge-default-size - 6; +$mat-badge-large-size: $mat-badge-default-size + 6; -@mixin mat-badge-size($size) { +// Mixin for building offset given different sizes +@mixin _mat-badge-size($size) { .mat-badge-content { width: $size; height: $size; @@ -30,6 +31,11 @@ $mat-badge-large-size: $mat-badge-size + 6; &.mat-badge-before { margin-left: $size; + &[dir='rtl'] { + margin-left: 0; + margin-right: $size; + } + .mat-badge-content { left: -$size; } @@ -38,6 +44,11 @@ $mat-badge-large-size: $mat-badge-size + 6; &.mat-badge-after { margin-right: $size; + &[dir='rtl'] { + margin-right: 0; + margin-left: $size; + } + .mat-badge-content { right: -$size; } @@ -47,6 +58,11 @@ $mat-badge-large-size: $mat-badge-size + 6; &.mat-badge-before { margin-left: $size / 2; + &[dir='rtl'] { + margin-left: 0; + margin-right: $size / 2; + } + .mat-badge-content { left: -$size / 2; } @@ -54,6 +70,11 @@ $mat-badge-large-size: $mat-badge-size + 6; &.mat-badge-after { margin-right: $size / 2; + + &[dir='rtl'] { + margin-right: 0; + margin-left: $size; + } .mat-badge-content { right: -$size / 2; @@ -67,13 +88,13 @@ $mat-badge-large-size: $mat-badge-size + 6; $warn: map-get($theme, warn); $primary: map-get($theme, primary); - .mat-badge { .mat-badge-content { color: mat-color($primary, default-contrast); background: mat-color($primary); } - &.mat-badge-accent { + .mat-badge { + &.mat-badge-accent { .mat-badge-content { background: mat-color($accent); color: mat-color($accent, default-contrast); @@ -107,11 +128,11 @@ $mat-badge-large-size: $mat-badge-size + 6; .mat-badge { position: relative; +} - &.mat-badge-hidden { - .mat-badge-content { - visibility: hidden; - } +.mat-badge-hidden { + .mat-badge-content { + display: none; } } @@ -128,16 +149,18 @@ $mat-badge-large-size: $mat-badge-size + 6; pointer-events: none; } -.mat-badge-active.mat-badge-content { +// The active class is added after the element is added +// so it can animate scale to default +.mat-badge-content.mat-badge-active { transform: scale(1); } .mat-badge-small { - @include mat-badge-size($mat-badge-small-size); + @include _mat-badge-size($mat-badge-small-size); } .mat-badge-medium { - @include mat-badge-size($mat-badge-size); + @include _mat-badge-size($mat-badge-default-size); } .mat-badge-large { - @include mat-badge-size($mat-badge-large-size); + @include _mat-badge-size($mat-badge-large-size); } \ No newline at end of file diff --git a/src/lib/badge/badge.md b/src/lib/badge/badge.md index 4a35d56cdfc3..5dd00c03a89d 100644 --- a/src/lib/badge/badge.md +++ b/src/lib/badge/badge.md @@ -1,27 +1,6 @@ Badges are small status descriptors for UI elements. A badge consists of a small circle, typically containing a number or other short set of characters, that appears in proximity to another object. -### Icons -Badges can contain text or font icons and SVGs registered with the `MdIconRegistry`. - -#### Font Icons -Badges using font icons are declared with the `matIconBadge` tag and its content being -the name of the icon you wish to use. - -```html -Home is where I live. -``` - -You can also pass a custom font set using the `matBadgeFontSet` tag. - -#### SVG Icons -Badges using SVG icons are declared with the `matSvgIconBadge` and its content being -the name of the icon you wish to use. - -```html -Home is where I live. -``` - ### Badge position By default, the badge will be placed `above after`. The direction can be changed by defining the attribute `matBadgePosition` follow by `above|below` and `before|after`. diff --git a/src/lib/badge/badge.spec.ts b/src/lib/badge/badge.spec.ts index 4195289adf53..589b3b2506f8 100644 --- a/src/lib/badge/badge.spec.ts +++ b/src/lib/badge/badge.spec.ts @@ -3,18 +3,19 @@ import {Component, DebugElement} from '@angular/core'; import {By} from '@angular/platform-browser'; import {MatBadge, MatBadgeModule} from './index'; -fdescribe('MatBadge', () => { +describe('MatBadge', () => { let fixture: ComponentFixture; - let testComponent: TestApp; + let testComponent: BadgeWithTextContent; + let badgeNativeElement; beforeEach(() => { TestBed.configureTestingModule({ imports: [MatBadgeModule], - declarations: [TestApp], + declarations: [BadgeWithTextContent], }); TestBed.compileComponents(); - fixture = TestBed.createComponent(TestApp); + fixture = TestBed.createComponent(BadgeWithTextContent); testComponent = fixture.debugElement.componentInstance; fixture.detectChanges(); }); @@ -24,51 +25,50 @@ fdescribe('MatBadge', () => { beforeEach(() => { badgeDebugElement = fixture.debugElement.query(By.directive(MatBadge)); + badgeNativeElement = badgeDebugElement.nativeElement; fixture.detectChanges(); }); it('should update the badge based on attribute', () => { - let badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-content'); + let badgeContentDebugElement = badgeNativeElement.querySelector('.mat-badge-content'); - expect(badgeContentDebugElement.innerHTML).toContain('1'); + expect(badgeContentDebugElement.textContent).toContain('1'); testComponent.badgeContent = '22'; fixture.detectChanges(); - badgeContentDebugElement = - badgeDebugElement.nativeElement.querySelector('.mat-badge-content'); - expect(badgeContentDebugElement.innerHTML).toContain('22'); + badgeContentDebugElement = badgeNativeElement.querySelector('.mat-badge-content'); + expect(badgeContentDebugElement.textContent).toContain('22'); }); it('should apply class based on color attribute', () => { testComponent.badgeColor = 'primary'; fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-primary')).toBe(true); + expect(badgeNativeElement.classList.contains('mat-badge-primary')).toBe(true); testComponent.badgeColor = 'accent'; fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-accent')).toBe(true); + expect(badgeNativeElement.classList.contains('mat-badge-accent')).toBe(true); testComponent.badgeColor = 'warn'; fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-warn')).toBe(true); + expect(badgeNativeElement.classList.contains('mat-badge-warn')).toBe(true); testComponent.badgeColor = null; fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList).not.toContain('mat-badge-accent'); + expect(badgeNativeElement.classList).not.toContain('mat-badge-accent'); }); it('should update the badget position on direction change', () => { - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-above')).toBe(true); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-after')).toBe(true); + expect(badgeNativeElement.classList.contains('mat-badge-above')).toBe(true); + expect(badgeNativeElement.classList.contains('mat-badge-after')).toBe(true); testComponent.badgeDirection = 'below before'; fixture.detectChanges(); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-below')).toBe(true); - expect(badgeDebugElement.nativeElement.classList.contains('mat-badge-before')).toBe(true); + expect(badgeNativeElement.classList.contains('mat-badge-below')).toBe(true); + expect(badgeNativeElement.classList.contains('mat-badge-before')).toBe(true); }); }); @@ -85,7 +85,7 @@ fdescribe('MatBadge', () => { ` }) -class TestApp { +class BadgeWithTextContent { badgeColor; badgeContent = '1'; badgeDirection = 'above after'; diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index 0208efd6fc06..c9f4623038af 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -9,6 +9,7 @@ import {Directive, Input, Renderer2, ElementRef} from '@angular/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {ThemePalette} from '@angular/material/core'; +import {AriaDescriber} from '@angular/cdk/a11y'; let nextId = 0; @@ -32,63 +33,53 @@ let nextId = 0; export class MatBadge { /** Theme for the badge */ - @Input() - get matBadgeColor(): ThemePalette { return this._color; } - set matBadgeColor(value: ThemePalette) { - const colorPalette = value; - if (colorPalette !== this._color) { - if (this._color) { - this._renderer.removeClass(this._elementRef.nativeElement, `mat-badge-${this._color}`); - } - if (colorPalette) { - this._renderer.addClass(this._elementRef.nativeElement, `mat-badge-${colorPalette}`); - } - - this._color = colorPalette; - } + @Input('matBadgeColor') + get color(): ThemePalette { return this._color; } + set color(value: ThemePalette) { + this._color = value; + this._setColor(value); } private _color: ThemePalette = 'primary'; /** Whether the badge should overlap its contents or not */ - @Input() - set matBadgeOverlap(val: boolean) { + @Input('matBadgeOverlap') + get overlap(): boolean { return this._overlap; } + set overlap(val: boolean) { this._overlap = coerceBooleanProperty(val); } - get matBadgeOverlap(): boolean { - return this._overlap; - } private _overlap: boolean = true; - /** Position the badge should reside; 'above|below before|after' */ - @Input() - set matBadgePosition(val: string) { + /** + * Position the badge should reside. + * Accepts any combination of 'above'|'below' and 'before'|'after' + */ + @Input('matBadgePosition') + get position(): string { return this._position; } + set position(val: string) { this._position = val; this._isAbove = val.indexOf('below') === -1; this._isAfter = val.indexOf('before') === -1; } - get matBadgePosition(): string { - return this._position; - } private _position: string = 'above after'; @Input('matBadge') + get content(): string { return this._content; } set content(val: string) { this._content = val; - this._setContent(); + this._updateTextContent(); } - get content(): string { return this._content; } private _content: string; - /** Aria description */ - @Input() - set matBadgeDescription(val: string) { + /** Message used to describe the decorated element via aria-describedby */ + @Input('matBadgePosition') + get description(): string { return this._description; } + set description(val: string) { + this._setLabel(val, this._description); this._description = val; - this._setLabel(); } - get matBadgeDescription(): string { return this._description; } private _description: string; - /** Size of the badge */ + /** Size of the badge. 'small' | 'medium' | 'large' */ @Input() matBadgeSize: string = 'medium'; @Input() @@ -100,27 +91,37 @@ export class MatBadge { } private _hidden: boolean; + /** Unique id for the badge */ _id: number = nextId++; + + /** Whether the badge is above the host or not */ _isAbove: boolean = true; + + /** Whether the badge is after the host or not */ _isAfter: boolean = true; - constructor(private _renderer: Renderer2, private _elementRef: ElementRef) {} + constructor( + private _renderer: Renderer2, + private _elementRef: ElementRef, + private _ariaDescriber: AriaDescriber) {} /** Injects a span element into the DOM with the content. */ - private _setContent(): HTMLSpanElement { - let content = document.getElementById(`badge-content-${this._id}`); + private _updateTextContent(): HTMLSpanElement { + let content = document.getElementById(`mat-badge-content-${this._id}`); if (!content) { + content = this._renderer.createElement('span'); + content = document.createElement('span'); - content.setAttribute('id', `badge-content-${this._id}`); + content.setAttribute('id', `mat-badge-content-${this._id}`); content.classList.add('mat-badge-content'); content.textContent = this.content; - if (this.matBadgeDescription) { - content.setAttribute('aria-label', this.matBadgeDescription); + if (this.description) { + content.setAttribute('aria-label', this.description); } - this._elementRef.nativeElement.appendChild(content); + this._renderer.appendChild(this._elementRef.nativeElement, content); // animate in after insertion setTimeout(() => { @@ -137,10 +138,24 @@ export class MatBadge { } /** Sets the aria-label property on the element */ - private _setLabel(): void { + private _setLabel(val: string, prevVal: string): void { // ensure content available before setting label - const content = this._setContent(); - content.setAttribute('aria-label', this.matBadgeDescription); + const content = this._updateTextContent(); + this._ariaDescriber.removeDescription(content, prevVal); + this._ariaDescriber.describe(content, val); + } + + /** Adds css theme class given the color to the component host */ + private _setColor(value: ThemePalette) { + const colorPalette = value; + if (colorPalette !== this._color) { + if (this._color) { + this._renderer.removeClass(this._elementRef.nativeElement, `mat-badge-${this._color}`); + } + if (colorPalette) { + this._renderer.addClass(this._elementRef.nativeElement, `mat-badge-${colorPalette}`); + } + } } } From 26ccdaa23306ffdd10661e2b3cc7bd5b85be4bc4 Mon Sep 17 00:00:00 2001 From: Austin Date: Tue, 24 Oct 2017 12:27:08 -0500 Subject: [PATCH 11/15] chore(nit): badge updates --- src/demo-app/badge/badge-demo.html | 3 ++- src/demo-app/badge/badge-demo.ts | 16 +--------------- src/lib/badge/badge.ts | 3 ++- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/demo-app/badge/badge-demo.html b/src/demo-app/badge/badge-demo.html index 653f765cbb14..ba6735eeff31 100644 --- a/src/demo-app/badge/badge-demo.html +++ b/src/demo-app/badge/badge-demo.html @@ -2,7 +2,7 @@

Text

- + Hello @@ -31,6 +31,7 @@

Text

+
diff --git a/src/demo-app/badge/badge-demo.ts b/src/demo-app/badge/badge-demo.ts index fdb87a857bf6..7c8ac689951f 100644 --- a/src/demo-app/badge/badge-demo.ts +++ b/src/demo-app/badge/badge-demo.ts @@ -1,6 +1,4 @@ import {Component} from '@angular/core'; -import {DomSanitizer} from '@angular/platform-browser'; -import {MatIconRegistry} from '@angular/material'; @Component({ moduleId: module.id, @@ -9,17 +7,5 @@ import {MatIconRegistry} from '@angular/material'; styleUrls: ['badge-demo.css'], }) export class BadgeDemo { - - badgeContent = '1'; - svgIcon = 'cat'; - - constructor(_iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) { - _iconRegistry.addSvgIcon('home', - sanitizer.bypassSecurityTrustResourceUrl( - 'https://upload.wikimedia.org/wikipedia/commons/3/34/Home-icon.svg')); - - _iconRegistry.addSvgIcon('cat', - sanitizer.bypassSecurityTrustResourceUrl( - 'https://upload.wikimedia.org/wikipedia/commons/3/34/Home-icon.svg')); - } + visibile = true; } diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index c9f4623038af..72135e2a71a0 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -32,7 +32,7 @@ let nextId = 0; }) export class MatBadge { - /** Theme for the badge */ + /** The color of the badge. Can be `primary`, `accent`, or `warn`. */ @Input('matBadgeColor') get color(): ThemePalette { return this._color; } set color(value: ThemePalette) { @@ -82,6 +82,7 @@ export class MatBadge { /** Size of the badge. 'small' | 'medium' | 'large' */ @Input() matBadgeSize: string = 'medium'; + /** Toggle the visibility of the badge on the host element. */ @Input() set matBadgeHidden(val: boolean) { this._hidden = coerceBooleanProperty(val); From ecb0afa52fde2c983a776ea6cc57202efdd03f96 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 18 Jan 2018 18:37:58 -0600 Subject: [PATCH 12/15] chore: nit --- src/demo-app/badge/badge-demo.html | 2 +- src/lib/badge/_badge-theme.scss | 3 +- src/lib/badge/badge.md | 2 +- src/lib/badge/badge.spec.ts | 3 +- src/lib/badge/badge.ts | 90 ++++++++++++++++++------------ 5 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/demo-app/badge/badge-demo.html b/src/demo-app/badge/badge-demo.html index ba6735eeff31..1b2a773a57c9 100644 --- a/src/demo-app/badge/badge-demo.html +++ b/src/demo-app/badge/badge-demo.html @@ -41,7 +41,7 @@

Buttons

+
diff --git a/src/demo-app/badge/badge-demo.ts b/src/demo-app/badge/badge-demo.ts index 017fbbefafe0..120cfca9bd82 100644 --- a/src/demo-app/badge/badge-demo.ts +++ b/src/demo-app/badge/badge-demo.ts @@ -15,6 +15,6 @@ import {Component} from '@angular/core'; styleUrls: ['badge-demo.css'], }) export class BadgeDemo { - visibile = true; + visible = true; badgeContent = '0'; } diff --git a/src/demo-app/demo-app/demo-module.ts b/src/demo-app/demo-app/demo-module.ts index f33b0526f1e1..8203950520c0 100644 --- a/src/demo-app/demo-app/demo-module.ts +++ b/src/demo-app/demo-app/demo-module.ts @@ -58,7 +58,7 @@ import {TypographyDemo} from '../typography/typography-demo'; import {DemoApp, Home} from './demo-app'; import {DEMO_APP_ROUTES} from './routes'; import {TableDemoModule} from '../table/table-demo-module'; -import { BadgeDemo } from 'badge/badge-demo'; +import {BadgeDemo} from 'badge/badge-demo'; @NgModule({ imports: [ diff --git a/src/demo-app/demo-app/routes.ts b/src/demo-app/demo-app/routes.ts index 8ec96e611fee..4e0e70848d16 100644 --- a/src/demo-app/demo-app/routes.ts +++ b/src/demo-app/demo-app/routes.ts @@ -50,7 +50,7 @@ import {TypographyDemo} from '../typography/typography-demo'; import {DemoApp, Home} from './demo-app'; import {TableDemoPage} from '../table/table-demo-page'; import {TABLE_DEMO_ROUTES} from '../table/routes'; -import { BadgeDemo } from 'badge/badge-demo'; +import {BadgeDemo} from 'badge/badge-demo'; export const DEMO_APP_ROUTES: Routes = [ {path: '', component: DemoApp, children: [ diff --git a/src/lib/badge/badge-module.ts b/src/lib/badge/badge-module.ts index 45672b7e6a02..d324e2d8441a 100644 --- a/src/lib/badge/badge-module.ts +++ b/src/lib/badge/badge-module.ts @@ -8,11 +8,15 @@ import {NgModule} from '@angular/core'; import {MatCommonModule} from '@angular/material/core'; +import {A11yModule} from '@angular/cdk/a11y'; import {MatBadge} from './badge'; @NgModule({ - imports: [MatCommonModule], + imports: [ + MatCommonModule, + A11yModule, + ], exports: [ MatBadge, ], diff --git a/src/lib/badge/badge.spec.ts b/src/lib/badge/badge.spec.ts index 986e0a9fd9b6..8ad7572804af 100644 --- a/src/lib/badge/badge.spec.ts +++ b/src/lib/badge/badge.spec.ts @@ -72,6 +72,38 @@ describe('MatBadge', () => { expect(badgeNativeElement.classList.contains('mat-badge-below')).toBe(true); expect(badgeNativeElement.classList.contains('mat-badge-before')).toBe(true); }); + + it('should change visibility to hidden', () => { + expect(badgeNativeElement.classList.contains('mat-badge-hidden')).toBe(false); + + testComponent.badgeHidden = true; + fixture.detectChanges(); + + expect(badgeNativeElement.classList.contains('mat-badge-hidden')).toBe(true); + }); + + it('should change badge sizes', () => { + expect(badgeNativeElement.classList.contains('mat-badge-medium')).toBe(true); + + testComponent.badgeSize = 'small'; + fixture.detectChanges(); + + expect(badgeNativeElement.classList.contains('mat-badge-small')).toBe(true); + + testComponent.badgeSize = 'large'; + fixture.detectChanges(); + + expect(badgeNativeElement.classList.contains('mat-badge-large')).toBe(true); + }); + + it('should change badge overlap', () => { + expect(badgeNativeElement.classList.contains('mat-badge-overlap')).toBe(false); + + testComponent.badgeOverlap = true; + fixture.detectChanges(); + + expect(badgeNativeElement.classList.contains('mat-badge-overlap')).toBe(true); + }); }); }); @@ -82,7 +114,10 @@ describe('MatBadge', () => { template: ` + [matBadgePosition]="badgeDirection" + [matBadgeHidden]="badgeHidden" + [matBadgeSize]="badgeSize" + [matBadgeOverlap]="badgeOverlap"> home ` @@ -91,4 +126,7 @@ class BadgeWithTextContent { badgeColor: ThemePalette; badgeContent = '1'; badgeDirection = 'above after'; + badgeHidden = false; + badgeSize = 'medium'; + badgeOverlap = false; } diff --git a/src/lib/badge/badge.ts b/src/lib/badge/badge.ts index 74b0ad6beb47..9b44702e8bc8 100644 --- a/src/lib/badge/badge.ts +++ b/src/lib/badge/badge.ts @@ -14,7 +14,7 @@ import {DOCUMENT} from '@angular/common'; let nextId = 0; -export type MatBadgePosition = 'above after' | 'below after' | 'above before' | 'above after'; +export type MatBadgePosition = 'above after' | 'above before' | 'below before' | 'below after'; export type MatBadgeSize = 'small' | 'medium' | 'large'; /** Directive to display a text badge. */ @@ -39,8 +39,8 @@ export class MatBadge { @Input('matBadgeColor') get color(): ThemePalette { return this._color; } set color(value: ThemePalette) { - this._color = value; this._setColor(value); + this._color = value; } private _color: ThemePalette = 'primary'; @@ -122,7 +122,7 @@ export class MatBadge { /** Creates the badge element */ private _createBadgeElement(): HTMLElement { - let badgeElement = this._document.createElement('span'); + const badgeElement = this._document.createElement('span'); badgeElement.setAttribute('id', `mat-badge-content-${this._id}`); badgeElement.classList.add('mat-badge-content'); badgeElement.textContent = this.content; @@ -153,14 +153,13 @@ export class MatBadge { } /** Adds css theme class given the color to the component host */ - private _setColor(value: ThemePalette) { - const colorPalette = value; + private _setColor(colorPalette: ThemePalette) { if (colorPalette !== this._color) { if (this._color) { - this._elementRef.nativeElement.removeClass(`mat-badge-${this._color}`); + this._elementRef.nativeElement.classList.remove(`mat-badge-${this._color}`); } if (colorPalette) { - this._elementRef.nativeElement.addClass(`mat-badge-${colorPalette}`); + this._elementRef.nativeElement.classList.add(`mat-badge-${colorPalette}`); } } } From 7b40790df27412fa195e9f7c59ec9d34d6cdedfd Mon Sep 17 00:00:00 2001 From: Austin Date: Fri, 26 Jan 2018 07:26:02 -0600 Subject: [PATCH 15/15] chore: fix lint --- .github/CODEOWNERS | 2 ++ src/demo-app/badge/badge-demo.scss | 2 +- src/lib/badge/_badge-theme.scss | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3e027a0978d6..799cd646a024 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -31,6 +31,7 @@ /src/lib/tabs/** @andrewseguin /src/lib/toolbar/** @devversion /src/lib/tooltip/** @andrewseguin +/src/lib/badge/** @amcdnl # Angular Material core /src/lib/core/* @jelbourn @@ -127,6 +128,7 @@ /src/demo-app/toolbar/** @devversion /src/demo-app/tooltip/** @andrewseguin /src/demo-app/typography/** @crisbeto +/src/demo-app/badge/** @amcdnl # E2E app /e2e/* @jelbourn diff --git a/src/demo-app/badge/badge-demo.scss b/src/demo-app/badge/badge-demo.scss index f94a80a1fb67..bea7811f28cd 100644 --- a/src/demo-app/badge/badge-demo.scss +++ b/src/demo-app/badge/badge-demo.scss @@ -1,3 +1,3 @@ -.badge-examples { +.badge-examples { margin-bottom: 25px; } diff --git a/src/lib/badge/_badge-theme.scss b/src/lib/badge/_badge-theme.scss index c7942ff83f4d..9ea47f1cc12c 100644 --- a/src/lib/badge/_badge-theme.scss +++ b/src/lib/badge/_badge-theme.scss @@ -142,8 +142,8 @@ $mat-badge-large-size: $mat-badge-default-size + 6; text-align: center; display: inline-block; border-radius: 50%; - transition: all .2s ease-in-out; - transform: scale(.6); + transition: all 0.2 ease-in-out; + transform: scale(0.6); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -164,4 +164,4 @@ $mat-badge-large-size: $mat-badge-default-size + 6; } .mat-badge-large { @include _mat-badge-size($mat-badge-large-size); -} \ No newline at end of file +}