Skip to content
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

Fix dropdown menu positions when scrolling #22916

Merged
merged 16 commits into from
Jan 11, 2023

Conversation

sidp
Copy link
Contributor

@sidp sidp commented Jan 2, 2023

This PR fixes #14266, an issue where dropdown positions, primarily in the compose UI, scroll together with the main timeline instead of staying fixed as expected. This is resolved by updating react-overlays from 0.9.3 to 5.2.1 (latest version as of today) and using strategy: 'fixed' in popperConfig.

Here is an outline of the changes I have made.

DropdownMenu component:

  • Use react-overlays’ built-in arrow positioning feature.
  • Re-implemented .dropdown-menu__arrow. The element needs to have a width and height be positioned correctly and this was easier than the previous approach with
  • Moved wrapping div (.dropdown-menu from DropdownMenu to Dropdown).
  • Wrap button in a span to solve issue with ref.

EmojiPickerDropdown, LanguageDropdown, PrivacyDropdown and SearchPopout components:

  • Update for new API (render prop as children, set popperConfig, flip and target props.
  • Wrap menu components in a div where react-overlays’ props can be inserted.

Everywhere:

  • Re-implemented the animations using css @keyframes animations to simplify the JavaScript code. No need to keep track of the mounted state to make the positioning of overlays work, or duplicate the animation parameters in multiple places in the codebase.

Two thing to consider when reviewing this PR:

  • The placement (top, left, right, bottom) of the overlay. It was previously stored in the redux-managed state. This is no longer the case, since react-dropdowns flips the direction if needed to fit them in the viewport, and keeps track of this state itself. I don't know if this is still needed for some edge-case but I haven't seen that in my testing.
  • Because the arrows (.dropdown-menu__arrow) have been changed from being drawn with border-width to an inline SVG path, this will break any third-party CSS that relies on the border-width method. Not sure if this is a problem in practice.

Any comments and feedback is very welcome!

sidp added 10 commits January 2, 2023 15:21
* Use react-overlays built-in arrow positioning feature
* Re-implemented `.dropdown-menu__arrow` to have a defined width and height to improve positioning
* Moved wrapping div (`.dropdown-menu` from `DropdownMenu` to `Dropdown`)
* Wrap button in a span to solve issue with ref
* Temporarily remove animations
* Wrap EmojiPickerMenu in a div where react-overlays’ ref is added
@ClearlyClaire ClearlyClaire self-requested a review January 3, 2023 11:07
@ClearlyClaire
Copy link
Contributor

This will take a while to properly review, but it looks good, thanks for your contribution!

Copy link
Contributor

@ClearlyClaire ClearlyClaire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's an opportunity to simplify some of the code further.

I'm also running into an issue where the compose dropdowns are not flipped for some reason I cannot understand:
image

<span ref={this.setTargetRef}>
{button}
</span>
<Overlay show={open} offset={[5, 5]} placement={dropdownPlacement} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would make sense just removing the placement logic and let react-overlays handle it through the flip prop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the placement state from the individual components and also from the redux actions, so react-overlays is now handling all the positioning state.

Comment on lines 268 to 281
<Overlay show={open} placement={placement} flip target={this.findTarget} container={container} popperConfig={{ strategy: 'fixed' }}>
{({ props, placement }) => (
<div {...props} style={{ ...props.style, width: 350, maxWidth: '100vw' }}>
<div className={`dropdown-animation ${placement}`}>
<PrivacyDropdownMenu
items={this.options}
value={value}
onClose={this.handleClose}
onChange={this.handleChange}
placement={placement}
/>
</div>
</div>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is another regression here in that the dropdown is displayed below the boost modal:
image

From my understanding, this is because the dropdown div is now wrapped by two more divs, so while it has a z-index: 9999, the wrapper doesn't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I resolved this by removing the z-index from .modal-root__modal. z-index is already set to 9999 for .modal-root so this does not appear to make any practical difference other than that the overlays are now working properly in the modals.

@sidp
Copy link
Contributor Author

sidp commented Jan 3, 2023

@ClearlyClaire Thanks for the review! I've solved the issues you commented on, but discovered an additional one when I got the dropdowns in the compose UI to flip properly. This issue is that the button for the privacy dropdown should take on the color of the adjacent option, and now that the react-overlays is handling the positioning we don't have the state available to apply the correct color to the button anymore.

I'll request another review once I've fixed this.

Copy the placement state into the `PrivacyDropdown` and `LanguageDropdown` components, to apply correct styling to the buttons depending on which placement the Overlay has.
@ClearlyClaire
Copy link
Contributor

I'll request another review once I've fixed this.

Is this ready for another round of review?

@sidp
Copy link
Contributor Author

sidp commented Jan 5, 2023

Is this ready for another round of review?

Yes, please go ahead! I've done another round of testing here and I think it's ready for another review. CI fails on lint, but from what I can see it's not on lines touched by this PR.

Copy link
Contributor

@ClearlyClaire ClearlyClaire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have found no issue when testing, and I could only find one minor code issue. It should be fixed, but otherwise you can consider the PR approved.
Thank you for your contribution!

@Gargron Gargron merged commit fd33bcb into mastodon:main Jan 11, 2023
/>
<Overlay show={open} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
{({ props, placement }) => (
<div {...props} style={{ ...props.style, width: 280 }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I just noticed that, but is there a way we can avoid such hardcoded values? The ideal width of the components depend on their text content, which depends on language, so it really should not be hardcoded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

When opening the context menu in the timeline, it flies away.
3 participants