-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
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 | ||
[mode]="progressMode" | ||
[value]="progressValue" | ||
[attr.color]="progressColor"> | ||
</md-progress-circle> | ||
</div> | ||
</span> | ||
|
||
|
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; | ||
} | ||
} | ||
} | ||
} | ||
|
||
} |
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) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use the new |
||
return builder | ||
.createAsync(TestApp) | ||
.then((fixture) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
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; | ||
} |
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); | ||
} | ||
|
||
} |
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> |
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; | ||
} | ||
} | ||
} | ||
|
||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you flatten these style definitions (not nested)? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
} |
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; | ||
} | ||
} | ||
} |
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.
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#progressbarSpecifically the progressbar needs to specify
aria-valuenow
,aria-valuemin
andaria-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 andaria-busy
on whatever is loading.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.
Thanks @marcysutton, I'll try making the process circle a sibling.
Forwarding the
aria-label
/ or creating a default one, makes definitely sense. thx