| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 'use client' | 
					
						
							|  |  |  | import type { FC } from 'react' | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  | import React, { useEffect, useRef, useState } from 'react' | 
					
						
							|  |  |  | import { useBoolean } from 'ahooks' | 
					
						
							|  |  |  | import type { OffsetOptions, Placement } from '@floating-ui/react' | 
					
						
							|  |  |  | import { RiQuestionLine } from '@remixicon/react' | 
					
						
							|  |  |  | import cn from '@/utils/classnames' | 
					
						
							|  |  |  | import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' | 
					
						
							|  |  |  | export type TooltipProps = { | 
					
						
							|  |  |  |   position?: Placement | 
					
						
							|  |  |  |   triggerMethod?: 'hover' | 'click' | 
					
						
							|  |  |  |   triggerClassName?: string | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |   disabled?: boolean | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |   popupContent?: React.ReactNode | 
					
						
							|  |  |  |   children?: React.ReactNode | 
					
						
							|  |  |  |   popupClassName?: string | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |   noDecoration?: boolean | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |   offset?: OffsetOptions | 
					
						
							|  |  |  |   needsDelay?: boolean | 
					
						
							|  |  |  |   asChild?: boolean | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const Tooltip: FC<TooltipProps> = ({ | 
					
						
							|  |  |  |   position = 'top', | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |   triggerMethod = 'hover', | 
					
						
							|  |  |  |   triggerClassName, | 
					
						
							|  |  |  |   disabled = false, | 
					
						
							|  |  |  |   popupContent, | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |   children, | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |   popupClassName, | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |   noDecoration, | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |   offset, | 
					
						
							|  |  |  |   asChild = true, | 
					
						
							|  |  |  |   needsDelay = false, | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | }) => { | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |   const [open, setOpen] = useState(false) | 
					
						
							|  |  |  |   const [isHoverPopup, { | 
					
						
							|  |  |  |     setTrue: setHoverPopup, | 
					
						
							|  |  |  |     setFalse: setNotHoverPopup, | 
					
						
							|  |  |  |   }] = useBoolean(false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const isHoverPopupRef = useRef(isHoverPopup) | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     isHoverPopupRef.current = isHoverPopup | 
					
						
							|  |  |  |   }, [isHoverPopup]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [isHoverTrigger, { | 
					
						
							|  |  |  |     setTrue: setHoverTrigger, | 
					
						
							|  |  |  |     setFalse: setNotHoverTrigger, | 
					
						
							|  |  |  |   }] = useBoolean(false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const isHoverTriggerRef = useRef(isHoverTrigger) | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     isHoverTriggerRef.current = isHoverTrigger | 
					
						
							|  |  |  |   }, [isHoverTrigger]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleLeave = (isTrigger: boolean) => { | 
					
						
							|  |  |  |     if (isTrigger) | 
					
						
							|  |  |  |       setNotHoverTrigger() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       setNotHoverPopup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // give time to move to the popup
 | 
					
						
							|  |  |  |     if (needsDelay) { | 
					
						
							|  |  |  |       setTimeout(() => { | 
					
						
							|  |  |  |         if (!isHoverPopupRef.current && !isHoverTriggerRef.current) | 
					
						
							|  |  |  |           setOpen(false) | 
					
						
							|  |  |  |       }, 500) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							|  |  |  |       setOpen(false) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |   return ( | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |     <PortalToFollowElem | 
					
						
							|  |  |  |       open={disabled ? false : open} | 
					
						
							|  |  |  |       onOpenChange={setOpen} | 
					
						
							|  |  |  |       placement={position} | 
					
						
							|  |  |  |       offset={offset ?? 8} | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <PortalToFollowElemTrigger | 
					
						
							|  |  |  |         onClick={() => triggerMethod === 'click' && setOpen(v => !v)} | 
					
						
							|  |  |  |         onMouseEnter={() => { | 
					
						
							|  |  |  |           if (triggerMethod === 'hover') { | 
					
						
							|  |  |  |             setHoverTrigger() | 
					
						
							|  |  |  |             setOpen(true) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }} | 
					
						
							|  |  |  |         onMouseLeave={() => triggerMethod === 'hover' && handleLeave(true)} | 
					
						
							|  |  |  |         asChild={asChild} | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |         {children || <div className={triggerClassName || 'h-3.5 w-3.5 shrink-0 p-[1px]'}><RiQuestionLine className='h-full w-full text-text-quaternary hover:text-text-tertiary' /></div>} | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |       </PortalToFollowElemTrigger> | 
					
						
							|  |  |  |       <PortalToFollowElemContent | 
					
						
							|  |  |  |         className="z-[9999]" | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |       > | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |         {popupContent && (<div | 
					
						
							|  |  |  |           className={cn( | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |             !noDecoration && 'system-xs-regular relative break-words rounded-md bg-components-panel-bg px-3 py-2 text-text-tertiary shadow-lg', | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  |             popupClassName, | 
					
						
							|  |  |  |           )} | 
					
						
							|  |  |  |           onMouseEnter={() => triggerMethod === 'hover' && setHoverPopup()} | 
					
						
							|  |  |  |           onMouseLeave={() => triggerMethod === 'hover' && handleLeave(false)} | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           {popupContent} | 
					
						
							|  |  |  |         </div>)} | 
					
						
							|  |  |  |       </PortalToFollowElemContent> | 
					
						
							|  |  |  |     </PortalToFollowElem> | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |   ) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-26 13:00:02 +08:00
										 |  |  | export default React.memo(Tooltip) |