+
+
+```
+
+### Styles
+
+```scss
+@import "@material/tab-bar/mdc-tab-bar";
+@import "@material/tab-scroller/mdc-tab-scroller";
+@import "@material/tab-indicator/mdc-tab-indicator";
+@import "@material/tab/mdc-tab";
+```
+
+### JavaScript Instantiation
+
+```js
+import {MDCTabBar} from '@material/tab-bar';
+
+const tabBar = new MDCTabBar(document.querySelector('.mdc-tab-bar'));
+```
+
+> See [Importing the JS component](../../docs/importing-js.md) for more information on how to import JavaScript.
+
+## Style Customization
+
+### CSS Classes
+
+CSS Class | Description
+--- | ---
+`mdc-tab-bar` | Mandatory.
+
+### Sass Mixins
+
+To customize the width of the tab bar, use the following mixin.
+
+Mixin | Description
+--- | ---
+`mdc-tab-bar-width($width)` | Customizes the width of the tab bar.
+
+## `MDCTabBar` Properties and Methods
+
+Method Signature | Description
+--- | ---
+`activateTab(index: number) => void` | Activates the tab at the given index.
+`scrollIntoView(index: number) => void` | Scrolls the tab at the given index into view.
+
+Event Name | Event Data Structure | Description
+--- | --- | ---
+`MDCTabBar:activated` | `{"detail": {"index": number}}` | Emitted when a Tab is activated with the index of the activated Tab. Listen for this to update content when a Tab becomes active.
+
+## Usage within Web Frameworks
+
+If you are using a JavaScript framework, such as React or Angular, you can create a Tab Bar for your framework. Depending on your needs, you can use the _Simple Approach: Wrapping MDC Web Vanilla Components_, or the _Advanced Approach: Using Foundations and Adapters_. Please follow the instructions [here](../../docs/integrating-into-frameworks.md).
+
+### `MDCTabBarAdapter`
+
+Method Signature | Description
+--- | ---
+`scrollTo(scrollX: number) => void` | Scrolls the Tab Scroller to the given position.
+`incrementScroll(scrollXIncrement: number) => void` | Increments the Tab Scroller by the given value.
+`getScrollPosition() => number` | Returns the scroll position of the Tab Scroller.
+`getScrollContentWidth() => number` | Returns the width of the Tab Scroller's scroll content element.
+`getOffsetWidth() => number` | Returns the offsetWidth of the root element.
+`isRTL() => boolean` | Returns if the text direction is RTL.
+`activateTabAtIndex(index: number, clientRect: ClientRect) => void` | Activates the Tab at the given index with the given clientRect.
+`deactivateTabAtIndex(index) => void` | Deactivates the Tab at the given index.
+`getTabIndicatorClientRectAtIndex(index: number) => ClientRect` | Returns the client rect of the Tab at the given index.
+`getTabDimensionsAtIndex(index) => MDCTabDimensions` | Returns the dimensions of the Tab at the given index.
+`getTabListLength() => number` | Returns the number of child Tab components.
+`getActiveTabIndex() => number` | Returns the index of the active Tab.
+`getIndexOfTab(tab: MDCTab) => number` | Returns the index of the given Tab instance.
+`notifyTabActivated(index: number) => void` | Emits the `MDCTabBar:activated` event.
+
+### `MDCTabBarFoundation`
+
+Method Signature | Description
+--- | ---
+`activateTab(index: number) => void` | Activates the Tab at the given index.
+`handleKeyDown(evt: Event) => void` | Handles the logic for the `"keydown"` event.
+`handleTabInteraction(evt: Event) => void` | Handles the logic for the `"MDCTab:interacted"` event.
+`scrollIntoView(index: number) => void` | Scrolls the Tab at the given index into view.
diff --git a/packages/mdc-tab-bar/_mixins.scss b/packages/mdc-tab-bar/_mixins.scss
new file mode 100644
index 00000000000..0ea61ac3562
--- /dev/null
+++ b/packages/mdc-tab-bar/_mixins.scss
@@ -0,0 +1,20 @@
+/**
+ * @license
+ * Copyright 2018 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.
+*/
+
+@mixin mdc-tab-bar-width($width) {
+ width: $width;
+}
diff --git a/packages/mdc-tab-bar/adapter.js b/packages/mdc-tab-bar/adapter.js
new file mode 100644
index 00000000000..ed9afcd0594
--- /dev/null
+++ b/packages/mdc-tab-bar/adapter.js
@@ -0,0 +1,125 @@
+/**
+ * @license
+ * Copyright 2018 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.
+*/
+
+/* eslint no-unused-vars: [2, {"args": "none"}] */
+
+/* eslint-disable no-unused-vars */
+import {MDCTabDimensions} from '@material/tab/adapter';
+import {MDCTab} from '@material/tab/index';
+/* eslint-enable no-unused-vars */
+
+/**
+ * Adapter for MDC Tab Bar.
+ *
+ * Defines the shape of the adapter expected by the foundation. Implement this
+ * adapter to integrate the Tab Bar into your framework. See
+ * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md
+ * for more information.
+ *
+ * @record
+ */
+class MDCTabBarAdapter {
+ /**
+ * Scrolls to the given position
+ * @param {number} scrollX The position to scroll to
+ */
+ scrollTo(scrollX) {}
+
+ /**
+ * Increments the current scroll position by the given amount
+ * @param {number} scrollXIncrement The amount to increment scroll
+ */
+ incrementScroll(scrollXIncrement) {}
+
+ /**
+ * Returns the current scroll position
+ * @return {number}
+ */
+ getScrollPosition() {}
+
+ /**
+ * Returns the width of the scroll content
+ * @return {number}
+ */
+ getScrollContentWidth() {}
+
+ /**
+ * Returns the root element's offsetWidth
+ * @return {number}
+ */
+ getOffsetWidth() {}
+
+ /**
+ * Returns if the Tab Bar language direction is RTL
+ * @return {boolean}
+ */
+ isRTL() {}
+
+ /**
+ * Activates the tab at the given index with the given client rect
+ * @param {number} index The index of the tab to activate
+ * @param {!ClientRect} clientRect The client rect of the previously active Tab Indicator
+ */
+ activateTabAtIndex(index, clientRect) {}
+
+ /**
+ * Deactivates the tab at the given index
+ * @param {number} index The index of the tab to activate
+ */
+ deactivateTabAtIndex(index) {}
+
+ /**
+ * Returns the client rect of the tab's indicator
+ * @param {number} index The index of the tab
+ * @return {!ClientRect}
+ */
+ getTabIndicatorClientRectAtIndex(index) {}
+
+ /**
+ * Returns the tab dimensions of the tab at the given index
+ * @param {number} index The index of the tab
+ * @return {!MDCTabDimensions}
+ */
+ getTabDimensionsAtIndex(index) {}
+
+ /**
+ * Returns the length of the tab list
+ * @return {number}
+ */
+ getTabListLength() {}
+
+ /**
+ * Returns the index of the active tab
+ * @return {number}
+ */
+ getActiveTabIndex() {}
+
+ /**
+ * Returns the index of the given tab
+ * @param {!MDCTab} tab The tab whose index to determin
+ * @return {number}
+ */
+ getIndexOfTab(tab) {}
+
+ /**
+ * Emits the MDCTabBar:activated event
+ * @param {number} index The index of the activated tab
+ */
+ notifyTabActivated(index) {}
+}
+
+export default MDCTabBarAdapter;
diff --git a/packages/mdc-tab-bar/constants.js b/packages/mdc-tab-bar/constants.js
new file mode 100644
index 00000000000..26f7d4899a6
--- /dev/null
+++ b/packages/mdc-tab-bar/constants.js
@@ -0,0 +1,41 @@
+/**
+ * @license
+ * Copyright 2018 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.
+ */
+
+/** @enum {string} */
+const strings = {
+ TAB_ACTIVATED_EVENT: 'MDCTabBar:activated',
+ TAB_SCROLLER_SELECTOR: '.mdc-tab-scroller',
+ TAB_SELECTOR: '.mdc-tab',
+ END_KEY: 'End',
+ HOME_KEY: 'Home',
+ ARROW_LEFT_KEY: 'ArrowLeft',
+ ARROW_RIGHT_KEY: 'ArrowRight',
+};
+
+/** @enum {number} */
+const numbers = {
+ EXTRA_SCROLL_AMOUNT: 20,
+ END_KEYCODE: 35,
+ HOME_KEYCODE: 36,
+ ARROW_LEFT_KEYCODE: 37,
+ ARROW_RIGHT_KEYCODE: 39,
+};
+
+export {
+ numbers,
+ strings,
+};
diff --git a/packages/mdc-tab-bar/foundation.js b/packages/mdc-tab-bar/foundation.js
new file mode 100644
index 00000000000..ccc30691db5
--- /dev/null
+++ b/packages/mdc-tab-bar/foundation.js
@@ -0,0 +1,402 @@
+/**
+ * @license
+ * Copyright 2018 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/foundation';
+
+import {strings, numbers} from './constants';
+import MDCTabBarAdapter from './adapter';
+
+/* eslint-disable no-unused-vars */
+import MDCTabFoundation from '@material/tab/foundation';
+import {MDCTabDimensions} from '@material/tab/adapter';
+/* eslint-enable no-unused-vars */
+
+/**
+ * @type {Set}
+ */
+const ACCEPTABLE_KEYS = new Set();
+// IE11 has no support for new Set with iterable so we need to initialize this by hand
+ACCEPTABLE_KEYS.add(strings.ARROW_LEFT_KEY);
+ACCEPTABLE_KEYS.add(strings.ARROW_RIGHT_KEY);
+ACCEPTABLE_KEYS.add(strings.END_KEY);
+ACCEPTABLE_KEYS.add(strings.HOME_KEY);
+
+/**
+ * @type {Map}
+ */
+const KEYCODE_MAP = new Map();
+// IE11 has no support for new Map with iterable so we need to initialize this by hand
+KEYCODE_MAP.set(numbers.HOME_KEYCODE, strings.HOME_KEY);
+KEYCODE_MAP.set(numbers.END_KEYCODE, strings.END_KEY);
+KEYCODE_MAP.set(numbers.ARROW_LEFT_KEYCODE, strings.ARROW_LEFT_KEY);
+KEYCODE_MAP.set(numbers.ARROW_RIGHT_KEYCODE, strings.ARROW_RIGHT_KEY);
+
+/**
+ * @extends {MDCFoundation}
+ * @final
+ */
+class MDCTabBarFoundation extends MDCFoundation {
+ /** @return enum {string} */
+ static get strings() {
+ return strings;
+ }
+
+ /** @return enum {number} */
+ static get numbers() {
+ return numbers;
+ }
+
+ /**
+ * @see MDCTabBarAdapter for typing information
+ * @return {!MDCTabBarAdapter}
+ */
+ static get defaultAdapter() {
+ return /** @type {!MDCTabBarAdapter} */ ({
+ scrollTo: () => {},
+ incrementScroll: () => {},
+ getScrollPosition: () => {},
+ getScrollContentWidth: () => {},
+ getOffsetWidth: () => {},
+ isRTL: () => {},
+ activateTabAtIndex: () => {},
+ deactivateTabAtIndex: () => {},
+ getTabIndicatorClientRectAtIndex: () => {},
+ getTabDimensionsAtIndex: () => {},
+ getActiveTabIndex: () => {},
+ getIndexOfTab: () => {},
+ getTabListLength: () => {},
+ notifyTabActivated: () => {},
+ });
+ }
+
+ /**
+ * @param {!MDCTabBarAdapter} adapter
+ * */
+ constructor(adapter) {
+ super(Object.assign(MDCTabBarFoundation.defaultAdapter, adapter));
+ }
+
+ init() {
+ const activeIndex = this.adapter_.getActiveTabIndex();
+ this.scrollIntoView(activeIndex);
+ }
+
+ /**
+ * Activates the tab at the given index
+ * @param {number} index
+ */
+ activateTab(index) {
+ const previousActiveIndex = this.adapter_.getActiveTabIndex();
+ if (!this.indexIsInRange_(index)) {
+ return;
+ }
+
+ this.adapter_.deactivateTabAtIndex(previousActiveIndex);
+ this.adapter_.activateTabAtIndex(index, this.adapter_.getTabIndicatorClientRectAtIndex(previousActiveIndex));
+ this.scrollIntoView(index);
+
+ // Only notify the tab activation if the index is different than the previously active index
+ if (index !== previousActiveIndex) {
+ this.adapter_.notifyTabActivated(index);
+ }
+ }
+
+ /**
+ * Handles the keydown event
+ * @param {!Event} evt
+ */
+ handleKeyDown(evt) {
+ // Get the key from the event
+ const key = this.getKeyFromEvent_(evt);
+
+ // Early exit if the event key isn't one of the keyboard navigation keys
+ if (key === undefined) {
+ return;
+ }
+
+ evt.preventDefault();
+ this.activateTabFromKey_(key);
+ }
+
+ /**
+ * Handles the MDCTab:interacted event
+ * @param {!Event} evt
+ */
+ handleTabInteraction(evt) {
+ this.activateTab(this.adapter_.getIndexOfTab(evt.detail.tab));
+ }
+
+ /**
+ * Scrolls the tab at the given index into view
+ * @param {number} index The tab index to make visible
+ */
+ scrollIntoView(index) {
+ // Early exit if the index is out of range
+ if (!this.indexIsInRange_(index)) {
+ return;
+ }
+
+ // Always scroll to 0 if scrolling to the 0th index
+ if (index === 0) {
+ return this.adapter_.scrollTo(0);
+ }
+
+ // Always scroll to the max value if scrolling to the Nth index
+ // MDCTabScroller.scrollTo() will never scroll past the max possible value
+ if (index === this.adapter_.getTabListLength() - 1) {
+ return this.adapter_.scrollTo(this.adapter_.getScrollContentWidth());
+ }
+
+ if (this.isRTL_()) {
+ return this.scrollIntoViewRTL_(index);
+ }
+
+ this.scrollIntoView_(index);
+ }
+
+ /**
+ * Private method for activating a tab from a key
+ * @param {string} key The name of the key
+ * @private
+ */
+ activateTabFromKey_(key) {
+ const isRTL = this.isRTL_();
+ const maxTabIndex = this.adapter_.getTabListLength() - 1;
+ const shouldGoToEnd = key === strings.END_KEY;
+ const shouldDecrement = key === strings.ARROW_LEFT_KEY && !isRTL || key === strings.ARROW_RIGHT_KEY && isRTL;
+ const shouldIncrement = key === strings.ARROW_RIGHT_KEY && !isRTL || key === strings.ARROW_LEFT_KEY && isRTL;
+ let tabIndex = this.adapter_.getActiveTabIndex();
+
+ if (shouldGoToEnd) {
+ tabIndex = maxTabIndex;
+ } else if (shouldDecrement) {
+ tabIndex -= 1;
+ } else if (shouldIncrement) {
+ tabIndex += 1;
+ } else {
+ tabIndex = 0;
+ }
+
+ if (tabIndex < 0) {
+ tabIndex = maxTabIndex;
+ } else if (tabIndex > maxTabIndex) {
+ tabIndex = 0;
+ }
+
+ this.activateTab(tabIndex);
+ }
+
+ /**
+ * Calculates the scroll increment that will make the tab at the given index visible
+ * @param {number} index The index of the tab
+ * @param {number} nextIndex The index of the next tab
+ * @param {number} scrollPosition The current scroll position
+ * @param {number} barWidth The width of the Tab Bar
+ * @return {number}
+ * @private
+ */
+ calculateScrollIncrement_(index, nextIndex, scrollPosition, barWidth) {
+ const nextTabDimensions = this.adapter_.getTabDimensionsAtIndex(nextIndex);
+ const relativeContentLeft = nextTabDimensions.contentLeft - scrollPosition - barWidth;
+ const relativeContentRight = nextTabDimensions.contentRight - scrollPosition;
+ const leftIncrement = relativeContentRight - numbers.EXTRA_SCROLL_AMOUNT;
+ const rightIncrement = relativeContentLeft + numbers.EXTRA_SCROLL_AMOUNT;
+
+ if (nextIndex < index) {
+ return Math.min(leftIncrement, 0);
+ }
+
+ return Math.max(rightIncrement, 0);
+ }
+
+ /**
+ * Calculates the scroll increment that will make the tab at the given index visible in RTL
+ * @param {number} index The index of the tab
+ * @param {number} nextIndex The index of the next tab
+ * @param {number} scrollPosition The current scroll position
+ * @param {number} barWidth The width of the Tab Bar
+ * @param {number} scrollContentWidth The width of the scroll content
+ * @return {number}
+ * @private
+ */
+ calculateScrollIncrementRTL_(index, nextIndex, scrollPosition, barWidth, scrollContentWidth, ) {
+ const nextTabDimensions = this.adapter_.getTabDimensionsAtIndex(nextIndex);
+ const relativeContentLeft = scrollContentWidth - nextTabDimensions.contentLeft - scrollPosition;
+ const relativeContentRight = scrollContentWidth - nextTabDimensions.contentRight - scrollPosition - barWidth;
+ const leftIncrement = relativeContentRight + numbers.EXTRA_SCROLL_AMOUNT;
+ const rightIncrement = relativeContentLeft - numbers.EXTRA_SCROLL_AMOUNT;
+
+ if (nextIndex > index) {
+ return Math.max(leftIncrement, 0);
+ }
+
+ return Math.min(rightIncrement, 0);
+ }
+
+ /**
+ * Determines the index of the adjacent tab closest to either edge of the Tab Bar
+ * @param {number} index The index of the tab
+ * @param {!MDCTabDimensions} tabDimensions The dimensions of the tab
+ * @param {number} scrollPosition The current scroll position
+ * @param {number} barWidth The width of the tab bar
+ * @return {number}
+ * @private
+ */
+ findAdjacentTabIndexClosestToEdge_(index, tabDimensions, scrollPosition, barWidth) {
+ /**
+ * Tabs are laid out in the Tab Scroller like this:
+ *
+ * Scroll Position
+ * +---+
+ * | | Bar Width
+ * | +-----------------------------------+
+ * | | |
+ * | V V
+ * | +-----------------------------------+
+ * V | Tab Scroller |
+ * +------------+--------------+-------------------+
+ * | Tab | Tab | Tab |
+ * +------------+--------------+-------------------+
+ * | |
+ * +-----------------------------------+
+ *
+ * To determine the next adjacent index, we look at the Tab root left and
+ * Tab root right, both relative to the scroll position. If the Tab root
+ * left is less than 0, then we know it's out of view to the left. If the
+ * Tab root right minus the bar width is greater than 0, we know the Tab is
+ * out of view to the right. From there, we either increment or decrement
+ * the index.
+ */
+ const relativeRootLeft = tabDimensions.rootLeft - scrollPosition;
+ const relativeRootRight = tabDimensions.rootRight - scrollPosition - barWidth;
+ const relativeRootDelta = relativeRootLeft + relativeRootRight;
+ const leftEdgeIsCloser = relativeRootLeft < 0 || relativeRootDelta < 0;
+ const rightEdgeIsCloser = relativeRootRight > 0 || relativeRootDelta > 0;
+
+ if (leftEdgeIsCloser) {
+ return index - 1;
+ }
+
+ if (rightEdgeIsCloser) {
+ return index + 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Determines the index of the adjacent tab closest to either edge of the Tab Bar in RTL
+ * @param {number} index The index of the tab
+ * @param {!MDCTabDimensions} tabDimensions The dimensions of the tab
+ * @param {number} scrollPosition The current scroll position
+ * @param {number} barWidth The width of the tab bar
+ * @param {number} scrollContentWidth The width of the scroller content
+ * @return {number}
+ * @private
+ */
+ findAdjacentTabIndexClosestToEdgeRTL_(index, tabDimensions, scrollPosition, barWidth, scrollContentWidth) {
+ const rootLeft = scrollContentWidth - tabDimensions.rootLeft - barWidth - scrollPosition;
+ const rootRight = scrollContentWidth - tabDimensions.rootRight - scrollPosition;
+ const rootDelta = rootLeft + rootRight;
+ const leftEdgeIsCloser = rootLeft > 0 || rootDelta > 0;
+ const rightEdgeIsCloser = rootRight < 0 || rootDelta < 0;
+
+ if (leftEdgeIsCloser) {
+ return index + 1;
+ }
+
+ if (rightEdgeIsCloser) {
+ return index - 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns the key associated with a keydown event
+ * @param {!Event} evt The keydown event
+ * @return {string}
+ * @private
+ */
+ getKeyFromEvent_(evt) {
+ if (ACCEPTABLE_KEYS.has(evt.key)) {
+ return evt.key;
+ }
+
+ return KEYCODE_MAP.get(evt.keyCode);
+ }
+
+ /**
+ * Returns whether a given index is inclusively between the ends
+ * @param {number} index The index to test
+ * @private
+ */
+ indexIsInRange_(index) {
+ return index >= 0 && index < this.adapter_.getTabListLength();
+ }
+
+ /**
+ * Returns the view's RTL property
+ * @return {boolean}
+ * @private
+ */
+ isRTL_() {
+ return this.adapter_.isRTL();
+ }
+
+ /**
+ * Scrolls the tab at the given index into view for left-to-right useragents
+ * @param {number} index The index of the tab to scroll into view
+ * @private
+ */
+ scrollIntoView_(index) {
+ const scrollPosition = this.adapter_.getScrollPosition();
+ const barWidth = this.adapter_.getOffsetWidth();
+ const tabDimensions = this.adapter_.getTabDimensionsAtIndex(index);
+ const nextIndex = this.findAdjacentTabIndexClosestToEdge_(index, tabDimensions, scrollPosition, barWidth);
+
+ if (!this.indexIsInRange_(nextIndex)) {
+ return;
+ }
+
+ const scrollIncrement = this.calculateScrollIncrement_(index, nextIndex, scrollPosition, barWidth);
+ this.adapter_.incrementScroll(scrollIncrement);
+ }
+
+ /**
+ * Scrolls the tab at the given index into view in RTL
+ * @param {number} index The tab index to make visible
+ * @private
+ */
+ scrollIntoViewRTL_(index) {
+ const scrollPosition = this.adapter_.getScrollPosition();
+ const barWidth = this.adapter_.getOffsetWidth();
+ const tabDimensions = this.adapter_.getTabDimensionsAtIndex(index);
+ const scrollWidth = this.adapter_.getScrollContentWidth();
+ const nextIndex = this.findAdjacentTabIndexClosestToEdgeRTL_(
+ index, tabDimensions, scrollPosition, barWidth, scrollWidth);
+
+ if (!this.indexIsInRange_(nextIndex)) {
+ return;
+ }
+
+ const scrollIncrement = this.calculateScrollIncrementRTL_(index, nextIndex, scrollPosition, barWidth, scrollWidth);
+ this.adapter_.incrementScroll(scrollIncrement);
+ }
+}
+
+export default MDCTabBarFoundation;
diff --git a/packages/mdc-tab-bar/index.js b/packages/mdc-tab-bar/index.js
new file mode 100644
index 00000000000..1ec6ea937d6
--- /dev/null
+++ b/packages/mdc-tab-bar/index.js
@@ -0,0 +1,148 @@
+/**
+ * @license
+ * Copyright 2018 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/component';
+
+import {MDCTab, MDCTabFoundation} from '@material/tab/index';
+import {MDCTabScroller} from '@material/tab-scroller/index';
+
+import MDCTabBarAdapter from './adapter';
+import MDCTabBarFoundation from './foundation';
+
+/**
+ * @extends {MDCComponent}
+ * @final
+ */
+class MDCTabBar extends MDCComponent {
+ /**
+ * @param {...?} args
+ */
+ constructor(...args) {
+ super(...args);
+
+ /** @private {!Array} */
+ this.tabList_;
+
+ /** @type {(function(!Element): !MDCTab)} */
+ this.tabFactory_;
+
+ /** @private {?MDCTabScroller} */
+ this.tabScroller_;
+
+ /** @type {(function(!Element): !MDCTabScroller)} */
+ this.tabScrollerFactory_;
+
+ /** @private {?function(?Event): undefined} */
+ this.handleTabInteraction_;
+
+ /** @private {?function(?Event): undefined} */
+ this.handleKeyDown_;
+ }
+
+ /**
+ * @param {!Element} root
+ * @return {!MDCTabBar}
+ */
+ static attachTo(root) {
+ return new MDCTabBar(root);
+ }
+
+ /**
+ * @param {(function(!Element): !MDCTab)=} tabFactory A function which creates a new MDCTab
+ * @param {(function(!Element): !MDCTabScroller)=} tabScrollerFactory A function which creates a new MDCTabScroller
+ */
+ initialize(
+ tabFactory = (el) => new MDCTab(el),
+ tabScrollerFactory = (el) => new MDCTabScroller(el),
+ ) {
+ this.tabFactory_ = tabFactory;
+ this.tabScrollerFactory_ = tabScrollerFactory;
+
+ const tabElements = [].slice.call(this.root_.querySelectorAll(MDCTabBarFoundation.strings.TAB_SELECTOR));
+ this.tabList_ = tabElements.map((el) => this.tabFactory_(el));
+
+ const tabScrollerElement = this.root_.querySelector(MDCTabBarFoundation.strings.TAB_SCROLLER_SELECTOR);
+ if (tabScrollerElement) {
+ this.tabScroller_ = this.tabScrollerFactory_(tabScrollerElement);
+ }
+ }
+
+ initialSyncWithDOM() {
+ this.handleTabInteraction_ = (evt) => this.foundation_.handleTabInteraction(evt);
+ this.handleKeyDown_ = (evt) => this.foundation_.handleKeyDown(evt);
+
+ this.root_.addEventListener(MDCTabFoundation.strings.INTERACTED_EVENT, this.handleTabInteraction_);
+ this.root_.addEventListener('keydown', this.handleKeyDown_);
+ }
+
+ destroy() {
+ super.destroy();
+ this.root_.removeEventListener(MDCTabFoundation.strings.INTERACTED_EVENT, this.handleTabInteraction_);
+ this.root_.removeEventListener('keydown', this.handleKeyDown_);
+ this.tabList_.forEach((tab) => tab.destroy());
+ this.tabScroller_.destroy();
+ }
+
+ /**
+ * @return {!MDCTabBarFoundation}
+ */
+ getDefaultFoundation() {
+ return new MDCTabBarFoundation(
+ /** @type {!MDCTabBarAdapter} */ ({
+ scrollTo: (scrollX) => this.tabScroller_.scrollTo(scrollX),
+ incrementScroll: (scrollXIncrement) => this.tabScroller_.incrementScroll(scrollXIncrement),
+ getScrollPosition: () => this.tabScroller_.getScrollPosition(),
+ getScrollContentWidth: () => this.tabScroller_.getScrollContentWidth(),
+ getOffsetWidth: () => this.root_.offsetWidth,
+ isRTL: () => window.getComputedStyle(this.root_).getPropertyValue('direction') === 'rtl',
+ activateTabAtIndex: (index, clientRect) => this.tabList_[index].activate(clientRect),
+ deactivateTabAtIndex: (index) => this.tabList_[index].deactivate(),
+ getTabIndicatorClientRectAtIndex: (index) => this.tabList_[index].computeIndicatorClientRect(),
+ getTabDimensionsAtIndex: (index) => this.tabList_[index].computeDimensions(),
+ getActiveTabIndex: () => {
+ for (let i = 0; i < this.tabList_.length; i++) {
+ if (this.tabList_[i].active) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ getIndexOfTab: (tabToFind) => this.tabList_.indexOf(tabToFind),
+ getTabListLength: () => this.tabList_.length,
+ notifyTabActivated: (index) => this.emit(MDCTabBarFoundation.strings.TAB_ACTIVATED_EVENT, {index}, true),
+ })
+ );
+ }
+
+ /**
+ * Activates the tab at the given index
+ * @param {number} index The index of the tab
+ */
+ activateTab(index) {
+ this.foundation_.activateTab(index);
+ }
+
+ /**
+ * Scrolls the tab at the given index into view
+ * @param {number} index THe index of the tab
+ */
+ scrollIntoView(index) {
+ this.foundation_.scrollIntoView(index);
+ }
+}
+
+export {MDCTabBar, MDCTabBarFoundation};
diff --git a/packages/mdc-tab-bar/mdc-tab-bar.scss b/packages/mdc-tab-bar/mdc-tab-bar.scss
new file mode 100644
index 00000000000..df351f43383
--- /dev/null
+++ b/packages/mdc-tab-bar/mdc-tab-bar.scss
@@ -0,0 +1,22 @@
+/**
+ * @license
+ * Copyright 2018 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 "./mixins";
+
+.mdc-tab-bar {
+ @include mdc-tab-bar-width(100%);
+}
diff --git a/packages/mdc-tab-bar/package.json b/packages/mdc-tab-bar/package.json
new file mode 100644
index 00000000000..0ac5e8a37ec
--- /dev/null
+++ b/packages/mdc-tab-bar/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@material/tab-bar",
+ "description": "The Material Components for the web tab bar component",
+ "version": "0.0.0",
+ "license": "Apache-2.0",
+ "keywords": [
+ "material components",
+ "material design",
+ "tab",
+ "bar"
+ ],
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/material-components/material-components-web.git"
+ },
+ "dependencies": {
+ "@material/base": "^0.35.0",
+ "@material/tab": "^0.37.1",
+ "@material/tab-scroller": "^0.0.0",
+ "@material/elevation": "^0.36.1"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/mdc-tab-indicator/README.md b/packages/mdc-tab-indicator/README.md
new file mode 100644
index 00000000000..2e8860a3720
--- /dev/null
+++ b/packages/mdc-tab-indicator/README.md
@@ -0,0 +1,161 @@
+
+
+# Tab Indicator
+
+A Tab Indicator is a visual guide that shows which Tab is active.
+
+## Design & API Documentation
+
+
+
+## Installation
+
+```
+npm install @material/tab-indicator
+```
+
+## Basic Usage
+
+### HTML Structure
+
+```html
+
+
+
+```
+
+### Styles
+
+```scss
+@import "@material/tab/mdc-tab-indicator";
+```
+
+### JavaScript Instantiation
+
+```js
+import {MDCTabIndicator} from '@material/tab-indicator';
+
+const tabIndicator = new MDCTabIndicator(document.querySelector('.mdc-tab-indicator'));
+```
+
+> See [Importing the JS component](../../docs/importing-js.md) for more information on how to import JavaScript.
+
+## Variants
+
+### Active Indicator
+
+Add the `mdc-tab-indicator--active` class to the `mdc-tab-indicator` element to make the Tab Indicator active.
+
+### Indicator Types and Transitions
+
+The Tab Indicator may be represented in one of two ways:
+
+* Underline, indicated by the `mdc-tab-indicator__content--underline` class
+* Icon, indicated by the `mdc-tab-indicator__content--icon` class
+
+> *NOTE*: One of these classes _must_ be applied to the Tab Indicator's content element.
+
+The Tab Indicator may transition in one of two ways:
+
+* Slide, the default behavior
+* Fade, indicated by the `mdc-tab-indicator--fade` class
+
+#### Sliding Underline Indicator
+
+```html
+
+
+
+```
+
+#### Fading Icon Indicator
+
+You can use [Material Icons](https://material.io/icons/) from Google Fonts within your Fading Icon Indicator, or you can use your own icons.
+
+```html
+
+ star
+
+```
+
+#### Sliding Icon Indicator
+
+```html
+
+ star
+
+```
+
+## Style Customization
+
+### CSS Classes
+
+CSS Class | Description
+--- | ---
+`mdc-tab-indicator` | Mandatory. Contains the tab indicator content.
+`mdc-tab-indicator__content` | Mandatory. Denotes the tab indicator content.
+`mdc-tab-indicator--active` | Optional. Visually activates the indicator.
+`mdc-tab-indicator--fade` | Optional. Sets up the tab indicator to fade in on activation and fade out on deactivation.
+`mdc-tab-indicator__content--underline` | Optional. Denotes an underline tab indicator.
+`mdc-tab-indicator__content--icon` | Optional. Denotes an icon tab indicator.
+
+> *NOTE*: Exactly one of the `--underline` or `--icon` content modifier classes should be present.
+
+### Sass Mixins
+
+To customize the tab indicator, use the following mixins.
+
+Mixin | Description
+--- | ---
+`mdc-tab-indicator-surface` | Mandatory. Must be applied to the parent element of the `mdc-tab-indicator`.
+`mdc-tab-indicator-underline-color($color)` | Customizes the color of the underline.
+`mdc-tab-indicator-icon-color($color)` | Customizes the color of the icon subelement.
+`mdc-tab-indicator-underline-height($height)` | Customizes the height of the underline.
+`mdc-tab-indicator-icon-height($height)` | Customizes the height of the icon subelement.
+`mdc-tab-indicator-underline-top-corner-radius($radius)` | Customizes the top left and top right border radius of the underline child element.
+
+## `MDCTabIndicator` Methods
+
+Method Signature | Description
+--- | ---
+`activate(previousIndicatorClientRect: ClientRect) => void` | Activates the tab indicator.
+`deactivate() => void` | Deactivates the tab indicator.
+`computeContentClientRect() => ClientRect` | Returns the content element bounding client rect.
+
+## Usage within Web Frameworks
+
+If you are using a JavaScript framework, such as React or Angular, you can create a Tab Indicator for your framework. Depending on your needs, you can use the _Simple Approach: Wrapping MDC Web Vanilla Components_, or the _Advanced Approach: Using Foundations and Adapters_. Please follow the instructions [here](../../docs/integrating-into-frameworks.md).
+
+### `MDCTabIndicatorAdapter`
+
+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.
+`registerEventHandler(evtType: string, handler: EventListener) => void` | Registers an event listener on the root element.
+`deregisterEventHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the root element.
+`setContentStyleProp(property: string, value: string) => void` | Sets the style property of the content element.
+`computeContentClientRect() => ClientRect` | Returns the content element's bounding client rect.
+
+### `MDCTabIndicatorFoundation`
+
+Method Signature | Description
+--- | ---
+`handleTransitionEnd(evt: Event) => void` | Handles the logic for the `"transitionend"` event on the root element.
+`activate(previousIndicatorClientRect: ClientRect) => void` | Activates the tab indicator.
+`deactivate() => void` | Deactivates the tab indicator.
+`computeContentClientRect() => ClientRect` | Returns the content element's bounding client rect.
diff --git a/packages/mdc-tab-indicator/_mixins.scss b/packages/mdc-tab-indicator/_mixins.scss
new file mode 100644
index 00000000000..47919ff0837
--- /dev/null
+++ b/packages/mdc-tab-indicator/_mixins.scss
@@ -0,0 +1,54 @@
+/**
+ * @license
+ * Copyright 2018 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/theme/mixins";
+
+@mixin mdc-tab-indicator-surface {
+ position: relative;
+}
+
+@mixin mdc-tab-indicator-underline-color($color) {
+ > .mdc-tab-indicator__content--underline {
+ @include mdc-theme-prop(background-color, $color);
+ }
+}
+
+@mixin mdc-tab-indicator-underline-height($height) {
+ > .mdc-tab-indicator__content--underline {
+ height: $height;
+ }
+}
+
+@mixin mdc-tab-indicator-underline-top-corner-radius($radius) {
+ > .mdc-tab-indicator__content--underline {
+ border-top-left-radius: $radius;
+ border-top-right-radius: $radius;
+ }
+}
+
+@mixin mdc-tab-indicator-icon-color($color) {
+ > .mdc-tab-indicator__content--icon {
+ @include mdc-theme-prop(color, $color);
+ }
+}
+
+@mixin mdc-tab-indicator-icon-height($height) {
+ > .mdc-tab-indicator__content--icon {
+ height: $height;
+ font-size: $height;
+ }
+}
diff --git a/packages/mdc-tab-indicator/adapter.js b/packages/mdc-tab-indicator/adapter.js
new file mode 100644
index 00000000000..b3f3edf474b
--- /dev/null
+++ b/packages/mdc-tab-indicator/adapter.js
@@ -0,0 +1,71 @@
+/**
+ * @license
+ * Copyright 2018 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.
+*/
+
+/* eslint no-unused-vars: [2, {"args": "none"}] */
+
+/**
+ * Adapter for MDC Tab Indicator.
+ *
+ * Defines the shape of the adapter expected by the foundation. Implement this
+ * adapter to integrate the Tab Indicator into your framework. See
+ * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md
+ * for more information.
+ *
+ * @record
+ */
+class MDCTabIndicatorAdapter {
+ /**
+ * Registers an event listener on the root element for a given event.
+ * @param {string} evtType
+ * @param {function(!Event): undefined} handler
+ */
+ registerEventHandler(evtType, handler) {}
+
+ /**
+ * Deregisters an event listener on the root element for a given event.
+ * @param {string} evtType
+ * @param {function(!Event): undefined} handler
+ */
+ deregisterEventHandler(evtType, handler) {}
+
+ /**
+ * Adds the given className to the root element.
+ * @param {string} className The className to add
+ */
+ addClass(className) {}
+
+ /**
+ * Removes the given className from the root element.
+ * @param {string} className The className to remove
+ */
+ removeClass(className) {}
+
+ /**
+ * Returns the client rect of the content element.
+ * @return {!ClientRect}
+ */
+ computeContentClientRect() {}
+
+ /**
+ * Sets a style property of the content element to the passed value
+ * @param {string} propName The style property name to set
+ * @param {string} value The style property value
+ */
+ setContentStyleProperty(propName, value) {}
+}
+
+export default MDCTabIndicatorAdapter;
diff --git a/packages/mdc-tab-indicator/constants.js b/packages/mdc-tab-indicator/constants.js
new file mode 100644
index 00000000000..0fcc30e4543
--- /dev/null
+++ b/packages/mdc-tab-indicator/constants.js
@@ -0,0 +1,35 @@
+/**
+ * @license
+ * Copyright 2018 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.
+*/
+
+/** @enum {string} */
+const cssClasses = {
+ ACTIVE: 'mdc-tab-indicator--active',
+ FADE: 'mdc-tab-indicator--fade',
+ FADING_ACTIVATE: 'mdc-tab-indicator--fading-activate',
+ FADING_DEACTIVATE: 'mdc-tab-indicator--fading-deactivate',
+ SLIDING_ACTIVATE: 'mdc-tab-indicator--sliding-activate',
+};
+
+/** @enum {string} */
+const strings = {
+ CONTENT_SELECTOR: '.mdc-tab-indicator__content',
+};
+
+export {
+ cssClasses,
+ strings,
+};
diff --git a/packages/mdc-tab-indicator/fading-foundation.js b/packages/mdc-tab-indicator/fading-foundation.js
new file mode 100644
index 00000000000..b86a96306ad
--- /dev/null
+++ b/packages/mdc-tab-indicator/fading-foundation.js
@@ -0,0 +1,53 @@
+/**
+ * @license
+ * Copyright 2018 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 MDCTabIndicatorFoundation from './foundation';
+
+/**
+ * @extends {MDCTabIndicatorFoundation}
+ * @final
+ */
+class MDCFadingTabIndicatorFoundation extends MDCTabIndicatorFoundation {
+ /** @param {...?} args */
+ constructor(...args) {
+ super(...args);
+
+ /** @private {function(?Event): undefined} */
+ this.handleTransitionEnd_ = () => this.handleTransitionEnd();
+ }
+
+ /** Handles the transitionend event */
+ handleTransitionEnd() {
+ this.adapter_.deregisterEventHandler('transitionend', this.handleTransitionEnd_);
+ this.adapter_.removeClass(MDCTabIndicatorFoundation.cssClasses.FADING_ACTIVATE);
+ this.adapter_.removeClass(MDCTabIndicatorFoundation.cssClasses.FADING_DEACTIVATE);
+ }
+
+ activate() {
+ this.adapter_.registerEventHandler('transitionend', this.handleTransitionEnd_);
+ this.adapter_.addClass(MDCTabIndicatorFoundation.cssClasses.FADING_ACTIVATE);
+ this.adapter_.addClass(MDCTabIndicatorFoundation.cssClasses.ACTIVE);
+ }
+
+ deactivate() {
+ this.adapter_.registerEventHandler('transitionend', this.handleTransitionEnd_);
+ this.adapter_.addClass(MDCTabIndicatorFoundation.cssClasses.FADING_DEACTIVATE);
+ this.adapter_.removeClass(MDCTabIndicatorFoundation.cssClasses.ACTIVE);
+ }
+}
+
+export default MDCFadingTabIndicatorFoundation;
diff --git a/packages/mdc-tab-indicator/foundation.js b/packages/mdc-tab-indicator/foundation.js
new file mode 100644
index 00000000000..fd160f38386
--- /dev/null
+++ b/packages/mdc-tab-indicator/foundation.js
@@ -0,0 +1,76 @@
+/**
+ * @license
+ * Copyright 2018 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/foundation';
+import MDCTabIndicatorAdapter from './adapter';
+import {
+ cssClasses,
+ strings,
+} from './constants';
+
+/**
+ * @extends {MDCFoundation}
+ * @abstract
+ */
+class MDCTabIndicatorFoundation extends MDCFoundation {
+ /** @return enum {string} */
+ static get cssClasses() {
+ return cssClasses;
+ }
+
+ /** @return enum {string} */
+ static get strings() {
+ return strings;
+ }
+
+ /**
+ * @see MDCTabIndicatorAdapter for typing information
+ * @return {!MDCTabIndicatorAdapter}
+ */
+ static get defaultAdapter() {
+ return /** @type {!MDCTabIndicatorAdapter} */ ({
+ registerEventHandler: () => {},
+ deregisterEventHandler: () => {},
+ addClass: () => {},
+ removeClass: () => {},
+ computeContentClientRect: () => {},
+ setContentStyleProperty: () => {},
+ });
+ }
+
+ /** @param {!MDCTabIndicatorAdapter} adapter */
+ constructor(adapter) {
+ super(Object.assign(MDCTabIndicatorFoundation.defaultAdapter, adapter));
+ }
+
+ /** @return {!ClientRect} */
+ computeContentClientRect() {
+ return this.adapter_.computeContentClientRect();
+ }
+
+ /**
+ * Activates the indicator
+ * @param {!ClientRect=} previousIndicatorClientRect
+ * @abstract
+ */
+ activate(previousIndicatorClientRect) {} // eslint-disable-line no-unused-vars
+
+ /** @abstract */
+ deactivate() {}
+}
+
+export default MDCTabIndicatorFoundation;
diff --git a/packages/mdc-tab-indicator/index.js b/packages/mdc-tab-indicator/index.js
new file mode 100644
index 00000000000..47f120abeaa
--- /dev/null
+++ b/packages/mdc-tab-indicator/index.js
@@ -0,0 +1,92 @@
+/**
+ * @license
+ * Copyright 2018 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/component';
+
+import MDCTabIndicatorAdapter from './adapter';
+import MDCTabIndicatorFoundation from './foundation';
+
+import MDCSlidingTabIndicatorFoundation from './sliding-foundation';
+import MDCFadingTabIndicatorFoundation from './fading-foundation';
+
+/**
+ * @extends {MDCComponent}
+ * @final
+ */
+class MDCTabIndicator extends MDCComponent {
+ /**
+ * @param {!Element} root
+ * @return {!MDCTabIndicator}
+ */
+ static attachTo(root) {
+ return new MDCTabIndicator(root);
+ }
+
+ /**
+ * @param {...?} args
+ */
+ constructor(...args) {
+ super(...args);
+ /** @type {?Element} */
+ this.content_;
+ }
+
+ initialize() {
+ this.content_ = this.root_.querySelector(MDCTabIndicatorFoundation.strings.CONTENT_SELECTOR);
+ }
+
+ /**
+ * @return {!ClientRect}
+ */
+ computeContentClientRect() {
+ return this.foundation_.computeContentClientRect();
+ }
+
+ /**
+ * @return {!MDCTabIndicatorFoundation}
+ */
+ getDefaultFoundation() {
+ const adapter = /** @type {!MDCTabIndicatorAdapter} */ (Object.assign({
+ registerEventHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler),
+ deregisterEventHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler),
+ addClass: (className) => this.root_.classList.add(className),
+ removeClass: (className) => this.root_.classList.remove(className),
+ computeContentClientRect: () => this.content_.getBoundingClientRect(),
+ setContentStyleProperty: (prop, value) => this.content_.style.setProperty(prop, value),
+ }));
+
+ if (this.root_.classList.contains(MDCTabIndicatorFoundation.cssClasses.FADE)) {
+ return new MDCFadingTabIndicatorFoundation(adapter);
+ }
+
+ // Default to the sliding indicator
+ return new MDCSlidingTabIndicatorFoundation(adapter);
+ }
+
+ /**
+ * @param {!ClientRect=} previousIndicatorClientRect
+ */
+ activate(previousIndicatorClientRect) {
+ this.foundation_.activate(previousIndicatorClientRect);
+ }
+
+ deactivate() {
+ this.foundation_.deactivate();
+ }
+}
+
+export {MDCTabIndicator, MDCTabIndicatorFoundation, MDCSlidingTabIndicatorFoundation, MDCFadingTabIndicatorFoundation};
diff --git a/packages/mdc-tab-indicator/mdc-tab-indicator.scss b/packages/mdc-tab-indicator/mdc-tab-indicator.scss
new file mode 100644
index 00000000000..445403901d5
--- /dev/null
+++ b/packages/mdc-tab-indicator/mdc-tab-indicator.scss
@@ -0,0 +1,68 @@
+/**
+ * @license
+ * Copyright 2018 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/variables";
+@import "./mixins";
+
+.mdc-tab-indicator {
+ @include mdc-tab-indicator-underline-color(primary);
+ @include mdc-tab-indicator-underline-height(2px);
+ @include mdc-tab-indicator-icon-color(secondary);
+ @include mdc-tab-indicator-icon-height(34px);
+
+ display: flex;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.mdc-tab-indicator__content {
+ transform-origin: left;
+ opacity: 0;
+}
+
+.mdc-tab-indicator__content--underline {
+ align-self: flex-end;
+ width: 100%;
+}
+
+.mdc-tab-indicator__content--icon {
+ align-self: center;
+ margin: 0 auto;
+}
+
+.mdc-tab-indicator--active > .mdc-tab-indicator__content {
+ opacity: 1;
+}
+
+.mdc-tab-indicator--sliding-activate > .mdc-tab-indicator__content {
+ transition: 250ms transform $mdc-animation-standard-curve-timing-function;
+}
+
+.mdc-tab-indicator--fading-activate > .mdc-tab-indicator__content,
+.mdc-tab-indicator--fading-deactivate > .mdc-tab-indicator__content {
+ transition: 150ms opacity linear;
+}
+
+.mdc-tab-indicator--fading-activate > .mdc-tab-indicator__content {
+ transition-delay: 100ms;
+}
+
diff --git a/packages/mdc-tab-indicator/package.json b/packages/mdc-tab-indicator/package.json
new file mode 100644
index 00000000000..e954f317593
--- /dev/null
+++ b/packages/mdc-tab-indicator/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@material/tab-indicator",
+ "description": "The Material Components for the web tab indicator component",
+ "version": "0.0.0",
+ "license": "Apache-2.0",
+ "keywords": [
+ "material components",
+ "material design",
+ "tab",
+ "indicator"
+ ],
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/material-components/material-components-web.git"
+ },
+ "dependencies": {
+ "@material/animation": "^0.34.0",
+ "@material/base": "^0.35.0",
+ "@material/theme": "^0.35.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/mdc-tab-indicator/sliding-foundation.js b/packages/mdc-tab-indicator/sliding-foundation.js
new file mode 100644
index 00000000000..b601f7e8879
--- /dev/null
+++ b/packages/mdc-tab-indicator/sliding-foundation.js
@@ -0,0 +1,79 @@
+/**
+ * @license
+ * Copyright 2018 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 MDCTabIndicatorFoundation from './foundation';
+
+/**
+ * @extends {MDCTabIndicatorFoundation}
+ * @final
+ */
+class MDCSlidingTabIndicatorFoundation extends MDCTabIndicatorFoundation {
+ /** @param {...?} args */
+ constructor(...args) {
+ super(...args);
+
+ /** @private {function(?Event): undefined} */
+ this.handleTransitionEnd_ = () => this.handleTransitionEnd();
+ }
+
+ /** Handles the transitionend event */
+ handleTransitionEnd() {
+ this.adapter_.deregisterEventHandler('transitionend', this.handleTransitionEnd_);
+ this.adapter_.removeClass(MDCTabIndicatorFoundation.cssClasses.SLIDING_ACTIVATE);
+ }
+
+ /** @param {!ClientRect=} previousIndicatorClientRect */
+ activate(previousIndicatorClientRect) {
+ this.adapter_.addClass(MDCTabIndicatorFoundation.cssClasses.ACTIVE);
+
+ // Early exit if no indicator is present to handle cases where an indicator
+ // may be activated without a prior indicator state
+ if (!previousIndicatorClientRect) {
+ return;
+ }
+
+ // This animation uses the FLIP approach. You can read more about it at the link below:
+ // https://aerotwist.com/blog/flip-your-animations/
+
+ // Calculate the dimensions based on the dimensions of the previous indicator
+ const currentClientRect = this.computeContentClientRect();
+ const widthDelta = previousIndicatorClientRect.width / currentClientRect.width;
+ const xPosition = previousIndicatorClientRect.left - currentClientRect.left;
+ this.adapter_.setContentStyleProperty('transform', `translateX(${xPosition}px) scaleX(${widthDelta})`);
+
+ // Force repaint
+ this.computeContentClientRect();
+
+ // Add animating class and remove transformation in a new frame
+ requestAnimationFrame(() => {
+ this.adapter_.addClass(MDCTabIndicatorFoundation.cssClasses.SLIDING_ACTIVATE);
+ this.adapter_.setContentStyleProperty('transform', '');
+ });
+
+ this.adapter_.registerEventHandler('transitionend', this.handleTransitionEnd_);
+ }
+
+ deactivate() {
+ this.adapter_.removeClass(MDCTabIndicatorFoundation.cssClasses.ACTIVE);
+ // We remove the animating class in deactivate in case the Tab is deactivated before the animation completes and
+ // the "transitionend" handler isn't called.
+ this.adapter_.removeClass(MDCTabIndicatorFoundation.cssClasses.SLIDING_ACTIVATE);
+ this.adapter_.deregisterEventHandler('transitionend', this.handleTransitionEnd_);
+ }
+}
+
+export default MDCSlidingTabIndicatorFoundation;
diff --git a/packages/mdc-tab-scroller/README.md b/packages/mdc-tab-scroller/README.md
new file mode 100644
index 00000000000..01cc2267f85
--- /dev/null
+++ b/packages/mdc-tab-scroller/README.md
@@ -0,0 +1,117 @@
+
+
+# Tab Scroller
+
+A Tab Scroller allows for smooth native and animated scrolling of tabs.
+
+## Design & API Documentation
+
+