Skip to content

Commit

Permalink
feat(tab-bar): Add component (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
bonniezhou authored Sep 17, 2018
1 parent 3eb8a4b commit 731e980
Show file tree
Hide file tree
Showing 17 changed files with 770 additions and 109 deletions.
91 changes: 84 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@
"@material/select": "^0.39.0",
"@material/switch": "^0.39.0",
"@material/tab": "^0.39.0",
"@material/tab-bar": "^0.39.0",
"@material/tab-indicator": "^0.39.0",
"@material/tab-scroller": "^0.39.0",
"@material/textfield": "^0.39.0",
"@material/top-app-bar": "^0.39.0",
"@material/typography": "^0.39.0",
Expand Down
1 change: 1 addition & 0 deletions packages/tab-bar/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
index.js
70 changes: 70 additions & 0 deletions packages/tab-bar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# React Tab Bar

A React version of an [MDC Tab Bar](https://github.com/material-components/material-components-web/tree/master/packages/mdc-tab-bar).

## Installation

```
npm install @material/react-tab-bar
```

## Usage

### Styles

with Sass:
```scss
import '@material/react-tab-bar/index.scss';
```

with CSS:
```css
import '@material/react-tab-bar/dist/tab-bar.css';
```

### Javascript Instantiation

```js
import React from 'react';
import Tab from '@material/react-tab';
import TabBar from '@material/react-tab-bar';

class MyApp extends React.Component {
state = {activeIndex: 0};

render() {
return (
<div>
<TabBar
activeIndex={this.state.activeIndex}
handleActiveIndexUpdate={(activeIndex) => this.setState({activeIndex})}
>
<Tab>
<span className='mdc-tab__text-label'>One</span>
</Tab>
...
</TabBar>
</div>
);
}
}
```

> _NOTE_: You can also use a custom tab component with the `TabBar`, but it must implement the methods `activate`, `deactivate`, `focus`, `computeIndicatorClientRect`, and `computeDimensions`. See [`MDCTab` documentation](https://github.com/material-components/material-components-web/blob/master/packages/mdc-tab/README.md#mdctab-properties-and-methods) for more details.
## Props

Prop Name | Type | Description
--- | --- | ---
activeIndex | number | Index of the active tab.
indexInView | number | Index of the tab to be scrolled into view.
handleActiveIndexUpdate | Function(activeIndex: number) => void | Callback after the active index is updated.
className | string | Classes to appear on className attribute of root element.
isRtl | Boolean | Whether the direction of the tab bar is RTL.

## Sass Mixins

Sass mixins may be available to customize various aspects of the components. Please refer to the
MDC Web repository for more information on what mixins are available, and how to use them.

[Advanced Sass Mixins](https://github.com/material-components/material-components-web/blob/master/packages/mdc-tab-bar/README.md#sass-mixins)
165 changes: 165 additions & 0 deletions packages/tab-bar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';

import TabScroller from '@material/react-tab-scroller';
import {MDCTabBarFoundation} from '@material/tab-bar/dist/mdc.tabBar';

export default class TabBar extends Component {
tabBarElement_ = React.createRef();
tabScroller_ = React.createRef();
tabList_ = [];

foundation_ = null;
state = {
previousActiveIndex: -1,
}

componentDidMount() {
this.foundation_ = new MDCTabBarFoundation(this.adapter);
this.foundation_.init();

const {
activeIndex,
indexInView,
} = this.props;
if (this.tabList_[activeIndex]) {
this.tabList_[activeIndex].activate({} /* previousIndicatorClientRect */);
}
this.foundation_.scrollIntoView(indexInView);
}

componentDidUpdate(prevProps) {
if (this.props.activeIndex !== prevProps.activeIndex) {
this.foundation_.activateTab(this.props.activeIndex);
}
if (this.props.indexInView !== prevProps.indexInView) {
this.foundation_.scrollIntoView(this.props.indexInView);
}
}

componentWillUnmount() {
this.foundation_.destroy();
}

get classes() {
return classnames('mdc-tab-bar', this.props.className);
}

get adapter() {
return {
scrollTo: (scrollX) => this.tabScroller_.current.scrollTo(scrollX),
incrementScroll: (scrollXIncrement) => this.tabScroller_.current.incrementScroll(scrollXIncrement),
getScrollPosition: () => this.tabScroller_.current.getScrollPosition(),
getScrollContentWidth: () => this.tabScroller_.current.getScrollContentWidth(),
getOffsetWidth: () => this.tabBarElement_.current.offsetWidth,
isRTL: () => !!this.props.isRtl,
setActiveTab: (index) => this.props.handleActiveIndexUpdate(index),
activateTabAtIndex: (index, clientRect) => this.tabList_[index].activate(clientRect),
deactivateTabAtIndex: (index) => this.tabList_[index].deactivate(),
focusTabAtIndex: (index) => this.tabList_[index].focus(),
getTabIndicatorClientRectAtIndex: (index) => this.tabList_[index].computeIndicatorClientRect(),
getTabDimensionsAtIndex: (index) => this.tabList_[index].computeDimensions(),
getPreviousActiveTabIndex: () => this.state.previousActiveIndex,
getFocusedTabIndex: () => {
const activeElement = document.activeElement;
this.tabList_.forEach((tabList, index) => {
if (tabList.tabElement_.current === activeElement) {
return index;
}
});
},
getIndexOfTab: (tabToFind) => this.tabList_.indexOf(tabToFind),
getTabListLength: () => this.tabList_.length,
};
}

pushToTabList = (el) => {
this.tabList_.push(el);
}

onKeyDown = (e) => {
// Persist the synthetic event to access its `key`.
e.persist();
this.setState(
{previousActiveIndex: this.props.activeIndex},
() => this.foundation_.handleKeyDown(e));
this.props.onKeyDown(e);
}

render() {
const {
/* eslint-disable no-unused-vars */
className,
indexInView,
activeIndex,
handleActiveIndexUpdate,
onKeyDown,
/* eslint-enable no-unused-vars */
isRtl,
children,
...otherProps
} = this.props;

return (
<div
dir={isRtl ? 'rtl' : 'ltr'}
className={this.classes}
role='tablist'
onKeyDown={this.onKeyDown}
ref={this.tabBarElement_}
{...otherProps}
>
<TabScroller ref={this.tabScroller_}>
{React.Children.map(children, this.renderTab)}
</TabScroller>
</div>
);
}

renderTab = (tab, index) => {
const {
children,
onClick, // eslint-disable-line no-unused-vars
...otherProps
} = tab.props;

const props = {
onClick: (e) => {
this.setState(
{previousActiveIndex: this.props.activeIndex},
() => this.adapter.setActiveTab(index));
this.props.onClick(e);
},
ref: this.pushToTabList,
...otherProps,
};

return React.cloneElement(tab, props, children);
}
}

TabBar.propTypes = {
indexInView: PropTypes.number,
activeIndex: PropTypes.number,
handleActiveIndexUpdate: PropTypes.func,
className: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.element),
PropTypes.element,
]),
onClick: PropTypes.func,
onKeyDown: PropTypes.func,
isRtl: PropTypes.bool,
};

TabBar.defaultProps = {
indexInView: 0,
activeIndex: 0,
handleActiveIndexUpdate: () => {},
className: '',
children: [],
onClick: () => {},
onKeyDown: () => {},
isRtl: false,
};
1 change: 1 addition & 0 deletions packages/tab-bar/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "@material/tab-bar/mdc-tab-bar";
29 changes: 29 additions & 0 deletions packages/tab-bar/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@material/react-tab-bar",
"version": "0.0.0",
"description": "Material Components React Tab Bar",
"license": "Apache-2.0",
"main": "dist/index.js",
"keywords": [
"mdc web react",
"material components react",
"material design",
"tab bar",
"tabbar",
"tabs"
],
"repository": {
"type": "git",
"url": "https://github.com/material-components/material-components-web-react.git"
},
"dependencies": {
"@material/react-tab-scroller": "^0.0.0",
"@material/tab-bar": "^0.39.0",
"classnames": "^2.2.5",
"prop-types": "^15.6.1",
"react": "^16.3.2"
},
"publishConfig": {
"access": "public"
}
}
Loading

0 comments on commit 731e980

Please sign in to comment.