The
Drawer
package has been deprecated and will be removed in a future version. Use Sheet instead.
Requirements
To handle the stacking behaviour of drawers we track their mount/unmount using
React's context. You must wrap your application with the <DrawerProvider/>
for
this to work correctly.
return ( <Core> <DrawerProvider> <App /> </DrawerProvider> </Core>);
DrawerController
Each usage of a drawer needs to be wrapped in a DrawerController
. Generally, the usage of DrawerControllers WON'T look like it does in the live examples. The live examples are illustrating what you can do with them. The actual usage of the drawers will look more like the code below. What's important to note here is that the DrawerController
is outside of the component that renders the drawer. This is so that state is cleared out of the drawer when it's closed.
If you see a usage of the
DrawerController
in an app where it's the directly above a drawer, that will be updated. It's only like that to ease the transition period from isOpen being a prop on drawers to being on theDrawerController
.
Why can't I conditionally render?
We need the DrawerController
component so that we can do an exit transition for the drawer. If you just did isOpen && <MyDrawer />
, the drawer would unmount immediately so it wouldn't be possible to have an exit transition.
Why not have isOpen
as a prop?
We used to do that! It caused a big problem though. It forced you to structure your components in a way that meant the state of a drawer was preserved when it unmounted. This causes lots of subtle bugs.
For example, you might have had code like this:
const MyDrawer = ({ isOpen, onClose }) => { let [value, setValue] = useState(''); return ( <Drawer isOpen={isOpen} actions={{ confirm: { label: 'Submit', action: () => { // ...do something with the value onClose(); }, }, cancel: { label: 'Cancel', action: onClose }, }} > <input value={value} onChange={(event) => { setValue(event.target.value); }} /> </Drawer> );};
You could attempt to solve this by resetting the state to the initial values in the confirm and cancel actions. That is extremely error-prone though and even that is technically wrong if the isOpen prop changes for some other reason or if you're providing initial values based on the props. Using the DrawerController
in the right way removes that entire class of problems.
import { Drawer } from '@balance-web/drawer';export const MyDrawer = ({ onClose }) => { // ... some state things for the content of the drawer return ( <Drawer // etc... actions={{ confirm: { label: 'Submit', action: () => { // ... submit stuff onClose(); }, }, cancel: { label: 'Cancel', action: onClose, }, }} > {/* etc. */} </Drawer> );};// in another file where the drawer is usedimport { DrawerController } from '@balance-web/drawer';import { MyDrawer } from '../../path/to/my/drawer';// etc.let [isOpen, setIsOpen] = useState(false);<DrawerController isOpen={isOpen}> <MyDrawer onClose={() => { setIsOpen(false); }} /></DrawerController>;
Standard Drawer
A Drawer
is a modal dialog that contains flows or additional content, subordinate to
the application's main window.
Users must interact with the Drawer
before they can return to the parent
application. This avoids interrupting the workflow on the main window.
Type
Use type="form"
to render a form
as the drawer element. Under the hood this
will hook up your confirm action as the form's onSubmit
handler and correctly
type the submit button.
Drawers of type "form" will show a confirmation dialog when the user attempts to
"cancel", you can manage this behaviour using the
shouldShowCancelConfirmationDialog
property.
Focus
By default the first focusable element will receive focus when the drawer opens,
but you can provide an initialFocusRef
to focus instead.
Header
Replace the drawer's header with your own.
Overflow
The drawer "body" will scroll when its content overflows the available height. Dividers are introduced to improve affordance.
shouldShowCancelConfirmationDialog
Drawers with type="form"
accept shouldShowCancelConfirmationDialog
which defaults to true
, you can set it to false
if the user hasn't changed the value of the form. You should never set it to be always false, it always should be based on whether the current value of the form is different to the initial value. You can use useIsFormEqualToInitialValue
from @balance-web/forms
to determine whether the current value is different to the initial value or not.