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(): add progress-fab component #290

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/progress-circle/progress-circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class MdProgressCircle {
// total circumference.

// The total circumference is calculated based on the radius we use, 45.
// PI * 2 * 45
// PI * 2 * 40
return 251.3274 * (100 - this._value) / 100;
}

Expand Down
25 changes: 25 additions & 0 deletions src/components/progress-fab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# MdProgressFab
`MdProgressFab` is a normal FAB button surrounded with a `progress-circle`.

### Screenshots
![Preview](https://cloud.githubusercontent.com/assets/4987015/14406410/eaf20a54-fea6-11e5-8a24-4f751df7e80a.png)

## `[md-progress-fab]`
### Bound Properties

| Name | Type | Description |
| --- | --- | --- |
| `color` | `"primary" | "accent" | "warn"` | The color palette for the FAB button |
| `value` | `number` | Value for the `progress-circle`.<br/> Necessary when using the `determinate` mode. |
| `mode` | `"determinate" | "indeterminate"` | Mode for the `progress-circle`.<br/> |
| `progressColor` | `"primary" | "accent" | "warn"` | Color for the `progress-circle`.<br/> |


### Examples
A basic progress-fab will have the markup:
```html
<button md-progress-fab color="accent">
<i class="material-icons md-24">favorite</i>
</button>
```
It will use by default a `indeterminate` progress circle.
13 changes: 13 additions & 0 deletions src/components/progress-fab/progress-fab.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<span class="md-button-wrapper">
<ng-content></ng-content>

<div class="md-progress-wrap">
<md-progress-circle

Choose a reason for hiding this comment

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

To be announced, this progress circle needs to be a sibling to the focusable button, and it needs role="progressbar" along with other related ARIA attributes: https://www.w3.org/TR/wai-aria/roles#progressbar

Specifically the progressbar needs to specify aria-valuenow, aria-valuemin and aria-valuemax for screen readers to follow along.

Lastly, what is the progress circle indicating (i.e. what is loading)? That binding should be indicated somehow...the ARIA roles spec recommends using aria-describedby on the progressbar and aria-busy on whatever is loading.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @marcysutton, I'll try making the process circle a sibling.

  • The role is automatically applied from the Progress Circle Component
  • Valuenow, ValueMin, ValueNow are also covered by the circle component.

Forwarding the aria-label / or creating a default one, makes definitely sense. thx

[mode]="progressMode"
[value]="progressValue"
[attr.color]="progressColor">
</md-progress-circle>
</div>
</span>


34 changes: 34 additions & 0 deletions src/components/progress-fab/progress-fab.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@import "../button/button-base";

$md-fab-progress-stroke: 6px;

// Subtract 1px to avoid the ugly space between FAB button and progress circle.
$md-fab-progress-rectangle: ($md-fab-size + $md-fab-padding + $md-fab-progress-stroke / 2) - 1px;

:host {
@include md-fab($md-fab-size, $md-fab-padding);

margin: $md-fab-progress-stroke;

.md-progress-wrap {
position: absolute;
top: 50%;
left: 50%;

transform: translate3d(-50%, -50%, 0);

md-progress-circle {
width: $md-fab-progress-rectangle;
height: $md-fab-progress-rectangle;

svg {
overflow: visible;

circle {
stroke-width: $md-fab-progress-stroke;
}
}
}
}

}
123 changes: 123 additions & 0 deletions src/components/progress-fab/progress-fab.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import {
it,
describe,
expect,
beforeEach,
inject,
} from '@angular/core/testing';
import {TestComponentBuilder} from '@angular/compiler/testing';
import {Component} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MdProgressFab} from './progress-fab';
import {MdProgressCircle} from '../progress-circle/progress-circle';

export function main() {
describe('MdProgressFab', () => {
let builder: TestComponentBuilder;

beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
builder = tcb;
}));

it('should correctly apply the color attribute on the progress circle', (done: () => void) => {
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 new async function instead of the done callback now

return builder
.createAsync(TestApp)
.then((fixture) => {
Copy link
Member

@jelbourn jelbourn May 12, 2016

Choose a reason for hiding this comment

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

nit: fixture doesn't need parenthesis around it.

let testComponent = fixture.debugElement.componentInstance;
let progressDebugElement = fixture.debugElement.query(By.css('md-progress-circle'));

testComponent.progressColor = 'primary';
fixture.detectChanges();

expect(progressDebugElement.nativeElement.getAttribute('color')).toBe('primary');

testComponent.progressColor = 'accent';
fixture.detectChanges();

expect(progressDebugElement.nativeElement.getAttribute('color')).toBe('accent');

done();
});
});

it('should correctly apply the mode on the progress circle', (done: () => void) => {
return builder
.createAsync(TestApp)
.then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let progressComponent: MdProgressCircle = fixture.debugElement
.query(By.css('md-progress-circle')).componentInstance;

testComponent.progressMode = 'determinate';
fixture.detectChanges();

expect(progressComponent.mode).toBe('determinate');

testComponent.progressColor = 'indeterminate';
fixture.detectChanges();

expect(progressComponent.mode).toBe('determinate');

done();
});
});

it('should correctly apply the value on the progress circle', (done: () => void) => {
return builder
.createAsync(TestApp)
.then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let progressComponent: MdProgressCircle = fixture.debugElement
.query(By.css('md-progress-circle')).componentInstance;

testComponent.progressValue = 50;
fixture.detectChanges();

expect(progressComponent._value).toBe(50);

testComponent.progressValue = 70;
fixture.detectChanges();

expect(progressComponent._value).toBe(70);

done();
});
});

it('should correctly apply the color on the button', (done: () => void) => {
return builder
.createAsync(TestApp)
.then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));

testComponent.buttonColor = 'primary';
fixture.detectChanges();

expect(buttonDebugElement.nativeElement.classList.contains('md-primary')).toBe(true);

testComponent.buttonColor = 'accent';
fixture.detectChanges();
expect(buttonDebugElement.nativeElement.classList.contains('md-accent')).toBe(true);

done();
});
});

});
}

@Component({
selector: 'test-app',
template: `
<button md-progress-fab [color]="buttonColor" [progressColor]="progressColor"
[mode]="progressMode" [value]="progressValue">
</button>`,
directives: [MdProgressFab]
})
class TestApp {
buttonColor: string = 'primary';
progressColor: string = 'accent';
progressMode: string = 'indeterminate';
progressValue: number = 0;
}
35 changes: 35 additions & 0 deletions src/components/progress-fab/progress-fab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
Component,
ChangeDetectionStrategy,
Renderer,
ElementRef,
Input
} from '@angular/core';
import {MdProgressCircle} from '../progress-circle/progress-circle';
import {MdButton} from '../button/button';

@Component({
selector: '[md-progress-fab]:not(a)',
templateUrl: './components/progress-fab/progress-fab.html',
styleUrls: ['./components/progress-fab/progress-fab.css'],
directives: [MdProgressCircle],
inputs: ['color'],
host: {
'[class.md-button-focus]': 'isKeyboardFocused',
'(mousedown)': 'setMousedown()',
'(focus)': 'setKeyboardFocus()',
'(blur)': 'removeKeyboardFocus()'
},
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MdProgressFab extends MdButton {

@Input('mode') progressMode: string = 'indeterminate';
@Input('value') progressValue: number;
@Input('progressColor') progressColor: string;

constructor(elementRef: ElementRef, renderer: Renderer) {
super(elementRef, renderer);
}

}
10 changes: 10 additions & 0 deletions src/demo-app/button/button-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,14 @@ import {MdIcon} from '../../components/icon/icon';
export class ButtonDemo {
isDisabled: boolean = false;
clickCounter: number = 0;
fabProgressValue: number = 0;

constructor() {

setInterval(() => this.increaseFabProgress(), 200);
}

increaseFabProgress() {
this.fabProgressValue += 7;
}
}
1 change: 1 addition & 0 deletions src/demo-app/demo-app.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<a md-list-item [routerLink]="['overlay']">Overlay</a>
<a md-list-item [routerLink]="['portal']">Portal</a>
<a md-list-item [routerLink]="['progress-circle']">Progress Circle</a>
<a md-list-item [routerLink]="['progress-fab']">Progress Fab</a>
<a md-list-item [routerLink]="['progress-bar']">Progress Bar</a>
<a md-list-item [routerLink]="['radio']">Radio</a>
<a md-list-item [routerLink]="['sidenav']">Sidenav</a>
Expand Down
2 changes: 2 additions & 0 deletions src/demo-app/demo-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {InputDemo} from './input/input-demo';
import {LiveAnnouncerDemo} from './live-announcer/live-announcer-demo';
import {GesturesDemo} from './gestures/gestures-demo';
import {GridListDemo} from './grid-list/grid-list-demo';
import {ProgressFabDemo} from './progress-fab/progress-fab-demo';

@Component({
selector: 'home',
Expand Down Expand Up @@ -56,6 +57,7 @@ export class Home {}
new Route({path: '/sidenav', component: SidenavDemo}),
new Route({path: '/progress-circle', component: ProgressCircleDemo}),
new Route({path: '/progress-bar', component: ProgressBarDemo}),
new Route({path: '/progress-fab', component: ProgressFabDemo}),
new Route({path: '/portal', component: PortalDemo}),
new Route({path: '/overlay', component: OverlayDemo}),
new Route({path: '/checkbox', component: CheckboxDemo}),
Expand Down
42 changes: 42 additions & 0 deletions src/demo-app/progress-fab/progress-fab-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<div class="demo-progress-fab">

<md-card class="demo-progress-card">
<md-toolbar color="primary">Basic</md-toolbar>
<md-card-content>
<div class="demo-content">
<button md-progress-fab color="accent">
<i class="material-icons md-24">favorite</i>
</button>

<button md-progress-fab
color="primary"
progressColor="warn"
mode="determinate"
[value]="fabProgressValue">

<i class="material-icons md-24">feedback</i>
</button>
</div>
</md-card-content>
</md-card>

<md-card class="demo-progress-card">
<md-toolbar color="primary">Determinate</md-toolbar>
<md-card-content>
<div class="demo-content">

<button md-progress-fab
class="demo-determinate-progress"
[color]="determinateColor"
progressColor="warn"
mode="determinate"
[value]="fabProgressValue"
[class.hide-progress]="determinateHidden">

<i class="material-icons md-24">feedback</i>
</button>
</div>
</md-card-content>
</md-card>

</div>
26 changes: 26 additions & 0 deletions src/demo-app/progress-fab/progress-fab-demo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.demo-progress-fab {

.demo-progress-card {
padding: 0;
margin: 16px;

.demo-content {
padding: 7px;

.demo-determinate-progress.hide-progress {
.md-progress-wrap {
md-progress-circle {
transition-duration: 0.7s;
transition-property: transform, opacity;
transition-timing-function: ease-out;

transform: scale(0.8);
opacity: 0;
}
}
}

}
}
Copy link
Member

@jelbourn jelbourn May 12, 2016

Choose a reason for hiding this comment

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

Could you flatten these style definitions (not nested)?

Choose a reason for hiding this comment

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

wouldn't that hurt, albeit just a bit, maintainability? In the same way single-line 'if' blocks not surrounded by {} are usually considered kinda rude to the next developer who has to add further statements and is forced to add the braces and reindent


}
33 changes: 33 additions & 0 deletions src/demo-app/progress-fab/progress-fab-demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {Component, ViewEncapsulation} from '@angular/core';
import {MdProgressFab} from '../../components/progress-fab/progress-fab';
import {MdToolbar} from '../../components/toolbar/toolbar';
import {MdCard} from '../../components/card/card';

@Component({
selector: 'progress-fab-demo',
templateUrl: 'demo-app/progress-fab/progress-fab-demo.html',
styleUrls: ['demo-app/progress-fab/progress-fab-demo.css'],
directives: [MdProgressFab, MdToolbar, MdCard],
encapsulation: ViewEncapsulation.None
})
export class ProgressFabDemo {

fabProgressValue: number = 0;

determinateHidden: boolean = false;
determinateColor: string = 'primary';

constructor() {

setInterval(() => this.increaseFabProgress(), 200);
}

increaseFabProgress() {
this.fabProgressValue += 7;

if (this.fabProgressValue >= 100) {
this.determinateColor = 'warn';
this.determinateHidden = true;
}
}
}