Skip to content

Commit

Permalink
[added] NavBrand Component
Browse files Browse the repository at this point in the history
    * NavBrand react component implementation
    * Deprecation of brand attribute of Navbar component

    Docs and Example
    * Update examples that were using Navbar brand attribute
    * Update docs description adding NavBrand component usage, removed documentation for Navbar brand attribute
    * Update related components used Navbar brand attribute, replacing it with NavBrand component
    * Update react-bootstrap website header using NavBrand component

    Navbar Component
    * Add deprecation warning message in Navbar Component for the brand attribute usage
    * Change logic rendering for Navbar component based on its child components, using a specific method for NavBrand rendering and the other existing one for other children
      - passing navbar, toggleNavKey, toggleButton, handleToggle and key to NavBrand for its render functionality
    * Add functionality needed once brand attribute is totally removed from Navbar component

    Utils - ValidComponentChildren
    * Added find functionality, returning children based on condition specified in callback function

    Tests
    * Create NavBrand specs
    * Updated test of Navbar component
      - assert deprecation warning messages
      - add new assertions for the NavBrand component used inside Navbar component
  • Loading branch information
Konstantinos Leimonis committed Sep 27, 2015
1 parent 3edf4a1 commit b5a9f3a
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 14 deletions.
1 change: 1 addition & 0 deletions docs/examples/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"ListGroup",
"ListGroupItem",
"Nav",
"NavBrand",
"Navbar",
"NavDropdown",
"NavItem",
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/CollapsibleNav.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const navbarInstance = (
<Navbar brand="React-Bootstrap" toggleNavKey={0}>
<Navbar toggleNavKey={0}>
<NavBrand>React-Bootstrap</NavBrand>
<CollapsibleNav eventKey={0}> {/* This is the eventKey referenced */}
<Nav navbar>
<NavItem eventKey={1} href="#">Link</NavItem>
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/NavbarBasic.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const navbarInstance = (
<Navbar brand="React-Bootstrap">
<Navbar>
<NavBrand>React-Bootstrap</NavBrand>
<Nav>
<NavItem eventKey={1} href="#">Link</NavItem>
<NavItem eventKey={2} href="#">Link</NavItem>
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/NavbarBrand.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const navbarInstance = (
<Navbar brand={<a href="#">React-Bootstrap</a>}>
<Navbar>
<NavBrand><a href="#">React-Bootstrap</a></NavBrand>
<Nav>
<NavItem eventKey={1} href="#">Link</NavItem>
<NavItem eventKey={2} href="#">Link</NavItem>
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/NavbarCollapsible.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const navbarInstance = (
<Navbar brand="React-Bootstrap" inverse toggleNavKey={0}>
<Navbar inverse toggleNavKey={0}>
<NavBrand>React-Bootstrap</NavBrand>
<Nav right eventKey={0}> {/* This is the eventKey referenced */}
<NavItem eventKey={1} href="#">Link</NavItem>
<NavItem eventKey={2} href="#">Link</NavItem>
Expand Down
6 changes: 4 additions & 2 deletions docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ const ComponentsPage = React.createClass({

{/* Navbar */}
<div className="bs-docs-section">
<h1 className="page-header"><Anchor id="navbars">Navbars</Anchor> <small>Navbar, Nav, NavItem</small></h1>
<h1 className="page-header"><Anchor id="navbars">Navbars</Anchor> <small>Navbar, NavBrand, Nav, NavItem</small></h1>

<p>Navbars are by default accessible and will provide <code>role="navigation"</code>.</p>
<p>They also supports all the different Bootstrap classes as properties. Just camelCase the css class and remove navbar from it. For example <code>navbar-fixed-top</code> becomes the property <code>fixedTop</code>. The different properties are <code>fixedTop</code>, <code>fixedBottom</code>, <code>staticTop</code>, <code>inverse</code>, <code>fluid</code>.</p>
Expand All @@ -509,7 +509,9 @@ const ComponentsPage = React.createClass({
<ReactPlayground codeText={Samples.NavbarBasic} />

<h3><Anchor id="navbars-brand">Navbar Brand Example</Anchor></h3>
<p>You can specify a brand by passing in a string to <code>brand</code>, or you can pass in a renderable component.</p>
<p>You can specify a brand by passing a <code>NavBrand</code> component as a child to the <code>Navbar</code> component.</p>
<p><code>NavBrand</code> accepts either string or a renderable component as a child.</p>
<p><em>Note: <code>brand</code> attribute of <code>Navbar</code> component has been deprecated. Use <code>NavBrand</code> component instead.</em></p>
<ReactPlayground codeText={Samples.NavbarBrand} />

<h3><Anchor id="navbars-mobile-friendly">Mobile Friendly</Anchor></h3>
Expand Down
4 changes: 3 additions & 1 deletion docs/src/NavMain.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { Link } from 'react-router';
import Navbar from '../../src/Navbar';
import NavBrand from '../../src/NavBrand';
import Nav from '../../src/Nav';

const NAV_LINKS = {
Expand Down Expand Up @@ -36,7 +37,8 @@ const NavMain = React.createClass({
]);

return (
<Navbar componentClass="header" brand={brand} staticTop className="bs-docs-nav" role="banner" toggleNavKey={0}>
<Navbar componentClass="header" staticTop className="bs-docs-nav" role="banner" toggleNavKey={0}>
<NavBrand>{brand}</NavBrand>
<Nav className="bs-navbar-collapse" role="navigation" eventKey={0} id="top">
{links}
</Nav>
Expand Down
1 change: 1 addition & 0 deletions docs/src/ReactPlayground.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const ListGroupItem = require('../../src/ListGroupItem');
const MenuItem = require('../../src/MenuItem');
const Modal = require('../../src/Modal');
const Nav = require('../../src/Nav');
const NavBrand = require('../../src/NavBrand');
const Navbar = require('../../src/Navbar');
const NavItem = require('../../src/NavItem');
const NavDropdown = require('../../src/NavDropdown');
Expand Down
38 changes: 38 additions & 0 deletions src/NavBrand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { cloneElement } from 'react';
import BootstrapMixin from './BootstrapMixin';
import classNames from 'classnames';

const NavBrand = React.createClass({
mixins: [BootstrapMixin],

propTypes: {
bsRole: React.PropTypes.string,
navbar: React.PropTypes.bool
},

getDefaultProps() {
return {
bsRole: 'brand',
navbar: false
};
},

render() {
let brand;

if (React.isValidElement(this.props.children)) {
brand = cloneElement(this.props.children, {
className: classNames(this.props.children.props.className, 'navbar-brand'),
bsRole: this.props.bsRole,
navbar: this.props.navbar
});
} else {
brand = <span {...this.props} className="navbar-brand">{this.props.children}</span>;
}

return brand;
}

});

export default NavBrand;
49 changes: 44 additions & 5 deletions src/Navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import classNames from 'classnames';
import ValidComponentChildren from './utils/ValidComponentChildren';
import createChainedFunction from './utils/createChainedFunction';
import CustomPropTypes from './utils/CustomPropTypes';
import deprecationWarning from './utils/deprecationWarning';

const Navbar = React.createClass({
mixins: [BootstrapMixin],
Expand Down Expand Up @@ -57,6 +58,12 @@ const Navbar = React.createClass({
return !this._isChanging;
},

componentDidMount() {
if (this.props.brand) {
deprecationWarning('Navbar brand attribute', 'NavBrand Component');
}
},

handleToggle() {
if (this.props.onToggle) {
this._isChanging = true;
Expand All @@ -73,6 +80,19 @@ const Navbar = React.createClass({
return this.props.navExpanded != null ? this.props.navExpanded : this.state.navExpanded;
},

navbrandChild() {
let navChild =
ValidComponentChildren.findValidComponents(this.props.children, child => {
return child.props.bsRole === 'brand';
});

return navChild;
},

hasNavbrandChild() {
return this.navbrandChild().length > 0;
},

render() {
let classes = this.getBsClassSet();
let ComponentClass = this.props.componentClass;
Expand All @@ -82,16 +102,30 @@ const Navbar = React.createClass({
classes['navbar-static-top'] = this.props.staticTop;
classes['navbar-inverse'] = this.props.inverse;

let displayHeader = (this.props.brand || this.props.toggleButton || this.props.toggleNavKey != null) && !this.hasNavbrandChild();

return (
<ComponentClass {...this.props} className={classNames(this.props.className, classes)}>
<div className={this.props.fluid ? 'container-fluid' : 'container'}>
{(this.props.brand || this.props.toggleButton || this.props.toggleNavKey != null) ? this.renderHeader() : null}
{ValidComponentChildren.map(this.props.children, this.renderChild)}
{displayHeader ? this.renderHeader() : null}
{ValidComponentChildren.map(this.props.children, this.renderChildren)}
</div>
</ComponentClass>
);
},

renderNavBrand(child, index) {
let navbrandEl = cloneElement(child, {
navbar: true,
toggleNavKey: this.props.toggleNavKey,
toggleButton: this.props.toggleButton,
handleToggle: this.handleToggle,
key: child.key ? child.key : index
});

return this.renderHeader(navbrandEl);
},

renderChild(child, index) {
return cloneElement(child, {
navbar: true,
Expand All @@ -101,10 +135,14 @@ const Navbar = React.createClass({
});
},

renderHeader() {
let brand;
renderChildren(child, index) {
return (child.props.navbrand) ? this.renderNavBrand(child, index) : this.renderChild(child, index);
},

if (this.props.brand) {
renderHeader(navbrandEl) {
let brand = navbrandEl || '';

if (!brand && this.props.brand) {
if (React.isValidElement(this.props.brand)) {
brand = cloneElement(this.props.brand, {
className: classNames(this.props.brand.props.className, 'navbar-brand')
Expand Down Expand Up @@ -146,6 +184,7 @@ const Navbar = React.createClass({
</button>
);
}

});

export default Navbar;
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export ModalBody from './ModalBody';
export ModalFooter from './ModalFooter';

export Nav from './Nav';
export NavBrand from './NavBrand';
export Navbar from './Navbar';
export NavItem from './NavItem';

Expand Down
29 changes: 29 additions & 0 deletions src/utils/ValidComponentChildren.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,39 @@ function find(children, finder) {
return child;
}

/**
* Finds children that are typically specified as `props.children`,
* but only iterates over children that are "valid components".
*
* The provided forEachFunc(child, index) will be called for each
* leaf child with the index reflecting the position relative to "valid components".
*
* @param {?*} children Children tree container.
* @param {function(*, int)} findFunc.
* @param {*} findContext Context for findContext.
* @returns {array} of children that meet the findFunc return statement
*/
function findValidComponents(children, func, context) {
let index = 0;
let returnChildren = [];

React.Children.forEach(children, child => {
if (React.isValidElement(child)) {
if (func.call(context, child, index)) {
returnChildren.push(child);
}
index++;
}
});

return returnChildren;
}

export default {
map: mapValidComponents,
forEach: forEachValidComponents,
numberOf: numberOfValidComponents,
find,
findValidComponents,
hasValidComponent
};
32 changes: 32 additions & 0 deletions test/NavBrandSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
// import Navbar from '../src/Navbar';
import NavBrand from '../src/NavBrand';

describe('Navbrand', () => {

it('Should create navbrand SPAN element', () => {
let instance = ReactTestUtils.renderIntoDocument(
<NavBrand>Brand</NavBrand>
);

let brand = React.findDOMNode(instance);

assert.equal(brand.nodeName, 'SPAN');
assert.ok(brand.className.match(/\bnavbar-brand\b/));
assert.equal(brand.innerText, 'Brand');
});

it('Should create navbrand A (link) element', () => {
let instance = ReactTestUtils.renderIntoDocument(
<NavBrand><a href>BrandLink</a></NavBrand>
);

let brand = React.findDOMNode(instance);

assert.equal(brand.nodeName, 'A');
assert.ok(brand.className.match(/\bnavbar-brand\b/));
assert.equal(brand.innerText, 'BrandLink');
});

});

0 comments on commit b5a9f3a

Please sign in to comment.