Skip to content
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

Merged
merged 20 commits into from
Feb 13, 2018
Merged

feat(badge): add badge component #7483

merged 20 commits into from
Feb 13, 2018

Conversation

amcdnl
Copy link
Contributor

@amcdnl amcdnl commented Oct 2, 2017

@amcdnl amcdnl self-assigned this Oct 2, 2017
@amcdnl amcdnl requested a review from jelbourn October 2, 2017 21:10
@amcdnl amcdnl requested a review from devversion as a code owner October 2, 2017 21:10
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Oct 2, 2017
@amcdnl amcdnl removed the request for review from devversion October 2, 2017 21:10
@ocarreterom
Copy link
Contributor

Nice job, but I think the button implementation breaks the flexibility of the badge component.
I think it's more flexible something like this:

<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-list, mat-tab, etc.

<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?

@amcdnl
Copy link
Contributor Author

amcdnl commented Oct 3, 2017

@jelbourn - Do you think there is a case for just having a bubble with no text in it?


get _isAbove(): boolean { return this.direction.indexOf('above') > -1; }

get _isBefore(): boolean { return this.direction.indexOf('before') > -1; }
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea :)

@Input() content: string = '';

/** Direction the badge should reside; 'before|after above|below' */
@Input() direction: string = 'above after';
Copy link
Contributor

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.

Copy link
Contributor Author

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.

align-items: center;
position: absolute;
font-weight: 600;
font-size: 12px;
Copy link
Contributor

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?

}
private _overlap: boolean = true;

/** Content of the badget */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

@amcdnl amcdnl requested a review from crisbeto as a code owner October 3, 2017 21:30
@amcdnl amcdnl removed the request for review from crisbeto October 3, 2017 21:31
_isAbove: boolean = true;
_isBelow: boolean = false;
_isBefore: boolean = false;
_isAfter: boolean = true;
Copy link
Contributor

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.

<ng-content></ng-content>
<span
class="mat-badge-content"
[innerHTML]="content">
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in latest.

```

### Accessibility
Badges should be given a meaningful label via `aria-label` or `aria-labelledby` attributes.
Copy link
Member

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?

Copy link
Contributor Author

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.

position: relative;
display: inline-block;

&.mat-badge-above {
Copy link
Member

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?

Copy link
Contributor Author

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.

display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
Copy link
Member

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.

Copy link
Contributor Author

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.

import {MatBadge, MatBadgeModule} from './index';


fdescribe('MatBadge', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the fdescribe.

'[class.mat-badge-above]': '_isAbove',
'[class.mat-badge-below]': '!_isAbove',
'[class.mat-badge-before]': '!_isAfter',
'[class.mat-badge-after]': '_isAfter',
Copy link
Member

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.

Copy link
Contributor Author

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.

export const _MatBadgeMixinBase = mixinColor(MatBadgeBase);

@Component({
selector: 'mat-badge',
Copy link
Member

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.

Copy link
Contributor Author

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.

private _overlap: boolean = true;

/** Content of the badge */
@Input() content: string = '';
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in latest.

@amcdnl
Copy link
Contributor Author

amcdnl commented Oct 5, 2017

Updated w/ new API. Can you review again @crisbeto @willshowell

@amcdnl amcdnl requested a review from crisbeto October 5, 2017 18:01

/** Injects a span element into the DOM with the content. */
private _setContent(): void {
let pane = document.createElement('span');
Copy link
Contributor

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()

_id: number = nextId++;
_isAbove: boolean = true;
_isBelow: boolean = false;
_isBefore: boolean = false;
Copy link
Contributor

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


/** Theme for the badge */
get matBadgeColor(): ThemePalette { return this._color; }
set matBadgeColor(value: ThemePalette) {
Copy link
Contributor

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.

https://github.com/angular/material2/blob/4383f51a58de1fccad2ed64443a5e22ab435c02b/src/lib/form-field/form-field.ts#L97-L100

Copy link
Contributor Author

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.

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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Period

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delcared -> declared

*/

export * from './badge-module';
export * from './badge';
Copy link
Contributor

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

<mat-icon color="primary" matBadge="22" color="accent">home</mat-icon>
</button>

<br />
Copy link
Member

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?

constructor(_iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) {
_iconRegistry.addSvgIcon('home',
sanitizer.bypassSecurityTrustResourceUrl(
'https://upload.wikimedia.org/wikipedia/commons/3/34/Home-icon.svg'));
Copy link
Member

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.

Copy link
Contributor Author

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.

position: relative;
}

.mat-badge:after,
Copy link
Member

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.

.mat-badge-icon,
.mat-badge-text,
.mat-badge-svg-icon {
position: absolute;
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

.mat-badge-text,
.mat-badge .mat-badge-icon {
z-index: 2;
font-size: 16px;
Copy link
Member

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.

Copy link
Contributor Author

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.

.mat-badge-svg-icon {
z-index: 2;
width: 12px;
height: 12px;
Copy link
Member

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?


/** Clears the previous badge content */
_clearContents(): void {
const contents = document.getElementById(`badge-content-${this._id}`);
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

@amcdnl amcdnl Oct 11, 2017

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.

Copy link
Member

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.


/** Injects a span element into the DOM with the content. */
private _setContent(): void {
const content = document.createElement('span');
Copy link
Member

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?

Copy link
Contributor Author

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.

/** Gets the font icon set to use. */
private _getFontSet(): string {
return this.matBadgeFontSet ?
this._iconRegistry.classNameForFontAlias(this.matBadgeFontSet) :
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Member

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).

MatBadge,
MatIconBadge,
MatSvgIconBadge,
MatCommonModule,
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. thanks

* found in the LICENSE file at https://angular.io/license
*/

export * from './public_api';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public-api.

styleUrls: ['badge-demo.css'],
})
export class BadgeDemo {
visibile = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visibile -> visible.

@@ -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';
Copy link
Contributor

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.

@@ -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';
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stupid editor.


let nextId = 0;

export type MatBadgePosition = 'above after' | 'below after' | 'above before' | 'above after';
Copy link
Contributor

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')
Copy link
Contributor

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?

Copy link
Contributor Author

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.


/** Creates the badge element */
private _createBadgeElement(): HTMLElement {
let badgeElement = this._document.createElement('span');
Copy link
Contributor

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.

@amcdnl
Copy link
Contributor Author

amcdnl commented Jan 21, 2018

@jelbourn - I think we are good now! * crosses fingers *

@rafaelss95
Copy link
Contributor

@amcdnl It looks like CI isn't happy yet.

@jelbourn
Copy link
Member

@amcdnl needs update to CODEOWNERS for lint

@amcdnl
Copy link
Contributor Author

amcdnl commented Jan 26, 2018

@jelbourn - Done :)

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: minor This PR is targeted for the next minor release labels Jan 31, 2018
@jelbourn
Copy link
Member

Caretaker note: this will need new build rules

@mmalerba mmalerba added pr: needs rebase and removed action: merge The PR is ready for merge by the caretaker labels Feb 9, 2018
@mmalerba
Copy link
Contributor

mmalerba commented Feb 9, 2018

@amcdnl please rebase

@amcdnl
Copy link
Contributor Author

amcdnl commented Feb 10, 2018

@mmalerba - Done :)

@mmalerba mmalerba added the action: merge The PR is ready for merge by the caretaker label Feb 10, 2018
@jelbourn jelbourn merged commit c98d217 into angular:master Feb 13, 2018
@amcdnl amcdnl deleted the badge branch February 17, 2018 17:17
@jagomf
Copy link

jagomf commented Feb 26, 2018

Is badge not going to be included in 5.2.x but only 6.0?

@mmalerba
Copy link
Contributor

@jagomf Yes, it's actually tagged as release: minor, but since there probably won't be another minor release before 6.0.0, it will likely wind up in 6.0.0

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.