diff --git a/demos/images/ic_progress_activity.svg b/demos/images/ic_progress_activity.svg
new file mode 100644
index 00000000000..209cd1068bf
--- /dev/null
+++ b/demos/images/ic_progress_activity.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/demos/index.html b/demos/index.html
index e7ba7a25f4a..f68b055dbb6 100644
--- a/demos/index.html
+++ b/demos/index.html
@@ -166,6 +166,14 @@
+
+
+
+ Linear progress
+ Fills from 0% to 100%, represented by bars
+
+
+
diff --git a/demos/linear-progress.html b/demos/linear-progress.html
new file mode 100644
index 00000000000..4c45492cb20
--- /dev/null
+++ b/demos/linear-progress.html
@@ -0,0 +1,190 @@
+
+
+
+
+
+ Linear Progress - Material Compoonents Catalog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index e7e7033c77b..824d16136da 100644
--- a/package.json
+++ b/package.json
@@ -129,6 +129,7 @@
"grid-list",
"icon-toggle",
"layout-grid",
+ "linear-progress",
"list",
"menu",
"radio",
diff --git a/packages/material-components-web/index.js b/packages/material-components-web/index.js
index 003c4d565b1..f6e2c911844 100644
--- a/packages/material-components-web/index.js
+++ b/packages/material-components-web/index.js
@@ -25,6 +25,7 @@ import * as dialog from '@material/dialog';
import * as drawer from '@material/drawer';
import * as textfield from '@material/textfield';
import * as snackbar from '@material/snackbar';
+import * as linearProgress from '@material/linear-progress';
import * as menu from '@material/menu';
import * as select from '@material/select';
import * as tabs from '@material/tabs';
@@ -39,6 +40,7 @@ autoInit.register('MDCTemporaryDrawer', drawer.MDCTemporaryDrawer);
autoInit.register('MDCRipple', ripple.MDCRipple);
autoInit.register('MDCGridList', gridList.MDCGridList);
autoInit.register('MDCIconToggle', iconToggle.MDCIconToggle);
+autoInit.register('MDCLinearProgress', linearProgress.MDCLinearProgress);
autoInit.register('MDCRadio', radio.MDCRadio);
autoInit.register('MDCSnackbar', snackbar.MDCSnackbar);
autoInit.register('MDCTab', tabs.MDCTab);
@@ -52,14 +54,15 @@ autoInit.register('MDCToolbar', toolbar.MDCToolbar);
export {
base,
checkbox,
+ dialog,
+ drawer,
formField,
gridList,
iconToggle,
+ linearProgress,
radio,
ripple,
snackbar,
- dialog,
- drawer,
tabs,
textfield,
menu,
diff --git a/packages/material-components-web/material-components-web.scss b/packages/material-components-web/material-components-web.scss
index e8f91b111e3..81f17d693ad 100644
--- a/packages/material-components-web/material-components-web.scss
+++ b/packages/material-components-web/material-components-web.scss
@@ -26,6 +26,7 @@
@import "@material/grid-list/mdc-grid-list";
@import "@material/icon-toggle/mdc-icon-toggle";
@import "@material/layout-grid/mdc-layout-grid";
+@import "@material/linear-progress/mdc-linear-progress";
@import "@material/list/mdc-list";
@import "@material/menu/mdc-menu";
@import "@material/radio/mdc-radio";
diff --git a/packages/material-components-web/package.json b/packages/material-components-web/package.json
index 52570fe6e35..64d5976e1ab 100644
--- a/packages/material-components-web/package.json
+++ b/packages/material-components-web/package.json
@@ -27,6 +27,7 @@
"@material/grid-list": "^0.2.2",
"@material/icon-toggle": "^0.1.11",
"@material/layout-grid": "^0.1.2",
+ "@material/linear-progress": "^0.1.0",
"@material/list": "^0.2.8",
"@material/menu": "^0.2.6",
"@material/radio": "^0.2.4",
diff --git a/packages/mdc-animation/index.js b/packages/mdc-animation/index.js
index 38d5f5bad1d..7c1cefe32f8 100644
--- a/packages/mdc-animation/index.js
+++ b/packages/mdc-animation/index.js
@@ -118,6 +118,8 @@ function getAnimationName(windowObj, eventType) {
// Public functions to access getAnimationName() for JavaScript events or CSS
// property names.
+export const transformStyleProperties = ['transform', 'WebkitTransform', 'MozTransform', 'OTransform', 'MSTransform'];
+
/**
* @param {!Object} windowObj
* @param {string} eventType
diff --git a/packages/mdc-linear-progress/README.md b/packages/mdc-linear-progress/README.md
new file mode 100644
index 00000000000..b1ef90cdd15
--- /dev/null
+++ b/packages/mdc-linear-progress/README.md
@@ -0,0 +1,78 @@
+
+
+# Linear Progress
+
+
+
+The MDC Linear Progress component is a spec-aligned linear progress indicator component adhering to the
+[Material Design progress & activity requirements](https://material.io/guidelines/components/progress-activity.html).
+
+## Design & API Documentation
+
+
+
+## Installation
+
+```
+npm install --save @material/linear-progress
+```
+
+## Usage
+
+### CSS Modifiers
+
+The provided modifiers are:
+
+| Class | Description |
+| --------------------- | ------------------------------------------------------- |
+| `mdc-linear-progress--indeterminate` | Puts the linear progress indicator in an indeterminate state. |
+| `mdc-linear-progress--reversed` | Reverses the direction of the linear progress indicator. |
+| `mdc-linear-progress--accent` | Colors the button with the accent color. |
+
+### Using the Foundation Class
+
+MDC Linear Progress ships with an `MDCLinearProgressFoundation` class that external frameworks and libraries can
+use to integrate the component. As with all foundation classes, an adapter object must be provided.
+The adapter for temporary drawers must provide the following functions, with correct signatures:
+
+| Method Signature | Description |
+| --- | --- |
+| `addClass(className: string) => void` | Adds a class to the root element. |
+| `removeClass(className: string) => void` | Removes a class from the root element. |
+| `hasClass(className: string) => boolean` | Returns boolean indicating whether the root element has a given class. |
+| `getPrimaryBar() => Element` | Returns the primary bar element. |
+| `getBuffer() => Element` | Returns the buffer element. |
+| `setTransform(el: Element, value: string) => void` | Sets the css transform property on the given element. |
+
+### MDCLinearProgress API
+
+MDC Linear Progress exposes the following methods:
+
+| Method Signature | Description |
+| --- | --- |
+| `set determinate(value: boolean) => void` | Toggles the components between the determinate and indeterminate state. |
+| `set progress(value: number) => void` | Sets the progress bar to this value. Value should be between [0, 1]. |
+| `set buffer(value: number) => void` | Sets the buffer bar to this value. Value should be between [0, 1]. |
+| `set reverse(value: boolean) => void` | Reverses the direction of the linear progress indicator. |
+| `open() => void` | Puts the component in the open state. |
+| `close() => void` | Puts the component in the closed state. |
diff --git a/packages/mdc-linear-progress/_keyframes.scss b/packages/mdc-linear-progress/_keyframes.scss
new file mode 100644
index 00000000000..dbdf35baaad
--- /dev/null
+++ b/packages/mdc-linear-progress/_keyframes.scss
@@ -0,0 +1,150 @@
+//
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+@keyframes primary-indeterminate-translate {
+ 0% {
+ transform: translateX(0);
+ }
+
+ 20% {
+ animation-timing-function: cubic-bezier(.5, 0, .701732, .495819);
+ transform: translateX(0);
+ }
+
+ 59.15% {
+ animation-timing-function: cubic-bezier(.302435, .381352, .55, .956352);
+ transform: translateX(83.67142%);
+ }
+
+ 100% {
+ transform: translateX(200.611057%);
+ }
+}
+
+@keyframes primary-indeterminate-scale {
+ 0% {
+ transform: scaleX(.08);
+ }
+
+ 36.65% {
+ animation-timing-function: cubic-bezier(.334731, .12482, .785844, 1);
+ transform: scaleX(.08);
+ }
+
+ 69.15% {
+ animation-timing-function: cubic-bezier(.06, .11, .6, 1);
+ transform: scaleX(.661479);
+ }
+
+ 100% {
+ transform: scaleX(.08);
+ }
+}
+
+@keyframes secondary-indeterminate-translate {
+ 0% {
+ animation-timing-function: cubic-bezier(.15, 0, .515058, .409685);
+ transform: translateX(0);
+ }
+
+ 25% {
+ animation-timing-function: cubic-bezier(.31033, .284058, .8, .733712);
+ transform: translateX(37.651913%);
+ }
+
+ 48.35% {
+ animation-timing-function: cubic-bezier(.4, .627035, .6, .902026);
+ transform: translateX(84.386165%);
+ }
+
+ 100% {
+ transform: translateX(160.277782%);
+ }
+}
+
+@keyframes secondary-indeterminate-scale {
+ 0% {
+ animation-timing-function: cubic-bezier(.205028, .057051, .57661, .453971);
+ transform: scaleX(.08);
+ }
+
+ 19.15% {
+ animation-timing-function: cubic-bezier(.152313, .196432, .648374, 1.004315);
+ transform: scaleX(.457104);
+ }
+
+ 44.15% {
+ animation-timing-function: cubic-bezier(.257759, -.003163, .211762, 1.38179);
+ transform: scaleX(.72796);
+ }
+
+ 100% {
+ transform: scaleX(.08);
+ }
+}
+
+@keyframes buffering {
+ to {
+ transform: translateX(-10px);
+ }
+}
+
+@keyframes primary-indeterminate-translate-reverse {
+ 0% {
+ transform: translateX(0);
+ }
+
+ 20% {
+ animation-timing-function: cubic-bezier(.5, 0, .701732, .495819);
+ transform: translateX(0);
+ }
+
+ 59.15% {
+ animation-timing-function: cubic-bezier(.302435, .381352, .55, .956352);
+ transform: translateX(-83.67142%);
+ }
+
+ 100% {
+ transform: translateX(-200.611057%);
+ }
+}
+
+@keyframes secondary-indeterminate-translate-reverse {
+ 0% {
+ animation-timing-function: cubic-bezier(.15, 0, .515058, .409685);
+ transform: translateX(0);
+ }
+
+ 25% {
+ animation-timing-function: cubic-bezier(.31033, .284058, .8, .733712);
+ transform: translateX(-37.651913%);
+ }
+
+ 48.35% {
+ animation-timing-function: cubic-bezier(.4, .627035, .6, .902026);
+ transform: translateX(-84.386165%);
+ }
+
+ 100% {
+ transform: translateX(-160.277782%);
+ }
+}
+
+@keyframes buffering-reverse {
+ to {
+ transform: translateX(10px);
+ }
+}
diff --git a/packages/mdc-linear-progress/constants.js b/packages/mdc-linear-progress/constants.js
new file mode 100644
index 00000000000..232d686a92a
--- /dev/null
+++ b/packages/mdc-linear-progress/constants.js
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const cssClasses = {
+ CLOSED_CLASS: 'mdc-linear-progress--closed',
+ INDETERMINATE_CLASS: 'mdc-linear-progress--indeterminate',
+ REVERSED_CLASS: 'mdc-linear-progress--reversed',
+};
+
+export const strings = {
+ PRIMARY_BAR_SELECTOR: '.mdc-linear-progress__primary-bar',
+ BUFFER_SELECTOR: '.mdc-linear-progress__buffer',
+};
diff --git a/packages/mdc-linear-progress/foundation.js b/packages/mdc-linear-progress/foundation.js
new file mode 100644
index 00000000000..c6aa64d6859
--- /dev/null
+++ b/packages/mdc-linear-progress/foundation.js
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {MDCFoundation} from '@material/base';
+import {transformStyleProperties} from '@material/animation';
+
+import {cssClasses, strings} from './constants';
+
+export default class MDCLinearProgressFoundation extends MDCFoundation {
+ static get cssClasses() {
+ return cssClasses;
+ }
+
+ static get strings() {
+ return strings;
+ }
+
+ static get defaultAdapter() {
+ return {
+ addClass: (/* className: string */) => {},
+ getPrimaryBar: () => /* el: Element */ {},
+ getBuffer: () => /* el: Element */ {},
+ hasClass: (/* className: string */) => false,
+ removeClass: (/* className: string */) => {},
+ setStyle: (/* el: Element, styleProperty: string, value: number */) => {},
+ };
+ }
+
+ constructor(adapter) {
+ super(Object.assign(MDCLinearProgressFoundation.defaultAdapter, adapter));
+ }
+
+ init() {
+ this.determinate_ = !this.adapter_.hasClass(cssClasses.INDETERMINATE_CLASS);
+ this.reverse_ = this.adapter_.hasClass(cssClasses.REVERSED_CLASS);
+ }
+
+ set determinate(isDeterminate) {
+ this.determinate_ = isDeterminate;
+ if (this.determinate_) {
+ this.adapter_.removeClass(cssClasses.INDETERMINATE_CLASS);
+ } else {
+ this.adapter_.addClass(cssClasses.INDETERMINATE_CLASS);
+ this.setScale(this.adapter_.getPrimaryBar(), 1);
+ this.setScale(this.adapter_.getBuffer(), 1);
+ }
+ }
+
+ set progress(value) {
+ if (this.determinate_) {
+ this.setScale(this.adapter_.getPrimaryBar(), value);
+ }
+ }
+
+ set buffer(value) {
+ if (this.determinate_) {
+ this.setScale(this.adapter_.getBuffer(), value);
+ }
+ }
+
+ set reverse(isReversed) {
+ this.reverse_ = isReversed;
+ if (this.reverse_) {
+ this.adapter_.addClass(cssClasses.REVERSED_CLASS);
+ } else {
+ this.adapter_.removeClass(cssClasses.REVERSED_CLASS);
+ }
+ }
+
+ open() {
+ this.adapter_.removeClass(cssClasses.CLOSED_CLASS);
+ }
+
+ close() {
+ this.adapter_.addClass(cssClasses.CLOSED_CLASS);
+ }
+
+ setScale(el, scaleValue) {
+ const value = 'scaleX(' + scaleValue + ')';
+ transformStyleProperties.forEach((transformStyleProperty) => {
+ this.adapter_.setStyle(el, transformStyleProperty, value);
+ });
+ }
+}
diff --git a/packages/mdc-linear-progress/index.js b/packages/mdc-linear-progress/index.js
new file mode 100644
index 00000000000..6dc9a7fc36a
--- /dev/null
+++ b/packages/mdc-linear-progress/index.js
@@ -0,0 +1,61 @@
+/**
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {MDCComponent} from '@material/base';
+import MDCLinearProgressFoundation from './foundation';
+
+export {MDCLinearProgressFoundation};
+
+export class MDCLinearProgress extends MDCComponent {
+ static attachTo(root) {
+ return new MDCLinearProgress(root);
+ }
+
+ set determinate(value) {
+ this.foundation_.determinate = value;
+ }
+
+ set progress(value) {
+ this.foundation_.progress = value;
+ }
+
+ set buffer(value) {
+ this.foundation_.buffer = value;
+ }
+
+ set reverse(value) {
+ this.foundation_.reverse = value;
+ }
+
+ open() {
+ this.foundation_.open();
+ }
+
+ close() {
+ this.foundation_.close();
+ }
+
+ getDefaultFoundation() {
+ return new MDCLinearProgressFoundation({
+ addClass: (className) => this.root_.classList.add(className),
+ getPrimaryBar: () => this.root_.querySelector(MDCLinearProgressFoundation.strings.PRIMARY_BAR_SELECTOR),
+ getBuffer: () => this.root_.querySelector(MDCLinearProgressFoundation.strings.BUFFER_SELECTOR),
+ hasClass: (className) => this.root_.classList.contains(className),
+ removeClass: (className) => this.root_.classList.remove(className),
+ setStyle: (el, styleProperty, value) => el.style[styleProperty] = value,
+ });
+ }
+}
diff --git a/packages/mdc-linear-progress/mdc-linear-progress.scss b/packages/mdc-linear-progress/mdc-linear-progress.scss
new file mode 100644
index 00000000000..4ed1f6f64ed
--- /dev/null
+++ b/packages/mdc-linear-progress/mdc-linear-progress.scss
@@ -0,0 +1,135 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@import "@material/animation/functions";
+@import "@material/theme/mixins";
+@import "./keyframes";
+
+.mdc-linear-progress {
+ position: relative;
+ width: 100%;
+ height: 4px;
+ transform: translateZ(0);
+ transition: mdc-animation-exit(opacity, 250ms);
+ overflow: hidden;
+
+ &__bar {
+ animation: none;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ transform-origin: top left;
+ transition: mdc-animation-exit(transform, 250ms);
+ }
+
+ &__bar-inner {
+ @include mdc-theme-prop(background-color, primary);
+
+ animation: none;
+ display: inline-block;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ }
+
+ &--accent .mdc-linear-progress__bar-inner {
+ @include mdc-theme-prop(background-color, accent);
+ }
+
+ &__buffering-dots {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ // SVG is optimized for data URI (https://codepen.io/tigt/post/optimizing-svgs-in-data-uris)
+ // stylelint-disable-next-line function-url-quotes
+ background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' enable-background='new 0 0 5 2' xml:space='preserve' viewBox='0 0 5 2' preserveAspectRatio='none slice'%3E%3Ccircle cx='1' cy='1' r='1' fill='%23e6e6e6'/%3E%3C/svg%3E");
+ background-repeat: repeat-x;
+ background-size: 10px 4px;
+ animation: buffering 250ms infinite linear;
+ }
+
+ &__buffer {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ transform-origin: top left;
+ transition: mdc-animation-exit(transform, 250ms);
+ background-color: #e6e6e6;
+ }
+
+ &__secondary-bar {
+ visibility: hidden;
+ }
+
+ &--indeterminate {
+ .mdc-linear-progress__bar {
+ transition: none;
+ }
+
+ .mdc-linear-progress__primary-bar {
+ animation: primary-indeterminate-translate 2s infinite linear;
+ left: -145.166611%;
+
+ > .mdc-linear-progress__bar-inner {
+ animation: primary-indeterminate-scale 2s infinite linear;
+ }
+ }
+
+ .mdc-linear-progress__secondary-bar {
+ animation: secondary-indeterminate-translate 2s infinite linear;
+ left: -54.888891%;
+ visibility: visible;
+
+ > .mdc-linear-progress__bar-inner {
+ animation: secondary-indeterminate-scale 2s infinite linear;
+ }
+ }
+ }
+
+ &--reversed {
+ .mdc-linear-progress__bar,
+ .mdc-linear-progress__buffer {
+ right: 0;
+ transform-origin: center right;
+ }
+
+ .mdc-linear-progress__primary-bar {
+ animation-name: primary-indeterminate-translate-reverse;
+ }
+
+ .mdc-linear-progress__secondary-bar {
+ animation-name: secondary-indeterminate-translate-reverse;
+ }
+
+ .mdc-linear-progress__buffering-dots {
+ animation: buffering-reverse 250ms infinite linear;
+ }
+ }
+
+ &--closed {
+ opacity: 0;
+ }
+}
+
+.mdc-linear-progress--indeterminate.mdc-linear-progress--reversed {
+ .mdc-linear-progress__primary-bar {
+ right: -145.166611%;
+ left: auto;
+ }
+
+ .mdc-linear-progress__secondary-bar {
+ right: -54.888891%;
+ left: auto;
+ }
+}
diff --git a/packages/mdc-linear-progress/package.json b/packages/mdc-linear-progress/package.json
new file mode 100644
index 00000000000..fffd7ca684f
--- /dev/null
+++ b/packages/mdc-linear-progress/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@material/linear-progress",
+ "description": "The Material Components for the web linear progress indicator component",
+ "version": "0.1.0",
+ "license": "Apache-2.0",
+ "keywords": [
+ "material components",
+ "material design",
+ "linear progress"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/material-components/material-components-web.git"
+ },
+ "dependencies": {
+ "@material/animation": "^0.2.2",
+ "@material/base": "^0.1.3",
+ "@material/theme": "^0.1.5"
+ }
+}
diff --git a/test/unit/mdc-linear-progress/foundation.test.js b/test/unit/mdc-linear-progress/foundation.test.js
new file mode 100644
index 00000000000..57771800044
--- /dev/null
+++ b/test/unit/mdc-linear-progress/foundation.test.js
@@ -0,0 +1,136 @@
+/**
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {assert} from 'chai';
+import td from 'testdouble';
+
+import {setupFoundationTest} from '../helpers/setup';
+import {verifyDefaultAdapter} from '../helpers/foundation';
+import MDCLinearProgressFoundation from '../../../packages/mdc-linear-progress/foundation';
+
+const {cssClasses} = MDCLinearProgressFoundation;
+
+suite('MDCLinearProgressFoundation');
+
+test('exports strings', () => {
+ assert.isOk('strings' in MDCLinearProgressFoundation);
+});
+
+test('exports cssClasses', () => {
+ assert.isOk('cssClasses' in MDCLinearProgressFoundation);
+});
+
+test('defaultAdapter returns a complete adapter implementation', () => {
+ verifyDefaultAdapter(MDCLinearProgressFoundation, [
+ 'addClass', 'getPrimaryBar', 'getBuffer', 'hasClass', 'removeClass', 'setStyle',
+ ]);
+});
+
+const setupTest = () => setupFoundationTest(MDCLinearProgressFoundation);
+
+test('#set indeterminate adds class and resets transforms', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(false);
+ const primaryBar = {};
+ td.when(mockAdapter.getPrimaryBar()).thenReturn(primaryBar);
+ const buffer = {};
+ td.when(mockAdapter.getBuffer()).thenReturn(buffer);
+ foundation.init();
+ foundation.determinate = false;
+ td.verify(mockAdapter.addClass(cssClasses.INDETERMINATE_CLASS));
+ td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(1)'));
+ td.verify(mockAdapter.setStyle(buffer, 'transform', 'scaleX(1)'));
+});
+
+test('#set determinate removes class', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(false);
+ foundation.init();
+ foundation.determinate = true;
+ td.verify(mockAdapter.removeClass(cssClasses.INDETERMINATE_CLASS));
+});
+
+test('#set progress sets transform', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(false);
+ const primaryBar = {};
+ td.when(mockAdapter.getPrimaryBar()).thenReturn(primaryBar);
+ foundation.init();
+ foundation.progress = 0.5;
+ td.verify(mockAdapter.setStyle(primaryBar, 'transform', 'scaleX(0.5)'));
+});
+
+test('#set progress on indeterminate does nothing', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(true);
+ const primaryBar = {};
+ td.when(mockAdapter.getPrimaryBar()).thenReturn(primaryBar);
+ foundation.init();
+ foundation.progress = 0.5;
+ td.verify(mockAdapter.setStyle(), {times: 0, ignoreExtraArgs: true});
+});
+
+test('#set buffer sets transform', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(false);
+ const buffer = {};
+ td.when(mockAdapter.getBuffer()).thenReturn(buffer);
+ foundation.init();
+ foundation.buffer = 0.5;
+ td.verify(mockAdapter.setStyle(buffer, 'transform', 'scaleX(0.5)'));
+});
+
+test('#set buffer on indeterminate does nothing', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.INDETERMINATE_CLASS)).thenReturn(true);
+ const buffer = {};
+ td.when(mockAdapter.getBuffer()).thenReturn(buffer);
+ foundation.init();
+ foundation.buffer = 0.5;
+ td.verify(mockAdapter.setStyle(), {times: 0, ignoreExtraArgs: true});
+});
+
+test('#set reverse adds class', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.REVERSED_CLASS)).thenReturn(false);
+ foundation.init();
+ foundation.reverse = true;
+ td.verify(mockAdapter.addClass(cssClasses.REVERSED_CLASS));
+});
+
+test('#set not reverse removes class', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.REVERSED_CLASS)).thenReturn(true);
+ foundation.init();
+ foundation.reverse = false;
+ td.verify(mockAdapter.removeClass(cssClasses.REVERSED_CLASS));
+});
+
+test('#open removes class', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.REVERSED_CLASS)).thenReturn(true);
+ foundation.init();
+ foundation.open();
+ td.verify(mockAdapter.removeClass(cssClasses.CLOSED_CLASS));
+});
+
+test('#close adds class', () => {
+ const {foundation, mockAdapter} = setupTest();
+ td.when(mockAdapter.hasClass(cssClasses.REVERSED_CLASS)).thenReturn(true);
+ foundation.init();
+ foundation.close();
+ td.verify(mockAdapter.addClass(cssClasses.CLOSED_CLASS));
+});
diff --git a/test/unit/mdc-linear-progress/mdc-linear-progress.test.js b/test/unit/mdc-linear-progress/mdc-linear-progress.test.js
new file mode 100644
index 00000000000..d83df518637
--- /dev/null
+++ b/test/unit/mdc-linear-progress/mdc-linear-progress.test.js
@@ -0,0 +1,87 @@
+/**
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {assert} from 'chai';
+import bel from 'bel';
+
+import {MDCLinearProgress, MDCLinearProgressFoundation} from '../../../packages/mdc-linear-progress';
+
+function getFixture() {
+ return bel`
+
+ `;
+}
+
+function setupTest() {
+ const root = getFixture();
+ const component = new MDCLinearProgress(root);
+ return {root, component};
+}
+
+suite('MDCLinearProgress');
+
+test('attachTo initializes and returns a MDCLinearProgress instance', () => {
+ assert.isOk(MDCLinearProgress.attachTo(getFixture()) instanceof MDCLinearProgress);
+});
+
+test('set indeterminate', () => {
+ const {root, component} = setupTest();
+
+ component.determinate = false;
+ assert.isOk(root.classList.contains('mdc-linear-progress--indeterminate'));
+});
+
+test('set progress', () => {
+ const {root, component} = setupTest();
+
+ component.progress = 0.5;
+ const primaryBar = root.querySelector(MDCLinearProgressFoundation.strings.PRIMARY_BAR_SELECTOR);
+ assert.equal('scaleX(0.5)', primaryBar.style.transform);
+});
+
+test('set buffer', () => {
+ const {root, component} = setupTest();
+
+ component.buffer = 0.5;
+ const buffer = root.querySelector(MDCLinearProgressFoundation.strings.BUFFER_SELECTOR);
+ assert.equal('scaleX(0.5)', buffer.style.transform);
+});
+
+test('set reverse', () => {
+ const {root, component} = setupTest();
+
+ component.reverse = true;
+ assert.isOk(root.classList.contains('mdc-linear-progress--reversed'));
+});
+
+test('open and close', () => {
+ const {root, component} = setupTest();
+
+ component.close();
+ assert.isOk(root.classList.contains('mdc-linear-progress--closed'));
+
+ component.open();
+ assert.isNotOk(root.classList.contains('mdc-linear-progress--closed'));
+});
diff --git a/webpack.config.js b/webpack.config.js
index fb87bf3f43c..7e93065facf 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -161,6 +161,7 @@ module.exports = [{
'mdc.grid-list': path.resolve('./packages/mdc-grid-list/mdc-grid-list.scss'),
'mdc.icon-toggle': path.resolve('./packages/mdc-icon-toggle/mdc-icon-toggle.scss'),
'mdc.layout-grid': path.resolve('./packages/mdc-layout-grid/mdc-layout-grid.scss'),
+ 'mdc.linear-progress': path.resolve('./packages/mdc-linear-progress/mdc-linear-progress.scss'),
'mdc.list': path.resolve('./packages/mdc-list/mdc-list.scss'),
'mdc.menu': path.resolve('./packages/mdc-menu/mdc-menu.scss'),
'mdc.radio': path.resolve('./packages/mdc-radio/mdc-radio.scss'),