Skip to content

Commit

Permalink
Add basic toggle button option to Navbar
Browse files Browse the repository at this point in the history
  • Loading branch information
stevoland committed May 9, 2014
1 parent 148b522 commit d29cafb
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 123 deletions.
12 changes: 10 additions & 2 deletions docs/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@ body {
.bs-docs-nav .navbar-nav>li>a {
color: #aaa;
font-weight: normal;
border-bottom: 3px solid #222;
}

.bs-docs-nav .navbar-nav>li>a:hover,.bs-docs-nav .navbar-nav>.active>a, .bs-docs-nav .navbar-nav>.active>a:hover {
background: #333;
color: #fafafa;
border-bottom: 3px solid #cc7a6f;
}

@media (min-width: 768px) {

.bs-docs-nav .navbar-nav>li>a {
border-bottom: 3px solid #222;
}
.bs-docs-nav .navbar-nav>li>a:hover,.bs-docs-nav .navbar-nav>.active>a, .bs-docs-nav .navbar-nav>.active>a:hover {
border-bottom: 3px solid #cc7a6f;
}
}

.navbar>.container .navbar-brand, .navbar>.container-fluid .navbar-brand {
Expand Down
27 changes: 9 additions & 18 deletions docs/src/NavMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
var React = require('react');
var Router = require('react-router-component');
var Navbar = require('../../cjs/Navbar');
var Nav = require('../../cjs/Nav');

var InternalLink = Router.Link;

Expand All @@ -25,30 +26,20 @@ var NavMain = React.createClass({
},

render: function () {
var brand = <InternalLink href="/" className="navbar-brand">React Bootstrap</InternalLink>;

return (
<Navbar componentClass={React.DOM.header} staticTop className="bs-docs-nav" role="banner">
<div className="container">
<div className="navbar-header">
<button className="navbar-toggle" type="button" data-toggle="collapse" data-target=".bs-navbar-collapse">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar" />
<span className="icon-bar" />
<span className="icon-bar" />
</button>
<InternalLink href="/" className="navbar-brand">React Bootstrap</InternalLink>
</div>
<nav className="collapse navbar-collapse bs-navbar-collapse" role="navigation">
<ul className="nav navbar-nav">
{Object.keys(NAV_LINKS).map(this.renderNavItem)}
</ul>
</nav>
</div>
<Navbar componentClass={React.DOM.header} brand={brand} staticTop className="bs-docs-nav" role="banner" toggleNavKey={0}>
<Nav className="bs-navbar-collapse" role="navigation" key={0} id="top" inNavbar>
{Object.keys(NAV_LINKS).map(this.renderNavItem)}
</Nav>
</Navbar>
);
);
},

renderNavItem: function (linkName) {
var link = NAV_LINKS[linkName];

return (
<li className={this.props.activePage === linkName ? 'active' : null} key={linkName}>
<InternalLink href={link.link}>{link.title}</InternalLink>
Expand Down
112 changes: 112 additions & 0 deletions src/CollapsableMixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import ReactTransitionEvents from './react-es6/lib/ReactTransitionEvents';

var CollapsableMixin = {

getInitialState: function() {
return {
isOpen: this.props.defaultOpen != null ? this.props.defaultOpen : null,
isCollapsing: false
};
},

handleTransitionEnd: function () {
this._collapseEnd = true;
this.setState({
isCollapsing: false
});
},

componentWillReceiveProps: function (newProps) {
if (newProps.isOpen !== this.props.isOpen) {
this._collapseEnd = false;
this.setState({
isCollapsing: true
});
}
},

_addEndTransitionListener: function () {
var node = this.getCollapsableDOMNode();

if (node) {
ReactTransitionEvents.addEndEventListener(
node,
this.handleTransitionEnd
);
}
},

_removeEndTransitionListener: function () {
var node = this.getCollapsableDOMNode();

if (node) {
ReactTransitionEvents.addEndEventListener(
node,
this.handleTransitionEnd
);
}
},

componentDidMount: function () {
this._afterRender();
},

componentWillUnmount: function () {
this._removeEndTransitionListener();
},

componentWillUpdate: function (nextProps) {
var dimension = (typeof this.getCollapsableDimension === 'function') ?
this.getCollapsableDimension() : 'height';
var node = this.getCollapsableDOMNode();

this._removeEndTransitionListener();
if (node && nextProps.isOpen !== this.props.isOpen && this.props.isOpen) {
node.style[dimension] = this.getCollapsableDimensionValue() + 'px';
}
},

componentDidUpdate: function () {
this._afterRender();
},

_afterRender: function () {
this._addEndTransitionListener();
setTimeout(this._updateDimensionAfterRender, 0);
},

_updateDimensionAfterRender: function () {
var dimension = (typeof this.getCollapsableDimension === 'function') ?
this.getCollapsableDimension() : 'height';
var node = this.getCollapsableDOMNode();

if (node) {
node.style[dimension] = this.isOpen() ?
this.getCollapsableDimensionValue() + 'px' : '0px';
}
},

isOpen: function () {
return (this.props.isOpen != null) ? this.props.isOpen : this.state.isOpen;
},

getCollapsableClassSet: function (className) {
var classes = {};

if (typeof className === 'string') {
className.split(' ').forEach(function (className) {
if (className) {
classes[className] = true;
}
});
}

classes.collapsing = this.state.isCollapsing;
classes.collapse = !this.state.isCollapsing;
classes['in'] = this.isOpen() && !this.state.isCollapsing;

return classes;
}
};

export default = CollapsableMixin;
43 changes: 32 additions & 11 deletions src/Nav.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
/** @jsx React.DOM */

import React from './react-es6';
import classSet from './react-es6/lib/cx';
import BootstrapMixin from './BootstrapMixin';
import utils from './utils';
import React from './react-es6';
import classSet from './react-es6/lib/cx';
import BootstrapMixin from './BootstrapMixin';
import CollapsableMixin from './CollapsableMixin';
import utils from './utils';
import domUtils from './domUtils';


var Nav = React.createClass({
mixins: [BootstrapMixin],
mixins: [BootstrapMixin, CollapsableMixin],

propTypes: {
bsStyle: React.PropTypes.oneOf(['tabs','pills']),
stacked: React.PropTypes.bool,
justified: React.PropTypes.bool,
onSelect: React.PropTypes.func
onSelect: React.PropTypes.func,
isCollapsable: React.PropTypes.bool,
isOpen: React.PropTypes.bool,
inNavbar: React.PropTypes.bool
},

getDefaultProps: function () {
Expand All @@ -22,15 +27,31 @@ var Nav = React.createClass({
};
},

getCollapsableDOMNode: function () {
return this.getDOMNode();
},

getCollapsableDimensionValue: function () {
var node = this.refs.ul.getDOMNode(),
height = node.offsetHeight,
computedStyles = domUtils.getComputedStyles(node);

return height + parseInt(computedStyles.marginTop, 10) + parseInt(computedStyles.marginBottom, 10);
},

render: function () {
var classes = this.getBsClassSet();
var classes = this.getCollapsableClassSet(),
ulClasses = this.getBsClassSet();

classes['navbar-collapse'] = this.props.isCollapsable;

classes['nav-stacked'] = this.props.stacked;
classes['nav-justified'] = this.props.justified;
ulClasses['nav-stacked'] = this.props.stacked;
ulClasses['nav-justified'] = this.props.justified;
ulClasses['navbar-nav'] = this.props.inNavbar;

return this.transferPropsTo(
<nav>
<ul className={classSet(classes)}>
<nav className={classSet(classes)}>
<ul className={classSet(ulClasses)} ref="ul">
{utils.modifyChildren(this.props.children, this.renderNavItem)}
</ul>
</nav>
Expand Down
85 changes: 78 additions & 7 deletions src/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import classSet from './react-es6/lib/cx';
import BootstrapMixin from './BootstrapMixin';
import PropTypes from './PropTypes';
import utils from './utils';
import Nav from './Nav';


var Navbar = React.createClass({
Expand All @@ -17,7 +18,10 @@ var Navbar = React.createClass({
inverse: React.PropTypes.bool,
role: React.PropTypes.string,
componentClass: PropTypes.componentClass,
brand: React.PropTypes.renderable
brand: React.PropTypes.renderable,
toggleButton: React.PropTypes.renderable,
onToggle: React.PropTypes.func,
fluid: React.PropTypes.func
},

getDefaultProps: function () {
Expand All @@ -29,6 +33,33 @@ var Navbar = React.createClass({
};
},

getInitialState: function () {
return {
navOpen: this.props.defaultNavOpen
};
},

shouldComponentUpdate: function() {
// Defer any updates to this component during the `onSelect` handler.
return !this._isChanging;
},

handleToggle: function () {
if (this.props.onToggle) {
this._isChanging = true;
this.props.onToggle();
this._isChanging = false;
}

this.setState({
navOpen: !this.state.navOpen
});
},

isNavOpen: function () {
return this.props.navOpen != null ? this.props.navOpen : this.state.navOpen;
},

render: function () {
var classes = this.getBsClassSet();
var componentClass = this.props.componentClass;
Expand All @@ -39,28 +70,68 @@ var Navbar = React.createClass({
classes['navbar-inverse'] = this.props.inverse;

return this.transferPropsTo(
<componentClass className={classSet(classes)} role={this.props.role}>
{this.props.brand ? this.renderHeader() : null}
{this.props.children}
<componentClass className={classSet(classes)}>
<div className={this.props.fluid ? 'container-fluid' : 'container'}>
{(this.props.brand || this.props.toggleButton || this.props.toggleNavKey) ? this.renderHeader() : null}
{this.props.toggleNavKey != null ?
React.Children.map(this.props.children, this.renderChild) : this.props.children}
</div>
</componentClass>
);
},

renderChild: function (child) {
if (this.props.toggleNavKey === child.props.key) {
return utils.cloneWithProps(child, {
isCollapsable: true,
isOpen: this.isNavOpen()
});
}

return child;
},

renderHeader: function () {
var brand;

if (this.props.brand) {
brand = React.isValidComponent(this.props.brand) ?
utils.cloneWithProps(this.props.brand, {
className: 'navbar-brand'
}) : <span className="navbar-brand">{this.props.brand}</span>;
utils.cloneWithProps(this.props.brand, {
className: 'navbar-brand'
}) : <span className="navbar-brand">{this.props.brand}</span>;
}

return (
<div className="navbar-header">
{brand}
{(this.props.toggleButton || this.props.toggleNavKey != null) ? this.renderToggleButton() : null}
</div>
);
},

renderToggleButton: function () {
var children;

if (React.isValidComponent(this.props.toggleButton)) {
return utils.cloneWithProps(this.props.toggleButton, {
className: 'navbar-toggle',
onClick: utils.createChainedFunction(this.handleToggle, this.props.toggleButton.props.onClick)
});
}

children = (this.props.toggleButton != null) ?
this.props.toggleButton : [
<span className="sr-only" key={0}>Toggle navigation</span>,
<span className="icon-bar" key={1}></span>,
<span className="icon-bar" key={2}></span>,
<span className="icon-bar" key={3}></span>
];

return (
<button className="navbar-toggle" type="button" onClick={this.handleToggle}>
{children}
</button>
);
}
});

Expand Down
Loading

0 comments on commit d29cafb

Please sign in to comment.