Skip to content

Commit 7a316ff

Browse files
[added] Prop for adding aria-hidden to application element (#8)
This makes it so that when provided function that returns a DOM node to the getAriaHideElement prop, it will augment that element with the aria-hidden attribute effectively hiding everything except the tray from screenreaders.
1 parent 765c5b8 commit 7a316ff

File tree

4 files changed

+62
-3
lines changed

4 files changed

+62
-3
lines changed

lib/components/Tray.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
33
import TrayPortal from './TrayPortal';
4+
import { a11yFunction } from '../helpers/customPropTypes';
45
const renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer;
56

67
export default React.createClass({
@@ -13,7 +14,8 @@ export default React.createClass({
1314
closeTimeoutMS: React.PropTypes.number,
1415
closeOnBlur: React.PropTypes.bool,
1516
maintainFocus: React.PropTypes.bool,
16-
elementToFocus: React.PropTypes.string
17+
elementToFocus: React.PropTypes.string,
18+
getAriaHideElement: a11yFunction
1719
},
1820

1921
getDefaultProps() {

lib/components/TrayPortal.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export default React.createClass({
5959
closeTimeoutMS: PropTypes.number,
6060
children: PropTypes.any,
6161
maintainFocus: PropTypes.bool,
62-
elementToFocus: PropTypes.string
62+
elementToFocus: PropTypes.string,
63+
getAriaHideElement: PropTypes.func
6364
},
6465

6566
getInitialState() {
@@ -104,12 +105,25 @@ export default React.createClass({
104105
this.refs.content.focus();
105106
},
106107

107-
focusSelector(querySelectorToUse) {
108+
findSingleElement(querySelectorToUse) {
108109
const el = document.querySelectorAll(querySelectorToUse);
109110
const element = (el.length) ? el[0] : el;
111+
return element;
112+
},
113+
114+
focusSelector(querySelectorToUse) {
115+
const element = this.findSingleElement(querySelectorToUse);
110116
element.focus();
111117
},
112118

119+
toggleAriaHidden(element) {
120+
if (!element.getAttribute('aria-hidden')) {
121+
element.setAttribute('aria-hidden', true);
122+
} else {
123+
element.removeAttribute('aria-hidden');
124+
}
125+
},
126+
113127
handleOverlayClick(e) {
114128
if (!isChild(this.refs.content, e.target)) {
115129
this.props.onBlur();
@@ -144,6 +158,9 @@ export default React.createClass({
144158
if (this.props.onOpen) {
145159
this.props.onOpen();
146160
}
161+
if (this.props.getAriaHideElement) {
162+
this.toggleAriaHidden(this.props.getAriaHideElement());
163+
}
147164
this.setState({afterOpen: true});
148165
});
149166
},
@@ -154,6 +171,9 @@ export default React.createClass({
154171
} else {
155172
this.closeWithoutTimeout();
156173
}
174+
if (this.props.getAriaHideElement) {
175+
this.toggleAriaHidden(this.props.getAriaHideElement());
176+
}
157177
},
158178

159179
closeWithTimeout() {

lib/components/__tests__/Tray-test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,31 @@ describe('react-tray', function() {
135135
equal(document.activeElement, secondItem);
136136
});
137137
});
138+
139+
describe('getAriaHideElement prop', function() {
140+
const getAriaHideElement = () => {
141+
return document.getElementById('main_application_div');
142+
};
143+
144+
beforeEach(function() {
145+
const mainDiv = document.createElement('div');
146+
mainDiv.id = 'main_application_div';
147+
document.body.appendChild(mainDiv);
148+
});
149+
150+
it('adds aria-hidden to the given element when open', function() {
151+
renderTray({isOpen: true, getAriaHideElement: getAriaHideElement});
152+
const el = document.getElementById('main_application_div');
153+
equal(el.getAttribute('aria-hidden'), 'true');
154+
});
155+
156+
it('removes aria-hidden from the given element when closed', function() {
157+
renderTray({isOpen: true, onBlur: function() {}, closeTimeoutMS: 0, getAriaHideElement: getAriaHideElement});
158+
TestUtils.Simulate.keyDown(document.querySelector('.ReactTray__Content'), {key: 'Esc'});
159+
setTimeout(function() {
160+
const el = document.getElementById('main_application_div');
161+
equal(el.getAttribute('aria-hidden'), null);
162+
}, 0);
163+
});
164+
});
138165
});

lib/helpers/customPropTypes.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Adapted from https://github.com/react-bootstrap/react-prop-types/blob/master/src/isRequiredForA11y.js
2+
export function a11yFunction(props, propName, componentName) {
3+
if ((!props[propName]) || (typeof props[propName] !== 'function')) {
4+
return new Error(
5+
`The prop '${propName}' is required to make '${componentName}' fully accessible. ` +
6+
`This will greatly improve the experience for users of assistive technologies. ` +
7+
`You should provide a function that returns a DOM node.`
8+
);
9+
}
10+
}

0 commit comments

Comments
 (0)