-
-
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
Dropdown component #9893
Comments
I would love this feature as well. I wrote my own Dropdown component that wraps Menu and I've tried all manner of tweaks to the Menu props (and Popover props and Modal props and Paper props) to try and get the backdrop to stop eating clicks, but still close when clicking outside. Would be awesome for there to be 1st class support for this use case in MUI. |
I'm also looking for a Dropdown menu component, would you mind sharing your code? @justinryder |
Update: we have a new Popper component. It's a building block we use to create a Dropdown component.
@turnerhayes I'm wondering if we shouldn't allow people to switch between the Popper and Popover component when using the Menu component. They use different tradeoffs, Popover is better suited for mobile when Popper is better suited for desktop.
We have a |
This demo demonstrates how a dropdown like experience can be built: https://material-ui.com/demos/menus/#menulist-composition. It composes the ClickAwayListener, Popper and Grow components. Also, if you don't care about the scroll locking you can use the Menu and change the display position like here: https://next.material-ui.com/guides/interoperability/#portals. |
@oliviertassinari I plan on tackling dropdowns next (while continuing to also help gradually move #15597 forward). It will be a while before I get to the point of a pull request. I'll need to spend some time digging into the details of how the positioning works for Popper and Popover. I'll probably start doing some proof-of-concept work in sandboxes over the next couple weeks. I'll post links here to any sandboxes showing anything of interest that I want feedback on. My intent is to support this via a As far as related issues, I see #11243, #10804, and #15055. Are there any others you know about that I should be looking at while working on this? For my own reference, this is a modified version of the MenuList composition demo as a starting point for exploration: https://codesandbox.io/s/x739zr9684 Material Design references: |
@ryancogswell This is a weak spot of the library. I know that the documentation could be improved to better illustrate how to change the menu position, same for the select component. But we have more important issues with the implementation. I think that we should first agree with what's wrong. The issues I'm aware of:
|
@oliviertassinari My thought was that the
For |
@ryancogswell I agree, a Dropdown menu without Popper is something else. Once we have wrapped our head around the use cases we want to support, we can think of the best API. |
@oliviertassinari Here is a relevant resource that provide further support for that keyboard approach: https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/. down arrow and up arrow are listed as optional controls for menu buttons that open the menu and put focus on the first or last item respectively (though playing around with some different apps, I don't see this differentiation -- both arrow keys just open and select the first item). This means taking more control of the menu button which should allow us to improve the DX considerably for typical menu cases. Our Simple Menu example could become something like: <Menu id="simple-menu" menuButton={<Button>Open Menu</Button>}>
<MenuItem>Profile</MenuItem>
<MenuItem>My account</MenuItem>
<MenuItem>Logout</MenuItem>
</Menu> with Material-UI handling all of the aria properties, open/close, and anchor element. This paradigm could also be leveraged eventually for creating cascading menus: <Menu id="cascading-menu" menuButton={<Button>Pick a food</Button>}>
<MenuItem>Pie</MenuItem>
<SubMenu menuButton={<MenuItem>Fruit</MenuItem>}>
<MenuItem>Apple</MenuItem>
<MenuItem>Orange</MenuItem>
</SubMenu>
<MenuItem>Pizza</MenuItem>
</Menu> If you think this API approach ( Separate minor issue that would make sense to fix at the same time is the |
@ryancogswell I have looked at a couple of Menu dropdown components:
Out of this, I can observe that there is 2 popular APIs:
<MenuDropdown>
<MenuTrigger><Button>Oh snap</Button></MenuTrigger>
<Menu>
<MenuItem>Item n°1</MenuItem>
</Menu>
</MenuDropdown>
<MenuDropdown
content={
<Menu>
<MenuItem>Item n°1</MenuItem>
</Menu>
}
>
<Button>Oh snap</Button>
</MenuDropdown> What we do in Material-UI is uncommon. I have already seen people asking for a better API. material-ui-popup-state is a crystallization of this frustration (by @jedwards1211). But what should we do? I would probably avoid a hooks API as it might rerender too many elements and allow too many moving parts. |
@ryancogswell how do you like the hooks API in material-ui-popup-state? I definitely intend it to be the ideal balance of convenience and flexibility. It has a cascading submenus example, all it lacks is keyboard handlers for opening and closing submenus, which I have been meaning to add at some point |
@jedwards1211 Tonight is my first time looking at material-ui-popup-state, so I'm still digesting the approaches and the alternatives. A solution built directly into the Material-UI components has some options that aren't really available in userland. I see a lot of useful ideas in material-ui-popup-state, but I think a solution directly in the library can be implemented more declaratively and with approaches that are more consistent with the rest of the library. The hooks API exposes a lot that I would prefer to keep as implementation details that are encapsulated more from users, so that we have more freedom to change those details as it evolves. |
If someone needs to pass a custom component in the trigger or menu position in the API you're proposing, I guess it will have to make sure to pass through a bunch of props injected by the API? |
I have always been very skeptical of the flexibility of the approaches Olivier outlined. Though maybe that's not necessary if the API you're proposing is only intended for use with specific, unwrapped Material UI components. |
Also, relying on cloneElement to inject some props can cause headaches with TypeScript/Flow if any of those props are required, unfortunately. For that reason i have a strong bias against using cloneElement |
I'll try to keep that in mind -- I don't use TypeScript myself, so those issues aren't on the top of mind. The |
Yeah, merging event handlers is more of a hassle with the material-ui-popup-state approach |
I think it would be fine to make |
@jedwards1211 Not sure if you are aware yet, but your cascading menus are broken in v4: https://codesandbox.io/s/yv7v8vv00j. The |
Not surprising, I haven't had time to try out v4 yet, and preventing Modal from blocking mouse events is really ugly, at least in v3. I should add a peerDependency if I haven't already though. |
For the moment, I want to ignore the overall structure of the solution and just document the different aspects that the solution needs to control/impact. Below I've documented what I believe to be all the key aspects for common use cases. @oliviertassinari and @jedwards1211 -- let me know if you think I'm missing anything (or if I've included anything that you think we definitely don't need to include). Popup Open Trigger Button receives:
popup (Menu/Popover/Popper) receives:
MenuItem receives:
MenuItem doesn't apply to Popover/Popper, but they do need some way of signaling when the popup should be closed. The main options are click-away, Esc, or some sort of "close" button within the popup. A "close" button within the popup could be supported via a I have further thoughts on possible alternatives for the structure of the controlling component(s). I'll try to find time to document the rest of my thoughts sometime over the weekend. |
@jedwards1211 After some thought on context menus, The pattern I think I would want would look something like:
But I'm not sure if context menus are a common enough use case to warrant something like this in the library. They would typically only be in screens intended for advanced users (not typical end users). |
A more generic (not tied to
This would work for a broader number of use cases, so might have a better chance of eventually being in the library, but it also could be done fully in userland without looking any different than if it were in the library (I don't think it would require changes to existing components). The menu provider probably needs to be more like the following in order to be able to leverage the context data for rendering the menu:
|
As long as you guys don't get rid of the existing low-level API it's fine...this new API proposal would make simple, common cases super convenient, but wouldn't be able to help with some problems, so we'll need to be able to fall back on the existing API. material-ui-popup-state is not quite as convenient for the simple common cases, but it's handy enough in a large variety if contexts. I like its position on the flexibility/convenience plane. |
I picture doing this entirely as a non-breaking change that you would opt into via the |
I believe the reason n°1 behind the popularity of this API: <MenuDropdown
content={
<Menu>
<MenuItem>Item n°1</MenuItem>
</Menu>
}
>
<Button>Oh snap</Button>
</MenuDropdown> is the fact that it's closer to the actual DOM output. You get the button as a child of the parent hierarchy and the content mounted in a portal. <Popper button={<MyButton/>}>
<MyContent/>
</Popper> The Popper component was designed as a small alternative to react-popper. I think that it will be better if the component only focuses on the positioning relative to another element and do no handle the display coordination or the accessibility. |
FWIW I think this whole proposed API can be made with userland wrapper components too, and I'm now tempted to do so :) but it would be a welcome addition to MUI 4 as well. Also, I think MenuItem won't even need a special onClick handler...now that I think about it those events will propagate up to the Menu and we can just handle them there. |
@jedwards1211 The events will propagate up, but I don't think the Menu will know enough to know what to do. The user could have clicked on a divider, or a disabled item, or a button for opening a sub-menu. I think you need the context at the MenuItem to know whether or not the click should trigger a close.
I think that is definitely true of the wrappers for Popper and Popover, however the trigger for this whole conversation was wanting to support a |
@oliviertassinari I do get this argument, and if we were starting from scratch I might be more inclined to go that route. The main counter-argument is that the important thing the user is trying to achieve and the part with more potential for structural complexity is the menu, so it feels awkward to have it be the prop and to use the How strongly do you feel about which direction we should go? I feel pretty strongly that my proposal (just talking about To summarize, I think we're trying to choose between: a. <MenuDropdown
content={
<Menu>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</Menu>
}
>
<Button>Open Menu</Button>
</MenuDropdown> vs. b. <Menu button={<Button>Open Menu</Button>} variant="dropdown">
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</Menu> And for cascading menus (eventually): a. <MenuDropdown
content={
<Menu>
<MenuItem>Item 1</MenuItem>
<Menu button={<MenuItem>Item 2</MenuItem>}>
<MenuItem>Item 2a</MenuItem>
<MenuItem>Item 2b</MenuItem>
<MenuItem>Item 2c</MenuItem>
</Menu>
<MenuItem>Item 3</MenuItem>
</Menu>
}
>
<Button>Open Menu</Button>
</MenuDropdown> vs. b. <Menu button={<Button>Open Menu</Button>} variant="dropdown">
<MenuItem>Item 1</MenuItem>
<Menu button={<MenuItem>Item 2</MenuItem>}>
<MenuItem>Item 2a</MenuItem>
<MenuItem>Item 2b</MenuItem>
<MenuItem>Item 2c</MenuItem>
</Menu>
<MenuItem>Item 3</MenuItem>
</Menu> |
A userland Menu wrapper component/DropdownMenu wrapper component in this lib could inject the arrow key listener into the button props right? |
The main thing that can't be in userland is to have |
I see, yeah using a popper would probably avoid some of the tricky mouse blocking issues i was talking about with popover |
@ryancogswell Thanks for highlighting it. I would be happy with both approaches. I have found another library to benchmark (nested menus): https://reakit.io/docs/menu/. My main concern would be about considering what others are doing. Option b looks better for nested menus. |
@oliviertassinari Sounds good. I think I have enough definition to move forward. I think we can work through any other details during code review. I think even this aspect of the main API structure would be a fairly easy aspect to change during code review if we decide on a different direction. |
Option b looks fine. Would be nice to have a comprehensive comparison with other popular APIs though. |
@eps1lon I have done a short comparison in #9893 (comment). |
@eps1lon One thing to note when looking at the comparisons is just the difference in our starting point. For example if we look at one example using option "a": https://blueprintjs.com/docs/#core/components/menu.dropdowns
This Blueprint example includes this note:
So the Blueprint |
Stemming from discussion in this bug, it would be great to have a "Dropdown" component. I see it as a sort of non-modal Popover--it closes in response to clicks outside, but doesn't swallow those clicks, so that you don't have to click twice to activate another interactive element on the page (once to dismiss the open Popover, then again to activate the other element).
My specific use case is that I have a pair of icons next to each other in an AppBar, that each open a small dropdown menu. I would like users to be able to switch between menus (or go to an input/whatever else on the page) without having to click twice.
For reference, I am converting a project of mine from reactstrap to Material UI, and previously I was using reactstrap's Dropdown component, which behaves how I would like this to behave. @oliviertassinari linked me to https://material.io/guidelines/components/buttons.html#buttons-dropdown-buttons as the Material UI spec that seems to specify this case.
The text was updated successfully, but these errors were encountered: