Drawer

화면의 주요 콘텐츠를 보완하는 콘텐츠를 표시하도록 Dialog 구성 요소를 확장합니다.

Example

It can only be viewed in a mobile.

Steps

Prerequisite

Install Package

npm install lucide-react tailwindcss-animate

Add config in tailwind.config.js

tailwind.config.js
module.exports = {
  ...,
  plugins: [require("tailwindcss-animate")]
}

Add type

types/index.d.ts
interface ReactProps {
  children?: ReactNode
}

Copy Code

containers/drawer/index.tsx
'use client'
 
import { useCallback, useEffect } from 'react'
import { XIcon } from 'lucide-react'
import { createPortal } from 'react-dom'
import { cn } from 'utils'
 
export interface Props extends ReactProps {
  isOpen: boolean
  onClose: () => void
  position?: 'left' | 'top' | 'right' | 'bottom'
}
 
function Drawer({ isOpen, onClose, position = 'left', children }: Props) {
  const onEscape = useCallback(
    (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        onClose()
        window.removeEventListener('keydown', onEscape)
      }
    },
    [onClose]
  )
 
  useEffect(() => {
    if (!isOpen) return
    window.addEventListener('keydown', onEscape)
    return () => {
      window.removeEventListener('keydown', onEscape)
    }
  }, [isOpen, onEscape])
 
  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = 'hidden'
    } else if (document.body.style.overflow === 'hidden') {
      document.body.style.removeProperty('overflow')
    }
  }, [isOpen])
  return (
    <>
      {createPortal(
        <div
          aria-labelledby="drawer-title"
          aria-modal="true"
          role="dialog"
          tabIndex={-1}
        >
          <div
            className={cn('backdrop-blur-sm duration-200', {
              'fixed inset-0 z-20': isOpen
            })}
            onClick={onClose}
          />
          <div
            className={cn(
              'data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-30 transition duration-300 ease-in-out',
              {
                'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top top-0':
                  position === 'top',
                'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right right-0':
                  position === 'right',
                'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom bottom-0':
                  position === 'bottom',
                'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left left-0':
                  position === 'left',
 
                'left-0': position === 'top' || position === 'bottom',
                'top-0': position === 'left' || position === 'right',
 
                'translate-x-0':
                  (position === 'left' || position === 'right') && isOpen,
                'translate-y-0':
                  (position === 'top' || position === 'bottom') && isOpen,
                '-translate-y-full': position === 'top' && !isOpen,
                'translate-x-full': position === 'right' && !isOpen,
                'translate-y-full': position === 'bottom' && !isOpen,
                '-translate-x-full': position === 'left' && !isOpen
              }
            )}
            data-state={isOpen ? 'open' : 'closed'}
          >
            <div
              className={cn(
                'relative bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900',
                {
                  'border-b': position === 'top',
                  'border-l': position === 'right',
                  'border-t': position === 'bottom',
                  'border-r': position === 'left',
                  'h-80 w-screen': position === 'top' || position === 'bottom',
                  'h-screen w-80 overflow-auto':
                    position === 'left' || position === 'right'
                }
              )}
            >
              <button
                onClick={onClose}
                className="absolute right-4 top-4 inline-flex h-6 w-6 items-center justify-center rounded-md border dark:border-neutral-700"
              >
                <XIcon className="h-4 w-4" />
              </button>
              {children}
            </div>
          </div>
        </div>,
        document.body
      )}
    </>
  )
}
 
export default Drawer

Usage

Props

NameTypeDefault
positiontop right bottom leftleft