Modal

기존 흐름을 방해하지 않고 새로운 상호작용을 하고자 할 때 사용합니다.

Example

It can only be viewed in a mobile.

Steps

Prerequisite

Install Package

npm install lucide-react

Add types

types/index.d.ts
interface ReactProps {
  children?: ReactNode
}
 
interface ModalProps {
  isOpen: boolean
  onClose: () => void
  title?: string
  /**
   * @default "max-w-lg"
   */
  maxWidth?:
    | 'max-w-screen-2xl'
    | 'max-w-screen-xl'
    | 'max-w-screen-lg'
    | 'max-w-screen-md'
    | 'max-w-screen-sm'
    | 'max-w-full'
    | 'max-w-7xl'
    | 'max-w-6xl'
    | 'max-w-5xl'
    | 'max-w-4xl'
    | 'max-w-3xl'
    | 'max-w-2xl'
    | 'max-w-xl'
    | 'max-w-lg'
    | 'max-w-md'
    | 'max-w-sm'
    | 'max-w-xs'
  description?: ReactNode
  padding?: boolean
  footer?: ReactNode
}

Copy Code

containers/Modal/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, ModalProps {}
 
function Modal({
  isOpen,
  onClose,
  children,
  maxWidth = 'max-w-lg',
  title,
  description,
  padding = true,
  footer
}: 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])
  if (!isOpen) return <></>
  return createPortal(
    <div
      className="fixed inset-0 z-30 overflow-y-auto"
      aria-labelledby="modal-title"
      aria-modal="true"
      role="dialog"
    >
      <div className="flex min-h-screen items-center justify-center p-0 text-center md:block">
        <div
          className="fixed inset-0 bg-black/30 backdrop-blur-sm transition-opacity"
          aria-hidden="true"
          onClick={onClose}
        />
        <span
          className="hidden h-screen align-middle md:inline-block"
          aria-hidden="true"
        >
          &#8203;
        </span>
        <div
          className={cn(
            'my-8 inline-block w-full transform overflow-hidden rounded-lg text-left align-middle shadow-xl transition-all',
            maxWidth
          )}
        >
          {title && (
            <header className="border-t-4 border-blue-500 bg-white dark:bg-neutral-800">
              <div className="flex items-center border-b border-neutral-200 px-4 py-3 dark:border-neutral-700">
                <div className="flex-1">
                  <h1 className="text-xl font-semibold">{title}</h1>
                  {!!description && (
                    <p className="mt-1 text-sm text-neutral-400">
                      {description}
                    </p>
                  )}
                </div>
                <button
                  onClick={onClose}
                  className="rounded-full p-2 hover:bg-neutral-200 dark:hover:bg-neutral-900"
                >
                  <XIcon className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
                </button>
              </div>
            </header>
          )}
          <div
            className={cn('bg-white dark:bg-neutral-900', {
              'px-7 py-6': padding,
              'rounded-b-lg': !footer
            })}
          >
            {children}
          </div>
          {footer && (
            <footer className="rounded-b-lg border-t bg-white px-7 py-4 dark:border-neutral-700 dark:bg-neutral-900">
              {footer}
            </footer>
          )}
        </div>
      </div>
    </div>,
    document.body
  )
}
 
export default Modal

Usage

<Modal
  isOpen={isOpen}
  onClose={() => setState({ isOpen: false })}
  padding
  footer={<div>Footer</div>}
  maxWidth="max-w-lg"
  title="Title"
  description={<p>description</p>}
>
  children
</Modal>

Props

NameTypeDefault
titlestring
onCloseboolean
onClose() => void
descriptionstring
paddingboolean
footerReactNode
childrenReactNode
maxWidthmax-w-screen-2xl max-w-screen-xl max-w-screen-lg max-w-screen-md max-w-screen-sm max-w-full max-w-8xl max-w-7xl max-w-6xl max-w-5xl max-w-4xl max-w-3xl max-w-2xl max-w-xl max-w-lg max-w-md max-w-sm max-w-xs