diff --git a/src/ComboboxInput/ComboboxInput.js b/src/ComboboxInput/ComboboxInput.js index 1150719a5..c4b47427c 100644 --- a/src/ComboboxInput/ComboboxInput.js +++ b/src/ComboboxInput/ComboboxInput.js @@ -37,6 +37,7 @@ const ComboboxInput = React.forwardRef(({ placeholder, menu, compact, className, } + disableKeyPressHandler disableStyles={disableStyles} noArrow /> diff --git a/src/Dropdown/__snapshots__/Dropdown.test.js.snap b/src/Dropdown/__snapshots__/Dropdown.test.js.snap index 677cde2c1..284886ba2 100644 --- a/src/Dropdown/__snapshots__/Dropdown.test.js.snap +++ b/src/Dropdown/__snapshots__/Dropdown.test.js.snap @@ -16,8 +16,12 @@ exports[` create dropdown component 1`] = ` className="fd-popover__control" > Select @@ -43,8 +47,12 @@ exports[` create dropdown component 2`] = ` className="fd-popover__control" > Select @@ -70,8 +78,12 @@ exports[` create dropdown component 3`] = ` className="fd-popover__control" > Select @@ -98,8 +110,12 @@ exports[` create dropdown component 4`] = ` className="fd-popover__control" > Select @@ -126,9 +142,13 @@ exports[` create dropdown component 5`] = ` className="fd-popover__control" > Select diff --git a/src/Identifier/Identifier.js b/src/Identifier/Identifier.js index 1efbc5896..f16b0e2cb 100644 --- a/src/Identifier/Identifier.js +++ b/src/Identifier/Identifier.js @@ -5,7 +5,7 @@ import React from 'react'; import withStyles from '../utils/WithStyles/WithStyles'; import { IDENTIFIER_MODIFIERS, IDENTIFIER_SIZES } from '../utils/constants'; -const Identifier = React.forwardRef(({ glyph, size, modifier, color, label, backgroundImageUrl, children, className, disableStyles, ...props }, ref) => { +const Identifier = React.forwardRef(({ glyph, size, modifier, color, label, backgroundImageUrl, children, className, disableStyles, role, ...props }, ref) => { const styles = { backgroundImage: `url(${backgroundImageUrl})` }; @@ -21,7 +21,12 @@ const Identifier = React.forwardRef(({ glyph, size, modifier, color, label, back className ); - const ariaRole = !children ? 'presentation' : ''; + let ariaRole; + if (role) { + ariaRole = role; + } else { + ariaRole = !children ? 'presentation' : ''; + } return ( } + disableKeyPressHandler disableStyles={disableStyles} id={id} noArrow /> diff --git a/src/Popover/Popover.js b/src/Popover/Popover.js index 8c550e3f8..ece0b3e5a 100644 --- a/src/Popover/Popover.js +++ b/src/Popover/Popover.js @@ -1,5 +1,6 @@ import chain from 'chain-function'; import classnames from 'classnames'; +import keycode from 'keycode'; import Popper from '../utils/_Popper'; import { POPPER_PLACEMENTS } from '../utils/constants'; import PropTypes from 'prop-types'; @@ -15,6 +16,18 @@ class Popover extends Component { }; } + isButton = (node) => { + if (!node) { + return false; + } + if (typeof node.type === 'string') { + return node.type.toLowerCase() === 'button'; + } else if (node.type.displayName) { + return node.type.displayName.toLowerCase() === 'button'; + } + return false; + }; + triggerBody = () => { if (!this.props.disabled) { this.setState(prevState => ({ @@ -31,9 +44,23 @@ class Popover extends Component { } }; + handleKeyPress = (event, node, onClickFunctions) => { + if (!this.isButton(node)) { + switch (keycode(event)) { + case 'enter': + case 'space': + event.preventDefault(); + onClickFunctions(); + break; + default: + } + } + } + render() { const { disableEdgeDetection, + disableKeyPressHandler, disableStyles, onClickOutside, onEscapeKey, @@ -52,9 +79,21 @@ class Popover extends Component { onClickFunctions = chain(this.triggerBody, control.props.onClick); } - const referenceComponent = React.cloneElement(control, { + let controlProps = { onClick: onClickFunctions - }); + }; + + if (!disableKeyPressHandler) { + controlProps = { + ...controlProps, + tabIndex: 0, + role: 'button', + 'aria-haspopup': true, + onKeyPress: (event) => this.handleKeyPress(event, control, onClickFunctions) + }; + } + + const referenceComponent = React.cloneElement(control, controlProps); const popoverClasses = classnames('fd-popover', className); @@ -88,6 +127,7 @@ Popover.propTypes = { customStyles: PropTypes.object, disabled: PropTypes.bool, disableEdgeDetection: PropTypes.bool, + disableKeyPressHandler: PropTypes.bool, disableStyles: PropTypes.bool, noArrow: PropTypes.bool, placement: PropTypes.oneOf(POPPER_PLACEMENTS), @@ -105,6 +145,7 @@ Popover.propDescriptions = { body: 'Node(s) to render in the overlay.', control: 'Node to render as the reference element (that the `body` will be placed in relation to).', disableEdgeDetection: 'Set to **true** to render popover without edge detection so popover will not flip from top to bottom with scroll.', + disableKeyPressHandler: 'Set to **true** to remove onKeyPress handler and aria-* roles. Only do so if the control is a complex component such as a FormInput with Button.', noArrow: 'Set to **true** to render a popover without an arrow.', placement: 'Initial position of the `body` (overlay) related to the `control`.', popperProps: 'Additional props to be spread to the overlay element, supported by popper.js.', diff --git a/src/Popover/Popover.test.js b/src/Popover/Popover.test.js index 2cee18283..2292cce6b 100644 --- a/src/Popover/Popover.test.js +++ b/src/Popover/Popover.test.js @@ -182,6 +182,53 @@ describe('', () => { }); }); + describe('control accessibility', () => { + test('adds a tabindex of 0 to the control', () => { + const wrapper = mountComponentWithStyles(popOver); + const button = wrapper.find('Icon').at(0); + + expect(button.props().tabIndex).toEqual(0); + }); + + test('adds aria-haspopup to the control', () => { + const wrapper = mountComponentWithStyles(popOver); + const button = wrapper.find('Icon').at(0); + + expect(button.props()['aria-haspopup']).toEqual(true); + }); + + test('adds a role of button to the control', () => { + const wrapper = mountComponentWithStyles(popOver); + const button = wrapper.find('Icon').at(0); + + expect(button.props().role).toEqual('button'); + }); + + test('handle space key to open popover', () => { + const syntheticEvent = { + keyCode: 32, + preventDefault: () => {} + }; + const wrapper = mountComponentWithStyles(popOver); + const button = wrapper.find('Icon').at(0); + button.prop('onKeyPress')(syntheticEvent, 'Icon', wrapper.triggerBody); + + expect(wrapper.state('isExpanded')).toBeTruthy(); + }); + + test('handle enter key to open popover', () => { + const syntheticEvent = { + keyCode: 13, + preventDefault: () => {} + }; + const wrapper = mountComponentWithStyles(popOver); + const button = wrapper.find('Icon').at(0); + button.prop('onKeyPress')(syntheticEvent, 'Icon', wrapper.triggerBody); + + expect(wrapper.state('isExpanded')).toBeTruthy(); + }); + }); + describe('Callback handler', () => { test('should dispatch the onClickOutside callback with the event', () => { let f = jest.fn(); diff --git a/src/Popover/__snapshots__/Popover.test.js.snap b/src/Popover/__snapshots__/Popover.test.js.snap index 1dcd03c28..ddfa11dc7 100644 --- a/src/Popover/__snapshots__/Popover.test.js.snap +++ b/src/Popover/__snapshots__/Popover.test.js.snap @@ -13,8 +13,12 @@ exports[` create Popover 1`] = ` className="fd-popover__control" > @@ -34,8 +38,12 @@ exports[` create Popover 2`] = ` className="fd-popover__control" > @@ -55,8 +63,12 @@ exports[` create Popover 3`] = ` className="fd-popover__control" > @@ -76,8 +88,12 @@ exports[` create Popover 4`] = ` className="fd-popover__control" > diff --git a/src/SearchInput/SearchInput.js b/src/SearchInput/SearchInput.js index 62fc0f810..2a7423981 100644 --- a/src/SearchInput/SearchInput.js +++ b/src/SearchInput/SearchInput.js @@ -184,6 +184,7 @@ class SearchInput extends Component { )} } + disableKeyPressHandler disableStyles={disableStyles} /> ); diff --git a/src/Shellbar/__snapshots__/Shellbar.test.js.snap b/src/Shellbar/__snapshots__/Shellbar.test.js.snap index adf451e76..8c4b969af 100644 --- a/src/Shellbar/__snapshots__/Shellbar.test.js.snap +++ b/src/Shellbar/__snapshots__/Shellbar.test.js.snap @@ -42,9 +42,12 @@ exports[` create shellbar 1`] = ` className="fd-popover__control" > JS @@ -99,9 +102,12 @@ exports[` create shellbar 2`] = ` className="fd-popover__control" > JS @@ -144,8 +150,12 @@ exports[` create shellbar 3`] = ` className="fd-popover__control" > create shellbar 3`] = ` className="fd-popover__control" > create shellbar 3`] = ` className="fd-popover__control" > create shellbar 3`] = ` className="fd-popover__control" > create shellbar 3`] = ` className="fd-popover__control" > JS @@ -363,8 +387,12 @@ exports[` create shellbar 3`] = ` className="fd-popover__control" > @@ -405,8 +433,12 @@ exports[` create shellbar 4`] = ` className="fd-popover__control" > create shellbar 4`] = ` className="fd-popover__control" > create shellbar 4`] = ` className="fd-popover__control" > create shellbar 4`] = ` className="fd-popover__control" > create shellbar 4`] = ` className="fd-popover__control" > JS @@ -617,8 +663,12 @@ exports[` create shellbar 4`] = ` className="fd-popover__control" > @@ -659,8 +709,12 @@ exports[` create shellbar 5`] = ` className="fd-popover__control" > create shellbar 5`] = ` className="fd-popover__control" > create shellbar 5`] = ` className="fd-popover__control" > create shellbar 5`] = ` className="fd-popover__control" > JS @@ -857,8 +921,12 @@ exports[` create shellbar 5`] = ` className="fd-popover__control" > @@ -899,8 +967,12 @@ exports[` create shellbar 6`] = ` className="fd-popover__control" > create shellbar 6`] = ` className="fd-popover__control" > create shellbar 6`] = ` className="fd-popover__control" > create shellbar 6`] = ` className="fd-popover__control" > @@ -1107,8 +1189,12 @@ exports[` create shellbar 6`] = ` className="fd-popover__control" > diff --git a/src/Tile/__snapshots__/Tile.test.js.snap b/src/Tile/__snapshots__/Tile.test.js.snap index f9fbbff41..842a6e0c7 100644 --- a/src/Tile/__snapshots__/Tile.test.js.snap +++ b/src/Tile/__snapshots__/Tile.test.js.snap @@ -95,8 +95,12 @@ exports[` create tile component 4`] = ` className="fd-popover__control" > @@ -160,8 +164,12 @@ exports[` create tile component 6`] = ` className="fd-popover__control" > diff --git a/src/Tile/__snapshots__/TileActions.test.js.snap b/src/Tile/__snapshots__/TileActions.test.js.snap index 6b3240eb8..74bd2f200 100644 --- a/src/Tile/__snapshots__/TileActions.test.js.snap +++ b/src/Tile/__snapshots__/TileActions.test.js.snap @@ -16,8 +16,12 @@ exports[` create Tile.Actions component 1`] = ` className="fd-popover__control" >