Skip to content

Commit

Permalink
[added] Implements a generalized left-aligned version of tabs
Browse files Browse the repository at this point in the history
as an option on tabs, via the position and tabWidth props.

Signed-off-by: Dominick Reinhold <[email protected]>
  • Loading branch information
Caroline Taymor authored and August Toman-Yih committed Aug 19, 2015
1 parent e2c4c6a commit 5f0ac64
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 27 deletions.
9 changes: 9 additions & 0 deletions docs/examples/LeftTabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const tabsInstance = (
<Tabs defaultActiveKey={2} position='left' tabWidth={3}>
<Tab eventKey={1} title='Tab 1'>Tab 1 content</Tab>
<Tab eventKey={2} title='Tab 2'>Tab 2 content</Tab>
<Tab eventKey={3} title='Tab 3' disabled>Tab 3 content</Tab>
</Tabs>
);

React.render(tabsInstance, mountNode);
4 changes: 4 additions & 0 deletions docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,10 @@ const ComponentsPage = React.createClass({
<p>Set the <code>animation</code> prop to <code>false</code></p>
<ReactPlayground codeText={Samples.TabsNoAnimation} exampleClassName='bs-example-tabs' />

<h3><Anchor id='left-tabs'>Left tabs</Anchor></h3>
<p>Set <code>position</code> to <code>'left'</code>. Optionally, <code>tabWidth</code> can be passed the number of columns for the tabs.</p>
<ReactPlayground codeText={Samples.LeftTabs} exampleClassName='bs-example-tabs' />

<div className='bs-callout bs-callout-info'>
<h4>Extends tabbed navigation</h4>
<p>This plugin extends the <a href='#navs'>tabbed navigation component</a> to add tabbable areas.</p>
Expand Down
1 change: 1 addition & 0 deletions docs/src/Samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export default {
TabsUncontrolled: require('fs').readFileSync(__dirname + '/../examples/TabsUncontrolled.js', 'utf8'),
TabsControlled: require('fs').readFileSync(__dirname + '/../examples/TabsControlled.js', 'utf8'),
TabsNoAnimation: require('fs').readFileSync(__dirname + '/../examples/TabsNoAnimation.js', 'utf8'),
LeftTabs: require('fs').readFileSync(__dirname + '/../examples/LeftTabs.js', 'utf8'),
PagerDefault: require('fs').readFileSync(__dirname + '/../examples/PagerDefault.js', 'utf8'),
PagerAligned: require('fs').readFileSync(__dirname + '/../examples/PagerAligned.js', 'utf8'),
PagerDisabled: require('fs').readFileSync(__dirname + '/../examples/PagerDisabled.js', 'utf8'),
Expand Down
170 changes: 147 additions & 23 deletions src/Tabs.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { cloneElement } from 'react';
import ValidComponentChildren from './utils/ValidComponentChildren';

import Col from './Col';
import Grid from './Grid';
import Nav from './Nav';
import NavItem from './NavItem';
import Row from './Row';
import styleMaps from './styleMaps';

import ValidComponentChildren from './utils/ValidComponentChildren';

let panelId = (props, child) => child.props.id ? child.props.id : props.id && (props.id + '___panel___' + child.props.eventKey);
let paneId = (props, child) => child.props.id ? child.props.id : props.id && (props.id + '___pane___' + child.props.eventKey);
let tabId = (props, child) => child.props.id ? child.props.id + '___tab' : props.id && (props.id + '___tab___' + child.props.eventKey);

function getDefaultActiveKeyFromChildren(children) {
Expand All @@ -22,16 +28,44 @@ const Tabs = React.createClass({
propTypes: {
activeKey: React.PropTypes.any,
defaultActiveKey: React.PropTypes.any,
/**
* Navigation style for tabs
*
* If not specified, it will be treated as `'tabs'` when vertically
* positioned and `'pills'` when horizontally positioned.
*/
bsStyle: React.PropTypes.oneOf(['tabs', 'pills']),
animation: React.PropTypes.bool,
id: React.PropTypes.string,
onSelect: React.PropTypes.func
onSelect: React.PropTypes.func,
position: React.PropTypes.oneOf(['top', 'left', 'right']),
/**
* Number of grid columns for the tabs if horizontally positioned
*
* This accepts either a single width or a mapping of size to width.
*/
tabWidth: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.object
]),
/**
* Number of grid columns for the panes if horizontally positioned
*
* This accepts either a single width or a mapping of size to width. If not
* specified, it will be treated as `styleMaps.GRID_COLUMNS` minus
* `tabWidth`.
*/
paneWidth: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.object
])
},

getDefaultProps() {
return {
bsStyle: 'tabs',
animation: true
animation: true,
tabWidth: 2,
position: 'top'
};
},

Expand Down Expand Up @@ -73,26 +107,89 @@ const Tabs = React.createClass({
id,
className,
style, // eslint-disable-line react/prop-types
...props } = this.props;
position,
bsStyle,
tabWidth,
paneWidth,
children,
...props
} = this.props;

function renderTabIfSet(child) {
return child.props.title != null ? this.renderTab(child) : null;
const isHorizontal = position === 'left' || position === 'right';

if (bsStyle == null) {
bsStyle = isHorizontal ? 'pills' : 'tabs';
}

let nav = (
<Nav {...props} activeKey={this.getActiveKey()} onSelect={this.handleSelect} ref="tabs" role="tablist">
{ValidComponentChildren.map(this.props.children, renderTabIfSet, this)}
</Nav>
);
const containerProps = {id, className, style};

return (
<div id={id} className={className} style={style}>
{nav}
<div className="tab-content" ref="panes">
{ValidComponentChildren.map(this.props.children, this.renderPane)}
const tabsProps = {
...props,
bsStyle,
stacked: isHorizontal,
activeKey: this.getActiveKey(),
onSelect: this.handleSelect,
ref: 'tabs',
role: 'tablist'
};
const childTabs = ValidComponentChildren.map(children, this.renderTab);

const panesProps = {
className: 'tab-content',
ref: 'panes'
};
const childPanes = ValidComponentChildren.map(children, this.renderPane);

if (isHorizontal) {
const {tabsColProps, panesColProps} =
this.getColProps({tabWidth, paneWidth});

const tabs = (
<Col componentClass={Nav} {...tabsProps} {...tabsColProps}>
{childTabs}
</Col>
);
const panes = (
<Col {...panesProps} {...panesColProps}>
{childPanes}
</Col>
);

let body;
if (position === 'left') {
body = (
<Row {...containerProps}>
{tabs}
{panes}
</Row>
);
} else {
body = (
<Row {...containerProps}>
{panes}
{tabs}
</Row>
);
}

return (
<Grid>
{body}
</Grid>
);
} else {
return (
<div {...containerProps}>
<Nav {...tabsProps}>
{childTabs}
</Nav>

<div {...panesProps}>
{childPanes}
</div>
</div>
</div>
);
);
}
},

getActiveKey() {
Expand All @@ -111,7 +208,7 @@ const Tabs = React.createClass({
child,
{
active: shouldPaneBeSetActive && (thereIsNoActivePane || !this.props.animation),
id: panelId(this.props, child),
id: paneId(this.props, child),
'aria-labelledby': tabId(this.props, child),
key: child.key ? child.key : index,
animation: this.props.animation,
Expand All @@ -121,20 +218,47 @@ const Tabs = React.createClass({
},

renderTab(child) {
let {eventKey, title, disabled } = child.props;
if (child.props.title == null) {
return null;
}

let {eventKey, title, disabled} = child.props;

return (
<NavItem
linkId={tabId(this.props, child)}
ref={'tab' + eventKey}
aria-controls={panelId(this.props, child)}
aria-controls={paneId(this.props, child)}
eventKey={eventKey}
disabled={disabled}>
{title}
</NavItem>
);
},

getColProps({tabWidth, paneWidth}) {
let tabsColProps;
if (tabWidth instanceof Object) {
tabsColProps = tabWidth;
} else {
tabsColProps = {xs: tabWidth};
}

let panesColProps;
if (paneWidth == null) {
panesColProps = {};
Object.keys(tabsColProps).forEach(function (size) {
panesColProps[size] = styleMaps.GRID_COLUMNS - tabsColProps[size];
});
} else if (paneWidth instanceof Object) {
panesColProps = paneWidth;
} else {
panesColProps = {xs: paneWidth};
}

return {tabsColProps, panesColProps};
},

shouldComponentUpdate() {
// Defer any updates to this component during the `onSelect` handler.
return !this._isChanging;
Expand Down
3 changes: 2 additions & 1 deletion src/styleMaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ const styleMaps = {
'menu-right',
'menu-down',
'menu-up'
]
],
GRID_COLUMNS: 12
};

export default styleMaps;
Loading

0 comments on commit 5f0ac64

Please sign in to comment.