+
{this.renderBody()}
@@ -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) {
@@ -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
@@ -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});
@@ -180,23 +186,25 @@ const Panel = React.createClass({
);
},
- renderAnchor(header) {
+ renderAnchor(header, headerRole) {
return (
+ aria-selected={this.isExpanded()}
+ onClick={this.handleSelect}
+ role={headerRole}>
{header}
);
},
- renderCollapsibleTitle(header) {
+ renderCollapsibleTitle(header, headerRole) {
return (
- {this.renderAnchor(header)}
+ {this.renderAnchor(header, headerRole)}
);
},
diff --git a/src/PanelGroup.js b/src/PanelGroup.js
index 5ac0c55dab..16d5af2435 100644
--- a/src/PanelGroup.js
+++ b/src/PanelGroup.js
@@ -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 (
-
- {ValidComponentChildren.map(this.props.children, this.renderPanel)}
+
+ {ValidComponentChildren.map(props.children, this.renderPanel)}
);
},
@@ -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;
diff --git a/test/PanelGroupSpec.js b/test/PanelGroupSpec.js
index 43f8cbdae9..fc65496379 100644
--- a/test/PanelGroupSpec.js
+++ b/test/PanelGroupSpec.js
@@ -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(
+
+ Panel 1
+ Panel 2
+
+ );
+ 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));
+ }
+ });
+ });
});