Skip to content

Add a Portal component for rendering content outside of the current DOM element #1025

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

Merged
merged 16 commits into from
Feb 23, 2021

Conversation

T-Hugs
Copy link
Contributor

@T-Hugs T-Hugs commented Feb 8, 2021

Portals allow you to create a separation between the logical React component hierarchy and the physical DOM. See the React documentation on portals for an in-depth explanation, or see the rendered documentation for this component.

Closes #1024

Screenshots

Please provide before/after screenshots for any visual changes
image
Storybook story

Note: An update to this PR changed the behavior so that default portals render into the nearest <BaseStyles>, so the screenshot is slightly out-of-date.

Merge checklist

  • Added or updated TypeScript definitions (index.d.ts) if necessary
  • Added/updated tests
    • Partial (Storybook story, no automated tests)
  • Added/updated documentation
  • Tested in Chrome
  • Tested in Firefox
  • Tested in Safari
  • Tested in Edge

Open questions

  1. Should portaled content be rendered with the same theme found in its logical hierarchy? If so, how?
  2. Anyone know why the rollup build is failing? I'm getting the error: Error: 'PortalProps' is not exported by src/Portal/Portal.tsx, imported by src/Portal/index.ts.

@changeset-bot
Copy link

changeset-bot bot commented Feb 8, 2021

⚠️ No Changeset found

Latest commit: a0b8474

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@vercel
Copy link

vercel bot commented Feb 8, 2021

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/primer/primer-components/Bex2xGEevgk3cLZujC2tzUqiTr2q
✅ Preview: https://primer-components-git-portal-primer.vercel.app

@T-Hugs T-Hugs mentioned this pull request Feb 8, 2021
Copy link

@emplums emplums left a comment

Choose a reason for hiding this comment

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

I really like this idea, but I have some concerns about adding Portal to the public API right now:

  • Using a Portal with current behaviors that rely on checking for parent DOM elements such as the closeOnClickOutside behavior in SelectMenu breaks that behavior. I don't think we should add Portal to the public API until we can make sure that Portal will work seamlessly with all of our components.

  • We should add some more examples/instructions to the docs on how to position Portal components.

  • As you mentioned in your PR, it looks like because the Portal is registered above the ThemeProvider and BaseStyles they appear with incorrect styles:

image

Note that some of the styles still work, that's because within each component we set up a default fallback to our theme in case the consumer of PRC didn't set up a ThemeProvider.

We need to address this before we add this component to the public API as well.

  • There should be few situations where a Portal is needed by a consumer and in those situations, if we do add this to the public API, I think we should add something to the docs explaining that you should only use Portal if you know what you are doing, as a last option. It might also be useful to explain some of the side effects to be aware of when using Portals such as loss of access to the theme, inability to write behaviors that depend on traditional DOM structure, and need to accurately position Portal components.

Overall, I think we should address some of the above points, and merge this in for internal use only for now. If we include Portals inside of our overlay components by default, I wonder how often consumers will even need to hook up Portals themselves?

@T-Hugs
Copy link
Contributor Author

T-Hugs commented Feb 9, 2021

Using a Portal with current behaviors that rely on checking for parent DOM elements such as the closeOnClickOutside behavior in SelectMenu breaks that behavior. I don't think we should add Portal to the public API until we can make sure that Portal will work seamlessly with all of our components.

My expectation is that SelectMenu (and related components) will be refactored to use Portal, useAnchoredPosition, and other yet-to-be added behaviors and components. To me, it doesn't seem that important to built in extra functionality to Portal and other behaviors to support components that will be refactored, particularly since they're not dependent on Portal.

We should add some more examples/instructions to the docs on how to position Portal components.

The Portal component and the useAnchoredPosition hook are fundamentally de-coupled, even though they can (and often will) work together. Once I add useAnchoredPosition I can add examples in docs/Storybook on how to use them together.

As you mentioned in your PR, it looks like because the Portal is registered above the ThemeProvider and BaseStyles they appear with incorrect styles:

Correct. This is currently by design, although I think changing it would be fine. Right now, the default <Portal> will render into a root that is inserted at the root of the DOM. Instead, we can attempt to insert the default Portal root into the nearest ThemeProvider if we find one, otherwise at the root of the DOM. It's not immediately obvious to me how to implement it this way, but I'm hopeful I can figure it out!

Regarding the question of whether or not to add Portal to the public API (i.e. exported from src/index.ts), I can see both arguments, but I would lean toward "yes." I've used components similar to this Portal in other apps for types of UX that will likely not be implemented in Primer Components. Portal is very much a "building block" component and should have sufficient documentation warning of such. We might want to think about the best way to document "composite" vs. "building block" components and behaviors. Perhaps the best way to document this is something like:

Trying to do XYZ? Use the ABC component!

Overall, I think we should address some of the above points, and merge this in for internal use only for now.

Just to be sure, I take this to mean:

  1. Remove the export from src/index.ts
  2. Remove the documentation
  3. Keep the Storybook stories

Does that seem correct?

@emplums
Copy link

emplums commented Feb 9, 2021

@T-Hugs yes that seems correct! We should also address the styling issues.

Re:

My expectation is that SelectMenu (and related components) will be refactored to use Portal, useAnchoredPosition, and other yet-to-be added behaviors and components. To me, it doesn't seem that important to built in extra functionality to Portal and other behaviors to support components that will be refactored, particularly since they're not dependent on Portal.

Totally agree, but I think we should exclude Portal from the public API until those components are refactored. How do you feel about keeping this internal only, refactoring all those components during the behaviors epic, then revisiting whether or not Portal should be public at that time? As long as we fix the styling & behavior issues I am not totally against the idea of making it public once it's ready as long as we have the correct guidance needed written up in the docs! 🙌

@T-Hugs
Copy link
Contributor Author

T-Hugs commented Feb 9, 2021

Great, sounds good to me!

@vercel vercel bot temporarily deployed to Preview February 11, 2021 21:01 Inactive
@vercel vercel bot temporarily deployed to Preview February 12, 2021 14:36 Inactive
@T-Hugs
Copy link
Contributor Author

T-Hugs commented Feb 12, 2021

@emplums This is ready to go with the exception of tests. Since you are adding tests in #1041 I was going to wait for that before doing tests for this PR and for #1043. Is it okay to check this in now and follow-up later with tests (particularly since this isn't going into the public API just yet)?

Copy link

@emplums emplums left a comment

Choose a reason for hiding this comment

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

Just left two more recommendations for the docs, otherwise this looks good to go! #1041 is merged now, might be good to include tests now just so we don't forget to add them later!

This Portal component will render all children into the portal root DOM node instead of as children of this Portal's parent DOM element. This is useful for breaking out of the current stacking context. For example, popup menus and tool tips may need to render on top of (read: covering up) other UI. The best way to guarantee this is to add these elements to top-level DOM, such as directly on document.body. These elements can then be moved to the correct location using absolute positioning.

## Customizing the portal root
By default, Primer will create a portal root for you as a child of `document.body`. If you would like to specify your own portal root, there are two options:
Copy link

Choose a reason for hiding this comment

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

Should this be updated to say that it's added by default to the BaseStyles component which all applications should have as high up as possible in their component tree?

@vercel vercel bot temporarily deployed to Preview February 22, 2021 22:39 Inactive
Copy link

@emplums emplums left a comment

Choose a reason for hiding this comment

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

Just left a couple more suggestions for the docs (nit typos), feel free to merge!

Co-authored-by: emplums <emplums@github.com>
@vercel vercel bot temporarily deployed to Preview February 23, 2021 14:21 Inactive
@T-Hugs
Copy link
Contributor Author

T-Hugs commented Feb 23, 2021

Thanks for the suggestions! I'm going to keep physical DOM parent over browser DOM parent since the language, along with "logical" (used elsewhere in this doc) draws parallels to other physical-vs-logical distinctions, such as physical/logical CPUs and storage devices.

@T-Hugs T-Hugs merged commit b5f1a59 into main Feb 23, 2021
@T-Hugs T-Hugs deleted the portal branch February 23, 2021 14:25
@smockle smockle mentioned this pull request Mar 30, 2021
7 tasks
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.

Portal Component
2 participants