index.tsx 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { Popover, Transition } from '@headlessui/react'
  2. import { Fragment, cloneElement, useRef } from 'react'
  3. import s from './style.module.css'
  4. type IPopover = {
  5. className?: string
  6. htmlContent: React.ReactNode
  7. trigger?: 'click' | 'hover'
  8. position?: 'bottom' | 'br'
  9. btnElement?: string | React.ReactNode
  10. btnClassName?: string | ((open: boolean) => string)
  11. }
  12. const timeoutDuration = 100
  13. export default function CustomPopover({
  14. trigger = 'hover',
  15. position = 'bottom',
  16. htmlContent,
  17. btnElement,
  18. className,
  19. btnClassName,
  20. }: IPopover) {
  21. const buttonRef = useRef<HTMLButtonElement>(null)
  22. const timeOutRef = useRef<NodeJS.Timeout | null>(null)
  23. const onMouseEnter = (isOpen: boolean) => {
  24. timeOutRef.current && clearTimeout(timeOutRef.current)
  25. !isOpen && buttonRef.current?.click()
  26. }
  27. const onMouseLeave = (isOpen: boolean) => {
  28. timeOutRef.current = setTimeout(() => {
  29. isOpen && buttonRef.current?.click()
  30. }, timeoutDuration)
  31. }
  32. return (
  33. <Popover className="relative">
  34. {({ open }: { open: boolean }) => {
  35. return (
  36. <>
  37. <div
  38. {...(trigger !== 'hover'
  39. ? {}
  40. : {
  41. onMouseLeave: () => onMouseLeave(open),
  42. onMouseEnter: () => onMouseEnter(open),
  43. })
  44. }
  45. >
  46. <Popover.Button
  47. ref={buttonRef}
  48. className={`group ${s.popupBtn} ${open ? '' : 'bg-gray-100'} ${!btnClassName ? '' : typeof btnClassName === 'string' ? btnClassName : btnClassName?.(open)}`}
  49. >
  50. {btnElement}
  51. </Popover.Button>
  52. <Transition
  53. as={Fragment}
  54. enter="transition ease-out duration-200"
  55. enterFrom="opacity-0 translate-y-1"
  56. enterTo="opacity-100 translate-y-0"
  57. leave="transition ease-in duration-150"
  58. leaveFrom="opacity-100 translate-y-0"
  59. leaveTo="opacity-0 translate-y-1"
  60. >
  61. <Popover.Panel
  62. className={`${s.popupPanel} ${position === 'br' ? 'right-0' : 'transform -translate-x-1/2 left-1/2'} ${className}`}
  63. {...(trigger !== 'hover'
  64. ? {}
  65. : {
  66. onMouseLeave: () => onMouseLeave(open),
  67. onMouseEnter: () => onMouseEnter(open),
  68. })
  69. }>
  70. <div
  71. className={s.panelContainer}
  72. {...(trigger !== 'hover'
  73. ? {}
  74. : {
  75. onMouseLeave: () => onMouseLeave(open),
  76. onMouseEnter: () => onMouseEnter(open),
  77. })
  78. }
  79. >
  80. {cloneElement(htmlContent as React.ReactElement, {
  81. onClose: () => onMouseLeave(open),
  82. })}
  83. </div>
  84. </Popover.Panel>
  85. </Transition>
  86. </div>
  87. </>
  88. )
  89. }}
  90. </Popover>
  91. )
  92. }