Last active
September 3, 2021 06:06
-
-
Save diegocasmo/5cd978e9c5695aefca0c6a8a19fa4c69 to your computer and use it in GitHub Desktop.
Source code for implementing a React <Tabs/> component.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {PropTypes} from 'react'; | |
export const Tab = (props) => { | |
return ( | |
<li className="tab"> | |
<a className={`tab-link ${props.linkClassName} ${props.isActive ? 'active' : ''}`} | |
onClick={(event) => { | |
event.preventDefault(); | |
props.onClick(props.tabIndex); | |
}}> | |
<i className={`tab-icon ${props.iconClassName}`}/> | |
</a> | |
</li> | |
) | |
} | |
Tab.propTypes = { | |
onClick : PropTypes.func, | |
tabIndex : PropTypes.number, | |
isActive : PropTypes.bool, | |
iconClassName: PropTypes.string.isRequired, | |
linkClassName: PropTypes.string.isRequired | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import {mount} from 'enzyme'; | |
import {Tab} from '../Tab'; | |
describe('<Tab/>', () => { | |
let Component = null; | |
let onClickHandler = null; | |
beforeEach(() => { | |
onClickHandler = jasmine.createSpy('onClickHandler'); | |
Component = mount( | |
<Tab onClick={onClickHandler} | |
tabIndex={2} | |
isActive={true} | |
iconClassName={'foo'} | |
linkClassName={'test'}/> | |
); | |
}); | |
it('should render', () => { | |
expect(Component.length).toBeTruthy(); | |
}); | |
it('should call onClick() prop when link is clicked', () => { | |
Component.find('.tab-link').simulate('click', {preventDefault: () => {}}); | |
expect(onClickHandler).toHaveBeenCalledWith(Component.props().tabIndex); | |
}); | |
it("should add '.active' className to component if tab is active", () => { | |
const Component = mount( | |
<Tab isActive={true} | |
iconClassName={'foo'} | |
linkClassName={'test'}/> | |
); | |
expect(Component.find('.tab-link').hasClass('active')).toBeTruthy(); | |
}); | |
it("should not add '.active' className to component if tab is inactive", () => { | |
const Component = mount( | |
<Tab isActive={false} | |
iconClassName={'foo'} | |
linkClassName={'test'}/> | |
); | |
expect(Component.find('.tab-link').hasClass('active')).toBeFalsy(); | |
}); | |
it('should add correct className to tab link', () => { | |
expect(Component.find('.tab-link').hasClass(Component.props().linkClassName)).toBeTruthy(); | |
}); | |
it('should add correct className to tab icon', () => { | |
expect(Component.find('.tab-icon').hasClass(Component.props().iconClassName)).toBeTruthy(); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {Component, PropTypes} from 'react'; | |
export class Tabs extends Component { | |
constructor(props, context) { | |
super(props, context); | |
this.state = { | |
activeTabIndex: this.props.defaultActiveTabIndex | |
}; | |
this.handleTabClick = this.handleTabClick.bind(this); | |
} | |
handleTabClick(tabIndex) { | |
this.setState({ | |
activeTabIndex: tabIndex === this.state.activeTabIndex ? this.props.defaultActiveTabIndex : tabIndex | |
}); | |
} | |
// Encapsulate <Tabs/> component API as props for <Tab/> children | |
renderChildrenWithTabsApiAsProps() { | |
return React.Children.map(this.props.children, (child, index) => { | |
return React.cloneElement(child, { | |
onClick : this.handleTabClick, | |
tabIndex: index, | |
isActive: index === this.state.activeTabIndex | |
}); | |
}); | |
} | |
// Render current active tab content | |
renderActiveTabContent() { | |
const {children} = this.props; | |
const {activeTabIndex} = this.state; | |
if(children[activeTabIndex]) { | |
return children[activeTabIndex].props.children; | |
} | |
} | |
render() { | |
return ( | |
<div className="tabs"> | |
<ul className="tabs-nav nav navbar-nav navbar-left"> | |
{this.renderChildrenWithTabsApiAsProps()} | |
</ul> | |
<div className="tabs-active-content"> | |
{this.renderActiveTabContent()} | |
</div> | |
</div> | |
); | |
} | |
}; | |
Tabs.propTypes = { | |
defaultActiveTabIndex: PropTypes.number | |
}; | |
Tabs.defaultProps = { | |
defaultActiveTabIndex: 0 | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import {mount} from 'enzyme'; | |
import {Tabs} from '../Tabs'; | |
import {Tab} from '../Tab'; | |
describe('<Tabs/>', () => { | |
let Component = null; | |
beforeEach(() => { | |
Component = mount( | |
<Tabs> | |
<Tab iconClassName={'icon-class-0'} | |
linkClassName={'link-class-0'}> | |
<p>content 0</p> | |
</Tab> | |
<Tab iconClassName={'icon-class-1'} | |
linkClassName={'link-class-1'}> | |
<p>content 1</p> | |
</Tab> | |
</Tabs> | |
); | |
}); | |
it('should render', () => { | |
expect(Component.length).toBeTruthy(); | |
}); | |
it('should render tab 0 content by default', () => { | |
expect(Component.find('.tabs-active-content').text()).toEqual('content 0'); | |
}); | |
it("should allow to specify the 'defaultActiveTabIndex'", () => { | |
const Component = mount( | |
<Tabs defaultActiveTabIndex={1}> | |
<Tab iconClassName={'icon-class-0'} | |
linkClassName={'link-class-0'}> | |
<p>content 0</p> | |
</Tab> | |
<Tab iconClassName={'icon-class-1'} | |
linkClassName={'link-class-1'}> | |
<p>content 1</p> | |
</Tab> | |
</Tabs> | |
); | |
// Active tab content should be 1 instead of 0 now | |
expect(Component.find('.tabs-active-content').text()).toEqual('content 1'); | |
}); | |
it('should change tab content to the selected tab', () => { | |
expect(Component.find('.tabs-active-content').text()).toEqual('content 0'); | |
// Select tab at index 1, and expect text to change | |
Component.find('.link-class-1').simulate('click', {preventDefault: () => {}}); | |
expect(Component.find('.tabs-active-content').text()).toEqual('content 1'); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment