index.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import { Popover, Transition } from '@headlessui/react'
  2. import { Fragment, cloneElement, useRef } from 'react'
  3. import cn from '@/utils/classnames'
  4. export type HtmlContentProps = {
  5. onClose?: () => void
  6. onClick?: () => void
  7. }
  8. type IPopover = {
  9. className?: string
  10. htmlContent: React.ReactElement<HtmlContentProps>
  11. popupClassName?: string
  12. trigger?: 'click' | 'hover'
  13. position?: 'bottom' | 'br' | 'bl'
  14. btnElement?: string | React.ReactNode
  15. btnClassName?: string | ((open: boolean) => string)
  16. manualClose?: boolean
  17. disabled?: 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. disabled = false,
  30. }: IPopover) {
  31. const buttonRef = useRef<HTMLButtonElement>(null)
  32. const timeOutRef = useRef<NodeJS.Timeout | null>(null)
  33. const onMouseEnter = (isOpen: boolean) => {
  34. timeOutRef.current && clearTimeout(timeOutRef.current)
  35. !isOpen && buttonRef.current?.click()
  36. }
  37. const onMouseLeave = (isOpen: boolean) => {
  38. timeOutRef.current = setTimeout(() => {
  39. isOpen && buttonRef.current?.click()
  40. }, timeoutDuration)
  41. }
  42. return (
  43. <Popover className="relative">
  44. {({ open }: { open: boolean }) => {
  45. return (
  46. <>
  47. <div
  48. {...(trigger !== 'hover'
  49. ? {}
  50. : {
  51. onMouseLeave: () => onMouseLeave(open),
  52. onMouseEnter: () => onMouseEnter(open),
  53. })}
  54. >
  55. <Popover.Button
  56. ref={buttonRef}
  57. disabled={disabled}
  58. className={cn(
  59. 'group inline-flex items-center bg-components-button-secondary-bg px-3 py-2 rounded-lg text-base border border-components-button-secondary-border font-medium hover:bg-components-button-secondary-bg-hover hover:border-components-button-secondary-border-hover focus:outline-none',
  60. open && 'bg-components-button-secondary-bg-hover border-components-button-secondary-border',
  61. (btnClassName && typeof btnClassName === 'string') && btnClassName,
  62. (btnClassName && typeof btnClassName !== 'string') && btnClassName?.(open),
  63. )}
  64. >
  65. {btnElement}
  66. </Popover.Button>
  67. <Transition as={Fragment}>
  68. <Popover.Panel
  69. className={cn(
  70. 'absolute z-10 w-full max-w-sm px-4 mt-1 sm:px-0 lg:max-w-3xl',
  71. position === 'bottom' && '-translate-x-1/2 left-1/2',
  72. position === 'bl' && 'left-0',
  73. position === 'br' && 'right-0',
  74. className,
  75. )}
  76. {...(trigger !== 'hover'
  77. ? {}
  78. : {
  79. onMouseLeave: () => onMouseLeave(open),
  80. onMouseEnter: () => onMouseEnter(open),
  81. })
  82. }
  83. >
  84. {({ close }) => (
  85. <div
  86. className={cn('overflow-hidden bg-components-panel-bg w-fit min-w-[130px] rounded-lg shadow-lg ring-1 ring-black ring-opacity-5', popupClassName)}
  87. {...(trigger !== 'hover'
  88. ? {}
  89. : {
  90. onMouseLeave: () => onMouseLeave(open),
  91. onMouseEnter: () => onMouseEnter(open),
  92. })
  93. }
  94. >
  95. {cloneElement(htmlContent as React.ReactElement<HtmlContentProps>, {
  96. onClose: () => onMouseLeave(open),
  97. ...(manualClose
  98. ? {
  99. onClick: close,
  100. }
  101. : {}),
  102. })}
  103. </div>
  104. )}
  105. </Popover.Panel>
  106. </Transition>
  107. </div>
  108. </>
  109. )
  110. }}
  111. </Popover>
  112. )
  113. }