Command Palette

Search for a command to run...

Drawer

A drawer component that allows you to create a drawer with drag and drop functionality.

profile_image

Installation

Install the following dependencies:

pnpm add motion

Create a file at components/motion/drawer-rc.tsx and paste the given code.

"use client";
import React, {
    createContext,
    // useContext,
    useState,
    useEffect,
    ReactNode,
} from "react";
import { X } from "lucide-react";
import { Drawer as VaulSidebar } from "vaul";
import { cn } from "@/lib/utils";
 
interface DrawerContextProps {
    open: boolean;
    setOpen: (open: boolean) => void;
}
 
const DrawerContext = createContext<DrawerContextProps | undefined>(undefined);
 
// const useSidebarDrawer = () => {
//     const context = useContext(DrawerContext);
//     if (!context) {
//         throw new Error("useDrawer must be used within a DrawerProvider");
//     }
//     return context;
// };
 
interface DrawerSidebarProps {
    children: ReactNode;
    open?: boolean;
    setOpen?: (open: boolean) => void;
    direction?: "left" | "right";
    outsideClose?: boolean;
    className?: string;
    triggerClassName?: string;
    DefaultTrigger?: () => React.ReactNode; // Changed to a function that returns ReactNode
}
 
export function SidebarDrawer({
    children,
    open: controlledOpen,
    setOpen: controlledSetOpen,
    direction = "left",
    outsideClose = true,
    className,
    // triggerClassName,
    DefaultTrigger, // Now a function prop
}: DrawerSidebarProps) {
    const [internalOpen, setInternalOpen] = useState(false);
    const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
    const setOpen =
        controlledSetOpen !== undefined ? controlledSetOpen : setInternalOpen;
 
    const [isDesktop, setIsDesktop] = useState(false);
 
    useEffect(() => {
        const mediaQuery = window.matchMedia("(min-width: 768px)");
        const handleMediaChange = (event: MediaQueryListEvent) => {
            setIsDesktop(event.matches);
        };
 
        setIsDesktop(mediaQuery.matches);
        mediaQuery.addEventListener("change", handleMediaChange);
 
        return () => {
            mediaQuery.removeEventListener("change", handleMediaChange);
        };
    }, []);
 
    return (
        <DrawerContext.Provider value={{ open, setOpen }}>
            <>
                {DefaultTrigger && (
                    <div onClick={() => setOpen(true)}>{DefaultTrigger()}</div>
                )}
 
                <VaulSidebar.Root
                    open={open}
                    direction={direction === "right" ? "right" : "left"}
                    onOpenChange={setOpen}
                    dismissible={isDesktop ? false : true}
                >
                    <VaulSidebar.Portal>
                        <VaulSidebar.Overlay
                            className="fixed inset-0 z-50 bg-white/50 backdrop-blur-sm dark:bg-black/40"
                            onClick={() => setOpen(false)}
                        />
                        <VaulSidebar.Content
                            className={cn(
                                `z-50 border-l ${
                                    outsideClose
                                        ? "h-[100%] w-[90%] bg-zinc-100 sm:w-[450px] dark:bg-zinc-950"
                                        : `h-[100%] w-full`
                                } fixed bottom-0 ${
                                    direction === "right" ? "right-0" : "left-0"
                                }`,
                                className,
                            )}
                        >
                            <VaulSidebar.Title className="sr-only">
                                Hello
                            </VaulSidebar.Title>
                            <div
                                className={`${
                                    outsideClose
                                        ? "h-full w-full"
                                        : "relative h-full w-[90%] border-r bg-white sm:w-[450px] dark:bg-gray-900"
                                } `}
                            >
                                {isDesktop ? (
                                    <>
                                        <button
                                            className="absolute top-2 right-2 flex w-full justify-end"
                                            onClick={() => setOpen(false)}
                                        >
                                            <X />
                                        </button>
                                    </>
                                ) : (
                                    <>
                                        <div
                                            className={`absolute top-[40%] ${
                                                direction === "right"
                                                    ? "left-2"
                                                    : "right-2"
                                            } mx-auto my-4 h-16 w-[0.30rem] flex-shrink-0 rounded-full bg-gray-600`}
                                        />
                                    </>
                                )}
                                {children}
                            </div>
                        </VaulSidebar.Content>
                    </VaulSidebar.Portal>
                </VaulSidebar.Root>
            </>
        </DrawerContext.Provider>
    );
}
 
export function DrawerContent({ children }: { children: ReactNode }) {
    return <>{children}</>;
}

Update the import paths to match your project setup.

Examples