Skip to content

Commit

Permalink
feat(Tabs): add TabContent & TabPane components (#131)
Browse files Browse the repository at this point in the history
* feat(tabs): Adds TabContent and TabPane components #72

* feat(Tab): Adds docs for Tab (TabContent and TabPane) component

* feat(Tab): Adds test for Tab component

* feat(Tab): Adds \/tabs route to webpack.dev.config.js

* Fixes TabContent and TabPane to use contextTypes for communicating active Tab

* Updates Tabs.spec.js based on changes made to TabContent and TabPane + pushes code coverage to 100%

* Adds extra check in TabContent while setting activeTab + moves classnames out to const variables from jsx
  • Loading branch information
ajainarayanan authored and eddywashere committed Sep 11, 2016
1 parent af874bc commit 2957ede
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 2 deletions.
25 changes: 25 additions & 0 deletions docs/lib/Components/TabsPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint react/no-multi-comp: 0, react/prop-types: 0 */
import React from 'react';
import { PrismCode } from 'react-prism';
import Helmet from 'react-helmet';

import TabsExample from '../examples/Tabs';
const TabsExampleSource = require('!!raw!../examples/Tabs');

export default function TabsPage() {
return (
<div>
<Helmet title="Tabs" />
<h3>Tabs</h3>
<hr />
<div className="docs-example">
<TabsExample />
</div>
<pre>
<PrismCode className="language-jsx">
{TabsExampleSource}
</PrismCode>
</pre>
</div>
);
}
10 changes: 8 additions & 2 deletions docs/lib/Components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const ComponentLink = (props) => {
</NavItem>
);
};

const propTypes = {
children: React.PropTypes.node
};

class Components extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -95,6 +97,10 @@ class Components extends React.Component {
name: 'Pagination',
to: '/components/pagination/'
},
{
name: 'Tabs',
to: '/components/tabs/'
},
]
};
}
Expand Down Expand Up @@ -123,5 +129,5 @@ class Components extends React.Component {
);
}
}

Components.propTypes = propTypes;
export default Components;
73 changes: 73 additions & 0 deletions docs/lib/examples/Tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import { TabContent, TabPane, Nav, NavItem, NavLink, Card, Button, CardTitle, CardText, Row, Col } from 'reactstrap';
import classnames from 'classnames';

export default class Example extends React.Component {
constructor(props) {
super(props);

this.toggle = this.toggle.bind(this);
this.state = {
activeTab: '1'
};
}

toggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
render() {
return (
<div>
<Nav tabs>
<NavItem>
<NavLink
className={classnames({ active: this.state.activeTab === '1' })}
onClick={() => { this.toggle('1'); }}
>
Tab1
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({ active: this.state.activeTab === '2' })}
onClick={() => { this.toggle('2'); }}
>
Moar Tabs
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1">
<Row>
<Col sm="12">
<h4>Tab 1 Contents</h4>
</Col>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<Col sm="6">
<Card block>
<CardTitle>Special Title Treatment</CardTitle>
<CardText>With supporting text below as a natural lead-in to additional content.</CardText>
<Button>Go somewhere</Button>
</Card>
</Col>
<Col sm="6">
<Card block>
<CardTitle>Special Title Treatment</CardTitle>
<CardText>With supporting text below as a natural lead-in to additional content.</CardText>
<Button>Go somewhere</Button>
</Card>
</Col>
</Row>
</TabPane>
</TabContent>
</div>
);
}
}
2 changes: 2 additions & 0 deletions docs/lib/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import ModalsPage from './Components/ModalsPage';
import CardPage from './Components/CardPage';
import TablesPage from './Components/TablesPage';
import PaginationPage from './Components/PaginationPage';
import TabsPage from './Components/TabsPage';
import NotFound from './NotFound';
import Components from './Components';
import UI from './UI';
Expand Down Expand Up @@ -48,6 +49,7 @@ const routes = (
<Route path="navbar/" component={NavbarPage} />
<Route path="media/" component={MediaPage} />
<Route path="pagination/" component={PaginationPage} />
<Route path="tabs/" component={TabsPage} />
</Route>
<Route path="*" component={NotFound} />
</Route>
Expand Down
43 changes: 43 additions & 0 deletions src/TabContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { PropTypes, Component } from 'react';
import classnames from 'classnames';

const propTypes = {
children: PropTypes.node,
activeTab: PropTypes.any,
className: PropTypes.string
};

const childContextTypes = {
activeTabId: PropTypes.any
};

export default class TabContent extends Component {
constructor(props) {
super(props);
this.state = {
activeTab: this.props.activeTab
};
}
getChildContext() {
return {
activeTabId: this.state.activeTab
};
}
componentWillReceiveProps(nextProps) {
if (this.state.activeTab !== nextProps.activeTab) {
this.setState({
activeTab: nextProps.activeTab
});
}
}
render() {
const classes = classnames('tab-content', this.props.className);
return (
<div className={classes}>
{ this.props.children }
</div>
);
}
}
TabContent.propTypes = propTypes;
TabContent.childContextTypes = childContextTypes;
29 changes: 29 additions & 0 deletions src/TabPane.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

import React, { PropTypes } from 'react';
import classnames from 'classnames';

const propTypes = {
children: PropTypes.node,
className: PropTypes.string,
tabId: PropTypes.any
};
const contextTypes = {
activeTabId: PropTypes.any
};

export default function TabPane(props, context) {
const {
className,
tabId,
children,
...attributes
} = props;
const classes = classnames('tab-pane', className, { active: tabId === context.activeTabId });
return (
<div {...attributes} className={classes}>
{children}
</div>
);
}
TabPane.propTypes = propTypes;
TabPane.contextTypes = contextTypes;
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import Media from './Media';
import Pagination from './Pagination';
import PaginationItem from './PaginationItem';
import PaginationLink from './PaginationLink';
import TabContent from './TabContent';
import TabPane from './TabPane';

export {
Container,
Expand Down Expand Up @@ -120,4 +122,6 @@ export {
Pagination,
PaginationItem,
PaginationLink,
TabContent,
TabPane
};
60 changes: 60 additions & 0 deletions test/Tabs.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* eslint react/no-multi-comp: 0, react/prop-types: 0 */
import React from 'react';
import ReactDOM from 'react-dom';

import { mount } from 'enzyme';
import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap';
import classnames from 'classnames';
// Not sure if this is correct but didn't want to repeat a whole bunch of code.
import TabsExample from '../docs/lib/examples/Tabs';

describe('Tabs', () => {
it('should render', () => {
let tab1 = mount(<TabsExample />);
expect(tab1.find('.nav .nav-tabs').length).toBe(1);
expect(tab1.find('.nav .nav-item').length).toBe(2);
expect(tab1.find('.tab-content').length).toBe(1);
expect(tab1.find('.tab-pane').length).toBe(2);
});
it('should have tab1 as active', () => {
let tab1 = mount(<TabsExample />);
expect(tab1.find('.nav .nav-item .nav-link').at(0).hasClass('active')).toBe(true);
expect(tab1.find('.nav .nav-item .nav-link').at(1).hasClass('active')).toBe(false);
expect(tab1.find('.tab-content .tab-pane').at(0).hasClass('active')).toBe(true);
});
it('should switch to tab2 as active when clicked', () => {
let tab1 = mount(<TabsExample />);
expect(tab1.find('.nav .nav-item .nav-link').at(0).hasClass('active')).toBe(true);
expect(tab1.find('.nav .nav-item .nav-link').at(1).hasClass('active')).toBe(false);
expect(tab1.find('.tab-content .tab-pane').at(0).hasClass('active')).toBe(true);
tab1.find('.nav .nav-item .nav-link').at(1).simulate('click');
expect(tab1.find('.nav .nav-item .nav-link').at(0).hasClass('active')).toBe(false);
expect(tab1.find('.nav .nav-item .nav-link').at(1).hasClass('active')).toBe(true);
expect(tab1.find('.tab-content .tab-pane').at(1).hasClass('active')).toBe(true);
tab1.find('.nav .nav-item .nav-link').at(0).simulate('click');
});
it('should show no active tabs if active tab id is unknown', () => {
let tab1 = mount(<TabsExample />);
const instance = tab1.instance();
expect(instance instanceof TabsExample).toBe(true);
instance.toggle('3');
/* Not sure if this is what we want. Toggling to an unknown tab id should
render all tabs as inactive and should show no content.
This could be a warning during development that the user is not having a proper tab ids.
NOTE: Should this be different?
*/
expect(tab1.find('.nav .nav-item .nav-link').at(0).hasClass('active')).toBe(false);
expect(tab1.find('.nav .nav-item .nav-link').at(1).hasClass('active')).toBe(false);
expect(tab1.find('.tab-content .tab-pane').at(0).hasClass('active')).toBe(false);
});
it('should do nothing clicking on the same tab', () => {
let tab1 = mount(<TabsExample />);
expect(tab1.find('.nav .nav-item .nav-link').at(0).hasClass('active')).toBe(true);
expect(tab1.find('.nav .nav-item .nav-link').at(1).hasClass('active')).toBe(false);
expect(tab1.find('.tab-content .tab-pane').at(0).hasClass('active')).toBe(true);
tab1.find('.nav .nav-item .nav-link').at(0).simulate('click');
expect(tab1.find('.nav .nav-item .nav-link').at(0).hasClass('active')).toBe(true);
expect(tab1.find('.nav .nav-item .nav-link').at(1).hasClass('active')).toBe(false);
expect(tab1.find('.tab-content .tab-pane').at(0).hasClass('active')).toBe(true);
});
});
1 change: 1 addition & 0 deletions webpack.dev.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var paths = [
'/components/tables/',
'/components/media/',
'/components/pagination/',
'/components/tabs/',
'/404.html'
];

Expand Down

0 comments on commit 2957ede

Please sign in to comment.