Description
openedon Jul 18, 2024
Context
As we're re-writing some of the components in the @wordpress/components
package to use ariakit
, we can take advantage of the low-level and granular nature of the library components, and build our components with the desired level of abstraction:
- at one end of the spectrum, we could offer a very low-level and modular set of compound components (basically 1:1 to
ariakit
); - at the other end we would expose high-level monolithic components (similar to most components in the
@wordpress/components
package).
Each approach has pros and cons: monolithic components offer a pre-packaged, easy-to-use solution to consumers, but are very inflexible, and often end up causing severe YAPping and style overrides; low-level components are flexible, but are more difficult to use and offer little control and "art-direction" to the library.
We need to pick the compromises that we think suits best the role of the package, and decide how to mitigate the negative aspects of that choice.
Our shared approach so far has been to find a middle group:
- Expose compound components, but try to keep a minimum level of abstraction
- Potentially expose, alongside the "compound" version, a monolithic component representing the simpler and most frequent usage of the component (this aspect should be decided case-by-case and may be also influenced by backward-compatibility reasons).
- Use Storybook as a place to provide ready-made "recipes", ie. example usages of the compound components that fulfill a certain UI that can be copied and dropped in a codebase with little-to-no tweaking.
What
The tricky aspect when "abstracting" lower-level components into higher level, is that there can be a confluence of originally "separated" APIs in one place. This aspect started emerging when working on components that have both a trigger and a related popover, like dropdown menu or select controls (see example conversation).
We need to find ways to make sure that our APIs are flexible and future-proof and are clear to our consumers. For example:
- how would we make sure that consumers can pass props and grab refs or specific subcomponents, like the root wrapper vs the trigger vs the popover wrapper?
- do we want (and can we) achieve a certain level of control (like discussed here about a potential
Dialog
component)
A few potential solutions:
- lower the level of abstraction of our compound components, which would avoid the problem altogether, but would also introduce a different set of compromises;
- use render props
- for example, in
DropdownMenu
we are using atrigger
render prop for the trigger button, and thechildren
prop for menu items. Any aspect related to thetrigger
is passed directly by the consumer to the component rendered in thetrigger
prop; - in a hypothetical
Dialog
component, we could have use render props as "slots" in order to restrict the design and/or behavior of the component if needed.
- for example, in
Any thoughts?
cc @WordPress/gutenberg-components