index.tsx 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import { Fragment, useCallback } from 'react'
  2. import type { ElementType, ReactNode } from 'react'
  3. import { Dialog, Transition } from '@headlessui/react'
  4. import classNames from '@/utils/classnames'
  5. // https://headlessui.com/react/dialog
  6. type DialogProps = {
  7. className?: string
  8. titleClassName?: string
  9. bodyClassName?: string
  10. footerClassName?: string
  11. titleAs?: ElementType
  12. title?: ReactNode
  13. children: ReactNode
  14. footer?: ReactNode
  15. show: boolean
  16. onClose?: () => void
  17. }
  18. const CustomDialog = ({
  19. className,
  20. titleClassName,
  21. bodyClassName,
  22. footerClassName,
  23. titleAs,
  24. title,
  25. children,
  26. footer,
  27. show,
  28. onClose,
  29. }: DialogProps) => {
  30. const close = useCallback(() => onClose?.(), [onClose])
  31. return (
  32. <Transition appear show={show} as={Fragment}>
  33. <Dialog as="div" className="relative z-40" onClose={close}>
  34. <Transition.Child
  35. as={Fragment}
  36. enter="ease-out duration-300"
  37. enterFrom="opacity-0"
  38. enterTo="opacity-100"
  39. leave="ease-in duration-200"
  40. leaveFrom="opacity-100"
  41. leaveTo="opacity-0"
  42. >
  43. <div className="fixed inset-0 bg-black bg-opacity-25" />
  44. </Transition.Child>
  45. <div className="fixed inset-0 overflow-y-auto">
  46. <div className="flex items-center justify-center min-h-full p-4 text-center">
  47. <Transition.Child
  48. as={Fragment}
  49. enter="ease-out duration-300"
  50. enterFrom="opacity-0 scale-95"
  51. enterTo="opacity-100 scale-100"
  52. leave="ease-in duration-200"
  53. leaveFrom="opacity-100 scale-100"
  54. leaveTo="opacity-0 scale-95"
  55. >
  56. <Dialog.Panel className={classNames('w-full max-w-[800px] p-0 overflow-hidden text-left text-gray-900 align-middle transition-all transform bg-white shadow-xl rounded-2xl', className)}>
  57. {Boolean(title) && (
  58. <Dialog.Title
  59. as={titleAs || 'h3'}
  60. className={classNames('px-8 py-6 text-lg font-medium leading-6 text-gray-900', titleClassName)}
  61. >
  62. {title}
  63. </Dialog.Title>
  64. )}
  65. <div className={classNames('px-8 text-lg font-medium leading-6', bodyClassName)}>
  66. {children}
  67. </div>
  68. {Boolean(footer) && (
  69. <div className={classNames('flex items-center justify-end gap-2 px-8 py-6', footerClassName)}>
  70. {footer}
  71. </div>
  72. )}
  73. </Dialog.Panel>
  74. </Transition.Child>
  75. </div>
  76. </div>
  77. </Dialog>
  78. </Transition >
  79. )
  80. }
  81. export default CustomDialog