-
Notifications
You must be signed in to change notification settings - Fork 350
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
Dropdown arrow key support #1075
Conversation
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. |
PatternFly-React preview: https://1075-pr-patternfly-react-patternfly.surge.sh |
class DropdownItem extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.ref = React.createRef(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)} |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The context API https://reactjs.org/docs/context.html
There was a problem hiding this comment.
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.
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. |
f98ce68
to
11d1c53
Compare
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the tests.
f93d345
to
dd33618
Compare
Also removed mapping for custom children
Also add focus trap to dropdown panel
Handle tab and enter behavior
update tests, remove duplicate select, set toggle as focus when toggled
afc6e8a
to
4fabab7
Compare
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. |
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. |
Thanks @kmcfaul, this looks great! Worked well when I tried it out in Chrome. |
I was able to get it working in Safari, too. The issue I noted above is also the case in Safari:
But then I found this, which might explain why: 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. |
}; | ||
|
||
class DropdownItem extends React.Component { | ||
componentWillMount() { |
There was a problem hiding this comment.
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)} |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) => { ... }
}
There was a problem hiding this 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
What: Dropdown menus should be navigable via up and down arrow keys.
Refers to issue: #1002