| 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> | |
| ) | |
| } | |