WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.55k stars 4.22k forks source link

`@wordpress/components`: portal behavior of popover-based components #63697

Open ciampo opened 4 months ago

ciampo commented 4 months ago

Context

As we're re-writing some of the components in the @wordpress/components package to use ariakit, we have the chance to re-discuss our approach to architecting our APIs and the behavior of such components.

More specifically, as we're working on the new version of DropdownMenu and CustomSelectControl, one of the aspects that we should discuss and agree on at a package level is where in the DOM popover-based components should render.

This is, at the time of writing, the behavior of the first-party Popover component (and all components based on it) with regards to where it renders in the DOM:

This behavior can be changed via the __unstableSlotNameProvidercontext and __unstableSlotName prop (to pick another slot), and the inline prop (when set to true, the popover will render inline instead of in a slot).

[!Note] Slot is a Gutenberg-specific term and refers to the SlotFill APIs, a technology that exposes React's portal functionality as a set of React components. You can read more about it in this article, or you consult the official docs and the Storybook examples.

Using ariakit allows us to easily tweak, if necessary, this aspect for popover-based components (popover, tooltips, dropdowns, dropdown menus, dialogs, selects, comboboxes...) via the portal and portalElement props.

This topic was partially discussed in a related issue, where an initial consensus was reached on changing the default behavior of the Popover component to append to document.body by default.

What

Now that we're adding a new set of ariakit-based components, we have the chance of making changes to the behaviors explained above.

Therefore, we need to decide:

cc @WordPress/gutenberg-components

youknowriad commented 4 months ago

Quick side question: What are we hoping to gain in terms of UX by switching some of these low level components to ariakit?

ciampo commented 4 months ago

Here are my thoughts:

  • Where in the DOM should popover-based component render by default? Inline, or portal-ed? If portal-ed, where in the DOM?

I think that Popover and popover-based components should all render in a portal appended to the document.body by default.

  • Should we expose APIs in our components to change such behavior? If yes, for what components and in what shape?

All popover-based components could expose two props, mimicking ariakit's APIs:

  • How would the new behavior and APIs relate to the SlotFill APIs?

I think that the popover-based components should not use SlotFill APIs internally, nor in their public APIs (ie. having a slotName prop).

Keeping SlotFill and popover-based components decoupled simplifies the learning curve and allows for better flexibility and interoperability, especially when the components are rendered outside of the block editor (ie. dataviews and other admin UI).

The portalElement accepts any HTML element — and therefore, consumers who wish to render a popover-based component in a Slot could simply do the following:

const slotElemement = useSlot( slotName );

// ...

return ( <PopoverBasedComponent portalElement={ slotElemement } /> );

The __unstableSlotNameProvider context mechanism should not replicated inside the popover-based components either, and an equivalent React context should instead be used by consumers of the component as needed.

  • How does that impact layering and nesting popovers, and is it enough to stop relying on z-index by default?

One limitation of current popover-based components is caused by the fact that they rely on their z-index to render on top of existing UI, which is a brittle and inflexible technique.

If all components get appended to the document.body they should intrinsically render on top of most UI. Also, rendering popovers on top of popovers should also intrinsically work as expected as it would rely on DOM order.

Having said that, there may be instances in which a z-index will still be needed — although that should ideally be a tweak made by consumers of the component on a case-by-case basis, and not added systematically to the components library.

  • Do we need to take any additional precautions into account, given that there will be a transional phase during which some components will use the ariakit popover, and other components will use the first-party Popover from this package?

We may have to retain the z-index styles during the transition period to ensure that popovers with different underlying implementations still layer on top of each other as expected.

One thing to keep in mind is that both ariakit and our first-party popover are based on the @floating-ui library, which is a good common layer to have between the two.

ciampo commented 4 months ago

Quick side question: What are we hoping to gain in terms of UX by switching some of these low level components to ariakit?

There are a few reasons, but specifically to the UX side, using a headless component library (in this case, ariakit) allows us to delegate the task of providing standard, well-functioning, modular and accessible component base implementations, instead of having to build and maintain them ourselves. A few examples:

tyxla commented 4 months ago

Thanks for opening the discussion, @ciampo 🙌

Here are my thoughts:

I agree with all your suggestions:

youknowriad commented 4 months ago

@ciampo Thanks for the details Marco. Those are good reasons but nothing clear on the UX side. I'd love if we can focus more on unlocking more UX improvements from our components and refactoring as a mean not as a goal.

ciampo commented 4 months ago

focus more on unlocking more UX improvements from our components and refactoring as a mean not as a goal.

@youknowriad we are definitely aligned on this, the main goal of all of this work is to ultimately provide a better UX for our users!

These specific conversations that we're having right now about modality and portals are more of a consequence/opportunity of using ariakit and are quite technical, but they could also have UX implications, for example in avoiding issues like this one, this one, or this one.

Having more components based on the same underlying headless component library (for example, ariakit) gives us several advantages that, in my opinion, all contribute to a better UX all around:

With this in mind, the above examples can be considered improvements on the UX side.