Inject and use FontIcon and minor rendering changes for Tabs

old
Javi Velasco 2016-11-22 09:45:38 +01:00
parent b529e04f75
commit ad0bcc33d5
4 changed files with 85 additions and 90 deletions

View File

@ -2,10 +2,11 @@ import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import { themr } from 'react-css-themr';
import { TABS } from '../identifiers.js';
import InjectFontIcon from '../font_icon/FontIcon.js';
import InjectTab from './Tab.js';
import InjectTabContent from './TabContent.js';
const factory = (Tab, TabContent) => {
const factory = (Tab, TabContent, FontIcon) => {
class Tabs extends Component {
static propTypes = {
children: PropTypes.node,
@ -38,13 +39,12 @@ const factory = (Tab, TabContent) => {
};
componentDidMount () {
!this.props.disableAnimatedBottomBorder && this.updatePointer(this.props.index);
window.addEventListener('resize', this.handleResize);
this.handleResize();
}
componentWillReceiveProps (nextProps) {
!this.props.disableAnimatedBottomBorder && this.updatePointer(nextProps.index);
this.updatePointer(nextProps.index);
}
componentWillUnmount () {
@ -60,16 +60,52 @@ const factory = (Tab, TabContent) => {
};
handleResize = () => {
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
}
this.resizeTimeout = setTimeout(this.handleResizeEnd, 50);
if (this.resizeTimeout) clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(() => {
this.updatePointer(this.props.index);
this.updateArrows();
}, 100);
};
handleResizeEnd = () => {
this.updatePointer(this.props.index);
this.updateArrows();
};
updatePointer = idx => {
if (this.navigationNode && this.navigationNode.children[idx]) {
const nav = this.navigationNode.getBoundingClientRect();
const label = this.navigationNode.children[idx].getBoundingClientRect();
const scrollLeft = this.navigationNode.scrollLeft;
this.setState({
pointer: {
top: `${nav.height}px`,
left: `${label.left - nav.left + scrollLeft}px`,
width: `${label.width}px`
}
});
}
}
updateArrows = () => {
const nav = this.navigationNode;
this.setState({
arrows: {
left: nav.scrollLeft > 0,
right: nav.scrollWidth > nav.clientWidth
&& (nav.scrollLeft + nav.clientWidth) < nav.scrollWidth
}
});
}
scrollNavigation = (factor) => {
const oldScrollLeft = this.navigationNode.scrollLeft;
this.navigationNode.scrollLeft += factor * this.navigationNode.clientWidth;
if (this.navigationNode.scrollLeft !== oldScrollLeft) {
this.updateArrows();
}
}
scrollRight = () =>
this.scrollNavigation(-1);
scrollLeft = () =>
this.scrollNavigation(+1);
parseChildren () {
const headers = [];
@ -89,51 +125,6 @@ const factory = (Tab, TabContent) => {
return {headers, contents};
}
updatePointer (idx) {
clearTimeout(this.pointerTimeout);
this.pointerTimeout = setTimeout(() => {
const nav = this.refs.navigation.getBoundingClientRect();
const label = this.refs.navigation.children[idx].getBoundingClientRect();
const scrollLeft = this.refs.navigation.scrollLeft;
this.setState({
pointer: {
top: `${nav.height}px`,
left: `${label.left - nav.left + scrollLeft}px`,
width: `${label.width}px`
}
});
}, 20);
}
updateArrows () {
clearTimeout(this.arrowsTimeout);
this.arrowsTimeout = setTimeout(() => {
const nav = this.refs.navigation;
this.setState({
arrows: {
left: nav.scrollLeft > 0,
right: nav.scrollWidth > nav.clientWidth && (nav.scrollLeft + nav.clientWidth) < nav.scrollWidth
}
});
}, 20);
}
scrollNavigationLeft = () => {
const oldScrollLeft = this.refs.navigation.scrollLeft;
this.refs.navigation.scrollLeft -= this.refs.navigation.clientWidth;
if (this.refs.navigation.scrollLeft !== oldScrollLeft) {
this.updateArrows();
}
}
scrollNavigationRight = () => {
const oldScrollLeft = this.refs.navigation.scrollLeft;
this.refs.navigation.scrollLeft += this.refs.navigation.clientWidth;
if (this.refs.navigation.scrollLeft !== oldScrollLeft) {
this.updateArrows();
}
}
renderHeaders (headers) {
return headers.map((item, idx) => {
return React.cloneElement(item, {
@ -160,37 +151,37 @@ const factory = (Tab, TabContent) => {
});
});
if (this.props.hideMode === 'display') {
return contentElements;
}
return contentElements.filter((item, idx) => (idx === this.props.index));
return this.props.hideMode === 'display'
? contentElements
: contentElements.filter((item, idx) => (idx === this.props.index));
}
render () {
const { className, theme, fixed, inverse } = this.props;
const { className, disableAnimatedBottomBorder, theme, fixed, inverse } = this.props;
const { left: hasLeftArrow, right: hasRightArrow } = this.state.arrows;
const { headers, contents } = this.parseChildren();
const classes = classnames(
theme.tabs,
className,
{
[theme.fixed]: fixed,
[theme.inverse]: inverse
}
);
const classNamePointer = classnames(theme.pointer, {
[theme.disableAnimation]: disableAnimatedBottomBorder
});
const classNames = classnames(theme.tabs, {
[theme.fixed]: fixed,
[theme.inverse]: inverse
}, className);
return (
<div ref='tabs' data-react-toolbox='tabs' className={classes}>
<div data-react-toolbox='tabs' className={classNames}>
<div className={theme.navigationContainer}>
<div className={classnames(theme.arrowContainer, { [theme.invisible]: !(this.state.arrows.left || this.state.arrows.right) })} onClick={this.scrollNavigationLeft}>
<span className={classnames('material-icons', theme.arrow, { [theme.invisible]: !this.state.arrows.left })}>keyboard_arrow_left</span>
</div>
<nav className={theme.navigation} ref='navigation'>
{hasLeftArrow && <div className={theme.arrowContainer} onClick={this.scrollRight}>
<FontIcon className={theme.arrow} value="keyboard_arrow_left" />
</div>}
<nav className={theme.navigation} ref={node => {this.navigationNode = node; }}>
{this.renderHeaders(headers)}
<span className={theme.pointer} style={this.state.pointer} />
<span className={classNamePointer} style={this.state.pointer} />
</nav>
<div className={classnames(theme.arrowContainer, { [theme.invisible]: !(this.state.arrows.left || this.state.arrows.right) })} onClick={this.scrollNavigationRight}>
<span className={classnames('material-icons', theme.arrow, { [theme.invisible]: !this.state.arrows.right })}>keyboard_arrow_right</span>
</div>
{hasRightArrow && <div className={theme.arrowContainer} onClick={this.scrollLeft}>
<FontIcon className={theme.arrow} value="keyboard_arrow_right" />
</div>}
</div>
{this.renderContents(contents)}
</div>
@ -201,7 +192,7 @@ const factory = (Tab, TabContent) => {
return Tabs;
};
const Tabs = factory(InjectTab, InjectTabContent);
const Tabs = factory(InjectTab, InjectTabContent, InjectFontIcon);
export default themr(TABS)(Tabs);
export { factory as tabsFactory };
export { Tabs };

View File

@ -3,12 +3,13 @@ import { TABS } from '../identifiers.js';
import { tabsFactory } from './Tabs.js';
import { TabContent } from './TabContent.js';
import { Tab } from './Tab.js';
import FontIcon from '../font_icon/FontIcon.js';
import theme from './theme.scss';
const applyTheme = (Component) => themr(TABS, theme)(Component);
const ThemedTabContent = applyTheme(TabContent);
const ThemedTab = applyTheme(Tab);
const ThemedTabs = applyTheme(tabsFactory(ThemedTab, ThemedTabContent));
const ThemedTabs = applyTheme(tabsFactory(ThemedTab, ThemedTabContent, FontIcon));
export { ThemedTab as Tab };
export { ThemedTabs as Tabs };

View File

@ -27,10 +27,6 @@
.arrow {
padding: 0 $tab-label-h-padding;
color: $tab-text-color;
&.invisible {
visibility: hidden;
}
}
.arrowContainer {
@ -38,10 +34,6 @@
align-items: center;
cursor: pointer;
box-shadow: inset 0 -1px $tab-navigation-border-color;
&.invisible {
display: none;
}
}
.label {
@ -55,15 +47,19 @@
transition-timing-function: $animation-curve-default;
transition-duration: $animation-duration;
transition-property: box-shadow, color;
&.active {
color: $tab-text-color;
}
&.disabled {
opacity: $tab-label-disabled-opacity;
}
&:not(.disabled) {
cursor: pointer;
}
&.hidden {
display: none;
}
@ -96,15 +92,21 @@
transition-timing-function: $animation-curve-default;
transition-duration: $animation-duration;
transition-property: left, width;
&.disableAnimation {
transition: none;
}
}
.tab {
display: flex;
flex-direction: column;
padding: $tab-label-v-padding $tab-label-h-padding;
&:not(.active) {
display: none;
}
&.active {
display: flex;
}
@ -124,6 +126,7 @@
.label {
color: $tab-inverse-text-inactive-color;
&.active {
color: $tab-inverse-text-color;
}

View File

@ -29,7 +29,7 @@ class TabsTest extends React.Component {
<section>
<h5>Tabs</h5>
<p>This tabs can be disabled or hidden</p>
<Tabs index={this.state.index} onChange={this.handleTabChange}>
<Tabs disableAnimatedBottomBorder index={this.state.index} onChange={this.handleTabChange}>
<Tab label='Primary'><small>Primary content</small></Tab>
<Tab label='Secondary' onActive={this.handleActive}><small>Secondary content</small></Tab>
<Tab label='Third' disabled><small>Disabled content</small></Tab>