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

improve dropdown menu component placement #SHIELD-1235 #2837

Merged
merged 15 commits into from
Jun 25, 2024

Conversation

misama-ct
Copy link
Contributor

@misama-ct misama-ct commented Jun 7, 2024

Summary

The DropdownMenu component has some flaws when it comes to displaying & positioning the dropdown-content. This pull-request attempts to resolve known issues, introducing the following logic changes:

Changes

  • If there is no space below the trigger to display the dropdown content, display the content above the trigger
  • if there is not enough space to display the menu on the preferred position (left|right), display it on the opposite one
  • if the menu width is set to scale, the maximum menu-width is limited to ensure it fits into the viewport and preserves the dropdown appearance
  • part of the positioning logic is shifted into asetTimeout function, doing the positioning after an initial render, since horizontalConstraint options like 'auto' or 'scale' don't result in height- or width-values that can be used for computing positions.

Copy link

vercel bot commented Jun 7, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
ui-kit ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 25, 2024 7:32am

Copy link

changeset-bot bot commented Jun 7, 2024

🦋 Changeset detected

Latest commit: d95a1e0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 96 packages
Name Type
@commercetools-uikit/dropdown-menu Patch
@commercetools-uikit/data-table-manager Patch
@commercetools-frontend/ui-kit Patch
@commercetools-uikit/data-table Patch
@commercetools-uikit/design-system Patch
@commercetools-uikit/calendar-time-utils Patch
@commercetools-uikit/calendar-utils Patch
@commercetools-uikit/hooks Patch
@commercetools-uikit/i18n Patch
@commercetools-uikit/localized-utils Patch
@commercetools-uikit/utils Patch
@commercetools-uikit/accessible-hidden Patch
@commercetools-uikit/avatar Patch
@commercetools-uikit/card Patch
@commercetools-uikit/collapsible-motion Patch
@commercetools-uikit/collapsible-panel Patch
@commercetools-uikit/collapsible Patch
@commercetools-uikit/constraints Patch
@commercetools-uikit/field-errors Patch
@commercetools-uikit/field-label Patch
@commercetools-uikit/field-warnings Patch
@commercetools-uikit/grid Patch
@commercetools-uikit/icons Patch
@commercetools-uikit/label Patch
@commercetools-uikit/link Patch
@commercetools-uikit/loading-spinner Patch
@commercetools-uikit/messages Patch
@commercetools-uikit/notifications Patch
@commercetools-uikit/pagination Patch
@commercetools-uikit/primary-action-dropdown Patch
@commercetools-uikit/progress-bar Patch
@commercetools-uikit/stamp Patch
@commercetools-uikit/tag Patch
@commercetools-uikit/text Patch
@commercetools-uikit/tooltip Patch
@commercetools-uikit/view-switcher Patch
@commercetools-uikit/accessible-button Patch
@commercetools-uikit/flat-button Patch
@commercetools-uikit/icon-button Patch
@commercetools-uikit/link-button Patch
@commercetools-uikit/primary-button Patch
@commercetools-uikit/secondary-button Patch
@commercetools-uikit/secondary-icon-button Patch
@commercetools-uikit/async-creatable-select-field Patch
@commercetools-uikit/async-select-field Patch
@commercetools-uikit/creatable-select-field Patch
@commercetools-uikit/date-field Patch
@commercetools-uikit/date-range-field Patch
@commercetools-uikit/date-time-field Patch
@commercetools-uikit/localized-multiline-text-field Patch
@commercetools-uikit/localized-text-field Patch
@commercetools-uikit/money-field Patch
@commercetools-uikit/multiline-text-field Patch
@commercetools-uikit/number-field Patch
@commercetools-uikit/password-field Patch
@commercetools-uikit/radio-field Patch
@commercetools-uikit/search-select-field Patch
@commercetools-uikit/select-field Patch
@commercetools-uikit/text-field Patch
@commercetools-uikit/time-field Patch
@commercetools-uikit/async-creatable-select-input Patch
@commercetools-uikit/async-select-input Patch
@commercetools-uikit/checkbox-input Patch
@commercetools-uikit/creatable-select-input Patch
@commercetools-uikit/date-input Patch
@commercetools-uikit/date-range-input Patch
@commercetools-uikit/date-time-input Patch
@commercetools-uikit/input-utils Patch
@commercetools-uikit/localized-money-input Patch
@commercetools-uikit/localized-multiline-text-input Patch
@commercetools-uikit/localized-rich-text-input Patch
@commercetools-uikit/localized-text-input Patch
@commercetools-uikit/money-input Patch
@commercetools-uikit/multiline-text-input Patch
@commercetools-uikit/number-input Patch
@commercetools-uikit/password-input Patch
@commercetools-uikit/radio-input Patch
@commercetools-uikit/rich-text-input Patch
@commercetools-uikit/rich-text-utils Patch
@commercetools-uikit/search-select-input Patch
@commercetools-uikit/search-text-input Patch
@commercetools-uikit/select-input Patch
@commercetools-uikit/select-utils Patch
@commercetools-uikit/selectable-search-input Patch
@commercetools-uikit/text-input Patch
@commercetools-uikit/time-input Patch
@commercetools-uikit/toggle-input Patch
@commercetools-uikit/spacings-inline Patch
@commercetools-uikit/spacings-inset-squish Patch
@commercetools-uikit/spacings-inset Patch
@commercetools-uikit/spacings-stack Patch
@commercetools-uikit/buttons Patch
@commercetools-uikit/fields Patch
@commercetools-uikit/inputs Patch
@commercetools-uikit/spacings Patch
visual-testing-app Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@@ -111,4 +115,72 @@ storiesOf('Components|Dropdowns|DropdownMenu', module)
</SpacingsStack>
</DropdownMenu>
</Section>
));
))
.add('DropdownMenu - Reposition menu if necessary', () => {
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 don't think this story is needed for devs (or is it?), but I'm leaving it here for now, in case someone wants to check the positioning-logic in storybook during review.

@misama-ct misama-ct marked this pull request as ready for review June 17, 2024 08:32
@misama-ct misama-ct requested a review from a team June 17, 2024 08:32
@misama-ct misama-ct changed the title Shield 1235 improve dropdown menu component placement improve dropdown menu component placement #SHIELD-1235 Jun 17, 2024
@CarlosCortizasCT
Copy link
Contributor

Thanks a lot for working on this, Michael 🙇

To be honest, it's difficult for me to follow along the changes without going too deep since it's a lot of different styles calculation due to the nature of the task.

The thing that is more surprising to me in the setTimeout to wait for some styles to be applied as I wasn't expecting it.
I think I understand your explanation but it seems it might be introducing some flickering in some situations (right aligned menu).
It does not always happen, but here are some examples of why I found:

Screen.Recording.2024-06-18.at.10.14.23.mov
Screen.Recording.2024-06-18.at.10.13.04.mov

@misama-ct
Copy link
Contributor Author

misama-ct commented Jun 18, 2024

The thing that is more surprising to me in the setTimeout to wait for some styles to be applied as I wasn't expecting it.
I think I understand your explanation but it seems it might be introducing some flickering in some situations (right aligned menu).
It does not always happen, but here are some examples of why I found:

If there wasn't an 'auto' or 'scale' option, this function wouldn't be needed, because then we'd have full-control over the dropdowns dimensions. But those 2 options outsource the decision of how wide the dropdown will be to the browser (based on dropdown content) and only after the browser has rendered the dropdown, I can query for the actual width and do the proper positioning.

What I could do to avoid the flickering is, hide the dropdown from the eye with visibility: hidden initially and restore the visibility within the setTimeout function. This way the dimension calculation would work without flickering, but this only works in a browser-environment, it's basically not mockable in a test.

@CarlosCortizasCT
Copy link
Contributor

The thing that is more surprising to me in the setTimeout to wait for some styles to be applied as I wasn't expecting it.
I think I understand your explanation but it seems it might be introducing some flickering in some situations (right aligned menu).
It does not always happen, but here are some examples of why I found:

If there wasn't an 'auto' or 'scale' option, this function wouldn't be needed, because then we'd have full-control over the dropdowns dimensions. But those 2 options outsource the decision of how wide the dropdown will be to the browser (based on dropdown content) and only after the browser has rendered the dropdown, I can query for the actual width and do the proper positioning.

What I could do to avoid the flickering is, hide the dropdown from the eye with visibility: hidden initially and restore the visibility within the setTimeout function. This way the dimension calculation would work without flickering, but this only works in a browser-environment, it's basically not mockable in a test.

I think using the visibility style is a good option 👍

Another thing I'd like @FilPob to confirm is the size of the menu when we use the scale value.
In the current production version, this makes the menu to grow until the horizontal edge based on its direction but in this PR we're changing that behaviour for it to always use the whole horizontal space available.
Is this something we actually want to change?

@misama-ct
Copy link
Contributor Author

misama-ct commented Jun 18, 2024

Another thing I'd like @FilPob to confirm is the size of the menu when we use the scale value. In the current production version, this makes the menu to grow until the horizontal edge based on its direction [...]

The scale option basically translates to - css-wise - width: 100% which in this case means 100% of the available width of the closest parent which enforces a certain width. It actually is a terrible option, because you never know how wide it will be. It looks like it's going to the edge, but it actually goes beyond.

Screenshot 2024-06-18 at 11 08 05

But in any case: What I do is limit the maximum width. If the parents-width is not exceeding the viewport this behavior won't be applied.

@FilPob
Copy link

FilPob commented Jun 18, 2024

Uff, I am a bit lost with the scale thingy :D Could we rather have a call?

@misama-ct
Copy link
Contributor Author

Uff, I am a bit lost with the scale thingy :D Could we rather have a call?

Sure. Just ping me once you have time or schedule a remeet.

@misama-ct
Copy link
Contributor Author

@CarlosCortizasCT FYI @FilPob and I were checking out the previous behavior of the scale option yesterday.

It was consistently creating a dropdown which was as wide as the viewport, aligned with the trigger, appearing to end on the screen-edge but actually overflowing it.

The solution I proposed will - while not ideal - at least force-fit it on the screen, so it's at step forward. However, we failed to see a use-case for a window-wide dropdown. Is anything speaking against deprecating the scale value?

@CarlosCortizasCT
Copy link
Contributor

CarlosCortizasCT commented Jun 19, 2024

@CarlosCortizasCT FYI @FilPob and I were checking out the previous behavior of the scale option yesterday.

It was consistently creating a dropdown which was as wide as the viewport, aligned with the trigger, appearing to end on the screen-edge but actually overflowing it.

The solution I proposed will - while not ideal - at least force-fit it on the screen, so it's at step forward. However, we failed to see a use-case for a window-wide dropdown. Is anything speaking against deprecating the scale value?

Hi Michael ✋

However, we failed to see a use-case for a window-wide dropdown. Is anything speaking against deprecating the scale value?

I think this is a question for @FilPob to answer.
If we don't see a use case for it, I'm all in for deprecating it,

@FilPob
Copy link

FilPob commented Jun 19, 2024

Am also all in for removing the scale option for now

Copy link

gitstream-cm bot commented Jun 19, 2024

🥷 Code experts: no user matched threshold 10

See details

packages/components/dropdowns/dropdown-menu/README.md

Knowledge based on git-blame:

packages/components/dropdowns/dropdown-menu/src/dropdown-menu.spec.tsx

Knowledge based on git-blame:

packages/components/dropdowns/dropdown-menu/src/dropdown-menu.story.js

Knowledge based on git-blame:

packages/components/dropdowns/dropdown-menu/src/menu/dropdown-menu-menu.tsx

Knowledge based on git-blame:

To learn more about /:\ gitStream - Visit our Docs

@misama-ct
Copy link
Contributor Author

misama-ct commented Jun 19, 2024

I think using the visibility style is a good option 👍

@CarlosCortizasCT I added the visibility: hidden solution and it should™ work without flickering now. I was also able to make sure the tests continue to work (which I failed to do in my initial attempt).

@@ -3,6 +3,10 @@ import SecondaryButton from '@commercetools-uikit/secondary-button';
import { act, screen, render } from '../../../../../test/test-utils';
import DropdownMenu from './dropdown-menu';

// Pause test execution to wait for the DOM-operations to finish
const waitForDOMOperations = async () =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps we can use the utilities Jest already providers for dealing with timers in tests. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I understood, those utilities are there to avoid actual waiting while running the tests (basically mocking timer-functions). But the setTimeout function here is indeed needed to defer the execution of the code following after. The value of 0 milliseconds is making sure, that execution happens immediately after the recent call stack (= calculating the final DOM in this case) is cleared, so there is no actual waiting involved and test-execution won't take any longer than before.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the explanation, Michael.

I understand the use case and why we need somehow managing the timeout in these tests, what I'm suggesting is that, instead of using custom functions, we can use what the testing library we use already provides.

In this case, using jest.useFakeTimers() and await jest.runAllTimersAsync(); will work and we wouldn't need to introduce custom code in the test suite.

Would that be reasonable for you?

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 see, thanks for the hint, i would've never guessed that runAllTimersAsync is the key. I went through the jest docs but couldn't find anything fitting, and the crucial part that would eventually have made me realize it is, only shows up in my editor as inline-help after putting the function in use:

Screenshot 2024-06-20 at 09 27 56

I pushed the changes, please have another look. Thanky you very much!

Copy link
Contributor

@CarlosCortizasCT CarlosCortizasCT left a comment

Choose a reason for hiding this comment

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

Thanks Michael 👍

Comment on lines +7 to +13
beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Technically this it is not needed to restore the timers.
Since Jest executes every test file in an isolated process, faking the timers here won't affect other test files.

Copy link
Contributor

@ddouglasz ddouglasz left a comment

Choose a reason for hiding this comment

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

Quite an interesting change 💯

@misama-ct misama-ct merged commit 6b4d378 into main Jun 25, 2024
8 checks passed
@misama-ct misama-ct deleted the SHIELD-1235-improve-dropdown-menu-component-placement branch June 25, 2024 08:07
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.

4 participants