Skip to content

Commit

Permalink
[added] accessibility props for PanelGroup and Panels.
Browse files Browse the repository at this point in the history
Signed-off-by: Kenny Wang <kwang@pivotal.io>
  • Loading branch information
d-reinhold authored and Kenny Wang committed Aug 20, 2015
1 parent 136d442 commit 769781d
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 18 deletions.
40 changes: 24 additions & 16 deletions src/Panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,32 @@ const Panel = React.createClass({
},

render() {
let {headerRole, panelRole, ...props} = this.props;
return (
<div {...this.props}
<div {...props}
className={classNames(this.props.className, this.getBsClassSet())}
id={this.props.collapsible ? null : this.props.id} onSelect={null}>
{this.renderHeading()}
{this.props.collapsible ? this.renderCollapsibleBody() : this.renderBody()}
{this.renderHeading(headerRole)}
{this.props.collapsible ? this.renderCollapsibleBody(panelRole) : this.renderBody()}
{this.renderFooter()}
</div>
);
},

renderCollapsibleBody() {
let collapseClass = this.prefixClass('collapse');
renderCollapsibleBody(panelRole) {
let props = {
className: this.prefixClass('collapse'),
id: this.props.id,
ref: 'panel',
'aria-hidden': !this.isExpanded()
};
if (panelRole) {
props.role = panelRole;
}

return (
<Collapse in={this.isExpanded()}>
<div
className={collapseClass}
id={this.props.id}
ref='panel'>
<div {...props}>
{this.renderBody()}

</div>
Expand Down Expand Up @@ -148,7 +154,7 @@ const Panel = React.createClass({
return React.isValidElement(child) && child.props.fill != null;
},

renderHeading() {
renderHeading(headerRole) {
let header = this.props.header;

if (!header) {
Expand All @@ -157,7 +163,7 @@ const Panel = React.createClass({

if (!React.isValidElement(header) || Array.isArray(header)) {
header = this.props.collapsible ?
this.renderCollapsibleTitle(header) : header;
this.renderCollapsibleTitle(header, headerRole) : header;
} else {
const className = classNames(
this.prefixClass('title'), header.props.className
Expand All @@ -166,7 +172,7 @@ const Panel = React.createClass({
if (this.props.collapsible) {
header = cloneElement(header, {
className,
children: this.renderAnchor(header.props.children)
children: this.renderAnchor(header.props.children, headerRole)
});
} else {
header = cloneElement(header, {className});
Expand All @@ -180,23 +186,25 @@ const Panel = React.createClass({
);
},

renderAnchor(header) {
renderAnchor(header, headerRole) {
return (
<a
href={'#' + (this.props.id || '')}
aria-controls={this.props.collapsible ? this.props.id : null}
className={this.isExpanded() ? null : 'collapsed'}
aria-expanded={this.isExpanded()}
onClick={this.handleSelect}>
aria-selected={this.isExpanded()}
onClick={this.handleSelect}
role={headerRole}>
{header}
</a>
);
},

renderCollapsibleTitle(header) {
renderCollapsibleTitle(header, headerRole) {
return (
<h4 className={this.prefixClass('title')}>
{this.renderAnchor(header)}
{this.renderAnchor(header, headerRole)}
</h4>
);
},
Expand Down
8 changes: 6 additions & 2 deletions src/PanelGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ const PanelGroup = React.createClass({

render() {
let classes = this.getBsClassSet();
let {className, ...props} = this.props;
if (this.props.accordion) { props.role = 'tablist'; }
return (
<div {...this.props} className={classNames(this.props.className, classes)} onSelect={null}>
{ValidComponentChildren.map(this.props.children, this.renderPanel)}
<div {...props} className={classNames(className, classes)} onSelect={null}>
{ValidComponentChildren.map(props.children, this.renderPanel)}
</div>
);
},
Expand All @@ -53,6 +55,8 @@ const PanelGroup = React.createClass({
};

if (this.props.accordion) {
props.headerRole = 'tab';
props.panelRole = 'tabpanel';
props.collapsible = true;
props.expanded = (child.props.eventKey === activeKey);
props.onSelect = this.handleSelect;
Expand Down
54 changes: 54 additions & 0 deletions test/PanelGroupSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,58 @@ describe('PanelGroup', function () {

assert.notOk(panel.state.collapsing);
});

describe('Web Accessibility', function() {
let instance, panelBodies, panelGroup, links;

beforeEach(function() {
instance = ReactTestUtils.renderIntoDocument(
<PanelGroup defaultActiveKey='1' accordion>
<Panel header='Collapsible Group Item #1' eventKey='1' id='Panel1ID'>Panel 1</Panel>
<Panel header='Collapsible Group Item #2' eventKey='2' id='Panel2ID'>Panel 2</Panel>
</PanelGroup>
);
let accordion = ReactTestUtils.findRenderedComponentWithType(instance, PanelGroup);
panelGroup = ReactTestUtils.findRenderedDOMComponentWithClass(accordion, 'panel-group');
panelBodies = ReactTestUtils.scryRenderedDOMComponentsWithClass(panelGroup, 'panel-collapse');
links = ReactTestUtils.scryRenderedDOMComponentsWithClass(panelGroup, 'panel-heading')
.map(function(header) {
return ReactTestUtils.findRenderedDOMComponentWithTag(header, 'a');
});
});

it('Should have a role of tablist', function() {
assert.equal(panelGroup.props.role, 'tablist');
});

it('Should provide each header tab with role of tab', function() {
assert.equal(links[0].props.role, 'tab');
assert.equal(links[1].props.role, 'tab');
});

it('Should provide the panelBodies with role of tabpanel', function() {
assert.equal(panelBodies[0].props.role, 'tabpanel');
});

it('Should provide each panel with an aria-labelledby referencing the corresponding header', function() {
assert.equal(panelBodies[0].props.id, links[0].props['aria-controls']);
assert.equal(panelBodies[1].props.id, links[1].props['aria-controls']);
});

it('Should maintain each tab aria-selected state', function() {
assert.equal(links[0].props['aria-selected'], true);
assert.equal(links[1].props['aria-selected'], false);
});

it('Should maintain each tab aria-hidden state', function() {
assert.equal(panelBodies[0].props['aria-hidden'], false);
assert.equal(panelBodies[1].props['aria-hidden'], true);
});

afterEach(function() {
if (instance && ReactTestUtils.isCompositeComponent(instance) && instance.isMounted()) {
React.unmountComponentAtNode(React.findDOMNode(instance));
}
});
});
});

0 comments on commit 769781d

Please sign in to comment.