Skip to content
This repository was archived by the owner on Jul 29, 2025. It is now read-only.

Commit 220c823

Browse files
author
Matt Goo
authored
feat(tab-indicator): add new component (#202)
1 parent e619e52 commit 220c823

File tree

18 files changed

+545
-1
lines changed

18 files changed

+545
-1
lines changed

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"ripple",
3636
"top-app-bar",
3737
"infrastructure",
38+
"tab-indicator",
3839
"text-field"
3940
],
4041
"validate": true
@@ -52,6 +53,7 @@
5253
"@material/list": "^0.38.0",
5354
"@material/notched-outline": "^0.38.0",
5455
"@material/ripple": "^0.38.0",
56+
"@material/tab-indicator": "^0.38.0",
5557
"@material/select": "^0.38.0",
5658
"@material/textfield": "^0.38.0",
5759
"@material/top-app-bar": "^0.38.0",

packages/tab-indicator/.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
index.js

packages/tab-indicator/README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# React Tab Indicator
2+
3+
A React version of an [MDC Tab Indicator](https://github.com/material-components/material-components-web/tree/master/packages/mdc-tab-indicator).
4+
5+
## Installation
6+
7+
```
8+
npm install @material/react-tab-indicator
9+
```
10+
11+
## Usage
12+
13+
### Styles
14+
15+
with Sass:
16+
```js
17+
import '@material/react-tab-indicator/index.scss';
18+
```
19+
20+
with CSS:
21+
```js
22+
import '@material/react-tab-indicator/dist/tab-indicator.css';
23+
```
24+
25+
### Javascript Instantiation
26+
27+
#### With an Underline (default)
28+
29+
```js
30+
import React from 'react';
31+
import TabIndicator from '@material/react-tab-indicator';
32+
33+
class MyApp extends React.Component {
34+
state = {active: false};
35+
36+
render() {
37+
return (
38+
<div>
39+
<TabIndicator active={this.state.active} />
40+
</div>
41+
);
42+
}
43+
}
44+
```
45+
46+
47+
#### With Icon
48+
49+
If you want the underline instead of an icon, pass the icon element as a child
50+
of the Tab Indicator component.
51+
52+
```js
53+
import React from 'react';
54+
import TabIndicator from '@material/react-tab-indicator';
55+
56+
class MyApp extends React.Component {
57+
state = {active: false};
58+
59+
render() {
60+
return (
61+
<div>
62+
<TabIndicator
63+
active={this.state.active}
64+
icon
65+
>
66+
<MaterialIcon icon='star' />
67+
</TabIndicator>
68+
</div>
69+
);
70+
}
71+
}
72+
```
73+
74+
## Props
75+
76+
Prop Name | Type | Description
77+
--- | --- | ---
78+
active | boolean | If true will activate the indicator.
79+
className | string | Classes to appear on className attribute of root element.
80+
fade | boolean | If enabled will use the fade animation for transitioning to other tabs.
81+
icon | boolean | Indicates that the indicator is an icon instead of an underline.
82+
previousIndicatorClientRect | ClientRect | The indicator's clientRect that was previously activated.
83+
onTransitionEnd | function | transitionend event callback handler.
84+
85+
## Sass Mixins
86+
87+
Sass mixins may be available to customize various aspects of the components. Please refer to the
88+
MDC Web repository for more information on what mixins are available, and how to use them.
89+
90+
[Advanced Sass Mixins](https://github.com/material-components/material-components-web/blob/master/packages/mdc-tab-indicator/README.md#sass-mixins)

packages/tab-indicator/index.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import React, {Component} from 'react';
2+
import PropTypes from 'prop-types';
3+
import classnames from 'classnames';
4+
5+
import {
6+
MDCFadingTabIndicatorFoundation,
7+
MDCSlidingTabIndicatorFoundation,
8+
} from '@material/tab-indicator/dist/mdc.tabIndicator';
9+
10+
export default class TabIndicator extends Component {
11+
tabIndicatorContentElement_ = React.createRef();
12+
allowTransitionEnd_ = false;
13+
14+
state = {
15+
classList: new Set(),
16+
contentStyle: {},
17+
};
18+
19+
componentDidMount() {
20+
if (this.props.fade) {
21+
this.foundation_ = new MDCFadingTabIndicatorFoundation(this.adapter);
22+
} else {
23+
this.foundation_ = new MDCSlidingTabIndicatorFoundation(this.adapter);
24+
}
25+
this.foundation_.init();
26+
}
27+
28+
componentWillUnmount() {
29+
this.foundation_.destroy();
30+
}
31+
32+
componentDidUpdate(prevProps) {
33+
if (this.props.active !== prevProps.active) {
34+
this.allowTransitionEnd_ = true;
35+
if (this.props.active) {
36+
this.foundation_.activate(this.props.previousIndicatorClientRect);
37+
} else {
38+
this.foundation_.deactivate();
39+
}
40+
}
41+
}
42+
43+
get classes() {
44+
const {classList} = this.state;
45+
const {active, className, fade} = this.props;
46+
return classnames('mdc-tab-indicator', Array.from(classList), className, {
47+
'mdc-tab-indicator--fade': fade,
48+
'mdc-tab-indicator--active': active,
49+
});
50+
}
51+
52+
get contentClasses() {
53+
const {icon} = this.props;
54+
return classnames('mdc-tab-indicator__content', {
55+
'mdc-tab-indicator__content--icon': icon,
56+
'mdc-tab-indicator__content--underline': !icon,
57+
});
58+
}
59+
60+
get adapter() {
61+
return {
62+
addClass: (className) => {
63+
const classList = new Set(this.state.classList);
64+
classList.add(className);
65+
this.setState({classList});
66+
},
67+
removeClass: (className) => {
68+
const classList = new Set(this.state.classList);
69+
classList.delete(className);
70+
this.setState({classList});
71+
},
72+
computeContentClientRect:
73+
() => this.tabIndicatorContentElement_.current
74+
&& this.tabIndicatorContentElement_.current.getBoundingClientRect(),
75+
setContentStyleProperty: (prop, value) => this.setState({contentStyle: {[prop]: value}}),
76+
};
77+
}
78+
79+
handleTransitionEnd = (e) => {
80+
this.props.onTransitionEnd(e);
81+
82+
if (!this.allowTransitionEnd_) return;
83+
84+
this.allowTransitionEnd_ = false;
85+
this.foundation_.handleTransitionEnd();
86+
}
87+
88+
render() {
89+
const {
90+
/* eslint-disable */
91+
active,
92+
children,
93+
className,
94+
fade,
95+
icon,
96+
onTransitionEnd,
97+
previousIndicatorClientRect,
98+
/* eslint-enable */
99+
...otherProps
100+
} = this.props;
101+
102+
return (
103+
<span
104+
className={this.classes}
105+
onTransitionEnd={this.handleTransitionEnd}
106+
{...otherProps}
107+
>
108+
{this.renderContent()}
109+
</span>
110+
);
111+
}
112+
113+
addContentClassesToChildren = () => {
114+
const child = React.Children.only(this.props.children);
115+
const className = classnames(child.props.className, this.contentClasses);
116+
const props = Object.assign({}, child.props, {className});
117+
return React.cloneElement(child, props);
118+
};
119+
120+
renderContent() {
121+
if (this.props.children) {
122+
return this.addContentClassesToChildren();
123+
}
124+
return (
125+
<span
126+
className={this.contentClasses}
127+
ref={this.tabIndicatorContentElement_}
128+
style={this.state.contentStyle}
129+
/>
130+
);
131+
}
132+
}
133+
134+
TabIndicator.propTypes = {
135+
active: PropTypes.bool,
136+
className: PropTypes.string,
137+
children: PropTypes.element,
138+
fade: PropTypes.bool,
139+
icon: PropTypes.bool,
140+
previousIndicatorClientRect: PropTypes.object,
141+
onTransitionEnd: PropTypes.func,
142+
};
143+
144+
TabIndicator.defaultProps = {
145+
active: false,
146+
className: '',
147+
children: null,
148+
fade: false,
149+
icon: false,
150+
previousIndicatorClientRect: {},
151+
onTransitionEnd: () => {},
152+
};

packages/tab-indicator/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import "@material/tab-indicator/mdc-tab-indicator";

packages/tab-indicator/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@material/react-tab-indicator",
3+
"version": "0.0.0",
4+
"description": "Material Components React Tab Indicator",
5+
"license": "Apache-2.0",
6+
"main": "dist/index.js",
7+
"keywords": [
8+
"mdc web react",
9+
"material components react",
10+
"material design",
11+
"tab indicator",
12+
"tabindicator",
13+
"tabs"
14+
],
15+
"repository": {
16+
"type": "git",
17+
"url": "https://github.com/material-components/material-components-web-react.git"
18+
},
19+
"dependencies": {
20+
"@material/tab-indicator": "^0.38.0",
21+
"classnames": "^2.2.5",
22+
"prop-types": "^15.6.1",
23+
"react": "^16.3.2"
24+
},
25+
"publishConfig": {
26+
"access": "public"
27+
}
28+
}

packages/webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ function getMaterialExternals() {
8282
'notched-outline',
8383
'ripple',
8484
'select',
85+
'tab-indicator',
8586
'textfield',
8687
'top-app-bar',
8788
'typography',

test/screenshot/full-suite.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default [
88
require('./material-icon/screenshot-suite').default,
99
require('./notched-outline/screenshot-suite').default,
1010
require('./select/screenshot-suite').default,
11+
require('./tab-indicator/screenshot-suite').default,
1112
require('./text-field/screenshot-suite').default,
1213
require('./text-field/helper-text/screenshot-suite').default,
1314
require('./text-field/icon/screenshot-suite').default,

test/screenshot/golden.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818
"text-field/index.html": "5d753f3ebcc1ccfebf07057b87e579fa21448b0bb98329212d8420bcae21330e",
1919
"top-app-bar/fixed.html": "90534d59d40f8c050aae022e94b0afa022a00d1e00c19e3f92dcc1908efcd831",
2020
"chips/index.html": "949725ee6658e0c4edcdecd2bf057fda5fb814473f7a527da5c96e6392b84015",
21-
"select/index.html": "5ce288649fe5e78d21ab9315a35cc2a22504176b8abaf56b92579121df51f073"
21+
"select/index.html": "5ce288649fe5e78d21ab9315a35cc2a22504176b8abaf56b92579121df51f073",
22+
"tab-indicator/index.html": "19f1befc0947128b46d84e1fdee73741bffcd63950bf0cddaadd9ae283ab85f8"
2223
}

0 commit comments

Comments
 (0)