Skip to main content

Modal

Prerequisite

Code

types/index.d.ts
export interface IModal {
isOpen: boolean
onClose: () => void
title: string
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
}
components/Modal/index.tsx
import { useEffect } from 'react'
import type { FC } from 'react'
import type { IModal } from 'types'
import { XIcon } from '@heroicons/react/solid'
import classnames from 'classnames'
import { Portal } from 'components'

interface Props extends IModal {}

const Modal: FC<Props> = ({
isOpen,
onClose,
children,
maxWidth = 'max-w-lg',
title,
description,
padding = true
}) => {
if (!isOpen) return null
const onEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}

useEffect(() => {
window.addEventListener('keydown', onEscape)
return () => {
window.removeEventListener('keydown', onEscape)
}
}, [])
return (
<Portal role="dialog">
<div
className="fixed z-30 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
aria-modal="true"
>
<div className="flex items-end justify-center min-h-screen text-center md:block p-0">
<div
className="fixed inset-0 bg-black opacity-30 transition-opacity"
aria-hidden="true"
onClick={onClose}
/>
<span
className="hidden md:inline-block align-middle h-screen"
aria-hidden="true"
>
&#8203;
</span>
<div
className={classnames(
'inline-block rounded-lg text-left overflow-hidden shadow-xl transform transition-all my-8 align-middle w-full',
maxWidth
)}
>
<header className="bg-white border-t-4 border-gray-800">
<div className="flex items-start py-3 px-4 border-b border-gray-200">
<div className="flex-1">
<h1 className="text-2xl font-semibold">{title}</h1>
{!!description && (
<p className="text-sm mt-1 text-gray-400">{description}</p>
)}
</div>
<button
onClick={onClose}
className="p-2 rounded-full hover:bg-gray-300"
>
<XIcon className="w-5 h-5 text-gray-800" />
</button>
</div>
</header>
<div
className={classnames('bg-white', {
'py-6 px-7': padding
})}
>
{children}
</div>
</div>
</div>
</div>
</Portal>
)
}

export default Modal

Props

NameTypeDefault
isOpen*booleanfalse
onClose*function
title*string
maxWidthmax-w-xs max-w-sm max-w-md max-w-lg max-w-xl max-w-2xl max-w-3xl max-w-4xl max-w-5xl max-w-6xl max-w-7xl max-w-full max-w-screen-sm max-w-screen-md max-w-screen-lg max-w-screen-xl max-w-screen-2xlmax-w-lg
descriptionstring
paddingbooleantrue

Usage

import { useObjectState } from 'hooks'
import { Modal } from 'components'

interface State {
isOpen: boolean
}

const Page = () => {
const [{ isOpen }, setState] = useObjectState<State>({ isOpen: false })
return (
<div>
<Modal
title="Title"
isOpen={isOpen}
onClose={() => setState({ isOpen: false })}
>
<div>Modal!!</div>
</Modal>
</div>
)
}

Example