feat(ui): make dropdown positioning responsive#7828
Conversation
| position: relative; | ||
| font-size: 14px; | ||
| user-select: none; | ||
| touch-action: manipulation; |
There was a problem hiding this comment.
This is not directly related to positioning, but improves support on touch devices by removing a delay between a user’s tap and the subsequent click event. More details on MDN
| left: ${props.position === 'left' ? 0 : 'auto'}; | ||
| right: ${props.position === 'right' ? 0 : 'auto'}; | ||
| left: ${props.left}; | ||
| `}; | ||
| `; |
There was a problem hiding this comment.
The new hook calculates a desired left co-ordinate based on requested left/right placement, so we don’t need to switch between left and right here anymore.
| <StyledWrapper | ||
| closeOnSelection={closeOnSelection} | ||
| onSelection={handler => handler()} | ||
| onMenuToggle={({ isOpen }) => setOpen(isOpen)} |
There was a problem hiding this comment.
I’m not sure if there’s a better way to access React Aria’s open state than this with the extra useState hook, but this is important to be able to skip most of the positioning logic except when a dropdown is actually open.
| className={className} | ||
| > | ||
| {renderButton()} | ||
| <div ref={refs.source}>{renderButton()}</div> |
There was a problem hiding this comment.
In theory we could pass the ref directly to the dropdown buttons, but because the buttons are provided via a render prop, I preferred to wrap it for now. Happy to adjust this to be renderButton(ref) and update all the places that use <Dropdown> if that’s preferable.
Summary
This PR continues #7820, #7825, and #7827.
It makes the positioning of dropdown menus responsive by ensuring they remain within the viewport.
For example, here’s a dropdown in the collections UI from #7827 demonstrating how this PR fixes the overflow:
The implementation works by calculating the dropdown position for the desired left/right placement, and then shifting it along the x-axis if needed to ensure it remains within the viewport. (Within reason, at a narrow enough viewport, the dropdown can overflow on both sides, at which point there’s not much more to do. But Decap does not contain any very wide dropdowns, so this scenario can be ignored I think.) For most viewports, there is no change and menus are displayed as before. But for small viewports this approach fixes things.
It is heavily inspired by Floating UI’s API and the hook added in this PR is roughly equivalent to:
Decap’s needs are quite simple though, so I preferred to roll my own, minimal implementation, which is significantly smaller and less complex than Floating UI. I did a quick check to make sure the size benefits justified owning the code. The bundle size of this implementation is ~1.6 kB (753 B gzip) vs ~13.8 kB (5.52 kB gzip) for Floating UI. Add on the 500 kB install size of
@floating-ui/react-domand I think the benefits are pretty clear.Test plan
Existing tests pass and I manually tested the positioning using the local dev environment. If you want, I guess it’s possible to add an E2E test that would use a small viewport and assert that clicking a dropdown shows its contents within the viewport, but I haven’t added that currently.
Checklist
Please add a
xinside each checkbox:A picture of a cute animal (not mandatory but encouraged)