index.tsx 3.3 KB

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