diff --git a/docs/examples/ListGroupCustom.js b/docs/examples/ListGroupCustom.js new file mode 100644 index 0000000000..d1ea003f64 --- /dev/null +++ b/docs/examples/ListGroupCustom.js @@ -0,0 +1,21 @@ +const CustomComponent = React.createClass({ + render() { + return ( +
  • {}}> + {this.props.children} +
  • + ); + } +}); + +const listgroupInstance = ( + + Custom Child 1 + Custom Child 2 + Custom Child 3 + +); + +React.render(listgroupInstance, mountNode); diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js index 9ba1aaf324..21ba550f12 100644 --- a/docs/src/ComponentsPage.js +++ b/docs/src/ComponentsPage.js @@ -749,6 +749,15 @@ const ComponentsPage = React.createClass({

    Set the header prop to create a structured item, with a heading and a body area.

    +

    With custom component children

    +

    + When using ListGroupItems directly, ListGroup looks at whether the items have href + or onClick props to determine which DOM elements to emit. However, with custom item + components as children to ListGroup, set the + componentClass prop to specify which element ListGroup should output. +

    + +

    Props

    ListGroup

    diff --git a/docs/src/Samples.js b/docs/src/Samples.js index 736daa5ccb..0efd2efe7c 100644 --- a/docs/src/Samples.js +++ b/docs/src/Samples.js @@ -81,6 +81,7 @@ export default { GridBasic: require('fs').readFileSync(__dirname + '/../examples/GridBasic.js', 'utf8'), ThumbnailAnchor: require('fs').readFileSync(__dirname + '/../examples/ThumbnailAnchor.js', 'utf8'), ThumbnailDiv: require('fs').readFileSync(__dirname + '/../examples/ThumbnailDiv.js', 'utf8'), + ListGroupCustom: require('fs').readFileSync(__dirname + '/../examples/ListGroupCustom.js', 'utf8'), ListGroupDefault: require('fs').readFileSync(__dirname + '/../examples/ListGroupDefault.js', 'utf8'), ListGroupLinked: require('fs').readFileSync(__dirname + '/../examples/ListGroupLinked.js', 'utf8'), ListGroupActive: require('fs').readFileSync(__dirname + '/../examples/ListGroupActive.js', 'utf8'), diff --git a/src/ListGroup.js b/src/ListGroup.js index 1a6e330e0f..2c0355dce0 100644 --- a/src/ListGroup.js +++ b/src/ListGroup.js @@ -1,4 +1,5 @@ import React, { cloneElement } from 'react'; +import ListGroupItem from './ListGroupItem'; import classNames from 'classnames'; import ValidComponentChildren from './utils/ValidComponentChildren'; @@ -9,6 +10,17 @@ class ListGroup extends React.Component { (item, index) => cloneElement(item, { key: item.key ? item.key : index }) ); + if (this.areCustomChildren(items)) { + let Component = this.props.componentClass; + return ( + + {items} + + ); + } + let shouldRenderDiv = false; if (!this.props.children) { @@ -21,16 +33,25 @@ class ListGroup extends React.Component { }); } - if (shouldRenderDiv) { - return this.renderDiv(items); - } - return this.renderUL(items); + return shouldRenderDiv ? this.renderDiv(items) : this.renderUL(items); } isAnchorOrButton(props) { return (props.href || props.onClick); } + areCustomChildren(children) { + let customChildren = false; + + ValidComponentChildren.forEach(children, (child) => { + if (child.type !== ListGroupItem) { + customChildren = true; + } + }, this); + + return customChildren; + } + renderUL(items) { let listItems = ValidComponentChildren.map(items, (item) => cloneElement(item, { listItem: true }) @@ -56,8 +77,18 @@ class ListGroup extends React.Component { } } +ListGroup.defaultProps = { + componentClass: 'div' +}; + ListGroup.propTypes = { className: React.PropTypes.string, + /** + * The element for ListGroup if children are + * user-defined custom components. + * @type {("ul"|"div")} + */ + componentClass: React.PropTypes.oneOf(['ul', 'div']), id: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number diff --git a/test/ListGroupSpec.js b/test/ListGroupSpec.js index 13e1a58a8e..13c09c0107 100644 --- a/test/ListGroupSpec.js +++ b/test/ListGroupSpec.js @@ -5,145 +5,195 @@ import ListGroupItem from '../src/ListGroupItem'; describe('ListGroup', () => { - it('Should output a "div" with the class "list-group"', () => { - let instance = ReactTestUtils.renderIntoDocument( - - ); - assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); + describe('All children are of type ListGroupItem', () => { + + it('Should output a "div" with the class "list-group"', () => { + let instance = ReactTestUtils.renderIntoDocument( + + ); + assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); + }); + + it('Should support a single "ListGroupItem" child', () => { + let instance = ReactTestUtils.renderIntoDocument( + + Only Child + + ); + + let items = ReactTestUtils.scryRenderedComponentsWithType(instance, ListGroupItem); + + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[0], 'list-group-item')); + }); + + it('Should support a single "ListGroupItem" child contained in an array', () => { + let child = [Only Child in array]; + let instance = ReactTestUtils.renderIntoDocument( + + {child} + + ); + + let items = ReactTestUtils.scryRenderedComponentsWithType(instance, ListGroupItem); + + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[0], 'list-group-item')); + }); + + it('Should output a "ul" when single "ListGroupItem" child is a list item', () => { + let instance = ReactTestUtils.renderIntoDocument( + + Only Child + + ); + + assert.equal(React.findDOMNode(instance).nodeName, 'UL'); + assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'LI'); + }); + + it('Should output a "div" when single "ListGroupItem" child is an anchor', () => { + let instance = ReactTestUtils.renderIntoDocument( + + Only Child + + ); + + assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); + assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'A'); + }); + + it('Should support multiple "ListGroupItem" children', () => { + let instance = ReactTestUtils.renderIntoDocument( + + 1st Child + 2nd Child + + ); + + let items = ReactTestUtils.scryRenderedComponentsWithType(instance, ListGroupItem); + + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[0], 'list-group-item')); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[1], 'list-group-item')); + }); + + it('Should support multiple "ListGroupItem" children including a subset contained in an array', () => { + let itemArray = [ + 2nd Child nested, + 3rd Child nested + ]; + + let instance = ReactTestUtils.renderIntoDocument( + + 1st Child + {itemArray} + 4th Child + + ); + + let items = ReactTestUtils.scryRenderedComponentsWithType(instance, ListGroupItem); + + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[0], 'list-group-item')); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[1], 'list-group-item')); + }); + + it('Should output a "ul" when children are list items', () => { + let instance = ReactTestUtils.renderIntoDocument( + + 1st Child + 2nd Child + + ); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); + assert.equal(React.findDOMNode(instance).nodeName, 'UL'); + assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'LI'); + assert.equal(React.findDOMNode(instance).lastChild.nodeName, 'LI'); + }); + + + it('Should output a "div" when "ListGroupItem" children are anchors and spans', () => { + let instance = ReactTestUtils.renderIntoDocument( + + 1st Child + 2nd Child + + ); + assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); + assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'A'); + assert.equal(React.findDOMNode(instance).lastChild.nodeName, 'SPAN'); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); + }); + + + it('Should output a "div" when "ListGroupItem" children have an onClick handler', () => { + let instance = ReactTestUtils.renderIntoDocument( + + null}>1st Child + 2nd Child + + ); + assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); + assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'BUTTON'); + assert.equal(React.findDOMNode(instance).lastChild.nodeName, 'SPAN'); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); + }); + + it('Should support an element id through "id" prop', () => { + let instance = ReactTestUtils.renderIntoDocument( + + Child + + ); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); + assert.equal(React.findDOMNode(instance).nodeName, 'UL'); + assert.equal(React.findDOMNode(instance).id, 'testItem'); + }); }); - it('Should support a single "ListGroupItem" child', () => { - let instance = ReactTestUtils.renderIntoDocument( - - Only Child - - ); - - let items = ReactTestUtils.scryRenderedComponentsWithType(instance, ListGroupItem); - - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[0], 'list-group-item')); - }); - - it('Should support a single "ListGroupItem" child contained in an array', () => { - let child = [Only Child in array]; - let instance = ReactTestUtils.renderIntoDocument( - - {child} - - ); - - let items = ReactTestUtils.scryRenderedComponentsWithType(instance, ListGroupItem); - - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[0], 'list-group-item')); - }); - - it('Should output a "ul" when single "ListGroupItem" child is a list item', () => { - let instance = ReactTestUtils.renderIntoDocument( - - Only Child - - ); - - assert.equal(React.findDOMNode(instance).nodeName, 'UL'); - assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'LI'); - }); - - it('Should output a "div" when single "ListGroupItem" child is an anchor', () => { - let instance = ReactTestUtils.renderIntoDocument( - - Only Child - - ); - - assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); - assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'A'); - }); - - it('Should support multiple "ListGroupItem" children', () => { - let instance = ReactTestUtils.renderIntoDocument( - - 1st Child - 2nd Child - - ); - - let items = ReactTestUtils.scryRenderedComponentsWithType(instance, ListGroupItem); - - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[0], 'list-group-item')); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[1], 'list-group-item')); + describe('Some or all children are user-defined custom components', () => { + it('Should output a div by default when children are custom components', () => { + let CustomComponent = React.createClass({ + render() { + return ( +
  • + {this.props.children} +
  • + ); + } + }); + + let instance = ReactTestUtils.renderIntoDocument( + + Child + + ); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); + assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); + assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'LI'); + }); + + it('Should use a "componentClass" prop if specified if any children are custom components', () => { + let CustomComponent = React.createClass({ + render() { + return ( +
  • + {this.props.children} +
  • + ); + } + }); + + let instance = ReactTestUtils.renderIntoDocument( + + Custom Child + Custom Child + RB Child + + ); + assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); + assert.equal(React.findDOMNode(instance).nodeName, 'UL'); + assert.equal(React.findDOMNode(instance).lastChild.nodeName, 'LI'); + }); }); - - it('Should support multiple "ListGroupItem" children including a subset contained in an array', () => { - let itemArray = [ - 2nd Child nested, - 3rd Child nested - ]; - - let instance = ReactTestUtils.renderIntoDocument( - - 1st Child - {itemArray} - 4th Child - - ); - - let items = ReactTestUtils.scryRenderedComponentsWithType(instance, ListGroupItem); - - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[0], 'list-group-item')); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(items[1], 'list-group-item')); - }); - - it('Should output a "ul" when children are list items', () => { - let instance = ReactTestUtils.renderIntoDocument( - - 1st Child - 2nd Child - - ); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); - assert.equal(React.findDOMNode(instance).nodeName, 'UL'); - assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'LI'); - assert.equal(React.findDOMNode(instance).lastChild.nodeName, 'LI'); - }); - - - it('Should output a "div" when "ListGroupItem" children are anchors and spans', () => { - let instance = ReactTestUtils.renderIntoDocument( - - 1st Child - 2nd Child - - ); - assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); - assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'A'); - assert.equal(React.findDOMNode(instance).lastChild.nodeName, 'SPAN'); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); - }); - - - it('Should output a "div" when "ListGroupItem" children have an onClick handler', () => { - let instance = ReactTestUtils.renderIntoDocument( - - null}>1st Child - 2nd Child - - ); - assert.equal(React.findDOMNode(instance).nodeName, 'DIV'); - assert.equal(React.findDOMNode(instance).firstChild.nodeName, 'BUTTON'); - assert.equal(React.findDOMNode(instance).lastChild.nodeName, 'SPAN'); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); - }); - - it('Should support an element id through "id" prop', () => { - let instance = ReactTestUtils.renderIntoDocument( - - Child - - ); - assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'list-group')); - assert.equal(React.findDOMNode(instance).nodeName, 'UL'); - assert.equal(React.findDOMNode(instance).id, 'testItem'); - }); - });