Componentsv2.189.0
Iconsv1.19.0
MCPv0.8.61
Tokensv0.34.3

Programmatic control of drawers

Introduction

The PlumaDrawer component is used to display drawer dialogs. Using the PlumaDrawer component directly, however, can be difficult to manage, as it requires manually keeping track of the drawer's open state. This also makes it difficult to reuse the same drawer in multiple places, as it involves extra boilerplate code.

Pluma's Drawer Manager is a utility that simplifies the creation and management of drawers.

Defining a managed drawer

To use a drawer via the manager, we first need to define a drawer component, which will later be referenced in the manager.

Pluma exports utility types that help define the component's props - it's highly recommended to use these types to ensure type safety and type hints.

import type { PlumaManagedDrawerProps } from '@customerio/pluma-components/react';

PlumaManagedDrawerProps<Props, ConfirmArg, CloseArg> is a generic type that wraps up the props that are passed into a drawer component invoked by the drawer manager. It takes three generic parameters:

  1. Props: The custom props you plan to pass into the drawer component
  2. ConfirmArg: The argument you will pass into the onConfirm callback
  3. CloseArg: The argument you will pass into the onClose callback

To define a drawer component, you need to use this type as your components' props type.

import type { PlumaManagedDrawerProps } from '@customerio/pluma-components/react';

import {
	Drawer,
	DrawerBody,
	DrawerFooter,
	Paragraph,
	Button,
} from '@customerio/pluma-components/react';

export function MyDrawer(props: PlumaManagedDrawerProps<{ description: string }, string, string>) {
	const { drawerState, data, onClose, onConfirm } = props;

	return (
		<Drawer state={drawerState} title="My drawer title">
			<DrawerBody>
				<Paragraph>The description is: {data.description}</Paragraph>
			</DrawerBody>
			<DrawerFooter>
				<Button onClick={() => onClose('Add a close reason here')}>Close</Button>
				<Button onClick={() => onConfirm('Add a confirm reason here')}>Submit</Button>
			</DrawerFooter>
		</Drawer>
	);
}

When the drawer is opened, the manager will pass any of your custom Props into the drawer component under the data key. Additionally, two callbacks are available:

  • onClose: A callback to call when you want to close the drawer when the user cancels an action
  • onConfirm: A callback for when the user confirms an action, or otherwise performs a CTA-like action
    • this will also close the drawer - but it allows you to differentiate between different actions performed by the user

Finally, a drawerState object is also present. It is required to forward this prop into the PlumaDrawer's state prop - it holds the drawer's open state and other drawer-related props.

Using the managed drawer

Now that we have defined a managed drawer component, we can use it with the manager.

The useDrawers hook returns the drawer manager, which allows you to open and interact with drawers.

To open a drawer, you can use the open method. As the first argument it accepts the drawer component you previously defined. As the second argument, it expects the additional props that you expect to see under data.

Loading editor

There is also a useDrawer hook, which accepts a drawer component as its argument and returns a drawer manager API bound to that specific component. It's a shortcut to make it a little easier to reuse the same drawer in different places.

Loading editor

Handling drawer closure

The manager's open method returns a unique identifier for the opened drawer. This identifier can be used in the manager's waitForClose method, which returns a promise that resolves when the drawer is closed.

The value returned from this resolved promise is an object with the following properties:

  • reason: The reason the drawer was closed, a string with one of the following values:
    • close - called when the drawer is closed with the onClose callback, or when the drawer is closed by clicking the backdrop, the X close button, or by pressing the escape key (if those are enabled)
    • confirm - called when the drawer is closed with the onConfirm callback
  • data: The data that was passed to the drawer's onClose or onConfirm callback
  • explanation: The explanation for the drawer closure. Only present when the drawer is closed through the drawer's "native" close mechanisms, such as clicking the backdrop. The explanation is a string with one of Floating UI's onOpenChange reasons, close-button-press if closed by clicking the drawer's X close button, or drawer-manager:close-all if closed by calling the manager's closeAll method

This value can be used to handle the drawer closure in the parent component.

Loading editor

Updating a drawer's data props

Sometimes it might be necessary to update a drawer's data props after it has been opened. This can be done with the manager's update method. It takes the drawer's identifier as the first argument, and the new props as the second argument. The new props will be merged with the existing props, and the drawer will be updated accordingly.

Loading editor

Customizing drawer behavior

If you need to customize any of the Drawer component's behavior, you can still use any of it's arguments as usual. For example, if you'd like make the drawer non-dismissable, you can still use the isDismissable prop to achieve this.

If the configuration needs to depend on arguments you pass into the drawer, you may use your data props to pass that configuration further into the drawer.

Loading editor

Closing drawers on unmount

By default, a managed drawer automatically closes when the component that opened it is unmounted - for example, when a route transition happens.

In some cases, you may want to keep the drawer open even after its opening component unmounts, so it stays open until explicitly closed (either by user action or programmatically). To opt out of the automatic behavior, pass an additional configuration argument into the open method: { shouldCloseOnUnmount: false }. For useDrawers, this will be the third argument, while for useDrawer this will be the second argument.

import { useDrawers, Button } from '@customerio/pluma-components/react';
import MyDrawer from './my-drawer';

export default function Example() {
	const drawers = useDrawers();

	const handleClick = () => {
		drawers.open(MyDrawer, undefined, { shouldCloseOnUnmount: false });
	};

	return <Button onClick={handleClick}>Open Drawer</Button>;
}