-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[UI Framework] Improve KuiContextMenu keyboard navigation UX #14434
[UI Framework] Improve KuiContextMenu keyboard navigation UX #14434
Conversation
bef2de5
to
f63d868
Compare
jenkins, test this |
@cjcenizal you have to merge master into this, to get rid of the licencing issue. I merged a fix for that yesterday. |
I think this PR destroyed (on purpose?) some of the old behavior. In panels that are menu only, I cannot use Shift+Tab anymore to move focus to the header (and press enter there to move an layer up). I can now only use Left to move a layer up. But the content dialog in the example (i.e. a non menu dialog?) can still focus that header and use enter. Is this a desired behavior or got this broken by accident? |
Due to the To be honest I don't find that focus ring in this place very attractive and I would like to discuss other solutions. I would either try to create a more beautiful focus ring for this, though I had a hard time coming up with an idea how this could look like, or get rid of the focus ring for the dialog in general. Why would I even say such a thing? Here some arguments why I think we can get rid of it in this place:
I just don't see a good reason, that this focus ring would add much for the user in this case. That she would expect more likely that pressing keys will now interact with it? Wouldn't most users assume that anyway for something behaving very modal like? I just feel we lose too much from a design perspective for gaining little to nothing in usability. |
f63d868
to
2303ea7
Compare
@timroes Thanks for those terrific catches! I completely agree that the focus ring provides no benefit and sends the wrong message to the user (I didn't notice it being applied originally). I've removed it as you suggested. I also fixed that bug with the tabbing UX -- that was a regression. Could you take another look? |
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 UX appears fine to me. I have some reservations about the very frugal use of refs which are passed across component boundaries, which I think we could reduce. But that might better be done in a separate "clean up component interfaces and ref usage" PR.
isVisible
won't be missed, but it still seems to be present in the KuiContextMenu
tests. 😉
if (node) { | ||
this.content = node; | ||
} | ||
}; |
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.
If we store a reference to the dom element here, we should also clear it when we're called with null
. Just storing this.content = node
would do that.
@weltenwort Bah thanks for catching that mistake in the tests. I've addressed both of your points. Mind taking one last quick look? |
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.
Nice, thank you 😄
I just noticed a small thing with keyboard focus in the dashboard: After maximizing a panel via the context menu, I can not focus any other element in Kibana without first clicking somewhere. Maybe the focus trap was not reset properly?
this.panel = node; | ||
this.panel = node; | ||
|
||
if (this.panel) { | ||
this.panel.addEventListener('animationend', this.onTransitionComplete); |
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.
Now if we just could remove the event listener again when the previous this.panel
was an element but the node
isn't... or even better replace it with the onAnimationEnd
react prop 💭
@weltenwort Done and done! You are my new favorite code reviewer. |
Okay now I unfortunately must start nitpicking a bit 😅 When pressing Tab you can cycle through all options, and it will start from the top (i.e. the dialog itself) again when reaching the end. The same behavior doesn't work when using Shift+Tab to cycle backwards. You will be stuck on the dialog in this case. If this is an actual issue in one of the libraries, I wouldn't consider it blocking or too worse, just crossed my mind. |
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.
If you could fix the small findings, it would be great. Otherwise this looks good to me.
|
||
onDeletePanel = () => { | ||
this.closePopover(); | ||
this.props.onDeletePanel(); |
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.
Do you think we should check whether the onDeletePanel
prop exists, since it's not marked as required in the prop types, even though as far as I understand, this case can in our current setup never occur?
}], | ||
}; | ||
|
||
this.panels = flattenPanelTree(panelTree); | ||
} | ||
|
||
onButtonClick() { | ||
onButtonClick = () => { | ||
this.setState({ | ||
isPopoverOpen: !this.state.isPopoverOpen, |
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.
Not really touched in the PR, but since I currently see this in the review, might you change this to use an updater function?
@@ -18,21 +18,21 @@ export default class extends Component { | |||
}; | |||
} | |||
|
|||
onButtonClick() { | |||
onButtonClick = () => { | |||
this.setState({ | |||
isPopoverOpen: !this.state.isPopoverOpen, |
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 here with updater function.
As discussed in person: If the trigger button is already focused, and you scroll away, pressing Enter will open the menu, but not scroll back to the menu (in contrast to all other actions with the menu). Very nitpicking, but you might want to look into it :-) |
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 is such a great component. I didn't notice anything else beyond what has been mentioned by @timroes and my comment below.
// If there aren't any items then this is probably a form or something. | ||
// So let's focus the first tabbable item and expedite input from the user. | ||
if (!this.menuItems.length) { | ||
const tabbableItems = tabbable(this.content); |
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.
Maybe we should guard against this.content
being null | undefined
?
@timroes @weltenwort Comments addressed! Could you take another look? |
1f8862a
to
3374111
Compare
jenkins, test this |
…it when you select an option.
… the menu when an item is clicked.
9649eb0
to
c427b62
Compare
…#14434) * Refactor focus state logic to use the React lifecycle correctly. * Update KuiPopover snapshots. * Remove unnecessary isVisible prop from KuiContextMenu. * Allow user to both tab AND use the arrow keys for navigation. * Reinstate ability to tab and shift-tab to the title of KuiContextMenuPanel. * Release focus from Dashboard panel options KuiContextMenu by closing it when you select an option. * Update KuiContextMenu example to demonstrate best practice of closing the menu when an item is clicked. * Replace native transitionend event handler with onAnimationEnd React event handler.
…#14556) * Refactor focus state logic to use the React lifecycle correctly. * Update KuiPopover snapshots. * Remove unnecessary isVisible prop from KuiContextMenu. * Allow user to both tab AND use the arrow keys for navigation. * Reinstate ability to tab and shift-tab to the title of KuiContextMenuPanel. * Release focus from Dashboard panel options KuiContextMenu by closing it when you select an option. * Update KuiContextMenu example to demonstrate best practice of closing the menu when an item is clicked. * Replace native transitionend event handler with onAnimationEnd React event handler.
Resolves #14239
Also addresses #14183 (review)
@timroes I implemented your suggestions for the improved keyboard navigation. @weltenwort I refactored the code a bit and got rid of that
isVisible
prop. It turns out it wasn't necessary at all --embarrassingly, it didn't even do what I originally thought it was doing.