Skip to content

[Dialog][AlertDialog] Add portal part #936

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 3 commits into from
Oct 26, 2021
Merged

[Dialog][AlertDialog] Add portal part #936

merged 3 commits into from
Oct 26, 2021

Conversation

jjenzz
Copy link
Contributor

@jjenzz jjenzz commented Oct 26, 2021


Easier to review with hidden whitespace changes


First instalment for #760

Vlad is struggling with the inability to portal dialogs into a custom container on the new primitives website. This should unblock him while we find time to roll this out to other primitives.

Notes for docs

  • New Portal parts for Dialog and AlertDialog
  • Component parts will not portal if not wrapped in portal part
  • Portal part doesn't render a DOM node, need to apply position and z-index to Overlay and Content
  • Part has container prop which must be HTMLElement so document example of this
    const [container, setContainer] = React.useState<HTMLDivElement | null>(null);
    return (
        <>
          <Dialog.Portal container={container} />
          <div ref={setContainer} />
        </>
    );

@jjenzz jjenzz force-pushed the expose-portal-dialog branch 3 times, most recently from 4929a13 to b1f1361 Compare October 26, 2021 12:11
@jjenzz jjenzz force-pushed the expose-portal-dialog branch from b1f1361 to 171a975 Compare October 26, 2021 12:12
/>
</RemoveScroll>
</Portal>
<RemoveScroll as={Slot} allowPinchZoom={allowPinchZoom}>
Copy link
Contributor Author

@jjenzz jjenzz Oct 26, 2021

Choose a reason for hiding this comment

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

Realised we could as={Slot} this to reduce the number of divs rendered. I tested this on desktop and iOS and scroll is still prevented fine. The allowPinchZoom story also still works.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is neat too.


type UnstablePortalElement = React.ElementRef<typeof Primitive.div>;
interface UnstablePortalProps extends PrimitiveDivProps {
container?: HTMLElement | null;
Copy link
Contributor Author

@jjenzz jjenzz Oct 26, 2021

Choose a reason for hiding this comment

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

I've made this expect the node instead of a ref because if we expect a ref we have to build in assumptions that mightn't be correct.

You'll see in the old Portal implementation that we assume the element exists on the page and that the ref will be available when we force a re-render which might not be the case... Perhaps the node they want to portal into is mounted at the same time as the Dialog.Content (if they're controlling it) 🤷‍♂️

This approach means we will get the correct container whenever it becomes available.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit unsure about this because I remember that when we rejigged the portal implementation, we did try this but it broke some of our Portal chromatic stories around custom containers.

Copy link
Contributor Author

@jjenzz jjenzz Oct 26, 2021

Choose a reason for hiding this comment

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

I'll add a story for this new UnstablePortal to test this theory. I would expect this version to work the same in Chromatic because our previous Portal was forcing a re-render for it to work and this approach will be doing the same.

Copy link
Contributor Author

@jjenzz jjenzz Oct 26, 2021

Choose a reason for hiding this comment

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

Yeah, tested and it does behave differently... that's super strange, they're both forcing a re-render 🤔 Gonna look into this some more.

Oh no wait, I'm being a muppet... it obviously looked different cos the styles are removed haha. I've moved positioning into story and looks fine so let's see what Chromatic does when it finishes building.

UPDATE: both look the same here


const UnstablePortal: React.FC<UnstablePortalProps> = (props) => {
const { container = globalThis?.document?.body, children } = props;
return container ? ReactDOM.createPortal(<>{children}</>, container) : null;
Copy link
Contributor Author

@jjenzz jjenzz Oct 26, 2021

Choose a reason for hiding this comment

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

Portal wouldn't render a div anymore. This removes the confusion of applying zIndex to Dialog.Content and having the wrong stacking. Consumers can apply all layering CSS to Dialog.Content and Dialog.Overlay directly where they add their position: fixed styles and get the expected result.

Copy link
Contributor

Choose a reason for hiding this comment

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

I like the simplicity of this, but I think we've lost a big part of the overall solution though with regards to the layering. In my mind, I thought we would open up to provide a way to override our defaults, but right now it seems we just don't offer defaults anymore with this approach so the layering can very easily be wrong out of the box.

Copy link
Contributor Author

@jjenzz jjenzz Oct 26, 2021

Choose a reason for hiding this comment

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

I thought we would open up to provide a way to override our defaults, but right now it seems we just don't offer defaults anymore with this approach so the layering can very easily be wrong out of the box.

It seemed to me that we needed to offer defaults for this that "worked out of the box" because people couldn't access this part to style it. We created a problem for ourselves by closing over the part that required default styles to solve, but this change removes that problem.

so the layering can very easily be wrong out of the box.

Now that the portal doesn't render anything, all of their position: fixed; zIndex: 100 styles that they apply to Dialog.Content (which is the more natural place to apply them) will work as intended.

We don't provide position: fixed styles for Dialog.Content so not sure why we would hard-code positioning and layering into portal either, especially when portals don't have to be on top of things.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've had a chat with @jjenzz about this and I think I'm aligned with this now. @colmtuite might want to take a look at some point as this is a slight difference to what we were doing before though.

@jjenzz jjenzz force-pushed the expose-portal-dialog branch from 764ea49 to 43edbe7 Compare October 26, 2021 12:58
@jjenzz jjenzz force-pushed the expose-portal-dialog branch from 43edbe7 to 7c858d2 Compare October 26, 2021 13:00
@jjenzz jjenzz requested a review from benoitgrelard October 26, 2021 13:03

type UnstablePortalElement = React.ElementRef<typeof Primitive.div>;
interface UnstablePortalProps extends PrimitiveDivProps {
container?: HTMLElement | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit unsure about this because I remember that when we rejigged the portal implementation, we did try this but it broke some of our Portal chromatic stories around custom containers.


const UnstablePortal: React.FC<UnstablePortalProps> = (props) => {
const { container = globalThis?.document?.body, children } = props;
return container ? ReactDOM.createPortal(<>{children}</>, container) : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the simplicity of this, but I think we've lost a big part of the overall solution though with regards to the layering. In my mind, I thought we would open up to provide a way to override our defaults, but right now it seems we just don't offer defaults anymore with this approach so the layering can very easily be wrong out of the box.

Copy link
Contributor

@benoitgrelard benoitgrelard left a comment

Choose a reason for hiding this comment

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

I like the changes, this is definitely the right direction to go with all primitives I think.
Also, Dialog is a good one to have people try as it was probably the one with the most requests around this subject, so I'm looking forward to hear from users if we've addressed their concerns/issues.

@jjenzz jjenzz merged commit 8af7611 into main Oct 26, 2021
@jjenzz jjenzz deleted the expose-portal-dialog branch October 26, 2021 16:01
return container ? ReactDOM.createPortal(<>{children}</>, container) : null;
};

Portal.displayName = UNSTABLE_PORTAL_NAME;

Choose a reason for hiding this comment

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

UnstablePortal.displayName = UNSTABLE_PORTAL_NAME;

😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

😅 good spot! Fancy creating a new PR?

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.

3 participants