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