index.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  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. manualClose?: boolean
  12. }
  13. const timeoutDuration = 100
  14. export default function CustomPopover({
  15. trigger = 'hover',
  16. position = 'bottom',
  17. htmlContent,
  18. btnElement,
  19. className,
  20. btnClassName,
  21. manualClose,
  22. }: IPopover) {
  23. const buttonRef = useRef<HTMLButtonElement>(null)
  24. const timeOutRef = useRef<NodeJS.Timeout | null>(null)
  25. const onMouseEnter = (isOpen: boolean) => {
  26. timeOutRef.current && clearTimeout(timeOutRef.current)
  27. !isOpen && buttonRef.current?.click()
  28. }
  29. const onMouseLeave = (isOpen: boolean) => {
  30. timeOutRef.current = setTimeout(() => {
  31. isOpen && buttonRef.current?.click()
  32. }, timeoutDuration)
  33. }
  34. return (
  35. <Popover className="relative">
  36. {({ open }: { open: boolean }) => {
  37. return (
  38. <>
  39. <div
  40. {...(trigger !== 'hover'
  41. ? {}
  42. : {
  43. onMouseLeave: () => onMouseLeave(open),
  44. onMouseEnter: () => onMouseEnter(open),
  45. })}
  46. >
  47. <Popover.Button
  48. ref={buttonRef}
  49. className={`group ${s.popupBtn} ${open ? '' : 'bg-gray-100'} ${
  50. !btnClassName
  51. ? ''
  52. : typeof btnClassName === 'string'
  53. ? btnClassName
  54. : btnClassName?.(open)
  55. }`}
  56. >
  57. {btnElement}
  58. </Popover.Button>
  59. <Transition as={Fragment}>
  60. <Popover.Panel
  61. className={`${s.popupPanel} ${position === 'br' ? 'right-0' : 'translate-x-1/2 left-1/2'} ${className}`}
  62. {...(trigger !== 'hover'
  63. ? {}
  64. : {
  65. onMouseLeave: () => onMouseLeave(open),
  66. onMouseEnter: () => onMouseEnter(open),
  67. })
  68. }
  69. >
  70. {({ close }) => (
  71. <div
  72. className={s.panelContainer}
  73. {...(trigger !== 'hover'
  74. ? {}
  75. : {
  76. onMouseLeave: () => onMouseLeave(open),
  77. onMouseEnter: () => onMouseEnter(open),
  78. })
  79. }
  80. >
  81. {cloneElement(htmlContent as React.ReactElement, {
  82. onClose: () => onMouseLeave(open),
  83. ...(manualClose
  84. ? {
  85. onClick: close,
  86. }
  87. : {}),
  88. })}
  89. </div>
  90. )}
  91. </Popover.Panel>
  92. </Transition>
  93. </div>
  94. </>
  95. )
  96. }}
  97. </Popover>
  98. )
  99. }