index.tsx 3.3 KB

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