diff --git a/src/components/progress-circle/progress-circle.scss b/src/components/progress-circle/progress-circle.scss
new file mode 100644
index 00000000000..c314d1fe530
--- /dev/null
+++ b/src/components/progress-circle/progress-circle.scss
@@ -0,0 +1,110 @@
+@import "variables";
+
+@import "default-theme";
+
+/* Animation Durations */
+$md-progress-circle-duration : 5.25s !default;
+$md-progress-circle-value-change-duration : $md-progress-circle-duration * 0.25 !default;
+$md-progress-circle-constant-rotate-duration : $md-progress-circle-duration * 0.55 !default;
+$md-progress-circle-sporadic-rotate-duration : $md-progress-circle-duration !default;
+
+/** Component sizing */
+$md-progress-circle-stroke-width: 10px !default;
+$md-progress-circle-radius: 40px !default;
+$md-progress-circle-circumference: $pi * $md-progress-circle-radius * 2 !default;
+$md-progress-circle-center-point: 50px !default;
+// Height and weight of the viewport for md-progress-circle.
+$md-progress-circle-viewport-size : 100px !default;
+
+
+:host {
+ display: block;
+ /** Height and width are provided for md-progress-circle to act as a default.
+ The height and width are expected to be overwritten by application css. */
+ height: $md-progress-circle-viewport-size;
+ width: $md-progress-circle-viewport-size;
+
+ /** SVG's viewBox is defined as 0 0 100 100, this means that all SVG children will placed
+ based on a 100px by 100px box.
+
+ The SVG and Circle dimensions/location:
+ SVG
+ Height: 100px
+ Width: 100px
+ Circle
+ Radius: 40px
+ Circumference: 251.3274px
+ Center x: 50px
+ Center y: 50px
+ */
+ svg {
+ height: 100%;
+ width: 100%;
+ }
+
+
+ circle {
+ cx: $md-progress-circle-center-point;
+ cy: $md-progress-circle-center-point;
+ fill: transparent;
+ r: $md-progress-circle-radius;
+ stroke: md-color($md-primary, 600);
+ /** Stroke width of 10px defines stroke as 10% of the viewBox */
+ stroke-width: $md-progress-circle-stroke-width;
+ /** SVG circle rotations begin rotated 90deg clockwise from the circle's center top */
+ transform: rotate(-90deg);
+ transform-origin: center;
+ transition: stroke-dashoffset 0.225s linear;
+ /** The dash array of the circle is defined as the circumference of the circle. */
+ stroke-dasharray: $md-progress-circle-circumference;
+ /** The stroke dashoffset is used to "fill" the circle, 0px represents an full circle,
+ while the circles full circumference represents an empty circle. */
+ stroke-dashoffset: 0px;
+ }
+
+
+ &.md-accent circle {
+ stroke: md-color($md-accent, 600);
+ }
+
+
+ &.md-warn circle {
+ stroke: md-color($md-warn, 600);
+ }
+
+
+ &[mode="indeterminate"] circle {
+ animation-duration: $md-progress-circle-sporadic-rotate-duration,
+ $md-progress-circle-constant-rotate-duration,
+ $md-progress-circle-value-change-duration;
+ animation-name: md-progress-circle-sporadic-rotate,
+ md-progress-circle-linear-rotate,
+ md-progress-circle-value-change;
+ animation-timing-function: $ease-in-out-curve-function,
+ linear,
+ $ease-in-out-curve-function;
+ animation-iteration-count: infinite;
+ transition: none;
+ }
+}
+
+
+/** Animations for indeterminate mode */
+@keyframes md-progress-circle-linear-rotate {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+@keyframes md-progress-circle-sporadic-rotate {
+ 12.5% { transform: rotate( 135deg); }
+ 25% { transform: rotate( 270deg); }
+ 37.5% { transform: rotate( 405deg); }
+ 50% { transform: rotate( 540deg); }
+ 62.5% { transform: rotate( 675deg); }
+ 75% { transform: rotate( 810deg); }
+ 87.5% { transform: rotate( 945deg); }
+ 100% { transform: rotate(1080deg); }
+}
+@keyframes md-progress-circle-value-change {
+ 0% { stroke-dashoffset: 261.3274px; }
+ 100% { stroke-dashoffset: -241.3274px; }
+}
diff --git a/src/components/progress-circle/progress_circle.html b/src/components/progress-circle/progress_circle.html
new file mode 100644
index 00000000000..2f9bc9b636d
--- /dev/null
+++ b/src/components/progress-circle/progress_circle.html
@@ -0,0 +1,9 @@
+
+
diff --git a/src/components/progress-circle/progress_circle.spec.ts b/src/components/progress-circle/progress_circle.spec.ts
new file mode 100644
index 00000000000..5198f0fc5b4
--- /dev/null
+++ b/src/components/progress-circle/progress_circle.spec.ts
@@ -0,0 +1,99 @@
+import {beforeEach, ddescribe, expect, inject, it, TestComponentBuilder} from 'angular2/testing';
+import {Component, DebugElement} from 'angular2/core';
+import {By} from 'angular2/platform/browser'
+import {MdProgressCircle} from './progress_circle';
+
+
+export function main() {
+ describe('MdProgressCircular', () => {
+ let builder:TestComponentBuilder;
+
+ beforeEach(inject([TestComponentBuilder], (tcb:TestComponentBuilder) => {
+ builder = tcb;
+ }));
+
+ it('should apply a mode of "determinate" if no mode is provided.', (done:() => void) => {
+ builder
+ .overrideTemplate(TestApp, '')
+ .createAsync(TestApp)
+ .then((fixture) => {
+ fixture.detectChanges();
+ let progressElement = getChildDebugElement(fixture.debugElement, 'md-progress-circle');
+ expect(progressElement.componentInstance.mode).toBe('determinate');
+ done();
+ });
+ });
+
+ it('should apply a mode of "determinate" if an invalid mode is provided.', (done:() => void) => {
+ builder
+ .overrideTemplate(TestApp, '')
+ .createAsync(TestApp)
+ .then((fixture) => {
+ fixture.detectChanges();
+ let progressElement = getChildDebugElement(fixture.debugElement, 'md-progress-circle');
+ expect(progressElement.componentInstance.mode).toBe('determinate');
+ done();
+ });
+ });
+
+ it('should not modify the mode if a valid mode is provided.', (done:() => void) => {
+ builder
+ .overrideTemplate(TestApp, '')
+ .createAsync(TestApp)
+ .then((fixture) => {
+ fixture.detectChanges();
+ let progressElement = getChildDebugElement(fixture.debugElement, 'md-progress-circle');
+ expect(progressElement.componentInstance.mode).toBe('indeterminate');
+ done();
+ });
+ });
+
+ it('should define a default value for the value attribute', (done:() => void) => {
+ builder
+ .overrideTemplate(TestApp, '')
+ .createAsync(TestApp)
+ .then((fixture) => {
+ fixture.detectChanges();
+ let progressElement = getChildDebugElement(fixture.debugElement, 'md-progress-circle');
+ expect(progressElement.componentInstance.value).toBe(0);
+ done();
+ });
+ });
+
+ it('should clamp the value of the progress between 0 and 100', (done:() => void) => {
+ builder
+ .overrideTemplate(TestApp, '')
+ .createAsync(TestApp)
+ .then((fixture) => {
+ fixture.detectChanges();
+ let progressElement = getChildDebugElement(fixture.debugElement, 'md-progress-circle');
+ let progressComponent = progressElement.componentInstance;
+
+ progressComponent.value = 50;
+ expect(progressComponent.value).toBe(50);
+
+ progressComponent.value = 999;
+ expect(progressComponent.value).toBe(100);
+
+ progressComponent.value = -10;
+ expect(progressComponent.value).toBe(0);
+ done();
+ });
+ });
+ });
+}
+
+
+/** Gets a child DebugElement by tag name. */
+function getChildDebugElement(parent: DebugElement, selector: string): DebugElement {
+ return parent.query(By.css(selector));
+}
+
+
+
+/** Test component that contains an MdButton. */
+@Component({
+ directives: [MdProgressCircle],
+ template: '',
+})
+class TestApp {}
\ No newline at end of file
diff --git a/src/components/progress-circle/progress_circle.ts b/src/components/progress-circle/progress_circle.ts
new file mode 100644
index 00000000000..ffe9982728a
--- /dev/null
+++ b/src/components/progress-circle/progress_circle.ts
@@ -0,0 +1,123 @@
+import {
+ Attribute,
+ Component,
+ ChangeDetectionStrategy,
+ ElementRef,
+ HostBinding,
+ Input,
+ ViewEncapsulation,
+} from 'angular2/core';
+import {isPresent, CONST} from 'angular2/src/facade/lang';
+import {OneOf} from '../../core/annotations/one-of';
+
+
+// TODO(josephperrott): Benchpress tests.
+
+/** Display modes of Progress Circle */
+@CONST()
+class ProgressMode {
+ @CONST() static DETERMINATE = 'determinate';
+ @CONST() static INDETERMINATE = 'indeterminate';
+}
+
+
+/**
+ * component.
+ */
+@Component({
+ selector: 'md-progress-circle',
+ host: {
+ 'role': 'progressbar',
+ 'aria-valuemin': '0',
+ 'aria-valuemax': '100',
+ },
+ templateUrl: './components/progress-circle/progress_circle.html',
+ styleUrls: ['./components/progress-circle/progress-circle.css'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class MdProgressCircle {
+ /**
+ * Value of the progress circle.
+ *
+ * Input:number, defaults to 0.
+ * value_ is bound to the host as the attribute aria-valuenow.
+ */
+ @Input('value')
+ value_: number = 0;
+ @HostBinding('attr.aria-valuenow')
+ get _value() {
+ return this.value_;
+ }
+
+ /**
+ * Mode of the progress circle
+ *
+ * Input must be one of the values from ProgressMode, defaults to 'determinate'.
+ * mode is bound to the host as the attribute host.
+ */
+ @Input()
+ @OneOf([ProgressMode.DETERMINATE, ProgressMode.INDETERMINATE])
+ mode: string;
+ @HostBinding('attr.mode')
+ get _mode() {
+ return this.mode;
+ }
+
+
+ /**
+ * Gets the current stroke dash offset to represent the progress circle.
+ *
+ * The stroke dash offset specifies the distance between dashes in the circle's stroke.
+ * Setting the offset to a percentage of the total circumference of the circle, fills this
+ * percentage of the overall circumference of the circle.
+ */
+ strokeDashOffset() {
+ // To determine how far the offset should be, we multiple the current percentage by the
+ // total circumference.
+
+ // The total circumference is calculated based on the radius we use, 45.
+ // PI * 2 * 45
+ return 251.3274 * (100 - this.value_) / 100;
+ }
+
+
+ /** Gets the progress value, returning the clamped value. */
+ get value() {
+ return this.value_;
+ }
+
+
+ /** Sets the progress value, clamping before setting the internal value. */
+ set value(v: number) {
+ if (isPresent(v)) {
+ this.value_ = MdProgressCircle.clamp(v);
+ }
+ }
+
+
+ /** Clamps a value to be between 0 and 100. */
+ static clamp(v: number) {
+ return Math.max(0, Math.min(100, v));
+ }
+}
+
+
+
+/**
+ * component.
+ *
+ * This is a component definition to be used as a convenience reference to create an
+ * indeterminate instance.
+ */
+@Component({
+ selector: 'md-spinner',
+ host: {
+ 'role': 'progressbar',
+ },
+ templateUrl: './components/progress-circle/progress_circle.html',
+ styleUrls: ['./components/progress-circle/progress-circle.css'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class MdSpinner extends MdProgressCircle {
+ mode: string = ProgressMode.INDETERMINATE;
+}
diff --git a/src/core/style/_default-theme.scss b/src/core/style/_default-theme.scss
index 198589e37f1..24624767c82 100644
--- a/src/core/style/_default-theme.scss
+++ b/src/core/style/_default-theme.scss
@@ -6,7 +6,7 @@
$md-is-dark-theme: false;
-$md-primary: md-palette($md-indigo, 500, 100, 700, $md-contrast-palettes);
+$md-primary: md-palette($md-blue, 500, 100, 700, $md-contrast-palettes);
$md-accent: md-palette($md-red, A200, A100, A400, $md-contrast-palettes);
$md-warn: md-palette($md-orange, 500, 300, 800, $md-contrast-palettes);
$md-foreground: if($md-is-dark-theme, $md-dark-theme-foreground, $md-light-theme-foreground);
diff --git a/src/core/style/_variables.scss b/src/core/style/_variables.scss
index b4a50ae55f1..5d89815cc06 100644
--- a/src/core/style/_variables.scss
+++ b/src/core/style/_variables.scss
@@ -12,8 +12,14 @@ $md-xsmall: "max-width: 600px";
$z-index-fab: 20 !default;
$z-index-drawer: 100 !default;
+// Global constants
+$pi: 3.14159265;
+
// Easing Curves
// TODO(jelbourn): all of these need to be revisited
+
+$ease-in-out-curve-function: cubic-bezier(0.35, 0, 0.25, 1) !default;
+
$swift-ease-out-duration: 0.4s !default;
$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default;
@@ -23,5 +29,5 @@ $swift-ease-in-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2) !default;
$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default;
$swift-ease-in-out-duration: 0.5s !default;
-$swift-ease-in-out-timing-function: cubic-bezier(0.35, 0, 0.25, 1) !default;
+$swift-ease-in-out-timing-function: $ease-in-out-curve-function !default;
$swift-ease-in-out: all $swift-ease-in-out-duration $swift-ease-in-out-timing-function !default;
diff --git a/src/demo-app/demo-app.html b/src/demo-app/demo-app.html
index 48dacf16d1d..18fb54d3bac 100644
--- a/src/demo-app/demo-app.html
+++ b/src/demo-app/demo-app.html
@@ -4,6 +4,7 @@
Button demo
Card demo
Sidenav demo
+ Progress Circle demo