選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

index.tsx 4.1KB

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