Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dropdown arrow key support #1075

Merged
merged 13 commits into from
Jan 14, 2019
Merged

Conversation

kmcfaul
Copy link
Contributor

@kmcfaul kmcfaul commented Dec 17, 2018

What: Dropdown menus should be navigable via up and down arrow keys.

Refers to issue: #1002

@kmcfaul
Copy link
Contributor Author

kmcfaul commented Dec 17, 2018

I removed the mapping for the key handler when the children elements are passed in, and not DropdownItems, for the moment, as they were causing invariant errors and React warnings because the keyHandler prop was being read as a DOM element.

@kmcfaul kmcfaul added PF4 React issues for the PF4 core effort accessibility labels Dec 17, 2018
@patternfly-build
Copy link
Contributor

PatternFly-React preview: https://1075-pr-patternfly-react-patternfly.surge.sh

@coveralls
Copy link

coveralls commented Dec 17, 2018

Pull Request Test Coverage Report for Build 3934

  • 30 of 72 (41.67%) changed or added relevant lines in 7 files are covered.
  • 4 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.9%) to 80.179%

Changes Missing Coverage Covered Lines Changed/Added Lines %
packages/patternfly-4/react-core/src/components/Dropdown/Dropdown.js 3 5 60.0%
packages/patternfly-4/react-core/src/components/Dropdown/Item.js 0 2 0.0%
packages/patternfly-4/react-core/src/components/Dropdown/DropdownItem.js 4 12 33.33%
packages/patternfly-4/react-core/src/components/Dropdown/Toggle.js 2 10 20.0%
packages/patternfly-4/react-core/src/components/Dropdown/DropdownMenu.js 18 40 45.0%
Files with Coverage Reduction New Missed Lines %
packages/patternfly-4/react-core/src/components/Modal/ModalContent.js 1 82.35%
packages/patternfly-4/react-core/src/components/Nav/NavItem.js 1 75.0%
packages/patternfly-4/react-core/src/components/Dropdown/Toggle.js 2 47.22%
Totals Coverage Status
Change from base Build 3899: -0.9%
Covered Lines: 4390
Relevant Lines: 5138

💛 - Coveralls

class DropdownItem extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this to be in the constructor. The class properties plugin is being used. This will match other components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to remove the onKeyDown binding but I'm not sure where the ref should be created if outside of the constructor. Removing this breaks the ref.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a babel plugin that pretty much abstracts away any use of the constructor & adds any definition within the class as a class property instead of having to explicitly do so in the constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, cool. I removed the constructor.

constructor(props) {
super(props);
this.ref = React.createRef();
this.onKeyDown = this.onKeyDown.bind(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this. If it is declared as a class property using the arrow notation there is no need to bind here.

{...additionalProps}
className={css(isDisabled && styles.modifiers.disabled, isHovered && styles.modifiers.hover, className)}
onKeyDown={this.onKeyDown}
ref={ref => (this.ref = ref)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are using createRef you can simply pass this.ref to it. Then you can access the current ref with this.ref.current

}

keyHandler(index, position, custom = false) {
const kids = this.props.children;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could break if consumers put items in a fragment. It might be better to make use of context to pass things around.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean by using context? I am seeing that having a child that is a React.Fragment will throw an error but should we just handle that when the children get extended (wrapping them in a div) or is there a better method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can move keyHandler and sendRef to a Context but they only get passed down one layer so I'm not sure it saves much. I'm also confused about the case of putting the items in a Fragment because the top level Dropdown component should be expecting an array.

@jgiardino
Copy link
Collaborator

When I test this component using just my keyboard, it works as expected. When I test in JAWS however, I don't see the behavior where hitting the Down arrow key on the last item shifts focus to the first item.

@jgiardino
Copy link
Collaborator

Something else I missed in my previous review, when using just the keyboard without screen reader to navigate this component, the user should be able to hit the Tab key while the menu is open to close the menu and shift focus to the next element on the page.

You can see this behavior in the wai-aria example

This behavior is expected for the variations that display a menu, but not for the variation that's labeled "Dropdown Panel" in our preview. In that case, Tab key is now the user would navigate with the contents within the panel.

@@ -81,7 +81,7 @@ describe('dropdown', () => {
test('basic', () => {
const view = mount(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are application launcher tests that run similar tests. Will most likely need some updates.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the tests.

@kmcfaul kmcfaul force-pushed the dropdown-arrow-key-support branch 2 times, most recently from f93d345 to dd33618 Compare January 9, 2019 20:50
@jgiardino
Copy link
Collaborator

Great job on this!!! 🎉

I am able to test in Chrome, both with VO and normal keyboard interaction, and it works as I'd expect for both cases.

When I try to test in FF or Safari, the Dropdown doesn't even open, but I have been having issues with the pf-react previews (e.g. sometimes they're cached and they don't behave as expected), so I'm not sure if that's an issue with the Dropdown, or with these browsers properly loading these previews. I'm hoping it's the latter :-)

When I test on my phone (Safari or Chrome), I'm able to touch the toggle to close the menu, but touching outside the toggle or menu does nothing. This was something that was fixed in #1152. But I also wouldn't be surprised if this is more of a preview issue than a component issue. I'm trying to test the latest version of the workspace, and the Dropdown there doesn't work on my phone at all.

@jgiardino
Copy link
Collaborator

UPDATE - I was able to get the preview to load in Firefox. It appears to work the same as in Chrome with one exception... When I click the toggle to display the menu, I should be able to hit the Esc key to dismiss the menu. That works in Chrome, but not in FF.

I'll keep trying to get the other previews to work, but wanted to pass that along.

@beanh66
Copy link
Member

beanh66 commented Jan 11, 2019

Thanks @kmcfaul, this looks great! Worked well when I tried it out in Chrome.

@jgiardino
Copy link
Collaborator

I was able to get it working in Safari, too. The issue I noted above is also the case in Safari:

When I click the toggle to display the menu, I should be able to hit the Esc key to dismiss the menu.

But then I found this, which might explain why:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus

Most browsers place focus on the button on click, but Safari and Firefox on a mac don't. 😕 Let me know your thoughts on this one. It is an edge case, and the user can still dismiss the menu with other ways, so I'm not sure how much we should try to handle that interaction.

dlabaj
dlabaj previously approved these changes Jan 11, 2019
};

class DropdownItem extends React.Component {
componentWillMount() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

willMount is being deprecated. You can write this like below

class DropdownItem extends React.Component {
  ref = React.createRef()
  ...

@@ -70,6 +105,8 @@ class DropdownItem extends React.Component {
<Component
{...additionalProps}
className={css(isDisabled && styles.modifiers.disabled, isHovered && styles.modifiers.hover, className)}
ref={ref => (this.ref = ref)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since tyou are already using createRef() this can be written as ref={this.ref}

} else if (event.key === 'ArrowDown') {
this.props.context.keyHandler(this.props.index, 'down');
} else if (event.key === 'Enter') {
this.ref.click();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, i beleive should be this.ref.current.click(). Also since it is possible to pass a component in you will need to make sure that click exists.

menu = (
<DropdownContext.Consumer>
{onSelect => (
class DropdownMenu extends React.Component {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to keep consistent with the repo modify this to be

class DropdownMenu extends React.Component {
  refsCollection = {};

  keyHandler = (index, position, custom = false) => { ... }
}

Copy link
Collaborator

@jgiardino jgiardino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great!
I was able to test this on my phone and verify that it works there.
I think the other observations I noted with FF and Safari can be addressed later if those come up as issues.

Copy link
Contributor

@tlabaj tlabaj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accessibility PF4 React issues for the PF4 core effort
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants