Skip to content

Commit b5a9f3a

Browse files
author
Konstantinos Leimonis
committed
[added] NavBrand Component
* 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
1 parent 3edf4a1 commit b5a9f3a

14 files changed

+239
-14
lines changed

docs/examples/.eslintrc

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"ListGroup",
3232
"ListGroupItem",
3333
"Nav",
34+
"NavBrand",
3435
"Navbar",
3536
"NavDropdown",
3637
"NavItem",

docs/examples/CollapsibleNav.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const navbarInstance = (
2-
<Navbar brand="React-Bootstrap" toggleNavKey={0}>
2+
<Navbar toggleNavKey={0}>
3+
<NavBrand>React-Bootstrap</NavBrand>
34
<CollapsibleNav eventKey={0}> {/* This is the eventKey referenced */}
45
<Nav navbar>
56
<NavItem eventKey={1} href="#">Link</NavItem>

docs/examples/NavbarBasic.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const navbarInstance = (
2-
<Navbar brand="React-Bootstrap">
2+
<Navbar>
3+
<NavBrand>React-Bootstrap</NavBrand>
34
<Nav>
45
<NavItem eventKey={1} href="#">Link</NavItem>
56
<NavItem eventKey={2} href="#">Link</NavItem>

docs/examples/NavbarBrand.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const navbarInstance = (
2-
<Navbar brand={<a href="#">React-Bootstrap</a>}>
2+
<Navbar>
3+
<NavBrand><a href="#">React-Bootstrap</a></NavBrand>
34
<Nav>
45
<NavItem eventKey={1} href="#">Link</NavItem>
56
<NavItem eventKey={2} href="#">Link</NavItem>

docs/examples/NavbarCollapsible.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const navbarInstance = (
2-
<Navbar brand="React-Bootstrap" inverse toggleNavKey={0}>
2+
<Navbar inverse toggleNavKey={0}>
3+
<NavBrand>React-Bootstrap</NavBrand>
34
<Nav right eventKey={0}> {/* This is the eventKey referenced */}
45
<NavItem eventKey={1} href="#">Link</NavItem>
56
<NavItem eventKey={2} href="#">Link</NavItem>

docs/src/ComponentsPage.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ const ComponentsPage = React.createClass({
499499

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

504504
<p>Navbars are by default accessible and will provide <code>role="navigation"</code>.</p>
505505
<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>
@@ -509,7 +509,9 @@ const ComponentsPage = React.createClass({
509509
<ReactPlayground codeText={Samples.NavbarBasic} />
510510

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

515517
<h3><Anchor id="navbars-mobile-friendly">Mobile Friendly</Anchor></h3>

docs/src/NavMain.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { Link } from 'react-router';
33
import Navbar from '../../src/Navbar';
4+
import NavBrand from '../../src/NavBrand';
45
import Nav from '../../src/Nav';
56

67
const NAV_LINKS = {
@@ -36,7 +37,8 @@ const NavMain = React.createClass({
3637
]);
3738

3839
return (
39-
<Navbar componentClass="header" brand={brand} staticTop className="bs-docs-nav" role="banner" toggleNavKey={0}>
40+
<Navbar componentClass="header" staticTop className="bs-docs-nav" role="banner" toggleNavKey={0}>
41+
<NavBrand>{brand}</NavBrand>
4042
<Nav className="bs-navbar-collapse" role="navigation" eventKey={0} id="top">
4143
{links}
4244
</Nav>

docs/src/ReactPlayground.js

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const ListGroupItem = require('../../src/ListGroupItem');
3434
const MenuItem = require('../../src/MenuItem');
3535
const Modal = require('../../src/Modal');
3636
const Nav = require('../../src/Nav');
37+
const NavBrand = require('../../src/NavBrand');
3738
const Navbar = require('../../src/Navbar');
3839
const NavItem = require('../../src/NavItem');
3940
const NavDropdown = require('../../src/NavDropdown');

src/NavBrand.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { cloneElement } from 'react';
2+
import BootstrapMixin from './BootstrapMixin';
3+
import classNames from 'classnames';
4+
5+
const NavBrand = React.createClass({
6+
mixins: [BootstrapMixin],
7+
8+
propTypes: {
9+
bsRole: React.PropTypes.string,
10+
navbar: React.PropTypes.bool
11+
},
12+
13+
getDefaultProps() {
14+
return {
15+
bsRole: 'brand',
16+
navbar: false
17+
};
18+
},
19+
20+
render() {
21+
let brand;
22+
23+
if (React.isValidElement(this.props.children)) {
24+
brand = cloneElement(this.props.children, {
25+
className: classNames(this.props.children.props.className, 'navbar-brand'),
26+
bsRole: this.props.bsRole,
27+
navbar: this.props.navbar
28+
});
29+
} else {
30+
brand = <span {...this.props} className="navbar-brand">{this.props.children}</span>;
31+
}
32+
33+
return brand;
34+
}
35+
36+
});
37+
38+
export default NavBrand;

src/Navbar.js

+44-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import classNames from 'classnames';
55
import ValidComponentChildren from './utils/ValidComponentChildren';
66
import createChainedFunction from './utils/createChainedFunction';
77
import CustomPropTypes from './utils/CustomPropTypes';
8+
import deprecationWarning from './utils/deprecationWarning';
89

910
const Navbar = React.createClass({
1011
mixins: [BootstrapMixin],
@@ -57,6 +58,12 @@ const Navbar = React.createClass({
5758
return !this._isChanging;
5859
},
5960

61+
componentDidMount() {
62+
if (this.props.brand) {
63+
deprecationWarning('Navbar brand attribute', 'NavBrand Component');
64+
}
65+
},
66+
6067
handleToggle() {
6168
if (this.props.onToggle) {
6269
this._isChanging = true;
@@ -73,6 +80,19 @@ const Navbar = React.createClass({
7380
return this.props.navExpanded != null ? this.props.navExpanded : this.state.navExpanded;
7481
},
7582

83+
navbrandChild() {
84+
let navChild =
85+
ValidComponentChildren.findValidComponents(this.props.children, child => {
86+
return child.props.bsRole === 'brand';
87+
});
88+
89+
return navChild;
90+
},
91+
92+
hasNavbrandChild() {
93+
return this.navbrandChild().length > 0;
94+
},
95+
7696
render() {
7797
let classes = this.getBsClassSet();
7898
let ComponentClass = this.props.componentClass;
@@ -82,16 +102,30 @@ const Navbar = React.createClass({
82102
classes['navbar-static-top'] = this.props.staticTop;
83103
classes['navbar-inverse'] = this.props.inverse;
84104

105+
let displayHeader = (this.props.brand || this.props.toggleButton || this.props.toggleNavKey != null) && !this.hasNavbrandChild();
106+
85107
return (
86108
<ComponentClass {...this.props} className={classNames(this.props.className, classes)}>
87109
<div className={this.props.fluid ? 'container-fluid' : 'container'}>
88-
{(this.props.brand || this.props.toggleButton || this.props.toggleNavKey != null) ? this.renderHeader() : null}
89-
{ValidComponentChildren.map(this.props.children, this.renderChild)}
110+
{displayHeader ? this.renderHeader() : null}
111+
{ValidComponentChildren.map(this.props.children, this.renderChildren)}
90112
</div>
91113
</ComponentClass>
92114
);
93115
},
94116

117+
renderNavBrand(child, index) {
118+
let navbrandEl = cloneElement(child, {
119+
navbar: true,
120+
toggleNavKey: this.props.toggleNavKey,
121+
toggleButton: this.props.toggleButton,
122+
handleToggle: this.handleToggle,
123+
key: child.key ? child.key : index
124+
});
125+
126+
return this.renderHeader(navbrandEl);
127+
},
128+
95129
renderChild(child, index) {
96130
return cloneElement(child, {
97131
navbar: true,
@@ -101,10 +135,14 @@ const Navbar = React.createClass({
101135
});
102136
},
103137

104-
renderHeader() {
105-
let brand;
138+
renderChildren(child, index) {
139+
return (child.props.navbrand) ? this.renderNavBrand(child, index) : this.renderChild(child, index);
140+
},
106141

107-
if (this.props.brand) {
142+
renderHeader(navbrandEl) {
143+
let brand = navbrandEl || '';
144+
145+
if (!brand && this.props.brand) {
108146
if (React.isValidElement(this.props.brand)) {
109147
brand = cloneElement(this.props.brand, {
110148
className: classNames(this.props.brand.props.className, 'navbar-brand')
@@ -146,6 +184,7 @@ const Navbar = React.createClass({
146184
</button>
147185
);
148186
}
187+
149188
});
150189

151190
export default Navbar;

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export ModalBody from './ModalBody';
3939
export ModalFooter from './ModalFooter';
4040

4141
export Nav from './Nav';
42+
export NavBrand from './NavBrand';
4243
export Navbar from './Navbar';
4344
export NavItem from './NavItem';
4445

src/utils/ValidComponentChildren.js

+29
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,39 @@ function find(children, finder) {
9494
return child;
9595
}
9696

97+
/**
98+
* Finds children that are typically specified as `props.children`,
99+
* but only iterates over children that are "valid components".
100+
*
101+
* The provided forEachFunc(child, index) will be called for each
102+
* leaf child with the index reflecting the position relative to "valid components".
103+
*
104+
* @param {?*} children Children tree container.
105+
* @param {function(*, int)} findFunc.
106+
* @param {*} findContext Context for findContext.
107+
* @returns {array} of children that meet the findFunc return statement
108+
*/
109+
function findValidComponents(children, func, context) {
110+
let index = 0;
111+
let returnChildren = [];
112+
113+
React.Children.forEach(children, child => {
114+
if (React.isValidElement(child)) {
115+
if (func.call(context, child, index)) {
116+
returnChildren.push(child);
117+
}
118+
index++;
119+
}
120+
});
121+
122+
return returnChildren;
123+
}
124+
97125
export default {
98126
map: mapValidComponents,
99127
forEach: forEachValidComponents,
100128
numberOf: numberOfValidComponents,
101129
find,
130+
findValidComponents,
102131
hasValidComponent
103132
};

test/NavBrandSpec.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import ReactTestUtils from 'react/lib/ReactTestUtils';
3+
// import Navbar from '../src/Navbar';
4+
import NavBrand from '../src/NavBrand';
5+
6+
describe('Navbrand', () => {
7+
8+
it('Should create navbrand SPAN element', () => {
9+
let instance = ReactTestUtils.renderIntoDocument(
10+
<NavBrand>Brand</NavBrand>
11+
);
12+
13+
let brand = React.findDOMNode(instance);
14+
15+
assert.equal(brand.nodeName, 'SPAN');
16+
assert.ok(brand.className.match(/\bnavbar-brand\b/));
17+
assert.equal(brand.innerText, 'Brand');
18+
});
19+
20+
it('Should create navbrand A (link) element', () => {
21+
let instance = ReactTestUtils.renderIntoDocument(
22+
<NavBrand><a href>BrandLink</a></NavBrand>
23+
);
24+
25+
let brand = React.findDOMNode(instance);
26+
27+
assert.equal(brand.nodeName, 'A');
28+
assert.ok(brand.className.match(/\bnavbar-brand\b/));
29+
assert.equal(brand.innerText, 'BrandLink');
30+
});
31+
32+
});

0 commit comments

Comments
 (0)