Balance Logo
Balance
Reckon Design System
Open playroom

Drawer

Deprecated
Drawer is used to overlay content on top of the interface. They are intended to capture the user’s attention in order to inform or shift focus to a pertinent task.
Install
pnpm add @balance-web/drawer
Import usage
import {
Drawer,
TabbedDrawer,
DrawerProvider,
DrawerController
} from '@balance-web/drawer';
  • Code
  • API

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 the DrawerController.

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 used
import { 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.

Edit in Playroom

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.

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.

Tabbed Drawer

Use the TabbedDrawer for multi-part form interfaces. Tabbed drawers are always forms so you don't have to apply any type property to this component.

The tabbed drawer does not accept children; instead you must provide panel content as data so everything stays in sync.

shouldShowCancelConfirmationDialog

TabbedDrawers and 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.

Copyright © 2024 Reckon. Designed and developed in partnership with Thinkmill.
Bitbucket logoJira software logoConfluence logo