| import { Popover, Transition } from '@headlessui/react' |
| import { Fragment, cloneElement, useRef } from 'react' |
| import s from './style.module.css' |
| import cn from '@/utils/classnames' |
|
|
| export type HtmlContentProps = { |
| onClose?: () => void |
| onClick?: () => void |
| } |
|
|
| type IPopover = { |
| className?: string |
| htmlContent: React.ReactElement<HtmlContentProps> |
| popupClassName?: string |
| trigger?: 'click' | 'hover' |
| position?: 'bottom' | 'br' | 'bl' |
| btnElement?: string | React.ReactNode |
| btnClassName?: string | ((open: boolean) => string) |
| manualClose?: boolean |
| disabled?: boolean |
| } |
|
|
| const timeoutDuration = 100 |
|
|
| export default function CustomPopover({ |
| trigger = 'hover', |
| position = 'bottom', |
| htmlContent, |
| popupClassName, |
| btnElement, |
| className, |
| btnClassName, |
| manualClose, |
| disabled = false, |
| }: IPopover) { |
| const buttonRef = useRef<HTMLButtonElement>(null) |
| const timeOutRef = useRef<NodeJS.Timeout | null>(null) |
|
|
| const onMouseEnter = (isOpen: boolean) => { |
| timeOutRef.current && clearTimeout(timeOutRef.current) |
| !isOpen && buttonRef.current?.click() |
| } |
|
|
| const onMouseLeave = (isOpen: boolean) => { |
| timeOutRef.current = setTimeout(() => { |
| isOpen && buttonRef.current?.click() |
| }, timeoutDuration) |
| } |
|
|
| return ( |
| <Popover className="relative"> |
| {({ open }: { open: boolean }) => { |
| return ( |
| <> |
| <div |
| {...(trigger !== 'hover' |
| ? {} |
| : { |
| onMouseLeave: () => onMouseLeave(open), |
| onMouseEnter: () => onMouseEnter(open), |
| })} |
| > |
| <Popover.Button |
| ref={buttonRef} |
| disabled={disabled} |
| className={`group ${s.popupBtn} ${open ? '' : 'bg-gray-100'} ${!btnClassName |
| ? '' |
| : typeof btnClassName === 'string' |
| ? btnClassName |
| : btnClassName?.(open) |
| }`} |
| > |
| {btnElement} |
| </Popover.Button> |
| <Transition as={Fragment}> |
| <Popover.Panel |
| className={cn( |
| s.popupPanel, |
| position === 'bottom' && '-translate-x-1/2 left-1/2', |
| position === 'bl' && 'left-0', |
| position === 'br' && 'right-0', |
| className, |
| )} |
| {...(trigger !== 'hover' |
| ? {} |
| : { |
| onMouseLeave: () => onMouseLeave(open), |
| onMouseEnter: () => onMouseEnter(open), |
| }) |
| } |
| > |
| {({ close }) => ( |
| <div |
| className={cn(s.panelContainer, popupClassName)} |
| {...(trigger !== 'hover' |
| ? {} |
| : { |
| onMouseLeave: () => onMouseLeave(open), |
| onMouseEnter: () => onMouseEnter(open), |
| }) |
| } |
| > |
| {cloneElement(htmlContent as React.ReactElement<HtmlContentProps>, { |
| onClose: () => onMouseLeave(open), |
| ...(manualClose |
| ? { |
| onClick: close, |
| } |
| : {}), |
| })} |
| </div> |
| )} |
| </Popover.Panel> |
| </Transition> |
| </div> |
| </> |
| ) |
| }} |
| </Popover> |
| ) |
| } |
|
|