BalanceReckon Design System
Overlays

Drawer

A Drawer is a modal dialog that contains flows or additional content, subordinate to the application's main window.

import { Drawer, DrawerProvider, DrawerController } from '@balance-web/drawer';
Loading demo…

The Drawer package has been deprecated and will be removed in a future version. Use Side Drawer 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

Loading demo…

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.

Loading demo…

Focus

By default the first focusable element will receive focus when the drawer opens, but you can provide an initialFocusRef to focus instead.

Loading demo…

Replace the drawer's header with your own.

Loading demo…

Overflow

The drawer "body" will scroll when its content overflows the available height. Dividers are introduced to improve affordance.

Loading demo…

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.

Loading demo…

On this page