-
-
Notifications
You must be signed in to change notification settings - Fork 266
Description
Feature request
Good day! First of all I'm a huge fan of this project. I've build and used many UI libraries in the past and I'm impressed about the consistency and general effort put into BaseUI. That being said, I'm missing a (in my opinion) quite important feature which I think would elevate the quality and DX of BaseUI even more.
Every component has its Root which manages and holds the internal state of the whole component. I'll use the Dialog component as the main example going forward.
Currently the basic example for the dialog component is as follows:
<Dialog.Root>
<Dialog.Trigger>
Open Dialog
</Dialog.Trigger>
// ...
</Dialog.Root>This markup is nice and clear, but it limits the developer to use the Dialog.Trigger as the element which opens the dialog. This limitation can be quite annoying in real world applications, because this limitation seems to have no benefit, because one could also do this:
const [open, setOpen] = useState(false);
<Dialog.Root open={open} onOpenChange={setOpen}>
<button onClick={() => setOpen(true)}>
Open Dialog
</button>
// ...
</Dialog.Root>Image in this case the button element to be a custom element which should open the dialog. As you can see, for this approach you need quite a bit of additional code because in order to achieve that you have to make the dialog root controlled, so you can manage the state yourself. In this simple case this is quite annoying because the controlled state is essentially doing the exact same thing what the uncontrolled state is doing.
The idea is now to introduce a possibility to make this need of making this controlled state obsolete:
<Dialog.Root>
{({ open, setOpen }) => <>
<button onClick={() => setOpen(true)}>
Open Dialog
</button>
// ...
</>}
</Dialog.Root>The Dialog.Root components children prop can be a function, which receives childrenProps. These children props contain internal state information and their setters. That children props object can be thought of a public state interface which represents the whole state of the component + its setters. It could (and should) not only be exposed via children props but also via a context which would even more increase DX in the case when components are nested over multiple layers.
Now you might think that all of this is pointless, because why not just use the render prop of the Dialog.Trigger component, and there are multiple reasons:
- In real world application a single button can do different things depending on the application state. For example if the user is logged in the button should do actionA while when logged out the button should do actionB, where any action could be a modal to be opened or something entierly different.
- BaseUI is an "abstraction" of a UI library and each adpoter can descide how to use the building blocks how every he / she sees fit. The addition of this feature would open up new patterns & lift restrictions which aren't needed.
Summary
- All
Rootcomponents define apublic state interfacewhich consists of one or multiple state values + their setters if it makes sense and no harm. Those values should be picked carefully. - The
childrenprop ofRootcomponents accepts not onlyReactNodeas its value but also a function with the interface(publicStateInterface) => ReactNode, so the state can be accessed directly in the children. - In nested components the
public state interfacecan be accessed via a hook which behaves likeuseContext, this can be either something likeconst { open, setOpen } = Dialog.Root.useContext()or a separate export likeconst { open, setOpen } = useDialogRoot(). Rootcomponents have arefprop which exposes thepublic state interfaceas a way of controlling the component imperatively. (thats just a nice to have)
Examples in other libraries
I'm using this pattern in my own UI library, headlessUI and react-aria, are using this pattern as well (its a little bit inconsitent in headlessUI).
Motivation
In my opinion not having this feature is limiting DX greatly and BaseUI adopters are bound to restrictions which dont serve any benefit (please correct me if I'm wrong in this aspect).
If this is something BaseUI wants to implement, I would be ready to help with the implementation & make a PR.