You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

99 lines
3.0 KiB

  1. import { useState, createContext, useContext, Fragment, PropsWithChildren, Dispatch, SetStateAction } from 'react';
  2. import { Link, InertiaLinkProps } from '@inertiajs/react';
  3. import { Transition } from '@headlessui/react';
  4. const DropDownContext = createContext<{
  5. open: boolean;
  6. setOpen: Dispatch<SetStateAction<boolean>>;
  7. toggleOpen: () => void;
  8. }>({
  9. open: false,
  10. setOpen: () => {},
  11. toggleOpen: () => {},
  12. });
  13. const Dropdown = ({ children }: PropsWithChildren) => {
  14. const [open, setOpen] = useState(false);
  15. const toggleOpen = () => {
  16. setOpen((previousState) => !previousState);
  17. };
  18. return (
  19. <DropDownContext.Provider value={{ open, setOpen, toggleOpen }}>
  20. <div className="relative">{children}</div>
  21. </DropDownContext.Provider>
  22. );
  23. };
  24. const Trigger = ({ children }: PropsWithChildren) => {
  25. const { open, setOpen, toggleOpen } = useContext(DropDownContext);
  26. return (
  27. <>
  28. <div onClick={toggleOpen}>{children}</div>
  29. {open && <div className="fixed inset-0 z-40" onClick={() => setOpen(false)}></div>}
  30. </>
  31. );
  32. };
  33. const Content = ({ align = 'right', width = '48', contentClasses = 'py-1 bg-white', children }: PropsWithChildren<{ align?: 'left'|'right', width?: '48', contentClasses?: string }>) => {
  34. const { open, setOpen } = useContext(DropDownContext);
  35. let alignmentClasses = 'origin-top';
  36. if (align === 'left') {
  37. alignmentClasses = 'ltr:origin-top-left rtl:origin-top-right start-0';
  38. } else if (align === 'right') {
  39. alignmentClasses = 'ltr:origin-top-right rtl:origin-top-left end-0';
  40. }
  41. let widthClasses = '';
  42. if (width === '48') {
  43. widthClasses = 'w-48';
  44. }
  45. return (
  46. <>
  47. <Transition
  48. as={Fragment}
  49. show={open}
  50. enter="transition ease-out duration-200"
  51. enterFrom="opacity-0 scale-95"
  52. enterTo="opacity-100 scale-100"
  53. leave="transition ease-in duration-75"
  54. leaveFrom="opacity-100 scale-100"
  55. leaveTo="opacity-0 scale-95"
  56. >
  57. <div
  58. className={`absolute z-50 mt-2 rounded-md shadow-lg ${alignmentClasses} ${widthClasses}`}
  59. onClick={() => setOpen(false)}
  60. >
  61. <div className={`rounded-md ring-1 ring-black ring-opacity-5 ` + contentClasses}>{children}</div>
  62. </div>
  63. </Transition>
  64. </>
  65. );
  66. };
  67. const DropdownLink = ({ className = '', children, ...props }: InertiaLinkProps) => {
  68. return (
  69. <Link
  70. {...props}
  71. className={
  72. 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out ' +
  73. className
  74. }
  75. >
  76. {children}
  77. </Link>
  78. );
  79. };
  80. Dropdown.Trigger = Trigger;
  81. Dropdown.Content = Content;
  82. Dropdown.Link = DropdownLink;
  83. export default Dropdown;