diff --git a/.eslintrc b/.eslintrc index a7119795..06dc1920 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,6 +28,7 @@ "react/no-find-dom-node": [0], "react/jsx-closing-bracket-location": [0], "react/jsx-filename-extension": ["error", {"extensions": [".js"]}], + "react/forbid-prop-types": [1, {"forbid": ["any"]}], "react/require-default-props": 0 } } diff --git a/README.md b/README.md index 41046df8..a42c30e7 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,28 @@ Modal.setAppElement(appElement); Modal.setAppElement('#your-app-element'); ``` +### Additional Aria Attributes + +Use the property `aria` to pass any additional aria attributes. It accepts +an object where the keys are the names of the attributes without the prefix +`aria-`. + +Example: + +```js + +

H1

+
+

Description goes here.

+
+
+``` + ## Styles Styles are passed as an object with 2 keys, 'overlay' and 'content' like so diff --git a/docs/README.md b/docs/README.md index 36d77d9e..1094f86b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -82,6 +82,13 @@ import ReactModal from 'react-modal'; Function that will be called to get the parent element that the modal will be attached to. */ parentSelector={() => document.body} + /* + Additional aria attributes (optional). + */ + aria={{ + labelledby: "heading", + describedby: "full_description" + }} /> ``` diff --git a/examples/basic/app.js b/examples/basic/app.js index 96fdad35..323f4d90 100644 --- a/examples/basic/app.js +++ b/examples/basic/app.js @@ -69,12 +69,19 @@ class App extends Component { {}} onRequestClose={this.toggleModal_2}> -

test

+

This is the modal 2!

+
+

This is a description of what it does: nothing :)

+
); diff --git a/specs/Modal.spec.js b/specs/Modal.spec.js index 867ba41b..9a407851 100644 --- a/specs/Modal.spec.js +++ b/specs/Modal.spec.js @@ -239,7 +239,7 @@ describe('State', () => { unmountModal(); expect(!isBodyWithReactModalOpenClass()).toBeTruthy(); }); - + it('should not add classes to document.body for unopened modals', () => { renderModal({ isOpen: true }); expect(isBodyWithReactModalOpenClass()).toBeTruthy(); @@ -259,6 +259,14 @@ describe('State', () => { expect(isBodyWithReactModalOpenClass()).toBeTruthy(); }); + it('additional aria attributes', () => { + const modal = renderModal({ isOpen: true, aria: { labelledby: "a" }}, 'hello'); + expect( + mcontent(modal).getAttribute('aria-labelledby') + ).toEqual("a"); + unmountModal(); + }); + it('adding/removing aria-hidden without an appElement will try to fallback to document.body', () => { ariaAppHider.documentNotReadyOrSSRTesting(); const node = document.createElement('div'); diff --git a/src/components/Modal.js b/src/components/Modal.js index 9f8b1eee..db3077d1 100644 --- a/src/components/Modal.js +++ b/src/components/Modal.js @@ -53,6 +53,7 @@ export default class Modal extends Component { ariaHideApp: PropTypes.bool, shouldCloseOnOverlayClick: PropTypes.bool, parentSelector: PropTypes.func, + aria: PropTypes.object, role: PropTypes.string, contentLabel: PropTypes.string.isRequired }; diff --git a/src/components/ModalPortal.js b/src/components/ModalPortal.js index 066317d2..2dc16163 100644 --- a/src/components/ModalPortal.js +++ b/src/components/ModalPortal.js @@ -51,6 +51,7 @@ export default class ModalPortal extends Component { shouldCloseOnOverlayClick: PropTypes.bool, role: PropTypes.string, contentLabel: PropTypes.string, + aria: PropTypes.object, children: PropTypes.node }; @@ -254,6 +255,11 @@ export default class ModalPortal extends Component { `${className} ${additional}` : className; } + ariaAttributes = items => Object.keys(items).reduce((acc, name) => { + acc[`aria-${name}`] = items[name]; + return acc; + }, {}); + render() { const { className, overlayClassName, defaultStyles } = this.props; const contentStyles = className ? {} : defaultStyles.content; @@ -273,7 +279,8 @@ export default class ModalPortal extends Component { onKeyDown={this.handleKeyDown} onClick={this.handleContentOnClick} role={this.props.role} - aria-label={this.props.contentLabel}> + aria-label={this.props.contentLabel} + {...this.ariaAttributes(this.props.aria || {})}> {this.props.children}