Skip to content

Instantly share code, notes, and snippets.

@mtscout6
Created February 27, 2015 12:58
Show Gist options
  • Save mtscout6/82badde3a3baf9ba6db8 to your computer and use it in GitHub Desktop.
Save mtscout6/82badde3a3baf9ba6db8 to your computer and use it in GitHub Desktop.
Potential TODOs for the Panel Component in react-bootstrap
var React = require('react');
var joinClasses = require('./utils/joinClasses');
var classSet = require('./utils/classSet');
var cloneWithProps = require('./utils/cloneWithProps');
var BootstrapMixin = require('./BootstrapMixin');
var CollapsableMixin = require('./CollapsableMixin');
// TODO: Evaluate extracting collapsable logic to CollapsablePanel, The
// Accordian would likewise manage collapsed state itself.
var Panel = React.createClass({
mixins: [BootstrapMixin, CollapsableMixin],
propTypes: {
id: React.PropTypes.node,
onSelect: React.PropTypes.func,
header: React.PropTypes.node,
footer: React.PropTypes.node,
eventKey: React.PropTypes.any,
collapsable: React.PropTypes.bool,
fill: React.PropTypes.bool
},
getDefaultProps: function () {
return {
bsClass: 'panel',
bsStyle: 'default'
};
},
handleSelect: function (e) {
if (this.props.onSelect) {
// TODO: This feels wonky why would a component try to update when the
// following function is called? Should probably be the concern of the
// caller to handle async work?
this._isChanging = true;
this.props.onSelect(this.props.eventKey);
this._isChanging = false;
}
// TODO: Allow dev to control this with return value from onSelect
e.preventDefault();
// TODO: Should this still happen if onSelect result does not permit e.preventDefault?
this.setState({
expanded: !this.state.expanded
});
},
shouldComponentUpdate: function () {
// TODO: This should go away, poor async handling
return !this._isChanging;
},
getCollapsableDimensionValue: function () {
return this.refs.panel.getDOMNode().scrollHeight;
},
getCollapsableDOMNode: function () {
if (!this.isMounted() || !this.refs || !this.refs.panel) {
return null;
}
return this.refs.panel.getDOMNode();
},
render: function () {
var classes = this.getBsClassSet();
// TODO: Why? this.getBsClassSet() should already set this to true. Unless
// the bsClass provided is different. Is that expected?
classes.panel = true;
return (
<div {...this.props} className={joinClasses(this.props.className, classSet(classes))}
id={this.props.collapsable ? null : this.props.id} onSelect={null}>
{this.renderHeading()}
{this.props.collapsable ? this.renderCollapsableBody() : this.renderBody()}
{this.renderFooter()}
</div>
);
},
renderCollapsableBody: function () {
// TODO: Do we need to add some kind of aria tag here when we collapse and uncollapse?
return (
<div className={classSet(this.getCollapsableClassSet('panel-collapse'))} id={this.props.id} ref="panel">
{this.renderBody()}
</div>
);
},
renderBody: function () {
var allChildren = this.props.children;
var bodyElements = [];
function getProps() {
return {key: bodyElements.length};
}
function addPanelChild (child) {
bodyElements.push(cloneWithProps(child, getProps()));
}
function addPanelBody (children) {
bodyElements.push(
<div className="panel-body" {...getProps()}>
{children}
</div>
);
}
// Handle edge cases where we should not iterate through children.
if (!Array.isArray(allChildren) || allChildren.length === 0) {
if (this.shouldRenderFill(allChildren)) {
addPanelChild(allChildren);
} else {
addPanelBody(allChildren);
}
} else {
var panelBodyChildren = [];
function maybeRenderPanelBody () {
if (panelBodyChildren.length === 0) {
return;
}
addPanelBody(panelBodyChildren);
panelBodyChildren = [];
}
allChildren.forEach(function(child) {
if (this.shouldRenderFill(child)) {
maybeRenderPanelBody();
// Separately add the filled element.
addPanelChild(child);
} else {
panelBodyChildren.push(child);
}
}.bind(this));
maybeRenderPanelBody();
}
return bodyElements;
},
shouldRenderFill: function (child) {
return React.isValidElement(child) && child.props.fill != null;
},
renderHeading: function () {
var header = this.props.header;
if (!header) {
return null;
}
if (!React.isValidElement(header) || Array.isArray(header)) {
header = this.props.collapsable ?
this.renderCollapsableTitle(header) : header;
} else if (this.props.collapsable) {
header = cloneWithProps(header, {
// TODO: inherit bsClass from prop
className: 'panel-title',
children: this.renderAnchor(header.props.children)
});
} else {
header = cloneWithProps(header, {
// TODO: inherit bsClass from prop
className: 'panel-title'
});
}
return (
// TODO: inherit bsClass from prop
<div className="panel-heading">
{header}
</div>
);
},
// TODO: Potentially use button here to address accessibility concerns
renderAnchor: function (header) {
return (
<a
href={'#' + (this.props.id || '')}
className={this.isExpanded() ? null : 'collapsed'}
onClick={this.handleSelect}>
{header}
</a>
);
},
renderCollapsableTitle: function (header) {
return (
// TODO: inherit bsClass from prop
// TODO: h4 may cause accessability concerns
<h4 className="panel-title">
{this.renderAnchor(header)}
</h4>
);
},
renderFooter: function () {
if (!this.props.footer) {
return null;
}
return (
// TODO: inherit bsClass from prop
<div className="panel-footer">
{this.props.footer}
</div>
);
}
});
module.exports = Panel;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment