Skip to content

Commit

Permalink
[added] Prop for adding aria-hidden to application element (#8)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
claydiffrient authored Jul 12, 2016
1 parent 765c5b8 commit 7a316ff
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 3 deletions.
4 changes: 3 additions & 1 deletion lib/components/Tray.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import TrayPortal from './TrayPortal';
import { a11yFunction } from '../helpers/customPropTypes';
const renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer;

export default React.createClass({
Expand All @@ -13,7 +14,8 @@ export default React.createClass({
closeTimeoutMS: React.PropTypes.number,
closeOnBlur: React.PropTypes.bool,
maintainFocus: React.PropTypes.bool,
elementToFocus: React.PropTypes.string
elementToFocus: React.PropTypes.string,
getAriaHideElement: a11yFunction
},

getDefaultProps() {
Expand Down
24 changes: 22 additions & 2 deletions lib/components/TrayPortal.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export default React.createClass({
closeTimeoutMS: PropTypes.number,
children: PropTypes.any,
maintainFocus: PropTypes.bool,
elementToFocus: PropTypes.string
elementToFocus: PropTypes.string,
getAriaHideElement: PropTypes.func
},

getInitialState() {
Expand Down Expand Up @@ -104,12 +105,25 @@ export default React.createClass({
this.refs.content.focus();
},

focusSelector(querySelectorToUse) {
findSingleElement(querySelectorToUse) {
const el = document.querySelectorAll(querySelectorToUse);
const element = (el.length) ? el[0] : el;
return element;
},

focusSelector(querySelectorToUse) {
const element = this.findSingleElement(querySelectorToUse);
element.focus();
},

toggleAriaHidden(element) {
if (!element.getAttribute('aria-hidden')) {
element.setAttribute('aria-hidden', true);
} else {
element.removeAttribute('aria-hidden');
}
},

handleOverlayClick(e) {
if (!isChild(this.refs.content, e.target)) {
this.props.onBlur();
Expand Down Expand Up @@ -144,6 +158,9 @@ export default React.createClass({
if (this.props.onOpen) {
this.props.onOpen();
}
if (this.props.getAriaHideElement) {
this.toggleAriaHidden(this.props.getAriaHideElement());
}
this.setState({afterOpen: true});
});
},
Expand All @@ -154,6 +171,9 @@ export default React.createClass({
} else {
this.closeWithoutTimeout();
}
if (this.props.getAriaHideElement) {
this.toggleAriaHidden(this.props.getAriaHideElement());
}
},

closeWithTimeout() {
Expand Down
27 changes: 27 additions & 0 deletions lib/components/__tests__/Tray-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,31 @@ describe('react-tray', function() {
equal(document.activeElement, secondItem);
});
});

describe('getAriaHideElement prop', function() {
const getAriaHideElement = () => {
return document.getElementById('main_application_div');
};

beforeEach(function() {
const mainDiv = document.createElement('div');
mainDiv.id = 'main_application_div';
document.body.appendChild(mainDiv);
});

it('adds aria-hidden to the given element when open', function() {
renderTray({isOpen: true, getAriaHideElement: getAriaHideElement});
const el = document.getElementById('main_application_div');
equal(el.getAttribute('aria-hidden'), 'true');
});

it('removes aria-hidden from the given element when closed', function() {
renderTray({isOpen: true, onBlur: function() {}, closeTimeoutMS: 0, getAriaHideElement: getAriaHideElement});
TestUtils.Simulate.keyDown(document.querySelector('.ReactTray__Content'), {key: 'Esc'});
setTimeout(function() {
const el = document.getElementById('main_application_div');
equal(el.getAttribute('aria-hidden'), null);
}, 0);
});
});
});
10 changes: 10 additions & 0 deletions lib/helpers/customPropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Adapted from https://github.com/react-bootstrap/react-prop-types/blob/master/src/isRequiredForA11y.js
export function a11yFunction(props, propName, componentName) {
if ((!props[propName]) || (typeof props[propName] !== 'function')) {
return new Error(
`The prop '${propName}' is required to make '${componentName}' fully accessible. ` +
`This will greatly improve the experience for users of assistive technologies. ` +
`You should provide a function that returns a DOM node.`
);
}
}

0 comments on commit 7a316ff

Please sign in to comment.