Drawer

A responsive drawer that opens like an app drawer on mobile devices and functions as a standard dialog or drawer on desktop

Installation

npm install framer-motion vaul

please add this property in your layout.js or app.js

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body className={poppins.className}>
        <div vaul-drawer-wrapper="">{children}</div>
      </body>
    </html>
  )
}
drawer.tsx
import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from 'react'
import { AnimatePresence, motion } from 'framer-motion'
import { X } from 'lucide-react'
import { Drawer as VaulDrawer } from 'vaul'
 
interface DrawerContextProps {
  open: boolean
  setOpen: (open: boolean) => void
}
 
const DrawerContext = createContext<DrawerContextProps | undefined>(undefined)
 
const useDrawer = () => {
  const context = useContext(DrawerContext)
  if (!context) {
    throw new Error('useDrawer must be used within a DrawerProvider')
  }
  return context
}
 
interface ResponsiveDrawerProps {
  children: ReactNode
  open?: boolean
  setOpen?: (open: boolean) => void
}
 
export function ResponsiveDrawer({
  children,
  open: controlledOpen,
  setOpen: controlledSetOpen,
}: ResponsiveDrawerProps) {
  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 }}>
      <>
        {isDesktop ? (
          <AnimatePresence>
            {open && (
              <motion.div
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                className="fixed inset-0 z-20 flex items-center justify-center bg-black/50 backdrop-blur-sm cursor-zoom-out"
                onClick={() => setOpen(false)}
              >
                <motion.div
                  initial={{ y: 50, opacity: 0 }}
                  animate={{ y: 0, opacity: 1 }}
                  exit={{ y: 50, opacity: 0 }}
                  transition={{ type: 'spring', duration: 0.5 }}
                  onClick={(e) => e.stopPropagation()}
                  className="relative w-full max-w-md p-6 border dark:bg-gray-900 bg-white rounded-lg cursor-default"
                >
                  <button
                    className="absolute top-2 right-2"
                    onClick={() => setOpen(false)}
                  >
                    <X />
                  </button>
                  {children}
                </motion.div>
              </motion.div>
            )}
          </AnimatePresence>
        ) : (
          <VaulDrawer.Root
            shouldScaleBackground
            open={open}
            onOpenChange={setOpen}
          >
            <VaulDrawer.Portal>
              <VaulDrawer.Overlay className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm" />
              <VaulDrawer.Content className="fixed bottom-0 left-0 z-50 w-full max-h-[96%] bg-white dark:bg-gray-900">
                <div className="mx-auto w-16 h-[0.30rem] flex-shrink-0 rounded-full bg-gray-600 my-4" />
                  <div className=" w-full mx-auto max-h-[96vh]  overflow-auto ">
                   {children}
                 </div>
              </VaulDrawer.Content>
            </VaulDrawer.Portal>
          </VaulDrawer.Root>
        )}
      </>
    </DrawerContext.Provider>
  )
}
 
export function DrawerContent({ children }: { children: ReactNode }) {
  return <>{children}</>
}
 
don't forget to add the state and props
const [drawerOpen, setDrawerOpen] = useState(false)
  return (
    <>
      <ResponsiveDrawer open={drawerOpen} setOpen={setDrawerOpen}>
        <DrawerContent>
         </DrawerContent>
      </ResponsiveDrawer>
        )

Props

PropTypeDescription
openbooleanThe content to be displayed within the AuroraBackground component.
setOpenbooleanthis is an function to close and open the drawer