-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(badge): add badge component #7483
Conversation
Nice job, but I think the button implementation breaks the flexibility of the badge component. <button mat-button>
Notifications
<mat-badge>10</mat-badge>
</button>
-- OR --
<button mat-button>
Notifications
<mat-badge content="10"></mat-badge>
</button>
-- OR --
<button mat-button>
<mat-badge content="10">Notifications</mat-badge>
</button> Moreover, in the future could be implemented in <mat-tab label="Notifications">
<mat-badge>10</mat-badge>
</mat-tab>
<mat-list-item>
Notifications
<mat-badge>10</mat-badge>
</mat-list-item> What do you think? |
@jelbourn - Do you think there is a case for just having a bubble with no text in it? |
src/lib/badge/badge.ts
Outdated
|
||
get _isAbove(): boolean { return this.direction.indexOf('above') > -1; } | ||
|
||
get _isBefore(): boolean { return this.direction.indexOf('before') > -1; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These getters will run every cd cycle, right? What about using a setter in direction
instead. Also OnPush for this component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea :)
src/lib/badge/badge.ts
Outdated
@Input() content: string = ''; | ||
|
||
/** Direction the badge should reside; 'before|after above|below' */ | ||
@Input() direction: string = 'above after'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default value here is inconsistent with how the _isBefore
getter works. If I don't pass a direction, it will end up as above after
. If I use "banana banana"
, it will be below after
.
As a nit, the overview guide implies there is an order to which the directions should be in, but that order is reversed in the jsdoc comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the getters more specific to prevent the monkeys ;P
Thanks for the heads up. Fixed that too.
src/lib/badge/badge.scss
Outdated
align-items: center; | ||
position: absolute; | ||
font-weight: 600; | ||
font-size: 12px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be set via a typography mixin?
src/lib/badge/badge.ts
Outdated
} | ||
private _overlap: boolean = true; | ||
|
||
/** Content of the badget */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo
src/lib/badge/badge.ts
Outdated
_isAbove: boolean = true; | ||
_isBelow: boolean = false; | ||
_isBefore: boolean = false; | ||
_isAfter: boolean = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if all of these are necessary. In my previous comment, I was thinking more like this
@Input()
set direction(val: string) {
this._direction = val;
this._isAbove = val.indexOf('below') === -1;
this._isAfter = val.indexOf('before') === -1;
}
get direction(): string {
return this._direction;
}
private _direction: string = 'above after';
_isAbove: boolean = true;
_isAfter: boolean = true;
That way it always defaults to above
unless below
is explicitly stated. Likewise for after
.
src/lib/badge/badge.html
Outdated
<ng-content></ng-content> | ||
<span | ||
class="mat-badge-content" | ||
[innerHTML]="content"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks dangerous. Is there any reason people would want to put anything besides plain text in a badge?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed in latest.
src/lib/badge/badge.md
Outdated
``` | ||
|
||
### Accessibility | ||
Badges should be given a meaningful label via `aria-label` or `aria-labelledby` attributes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure whether this is correct. Shouldn't the badge target have an aria-describedby
instead @jelbourn?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to reconsider how we want to do this with latest api changes.
src/lib/badge/badge.scss
Outdated
position: relative; | ||
display: inline-block; | ||
|
||
&.mat-badge-above { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This nesting seems like it's bumping the specificity unnecessarily. Consider moving it out instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this quite a bit, you might want to re-review.
src/lib/badge/badge.scss
Outdated
display: flex; | ||
flex-direction: row; | ||
flex-wrap: wrap; | ||
justify-content: center; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this really need flexbox? It can be done simpler with text-align
and line-height
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It now supports font icons and svgs, so its needed now.
src/lib/badge/badge.spec.ts
Outdated
import {MatBadge, MatBadgeModule} from './index'; | ||
|
||
|
||
fdescribe('MatBadge', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the fdescribe
.
src/lib/badge/badge.ts
Outdated
'[class.mat-badge-above]': '_isAbove', | ||
'[class.mat-badge-below]': '!_isAbove', | ||
'[class.mat-badge-before]': '!_isAfter', | ||
'[class.mat-badge-after]': '_isAfter', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that you can cut down on the amount of classes by making it appear above before
by default. The other classes can override it afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started with that but it got odd setting everything backwards. For instance before
would have to set right: auto
and left: 22px
. It felt odd to add that. Happy to change if you guys prefer.
src/lib/badge/badge.ts
Outdated
export const _MatBadgeMixinBase = mixinColor(MatBadgeBase); | ||
|
||
@Component({ | ||
selector: 'mat-badge', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a moduleId: module.id
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its a directive now so doesn't apply.
src/lib/badge/badge.ts
Outdated
private _overlap: boolean = true; | ||
|
||
/** Content of the badge */ | ||
@Input() content: string = ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than using an input to add the content, why not use content projection? It should allow for move flexibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed in latest.
Updated w/ new API. Can you review again @crisbeto @willshowell |
src/lib/badge/badge.ts
Outdated
|
||
/** Injects a span element into the DOM with the content. */ | ||
private _setContent(): void { | ||
let pane = document.createElement('span'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
? here and in _setFontIcon()
src/lib/badge/badge.ts
Outdated
_id: number = nextId++; | ||
_isAbove: boolean = true; | ||
_isBelow: boolean = false; | ||
_isBefore: boolean = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_isBelow
and _isBefore
can be removed I think
src/lib/badge/badge.ts
Outdated
|
||
/** Theme for the badge */ | ||
get matBadgeColor(): ThemePalette { return this._color; } | ||
set matBadgeColor(value: ThemePalette) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if it's a big deal or not, but undefined
appears to be invalid for this directive, but is valid in ThemePalette
. In form-field
, where undefined
is also invalid, a custom type is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, let me know which way you wanna go.
src/lib/badge/badge.md
Outdated
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` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Period
src/lib/badge/badge.md
Outdated
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delcared
-> declared
src/lib/badge/public_api.ts
Outdated
*/ | ||
|
||
export * from './badge-module'; | ||
export * from './badge'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename this file to public-api.ts
per #7403
src/demo-app/badge/badge-demo.html
Outdated
<mat-icon color="primary" matBadge="22" color="accent">home</mat-icon> | ||
</button> | ||
|
||
<br /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a div with a margin instead of br
-s?
src/demo-app/badge/badge-demo.ts
Outdated
constructor(_iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) { | ||
_iconRegistry.addSvgIcon('home', | ||
sanitizer.bypassSecurityTrustResourceUrl( | ||
'https://upload.wikimedia.org/wikipedia/commons/3/34/Home-icon.svg')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use the home icon from the icon demo instead of fetching one remotely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that, however, it was not working. That demo page is broken too if you open it up.
src/lib/badge/_badge-theme.scss
Outdated
position: relative; | ||
} | ||
|
||
.mat-badge:after, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These :after
instances should be ::after
. I think we had a linting rule for it at some point.
src/lib/badge/_badge-theme.scss
Outdated
.mat-badge-icon, | ||
.mat-badge-text, | ||
.mat-badge-svg-icon { | ||
position: absolute; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these positioning and alignment styles should be in the badge.scss
, not in the theme. The theme is only for color-related properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, however, since this is a directive there is no way to include styles w/ the component. Do you have a good solution for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make it a component that has a template of <ng-content>
. Having these styles in the theme will cause them to be loaded multiple times for pages with multiple themes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya, thats what I had. You can't do multiple components on the same element though, example: icon w/ a badge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't, but you can have an icon inside a badge.
src/lib/badge/_badge-theme.scss
Outdated
.mat-badge-text, | ||
.mat-badge .mat-badge-icon { | ||
z-index: 2; | ||
font-size: 16px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be in the typography mixin and the value, ideally, should come from the typography breakpoints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I've moved it inside that mixin.
This size is independent from the font-size of the page since its specifically designed for this fixed width/height of the badge.
src/lib/badge/_badge-theme.scss
Outdated
.mat-badge-svg-icon { | ||
z-index: 2; | ||
width: 12px; | ||
height: 12px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this into a variable?
src/lib/badge/badge.ts
Outdated
|
||
/** Clears the previous badge content */ | ||
_clearContents(): void { | ||
const contents = document.getElementById(`badge-content-${this._id}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't you take a hold of the badge content using a ViewChild
or ContentChild
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that only worked for angular inserted dom. This is manually inserted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This plays into my other comment (#7483 (comment)) that it probably shouldn't be constructing DOM in the first place. IMO it should be fine to have it as a component instead of a directive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya, thats what I had but @jelbourn wanted this to be a directive that can decorate components like icons. I totally agree w/ it not constructing dom though, open to suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could create a separate component and dynamically compile and attach it (for span
or svg
, then store it in an array for later removal.
src/lib/badge/badge.ts
Outdated
|
||
/** Injects a span element into the DOM with the content. */ | ||
private _setContent(): void { | ||
const content = document.createElement('span'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this has to create and insert elements manually. Could you wrap the element via content projection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I know, directives don't have content projection. You can't have multiple components on the same element either so that would't work. The SVG icons do a similar insertion and removal so this isn't the first place we are doing this type of thing.
I had this as a component at first but that API wasn't what @jelbourn had designed.
src/lib/badge/badge.ts
Outdated
/** Gets the font icon set to use. */ | ||
private _getFontSet(): string { | ||
return this.matBadgeFontSet ? | ||
this._iconRegistry.classNameForFontAlias(this.matBadgeFontSet) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that the consumer will use Material icons, which may not be the case. Why not let them provide the icon element themselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was built to implement Material Icons. I think custom icons might be out of scope for this. @jelbourn - thoughts?
Although I'm curious how you would implement providing the icon element themselves, could you elaborate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you convert it into a component and allow content projection, users will be able to override it as they want. Another approach can be to provide an ng-template
, but that might be overkill for this case (see #7482).
src/lib/badge/badge-module.ts
Outdated
MatBadge, | ||
MatIconBadge, | ||
MatSvgIconBadge, | ||
MatCommonModule, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason to export MatCommonModule
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. thanks
src/lib/badge/index.ts
Outdated
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
export * from './public_api'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public-api
.
src/demo-app/badge/badge-demo.ts
Outdated
styleUrls: ['badge-demo.css'], | ||
}) | ||
export class BadgeDemo { | ||
visibile = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
visibile -> visible.
src/demo-app/demo-app/demo-module.ts
Outdated
@@ -58,6 +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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spaces between braces should be omitted.
src/demo-app/demo-app/routes.ts
Outdated
@@ -50,11 +50,13 @@ 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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spaces between braces should be omitted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stupid editor.
src/lib/badge/badge.ts
Outdated
|
||
let nextId = 0; | ||
|
||
export type MatBadgePosition = 'above after' | 'below after' | 'above before' | 'above after'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above after
is duplicated. I think you want to say below before
instead?
export class MatBadge { | ||
|
||
/** The color of the badge. Can be `primary`, `accent`, or `warn`. */ | ||
@Input('matBadgeColor') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: The reason that you can't just use the mixinColor
here is because the input has an alias?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It needs to have a separate alias for color since the host can also describe a color.
src/lib/badge/badge.ts
Outdated
|
||
/** Creates the badge element */ | ||
private _createBadgeElement(): HTMLElement { | ||
let badgeElement = this._document.createElement('span'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use const
here.
@jelbourn - I think we are good now! * crosses fingers * |
@amcdnl It looks like CI isn't happy yet. |
@amcdnl needs update to CODEOWNERS for lint |
@jelbourn - Done :) |
Caretaker note: this will need new build rules |
@amcdnl please rebase |
@mmalerba - Done :) |
Is badge not going to be included in 5.2.x but only 6.0? |
@jagomf Yes, it's actually tagged as |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Implements a badge component.
Design Document
https://docs.google.com/document/d/12sksu2GHcITFBidL8nyRzIt8lnVUoGnzwQx4tXWesEw/edit?usp=sharing