Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for Breadcrumbs components #1354

Merged
merged 2 commits into from
Sep 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/examples/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"Accordion",
"Alert",
"Badge",
"Breadcrumb",
"BreadcrumbItem",
"Button",
"ButtonGroup",
"ButtonInput",
Expand Down
15 changes: 15 additions & 0 deletions docs/examples/Breadcrumb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const breadcrumbInstance = (
<Breadcrumb>
<BreadcrumbItem href="#">
Home
</BreadcrumbItem>
<BreadcrumbItem href="http://getbootstrap.com/components/#breadcrumbs">
Library
</BreadcrumbItem>
<BreadcrumbItem active>
Data
</BreadcrumbItem>
</Breadcrumb>
);

React.render(breadcrumbInstance, mountNode);
17 changes: 17 additions & 0 deletions docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,22 @@ const ComponentsPage = React.createClass({
<PropTable component="Navbar"/>
</div>

{/* Breadcrumb */}
<div className="bs-docs-section">
<h1 className="page-header"><Anchor id="breadcrumbs">Breadcrumbs</Anchor> <small>Breadcrumb, BreadcrumbItems</small></h1>
<p>Breadcrumbs are used to indicate the current page's location. Add <code>active</code> attribute to active <code>BreadcrumbItem</code>.</p>
<p>Do not set both <code>active</code> and <code>href</code> attributes. <code>active</code> overrides <code>href</code> and <code>span</code> element is rendered instead of <code>a</code>.</p>

<h3><Anchor id="breadcrumbs-example">Breadcrumbs Example</Anchor></h3>
<ReactPlayground codeText={Samples.Breadcrumb} />

<h3><Anchor id="breadcrumbs-props">Props</Anchor></h3>
<p><code>Breadcrumb</code> component itself doesn't have any specific public properties</p>

<h4><Anchor id="breadcrumbs-props-breadcrumbItem">BreadcrumbItem</Anchor></h4>
<PropTable component="BreadcrumbItem"/>
</div>

{/* Tabbed Areas */}
<div className="bs-docs-section">
<h1 className="page-header"><Anchor id="tabs">Togglable tabs</Anchor> <small>Tabs, Tab</small></h1>
Expand Down Expand Up @@ -947,6 +963,7 @@ const ComponentsPage = React.createClass({
<NavItem href="#progress" key={8}>Progress bars</NavItem>
<NavItem href="#navs" key={9}>Navs</NavItem>
<NavItem href="#navbars" key={10}>Navbars</NavItem>
<NavItem href="#breadcrumbs" key={30}>Breadcrumbs</NavItem>
<NavItem href="#tabs" key={11}>Tabs</NavItem>
<NavItem href="#pager" key={12}>Pager</NavItem>
<NavItem href="#pagination" key={13}>Pagination</NavItem>
Expand Down
2 changes: 2 additions & 0 deletions docs/src/ReactPlayground.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const React = require('react');
const Accordion = require('../../src/Accordion');
const Alert = require('../../src/Alert');
const Badge = require('../../src/Badge');
const Breadcrumb = require('../../src/Breadcrumb');
const BreadcrumbItem = require('../../src/BreadcrumbItem');
const Button = require('../../src/Button');
const ButtonGroup = require('../../src/ButtonGroup');
const ButtonInput = require('../../src/ButtonInput');
Expand Down
1 change: 1 addition & 0 deletions docs/src/Samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default {
Collapse: require('fs').readFileSync(__dirname + '/../examples/Collapse.js', 'utf8'),
Fade: require('fs').readFileSync(__dirname + '/../examples/Fade.js', 'utf8'),

Breadcrumb: require('fs').readFileSync(__dirname + '/../examples/Breadcrumb.js', 'utf8'),
ButtonTypes: require('fs').readFileSync(__dirname + '/../examples/ButtonTypes.js', 'utf8'),
ButtonSizes: require('fs').readFileSync(__dirname + '/../examples/ButtonSizes.js', 'utf8'),
ButtonBlock: require('fs').readFileSync(__dirname + '/../examples/ButtonBlock.js', 'utf8'),
Expand Down
39 changes: 39 additions & 0 deletions src/Breadcrumb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { cloneElement } from 'react';
import classNames from 'classnames';
import ValidComponentChildren from './utils/ValidComponentChildren';

const Breadcrumb = React.createClass({
propTypes: {
/**
* bootstrap className
* @private
*/
bsClass: React.PropTypes.string
},

getDefaultProps() {
return {
bsClass: 'breadcrumb'
};
},

render() {
const { className, ...props } = this.props;

return (
<ol
{...props}
role="navigation"
aria-label="breadcrumbs"
className={classNames(className, this.props.bsClass)}>
{ValidComponentChildren.map(this.props.children, this.renderBreadcrumbItem)}
</ol>
);
},

renderBreadcrumbItem(child, index) {
return cloneElement( child, { key: child.key ? child.key : index } );
}
});

export default Breadcrumb;
83 changes: 83 additions & 0 deletions src/BreadcrumbItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import classNames from 'classnames';
import SafeAnchor from './SafeAnchor';
import warning from 'react/lib/warning';

const BreadcrumbItem = React.createClass({
propTypes: {
/**
* If set to true, renders `span` instead of `a`
*/
active: React.PropTypes.bool,
/**
* HTML id for the wrapper `li` element
*/
id: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]),
/**
* HTML id for the inner `a` element
*/
linkId: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]),
/**
* `href` attribute for the inner `a` element
*/
href: React.PropTypes.string,
/**
* `title` attribute for the inner `a` element
*/
title: React.PropTypes.node,
/**
* `target` attribute for the inner `a` element
*/
target: React.PropTypes.string
},

getDefaultProps() {
return {
active: false,
};
},

render() {
const {
active,
className,
id,
linkId,
children,
href,
title,
target,
...props } = this.props;

warning(!(href && active), '[react-bootstrap] `href` and `active` properties cannot be set at the same time');

const linkProps = {
href,
title,
target,
id: linkId
};

return (
<li id={id} className={classNames(className, { active })}>
{
active ?
<span {...props}>
{ children }
</span> :
<SafeAnchor {...props} {...linkProps}>
{ children }
</SafeAnchor>
}
</li>
);
}
});

export default BreadcrumbItem;
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export Button from './Button';
export ButtonGroup from './ButtonGroup';
export ButtonInput from './ButtonInput';
export ButtonToolbar from './ButtonToolbar';
export Breadcrumb from './Breadcrumb';
export BreadcrumbItem from './BreadcrumbItem';
export Carousel from './Carousel';
export CarouselItem from './CarouselItem';
export Col from './Col';
Expand Down
140 changes: 140 additions & 0 deletions test/BreadcrumbItemSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import BreadcrumbItem from '../src/BreadcrumbItem';
import { shouldWarn } from './helpers';

describe('BreadcrumbItem', () => {
it('Should warn if `active` and `href` attributes set', () => {
ReactTestUtils.renderIntoDocument(
<BreadcrumbItem href='#' active>
Crumb
</BreadcrumbItem>
);

shouldWarn('[react-bootstrap] `href` and `active` properties cannot be set at the same time');
});

it('Should render `a` as inner element when is not active', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem href='#'>
Crumb
</BreadcrumbItem>
);

assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'a'));
assert.notInclude(React.findDOMNode(instance).className, 'active');
});

it('Should add `active` class with `active` attribute set.', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem active>
Active Crumb
</BreadcrumbItem>
);

assert.include(React.findDOMNode(instance).className, 'active');
});

it('Should render `span` as inner element when is active', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem active>
Crumb
</BreadcrumbItem>
);

assert.ok(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'span'));
});

it('Should add custom classes onto `li` wrapper element', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem className="custom-one custom-two">
Active Crumb
</BreadcrumbItem>
);

const classes = React.findDOMNode(instance).className;
assert.include(classes, 'custom-one');
assert.include(classes, 'custom-two');
});

it('Should spread additional props onto inner element', (done) => {
const handleClick = () => {
done();
};

const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem href='#' onClick={handleClick}>
Crumb
</BreadcrumbItem>
);

const anchorNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'a');
ReactTestUtils.Simulate.click(anchorNode);
});

it('Should apply id onto `li` wrapper element via `id` property', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem href='#' id='test-li-id'>
Crumb
</BreadcrumbItem>
);

assert.equal(React.findDOMNode(instance).id, 'test-li-id');
});

it('Should apply id onto `a` inner alement via `linkId` property', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem href='#' linkId='test-link-id'>
Crumb
</BreadcrumbItem>
);

const linkNode = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'a'));
assert.equal(linkNode.id, 'test-link-id');
});

it('Should apply `href` property onto `a` inner element', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem href='http://getbootstrap.com/components/#breadcrumbs'>
Crumb
</BreadcrumbItem>
);

const linkNode = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'a'));
assert.equal(linkNode.href, 'http://getbootstrap.com/components/#breadcrumbs');
});

it('Should apply `title` property onto `a` inner element', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem title='test-title' href='http://getbootstrap.com/components/#breadcrumbs'>
Crumb
</BreadcrumbItem>
);

const linkNode = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'a'));
assert.equal(linkNode.title, 'test-title');
});

it('Should not apply properties for inner `anchor` onto `li` wrapper element', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem title='test-title' href='/hi'>
Crumb
</BreadcrumbItem>
);

const liNode = React.findDOMNode(instance);
assert.notOk(liNode.hasAttribute('href'));
assert.notOk(liNode.hasAttribute('title'));
});

it('Should set `target` attribute on `anchor`', () => {
const instance = ReactTestUtils.renderIntoDocument(
<BreadcrumbItem target='_blank' href='http://getbootstrap.com/components/#breadcrumbs'>
Crumb
</BreadcrumbItem>
);

const linkNode = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'a'));
assert.equal(linkNode.target, '_blank');
});
});