-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
[MenuUnstyled] Create MenuUnstyled and useMenu #30961
Conversation
@material-ui/unstyled: parsed: +6.72% , gzip: +5.36% |
6d5f800
to
4948d60
Compare
c6659e4
to
2426b43
Compare
|
||
#### CSS classes | ||
|
||
The MenuUnstyled does not receive any state classes. |
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 MenuUnstyled does not receive any state classes. | |
The MenuUnstyled does not set any state classes. |
The components sets these classes no?
|
||
The MenuUnstyled does not receive any state classes. | ||
|
||
The MenuItemUnstyled can receive the following classes: |
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 as above.
border-bottom: none; | ||
} | ||
|
||
&:focus-visible { |
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.
Note that only the technology preview of Safari supports this at the moment. We should recommend the polyfill in the meantime.
Generally, the demos should have the same support matrix the library has in my opinion.
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 forgot to implement the focus-visible polyfill for menu items. Will fix.
</TriggerButton> | ||
<Popper | ||
placement="bottom-start" | ||
open={isOpen && buttonRef.current != null} |
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 reading a ref during render which is generally not safe. This is why we have the pattern for anchorEl
that puts the element in React state instead of using a 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.
Good point, thanks!
if (isOpen) { | ||
contentRef.current?.focus(); | ||
} | ||
}, [isOpen, contentRef]); |
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 may not be doing what you think it does. contentRef
has no impact on the effect behavior while the effect is not synced with contentRef.current
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 was exactly my intention. contentRef
isn't really needed in the dependency array.
It's a bit odd to me that the default pattern for a Menu does not use it as a popup. In my perception Menus are always in a popup. |
@siriwatknp Sure! Hooks are meant to be composed to make higher-level components. In case of
Indeed! Just note that the menu implements a roving focus (only the currently selected item has
The |
Some problems found after an a11y review:
References: |
}; | ||
|
||
const handleButtonKeyDown = (event) => { | ||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { |
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 the ArrowUp
is pressed, the last element should be foced when the menu opens.
Implemented in the demos. I'll consider creating an unstyled MenuButton (or DropdownButton) that has this functionality built-in. It could also be used for a Select.
This makes more sense in cases where items are ordered alphabetically (which is rarely the case in a menu). I am going to implement it anyway in Listbox (which is what Select and Menu use internally).
This is now fixed
👍 Working on it |
@mnajdova this should work as expected now. |
event.preventDefault(); | ||
setAnchorEl(event.currentTarget); | ||
if (event.key === 'ArrowUp') { | ||
menuActions.current?.highlightLastItem(); |
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 don't like that we need to use useImperativeHandle
here. Is there a different option? One additional reason to have the MenuButton as a standalone 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.
In addition to this:
<TriggerButton
type="button"
onClick={handleButtonClick}
onKeyDown={handleButtonKeyDown}
ref={buttonRef}
aria-controls={isOpen ? 'simple-menu' : undefined}
aria-expanded={isOpen || undefined}
aria-haspopup="menu"
>
Language
</TriggerButton>
Has too much boilerplate which is important to not be missed. I would propose to tackle this in the next PR.
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.
Yes, I absolutely agree it's too much code. I will create the MenuButton for sure.
As for useImperativeHandle
- I realize it's not quite in the spirit of React, but I couldn't think of a better option that would work with the current MenuUnstyled design. I'm happy to discuss alternatives.
<Popper {...popperProps} ref={forwardedRef}> | ||
<Listbox {...listboxProps}> |
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.
Just to confirm, we talked about merging these one into one DOM element right?
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.
Yes, if we had a usePopper hook, we could apply the popup behavior to the listbox itself and avoid creating a wrapper div.
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 behavior looks good. Next steps would be:
- create
MenuButton
component - merge the two dom elements we have in the
MenuUnstyled
- root and listbox into one.
This PR introduces the unstyled menu component and friends:
MenuUnstyled
,MenuItemUnstyled
,useMenu
anduseMenuItem
.This is a rewrite of the components used in Material UI and it's based on the new
useListbox
hook.The unstyled Menu differs from its Material counterpart as it contains only the list of items, without the element that triggers the appearance of the list. It's more similar to Material UI's MenuList in this matter. This helps developers make it more customizable. An example of creating a button triggering a menu is included in the docs.
This PR does not enable support for nested menus. This is to be implemented in a separate PR, taking into account work done in #20591 (cc @EsoterikStare).
Preview - https://deploy-preview-30961--material-ui.netlify.app/base/react-menu/