Drawer
An edge-anchored overlay panel that slides in for details, navigation, and side tasks.
Basic
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Drawer,
DrawerBody,
DrawerClose,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerHeaderContent,
DrawerPanel,
DrawerTitle,
DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";
export default function BasicDemo() {
return (
<Drawer>
<DrawerTrigger
render={
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
label="Open details"
/>
}
/>
<DrawerPanel side="end">
<DrawerHeader>
<DrawerHeaderContent>
<DrawerTitle>Work item details</DrawerTitle>
<DrawerDescription>Edit the fields for this work item.</DrawerDescription>
</DrawerHeaderContent>
<DrawerClose
render={
<IconButton
prominence="ghost"
tone="neutral"
magnitude="lg"
aria-label="Close"
icon={<Icon icon={X} />}
/>
}
/>
</DrawerHeader>
<DrawerBody>Panel body content goes here.</DrawerBody>
<DrawerFooter>
<DrawerClose
render={
<Button
sizing="hug"
prominence="ghost"
tone="neutral"
magnitude="lg"
label="Cancel"
/>
}
/>
<Button sizing="hug" prominence="primary" tone="neutral" magnitude="lg" label="Save" />
</DrawerFooter>
</DrawerPanel>
</Drawer>
);
}Side
The side prop anchors the panel to the inline-start or inline-end edge; the panel slides in from that edge.
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Drawer,
DrawerBody,
DrawerClose,
DrawerHeader,
DrawerHeaderContent,
DrawerPanel,
DrawerTitle,
DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";
export default function StartSideDemo() {
return (
<Drawer>
<DrawerTrigger
render={
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
label="Open navigation"
/>
}
/>
<DrawerPanel side="start">
<DrawerHeader>
<DrawerHeaderContent>
<DrawerTitle>Navigation</DrawerTitle>
</DrawerHeaderContent>
<DrawerClose
render={
<IconButton
prominence="ghost"
tone="neutral"
magnitude="lg"
aria-label="Close"
icon={<Icon icon={X} />}
/>
}
/>
</DrawerHeader>
<DrawerBody>Navigation links go here.</DrawerBody>
</DrawerPanel>
</Drawer>
);
}Nested
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Drawer,
DrawerBody,
DrawerDescription,
DrawerHeader,
DrawerHeaderContent,
DrawerPanel,
DrawerTitle,
DrawerTrigger,
} from "@makeplane/propel/components/drawer";
export default function NestedDemo() {
return (
<Drawer>
<DrawerTrigger
render={
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
label="Open settings"
/>
}
/>
<DrawerPanel side="end">
<DrawerHeader>
<DrawerHeaderContent>
<DrawerTitle>Settings</DrawerTitle>
<DrawerDescription>Workspace-wide preferences.</DrawerDescription>
</DrawerHeaderContent>
</DrawerHeader>
<DrawerBody>
<Drawer>
<DrawerTrigger
render={
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="lg"
label="Advanced options"
/>
}
/>
<DrawerPanel side="end">
<DrawerHeader>
<DrawerHeaderContent>
<DrawerTitle>Advanced options</DrawerTitle>
</DrawerHeaderContent>
</DrawerHeader>
<DrawerBody>Nested drawer content.</DrawerBody>
</DrawerPanel>
</Drawer>
</DrawerBody>
</DrawerPanel>
</Drawer>
);
}Detached triggers
createDrawerHandle() links triggers to a Drawer defined elsewhere in the tree; each trigger passes a payload the drawer renders through function-as-children.
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
createDrawerHandle,
Drawer,
DrawerDescription,
DrawerHeader,
DrawerHeaderContent,
DrawerPanel,
DrawerTitle,
DrawerTrigger,
} from "@makeplane/propel/components/drawer";
// A handle shared by triggers that live far from the drawer they open — the
// same details panel launched from unrelated corners of the UI, no lifted state.
const detailsDrawer = createDrawerHandle();
export default function DetachedTriggersDemo() {
return (
<div className="flex flex-wrap items-center gap-2">
<DrawerTrigger
handle={detailsDrawer}
payload="WEB-101"
render={
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
label="WEB-101"
/>
}
/>
<DrawerTrigger
handle={detailsDrawer}
payload="WEB-202"
render={
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
label="WEB-202"
/>
}
/>
<Drawer handle={detailsDrawer}>
{({ payload }) => (
<DrawerPanel side="end">
<DrawerHeader>
<DrawerHeaderContent>
<DrawerTitle>{typeof payload === "string" ? payload : "Details"}</DrawerTitle>
<DrawerDescription>Work item details.</DrawerDescription>
</DrawerHeaderContent>
</DrawerHeader>
</DrawerPanel>
)}
</Drawer>
</div>
);
}Controlled
A controlled Drawer can intercept its own dismissal — here a draft raises a confirmation via eventDetails.cancel() before the panel closes.
Show codeHide code
import {
AlertDialog,
AlertDialogActions,
AlertDialogContent,
AlertDialogDescription,
AlertDialogHeader,
AlertDialogIntro,
AlertDialogTitle,
} from "@makeplane/propel/components/alert-dialog";
import { Button } from "@makeplane/propel/components/button";
import {
Drawer,
DrawerBody,
DrawerClose,
DrawerDescription,
DrawerHeader,
DrawerHeaderContent,
DrawerPanel,
DrawerTitle,
DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { TextArea } from "@makeplane/propel/components/text-area";
import { X } from "lucide-react";
import * as React from "react";
export default function ControlledDemo() {
const [open, setOpen] = React.useState(false);
const [confirmOpen, setConfirmOpen] = React.useState(false);
const [draft, setDraft] = React.useState("");
return (
<>
<Drawer
open={open}
onOpenChange={(nextOpen, eventDetails) => {
if (!nextOpen && draft.trim() !== "") {
// Block the dismissal and ask for confirmation while a draft exists.
eventDetails.cancel();
setConfirmOpen(true);
return;
}
setOpen(nextOpen);
}}
>
<DrawerTrigger
render={
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
label="Add comment"
/>
}
/>
<DrawerPanel side="end">
<DrawerHeader>
<DrawerHeaderContent>
<DrawerTitle>New comment</DrawerTitle>
<DrawerDescription>Share feedback on this work item.</DrawerDescription>
</DrawerHeaderContent>
<DrawerClose
render={
<IconButton
prominence="ghost"
tone="neutral"
magnitude="lg"
aria-label="Close"
icon={<Icon icon={X} />}
/>
}
/>
</DrawerHeader>
<DrawerBody>
<TextArea
magnitude="lg"
surface="field"
resize="none"
rows={4}
aria-label="Comment"
value={draft}
onChange={(event) => setDraft(event.target.value)}
/>
</DrawerBody>
</DrawerPanel>
</Drawer>
<AlertDialog open={confirmOpen} onOpenChange={setConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogIntro>
<AlertDialogTitle>Discard comment?</AlertDialogTitle>
<AlertDialogDescription>
Your draft will be lost if you close the panel now.
</AlertDialogDescription>
</AlertDialogIntro>
</AlertDialogHeader>
<AlertDialogActions>
<Button
sizing="hug"
prominence="ghost"
tone="neutral"
magnitude="lg"
onClick={() => setConfirmOpen(false)}
label="Keep editing"
/>
<Button
sizing="hug"
prominence="primary"
tone="danger"
magnitude="lg"
onClick={() => {
setConfirmOpen(false);
setOpen(false);
setDraft("");
}}
label="Discard"
/>
</AlertDialogActions>
</AlertDialogContent>
</AlertDialog>
</>
);
}Installation
import { Drawer, DrawerTrigger, DrawerPanel } from "@makeplane/propel/components/drawer";
Props
Drawer
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | PayloadChildRenderFunction<unknown> | No | The content of the drawer. |
DrawerPanel
| Prop | Type | Required | Description |
|---|---|---|---|
| side | "end" | "start" | Yes | The viewport edge the drawer is anchored to. "end" pins to the inline-end (right in LTR); "start" pins to the inline-start (left in LTR). The slide animation direction and the leading-edge shadow border both follow this value. |
| children | ReactNode | No | The panel body — typically a `DrawerHeader`, `DrawerBody`, and `DrawerFooter`. |