index.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import {
  2. Dialog,
  3. DialogBackdrop,
  4. DialogTitle,
  5. Description,
  6. DialogPanel,
  7. CloseButton,
  8. Transition,
  9. } from "@headlessui/react";
  10. import { XMarkIcon } from "@heroicons/react/24/outline";
  11. import { type ReactNode, useCallback } from "react";
  12. import { Button } from "../Button";
  13. type Props = {
  14. open: boolean;
  15. onClose: () => void;
  16. closeDisabled?: boolean | undefined;
  17. afterLeave?: (() => void) | undefined;
  18. children?: ReactNode | ReactNode[] | undefined;
  19. title: ReactNode | ReactNode[];
  20. description?: string;
  21. additionalButtons?: ReactNode | ReactNode[] | undefined;
  22. };
  23. export const Modal = ({
  24. open,
  25. onClose,
  26. closeDisabled,
  27. afterLeave,
  28. children,
  29. title,
  30. description,
  31. additionalButtons,
  32. }: Props) => {
  33. const handleClose = useCallback(() => {
  34. if (!closeDisabled) {
  35. onClose();
  36. }
  37. }, [closeDisabled, onClose]);
  38. return (
  39. <Dialog open={open} onClose={handleClose} className="relative z-50">
  40. <DialogBackdrop
  41. transition
  42. className="fixed inset-0 bg-black/30 duration-300 ease-out data-[closed]:opacity-0"
  43. />
  44. <div className="fixed inset-0 flex w-screen items-center justify-center p-4">
  45. <Transition
  46. as={DialogPanel}
  47. show={open}
  48. static
  49. className="relative rounded-md bg-white p-8 duration-300 ease-out data-[closed]:scale-95 data-[closed]:opacity-0"
  50. {...(afterLeave && { afterLeave })}
  51. >
  52. <DialogTitle
  53. as="h1"
  54. className="text-lg font-medium leading-6 text-neutral-800 dark:text-neutral-200 md:text-xl lg:text-2xl"
  55. >
  56. {title}
  57. </DialogTitle>
  58. {closeDisabled !== true && (
  59. <CloseButton className="absolute right-3 top-3 rounded-md p-2 text-neutral-500 transition hover:bg-black/10 dark:hover:bg-white/5">
  60. <XMarkIcon className="size-5" />
  61. </CloseButton>
  62. )}
  63. {description && (
  64. <Description className="mb-10 mt-2 text-sm text-neutral-500 dark:text-neutral-400">
  65. {description}
  66. </Description>
  67. )}
  68. {children}
  69. <div className="mt-8 flex flex-row justify-end gap-4 text-right">
  70. <CloseButton
  71. as={Button}
  72. className="px-4 py-2"
  73. disabled={closeDisabled ?? false}
  74. >
  75. Close
  76. </CloseButton>
  77. {additionalButtons}
  78. </div>
  79. </Transition>
  80. </div>
  81. </Dialog>
  82. );
  83. };