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

Select: Portal select menu to document.body #36398

Merged
merged 12 commits into from Jul 14, 2021
Merged

Select: Portal select menu to document.body #36398

merged 12 commits into from Jul 14, 2021

Conversation

ashharrison90
Copy link
Contributor

@ashharrison90 ashharrison90 commented Jul 2, 2021

What this PR does / why we need it:

  • portals the Select menu to document.body
  • auto positions the Select menu for better placement
  • closes the Select menu when scrolling anywhere outside of the Select menu
  • adds a new portal z-index value set to higher than the modal
  • rewrites Portal component as a functional component so it can utilise useTheme2
  • rewrites SelectBase tests as RTL tests
  • updates other unit tests
    • react-select-event has an extra option to specify where the menu is contained. e.g.
      await selectEvent.select(selectEl, 'Option 2');
      becomes
      await selectEvent.select(selectEl, 'Option 2', { container: document.body });
      or use the new util method exposed from @grafana/ui:
      import { selectOptionInTest } from '@grafana/ui';
      await selectOptionInTest(selectEl, 'Option 2');
  • updates e2e tests
    • as the menu is no longer within the Select container, need to look for options on the body. e.g.
      e2e.components.TimeZonePicker.container()
        .should('be.visible')
        .within(() => {
          e2e.components.Select.input().should('be.visible').click();
          e2e.components.Select.option().should('be.visible').contains(toTimeZone).click();
        });
      should now be written as
      e2e.components.TimeZonePicker.container()
        .should('be.visible')
        .within(() => {
          e2e.components.Select.input().should('be.visible').click();
        });
      e2e.components.Select.option().should('be.visible').contains(toTimeZone).click();
  • have also updated the e2e util method selectOption here: https://github.com/grafana/grafana/blob/main/packages/grafana-e2e/src/flows/selectOption.ts to work with the new structure.
  • since the menu is portalled to body, wrapping the Select in something like ClickOutsideWrapper will no longer work. the same functionality can be achieved using Select's onCloseMenu/isOpen props.

some screenshots:
image
image
image

Which issue(s) this PR fixes:

Special notes for your reviewer:

@ashharrison90 ashharrison90 added area/frontend area/value-mapping old backport v8.0.x Mark PR for automatic backport to v8.0.x labels Jul 2, 2021
@ashharrison90 ashharrison90 added this to the 8.0.5 milestone Jul 2, 2021
@ashharrison90 ashharrison90 requested review from a team, tskarhed and kaydelaney and removed request for a team July 2, 2021 15:15
@torkelo
Copy link
Member

torkelo commented Jul 5, 2021

Awesome job! this is tricky stuff, and we have had max-width / overflow hidden/auto before in the Modal but it issues (esp with selects) made us change that back. There are some other Modals with selects in them (like dashboard SaveAs modal).

@torkelo torkelo requested review from mckn and Clarity-89 July 5, 2021 07:09
Copy link
Contributor

@Clarity-89 Clarity-89 left a comment

Choose a reason for hiding this comment

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

Looks good! We don't have any use cases of Selects in Modal in Enterprise as far as I can see. Regarding the value mappings and modal in general, I think a nice improvement would be if the button actions wouldn't be a pat of scrolling content and always stayed visible during overflow, but this is probably out of scope of this PR.

@ashharrison90
Copy link
Contributor Author

@Clarity-89 +1 on that. i think every major design library out there (bootstrap, material, carbon, etc.) separates the actions from the content for this reason.

@torkelo
Copy link
Member

torkelo commented Jul 5, 2021

An idea would to add a footer prop to Modal that automatically wraps the footer content in a ModalButtonRow, and the footer would live outside the content div

@@ -41,6 +41,7 @@ export interface SelectCommonProps<T> {
minMenuHeight?: number;
maxVisibleValues?: number;
menuPlacement?: 'auto' | 'bottom' | 'top';
menuPortalTarget?: HTMLElement;
Copy link
Contributor

Choose a reason for hiding this comment

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

The more react-select stuff we expose in our API, the harder it will be to move away from it. I don't think it's in the current todo, but the Select component has been a real pain.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it is okay in this case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm, that's a good point. i'm not sure how we can avoid it though.
we could attach all selects to document.body by default? that would have a pretty wide impact though, and is presumably slightly worse for performance.
ideally we'd want something where the select detects it's in a modal and attaches itself to document.body automatically 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

I played around a bit with the portaling etc. last year, and it never ended up good. When this is merged we might have to keep an eye out for Select bugs.

Let me just check a few places before you merge this PR.

@ashharrison90
Copy link
Contributor Author

An idea would to add a footer prop to Modal that automatically wraps the footer content in a ModalButtonRow, and the footer would live outside the content div

yep, i don't make raising that/taking it on as an enhancement following this PR 👍

Copy link
Contributor

@tskarhed tskarhed left a comment

Choose a reason for hiding this comment

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

Okay, let's go! Be on the lookout for weird Select bugs though!

@ashharrison90
Copy link
Contributor Author

ashharrison90 commented Jul 5, 2021

@tskarhed @torkelo what do you think about using context to determine where to attach the Select menu? 🤔

we could export a context object from SelectBase - something like SelectMenuTargetContext - default it to undefined and wrap the modal like:

    <Portal>
      <div className={cx(styles.modal, className)}>
        <SelectMenuTargetContext.Provider value={document.body}>
          <div className={headerClass}>
            {typeof title === 'string' && <DefaultModalHeader {...props} title={title} />}
            {typeof title !== 'string' && title}
            <div className={styles.modalHeaderClose}>
              <IconButton surface="header" name="times" size="xl" onClick={onDismiss} />
            </div>
          </div>
          <div className={cx(styles.modalContent, contentClassName)}>{children}</div>
        </SelectMenuTargetContext.Provider>
      </div>
      <div
        className={styles.modalBackdrop}
        onClick={onClickBackdrop || (closeOnBackdropClick ? onDismiss : undefined)}
      />
    </Portal>

that way we wouldn't have to expose any new props on SelectBase or ValuePicker, and it would apply to every modal and every component wrapping Select

@tskarhed tskarhed modified the milestones: 8.0.5, 8.0.6 Jul 8, 2021
@ashharrison90 ashharrison90 requested review from a team as code owners July 12, 2021 16:19
@ashharrison90 ashharrison90 requested review from sarahzinger, vickyyyyyyy and aocenas and removed request for a team July 12, 2021 16:19
@ashharrison90
Copy link
Contributor Author

ok, i've updated this to portal Select to document.body in all cases and updated the unit tests accordingly. the select menu now also closes when you scroll outside of the select menu.

the only thing left to discuss is what we do about z-index. in this PR the select menu overlays everything, including modals. i can imagine there's cases where that's not desirable.

but when a select is within a modal, it has to overlay the modal, else it's broken. so it seems like we'll need to make it configurable in some way. either by exposing a prop, or setting it via context, or something else.

@mckn
Copy link
Contributor

mckn commented Jul 14, 2021

Love that you included instructions on how to change the tests in this PR! Will help people that might run into this issue! 🙌🏻

Copy link
Member

@torkelo torkelo left a comment

Choose a reason for hiding this comment

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

Awesome job on this ash!

only thing that can be improved would be a test helper function to select options so you not have to always pass in the container argument, should make it easier for to change this in the future without updating all tests (I hope we do no need to!!)

@ashharrison90
Copy link
Contributor Author

unit test util method created. once everything goes green i'll merge 🤞

Comment on lines +337 to +339
menuPortal: (base: any) => ({
...base,
zIndex: theme.zIndex.portal,
Copy link
Member

Choose a reason for hiding this comment

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

was that it? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oooo maybe, forgot about that change 😂

@ashharrison90 ashharrison90 merged commit 54f8996 into main Jul 14, 2021
@ashharrison90 ashharrison90 deleted the ash/36317 branch July 14, 2021 13:04
cinaglia added a commit to cinaglia/grafana that referenced this pull request Jul 15, 2021
* main: (85 commits)
  Chore: Ease the migration of always using context.Context when interacting with the bus (grafana#36733)
  Datasource: Improve default timeout settings for HTTP client provider (grafana#36621)
  Expand the value of math and reduce expressions in annotations and labels (grafana#36611)
  User analytics: Use user email as Rudderstack id (grafana#36788)
  Select: Block scroll on select instead of trying to hide the menu (grafana#36783)
  Chore: Upgrade grafana-plugin-sdk-go to v0.110.0 (grafana#36763)
  Plugins: Use plugin.pluginDir as source of truth for plugin location (grafana#36711)
  switch to json resp for errors (grafana#36743)
  Docs: New index page with list of all visualizations and panels  (grafana#36756)
  OptionsUI: add standard field name picker (grafana#36732)
  Docs:Adjust azuread.md for Powershell UUID command (grafana#27323)
  Update _index.md (grafana#35595)
  AzureMonitor: Improve Azure Resource Graph docs (grafana#34450)
  Update queries.md (grafana#31941)
  Update configuration.md (grafana#30695)
  Remove verify-drone for Windows (grafana#36759)
  Modal: Force modal content to overflow with scroll (grafana#36754)
  InfluxDB: InfluxQL: adds tags to timeseries data (grafana#36702)
  Add AGPL license update to 8.0 changelog, release notes, What's New (grafana#36742)
  Select: Portal select menu to document.body (grafana#36398)
  ...
ifrost added a commit that referenced this pull request Jul 15, 2021
After changes in #36398 the menu is detached from the container div causing useClickAway not recognising that the click happened outside.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants