refactor(web): remove legacy tooltip implementation

This commit is contained in:
yyh 2026-05-09 12:25:55 +08:00
parent 7d27f5d6c4
commit 56abf8230e
No known key found for this signature in database
7 changed files with 0 additions and 851 deletions

View File

@ -1,27 +0,0 @@
class TooltipManager {
private activeCloser: (() => void) | null = null
register(closeFn: () => void) {
if (this.activeCloser)
this.activeCloser()
this.activeCloser = closeFn
}
clear(closeFn: () => void) {
if (this.activeCloser === closeFn)
this.activeCloser = null
}
/**
* Closes the currently active tooltip by calling its closer function
* and clearing the reference to it
*/
closeActiveTooltip() {
if (this.activeCloser) {
this.activeCloser()
this.activeCloser = null
}
}
}
export const tooltipManager = new TooltipManager()

View File

@ -1,129 +0,0 @@
import { tooltipManager } from '../TooltipManager'
describe('TooltipManager', () => {
// Test the singleton instance directly
let manager: typeof tooltipManager
beforeEach(() => {
// Get fresh reference to the singleton
manager = tooltipManager
// Clean up any active tooltip by calling closeActiveTooltip
// This ensures each test starts with a clean state
manager.closeActiveTooltip()
})
describe('register', () => {
it('should register a close function', () => {
const closeFn = vi.fn()
manager.register(closeFn)
expect(closeFn).not.toHaveBeenCalled()
})
it('should call the existing close function when registering a new one', () => {
const firstCloseFn = vi.fn()
const secondCloseFn = vi.fn()
manager.register(firstCloseFn)
manager.register(secondCloseFn)
expect(firstCloseFn).toHaveBeenCalledTimes(1)
expect(secondCloseFn).not.toHaveBeenCalled()
})
it('should replace the active closer with the new one', () => {
const firstCloseFn = vi.fn()
const secondCloseFn = vi.fn()
// Register first function
manager.register(firstCloseFn)
// Register second function - this should call firstCloseFn and replace it
manager.register(secondCloseFn)
// Verify firstCloseFn was called during register (replacement behavior)
expect(firstCloseFn).toHaveBeenCalledTimes(1)
// Now close the active tooltip - this should call secondCloseFn
manager.closeActiveTooltip()
// Verify secondCloseFn was called, not firstCloseFn
expect(secondCloseFn).toHaveBeenCalledTimes(1)
})
})
describe('clear', () => {
it('should not clear if the close function does not match', () => {
const closeFn = vi.fn()
const otherCloseFn = vi.fn()
manager.register(closeFn)
manager.clear(otherCloseFn)
manager.closeActiveTooltip()
expect(closeFn).toHaveBeenCalledTimes(1)
})
it('should clear the close function if it matches', () => {
const closeFn = vi.fn()
manager.register(closeFn)
manager.clear(closeFn)
manager.closeActiveTooltip()
expect(closeFn).not.toHaveBeenCalled()
})
it('should not call the close function when clearing', () => {
const closeFn = vi.fn()
manager.register(closeFn)
manager.clear(closeFn)
expect(closeFn).not.toHaveBeenCalled()
})
})
describe('closeActiveTooltip', () => {
it('should do nothing when no active closer is registered', () => {
expect(() => manager.closeActiveTooltip()).not.toThrow()
})
it('should call the active closer function', () => {
const closeFn = vi.fn()
manager.register(closeFn)
manager.closeActiveTooltip()
expect(closeFn).toHaveBeenCalledTimes(1)
})
it('should clear the active closer after calling it', () => {
const closeFn = vi.fn()
manager.register(closeFn)
manager.closeActiveTooltip()
manager.closeActiveTooltip()
expect(closeFn).toHaveBeenCalledTimes(1)
})
it('should handle multiple register and close cycles', () => {
const closeFn1 = vi.fn()
const closeFn2 = vi.fn()
const closeFn3 = vi.fn()
manager.register(closeFn1)
manager.closeActiveTooltip()
manager.register(closeFn2)
manager.closeActiveTooltip()
manager.register(closeFn3)
manager.closeActiveTooltip()
expect(closeFn1).toHaveBeenCalledTimes(1)
expect(closeFn2).toHaveBeenCalledTimes(1)
expect(closeFn3).toHaveBeenCalledTimes(1)
})
})
})

View File

@ -1,49 +0,0 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import { ToolTipContent } from '../content'
describe('ToolTipContent', () => {
it('should render children correctly', () => {
render(
<ToolTipContent>
<span>Tooltip body text</span>
</ToolTipContent>,
)
expect(screen.getByTestId('tooltip-content')).toBeInTheDocument()
expect(screen.getByTestId('tooltip-content-body')).toHaveTextContent('Tooltip body text')
expect(screen.queryByTestId('tooltip-content-title')).not.toBeInTheDocument()
expect(screen.queryByTestId('tooltip-content-action')).not.toBeInTheDocument()
})
it('should render title when provided', () => {
render(
<ToolTipContent title="Tooltip Title">
<span>Tooltip body text</span>
</ToolTipContent>,
)
expect(screen.getByTestId('tooltip-content-title')).toHaveTextContent('Tooltip Title')
})
it('should render action when provided', () => {
render(
<ToolTipContent action={<span>Action Text</span>}>
<span>Tooltip body text</span>
</ToolTipContent>,
)
expect(screen.getByTestId('tooltip-content-action')).toHaveTextContent('Action Text')
})
it('should handle action click', async () => {
const user = userEvent.setup()
const handleActionClick = vi.fn()
render(
<ToolTipContent action={<span onClick={handleActionClick}>Action Text</span>}>
<span>Tooltip body text</span>
</ToolTipContent>,
)
await user.click(screen.getByText('Action Text'))
expect(handleActionClick).toHaveBeenCalledTimes(1)
})
})

View File

@ -1,333 +0,0 @@
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import Tooltip from '../index'
import { tooltipManager } from '../TooltipManager'
afterEach(() => {
cleanup()
vi.clearAllTimers()
vi.useRealTimers()
})
describe('Tooltip', () => {
describe('Rendering', () => {
it('should render default tooltip with question icon', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
expect(trigger).not.toBeNull()
expect(trigger?.querySelector('svg')).not.toBeNull() // question icon
})
it('should render with custom children', () => {
const { getByText } = render(
<Tooltip popupContent="Tooltip content">
<button>Hover me</button>
</Tooltip>,
)
expect(getByText('Hover me').textContent).toBe('Hover me')
})
it('should render correctly when asChild is false', () => {
const { container } = render(
<Tooltip popupContent="Tooltip" asChild={false} triggerClassName="custom-parent-trigger">
<span>Trigger</span>
</Tooltip>,
)
const trigger = container.querySelector('.custom-parent-trigger')
expect(trigger).not.toBeNull()
})
it('should render with a fallback question icon when children are null', () => {
const { container } = render(
<Tooltip popupContent="Tooltip" triggerClassName="custom-fallback-trigger">
{null}
</Tooltip>,
)
const trigger = container.querySelector('.custom-fallback-trigger')
expect(trigger).not.toBeNull()
expect(trigger?.querySelector('svg')).not.toBeNull()
})
})
describe('Disabled state', () => {
it('should not show tooltip when disabled', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" disabled triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
})
})
describe('Trigger methods', () => {
beforeEach(() => {
vi.useFakeTimers()
})
it('should open on hover when triggerMethod is hover', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
expect(screen.queryByText('Tooltip content')).toBeInTheDocument()
})
it('should close on mouse leave when triggerMethod is hover and needsDelay is false', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerClassName={triggerClassName} needsDelay={false} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
fireEvent.mouseLeave(trigger!)
})
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
})
it('should toggle on click when triggerMethod is click', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerMethod="click" triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.click(trigger!)
})
expect(screen.queryByText('Tooltip content')).toBeInTheDocument()
// Test toggle off
act(() => {
fireEvent.click(trigger!)
})
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
})
it('should do nothing on mouse enter if triggerMethod is click', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerMethod="click" triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
})
it('should delay closing on mouse leave when needsDelay is true', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerMethod="hover" needsDelay triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
expect(screen.getByText('Tooltip content')).toBeInTheDocument()
act(() => {
fireEvent.mouseLeave(trigger!)
})
// Shouldn't close immediately
expect(screen.getByText('Tooltip content')).toBeInTheDocument()
act(() => {
vi.advanceTimersByTime(350)
})
// Should close after delay
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
})
it('should not close if mouse enters popup before delay finishes', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerMethod="hover" needsDelay triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
const popup = screen.getByText('Tooltip content')
expect(popup).toBeInTheDocument()
act(() => {
fireEvent.mouseLeave(trigger!)
})
act(() => {
vi.advanceTimersByTime(150)
// Simulate mouse entering popup area itself during the delay timeframe
fireEvent.mouseEnter(popup)
})
act(() => {
vi.advanceTimersByTime(200) // Complete the 300ms original delay
})
// Should still be open because we are hovering the popup
expect(screen.getByText('Tooltip content')).toBeInTheDocument()
// Now mouse leaves popup
act(() => {
fireEvent.mouseLeave(popup)
})
act(() => {
vi.advanceTimersByTime(350)
})
// Should now close
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
})
it('should do nothing on mouse enter/leave of popup when triggerMethod is not hover', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerMethod="click" needsDelay triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.click(trigger!)
})
const popup = screen.getByText('Tooltip content')
act(() => {
fireEvent.mouseEnter(popup)
fireEvent.mouseLeave(popup)
vi.advanceTimersByTime(350)
})
// Should still be open because click method requires another click to close, not hover leave
expect(screen.getByText('Tooltip content')).toBeInTheDocument()
})
it('should clear close timeout if trigger is hovered again before delay finishes', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerMethod="hover" needsDelay triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
expect(screen.getByText('Tooltip content')).toBeInTheDocument()
act(() => {
fireEvent.mouseLeave(trigger!)
})
act(() => {
vi.advanceTimersByTime(150)
// Re-hover trigger before it closes
fireEvent.mouseEnter(trigger!)
})
act(() => {
vi.advanceTimersByTime(200) // Original 300ms would be up
})
// Should still be open because we reset it
expect(screen.getByText('Tooltip content')).toBeInTheDocument()
})
it('should test clear close timeout if trigger is hovered again before delay finishes and isHoverPopupRef is true', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerMethod="hover" needsDelay triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
const popup = screen.getByText('Tooltip content')
expect(popup).toBeInTheDocument()
act(() => {
fireEvent.mouseEnter(popup)
fireEvent.mouseLeave(trigger!)
})
act(() => {
vi.advanceTimersByTime(350)
})
// Should still be open because we are hovering the popup
expect(screen.getByText('Tooltip content')).toBeInTheDocument()
})
})
describe('TooltipManager', () => {
it('should close active tooltips when triggered centrally, overriding other closes', () => {
const triggerClassName1 = 'custom-trigger-1'
const triggerClassName2 = 'custom-trigger-2'
const { container } = render(
<div>
<Tooltip popupContent="Tooltip content 1" triggerMethod="hover" triggerClassName={triggerClassName1} />
<Tooltip popupContent="Tooltip content 2" triggerMethod="hover" triggerClassName={triggerClassName2} />
</div>,
)
const trigger1 = container.querySelector(`.${triggerClassName1}`)
const trigger2 = container.querySelector(`.${triggerClassName2}`)
expect(trigger2).not.toBeNull()
// Open first tooltip
act(() => {
fireEvent.mouseEnter(trigger1!)
})
expect(screen.queryByText('Tooltip content 1')).toBeInTheDocument()
// TooltipManager should keep track of it
// Next, immediately open the second one without leaving first (e.g., via TooltipManager)
// TooltipManager registers the newest one and closes the old one when doing full external operations, but internally the manager allows direct closing
act(() => {
tooltipManager.closeActiveTooltip()
})
expect(screen.queryByText('Tooltip content 1')).not.toBeInTheDocument()
// Safe to call again
expect(() => tooltipManager.closeActiveTooltip()).not.toThrow()
})
})
describe('Styling and positioning', () => {
it('should apply custom trigger className', () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerClassName={triggerClassName} />)
const trigger = container.querySelector(`.${triggerClassName}`)
expect(trigger?.className).toContain('custom-trigger')
})
it('should pass triggerTestId to the fallback icon wrapper', () => {
render(<Tooltip popupContent="Tooltip content" triggerTestId="test-tooltip-icon" />)
expect(screen.getByTestId('test-tooltip-icon')).toBeInTheDocument()
})
it('should apply custom popup className', async () => {
const triggerClassName = 'custom-trigger'
const { container } = render(<Tooltip popupContent="Tooltip content" triggerClassName={triggerClassName} popupClassName="custom-popup" />)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
expect((await screen.findByText('Tooltip content'))?.className).toContain('custom-popup')
})
it('should apply noDecoration when specified', async () => {
const triggerClassName = 'custom-trigger'
const { container } = render(
<Tooltip
popupContent="Tooltip content"
triggerClassName={triggerClassName}
noDecoration
/>,
)
const trigger = container.querySelector(`.${triggerClassName}`)
act(() => {
fireEvent.mouseEnter(trigger!)
})
expect((await screen.findByText('Tooltip content'))?.className).not.toContain('bg-components-panel-bg')
})
})
})

View File

@ -1,22 +0,0 @@
import type { FC, PropsWithChildren, ReactNode } from 'react'
type ToolTipContentProps = {
title?: ReactNode
action?: ReactNode
} & PropsWithChildren
export const ToolTipContent: FC<ToolTipContentProps> = ({
title,
action,
children,
}) => {
return (
<div className="w-[180px]" data-testid="tooltip-content">
{!!title && (
<div className="mb-1.5 font-semibold text-text-secondary" data-testid="tooltip-content-title">{title}</div>
)}
<div className="mb-1.5 text-text-tertiary" data-testid="tooltip-content-body">{children}</div>
{!!action && <div className="cursor-pointer text-text-accent" data-testid="tooltip-content-action">{action}</div>}
</div>
)
}

View File

@ -1,60 +0,0 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import Tooltip from '.'
const TooltipGrid = () => {
return (
<div className="flex w-full max-w-xl flex-col gap-6 rounded-2xl border border-divider-subtle bg-components-panel-bg p-6">
<div className="text-xs tracking-[0.18em] text-text-tertiary uppercase">Hover tooltips</div>
<div className="flex flex-wrap gap-4">
<Tooltip popupContent="Helpful hint explaining the setting.">
<button
type="button"
className="rounded-md border border-divider-subtle bg-background-default px-3 py-1 text-xs font-medium text-text-secondary hover:bg-state-base-hover"
>
Hover me
</button>
</Tooltip>
<Tooltip popupContent="Placement can vary." position="right">
<span className="rounded-md bg-background-default px-3 py-1 text-xs text-text-secondary">
Right tooltip
</span>
</Tooltip>
</div>
<div className="text-xs tracking-[0.18em] text-text-tertiary uppercase">Click tooltips</div>
<div className="flex flex-wrap gap-4">
<Tooltip popupContent="Click again to close." triggerMethod="click" position="bottom-start">
<button
type="button"
className="rounded-md border border-divider-subtle bg-background-default px-3 py-1 text-xs font-medium text-text-secondary hover:bg-state-base-hover"
>
Click trigger
</button>
</Tooltip>
<Tooltip popupContent="Decoration disabled" triggerMethod="click" noDecoration>
<span className="rounded-md border border-dashed border-divider-regular px-3 py-1 text-xs text-text-secondary">
Plain content
</span>
</Tooltip>
</div>
</div>
)
}
const meta = {
title: 'Base/Feedback/Tooltip',
component: TooltipGrid,
parameters: {
layout: 'centered',
docs: {
description: {
component: 'Portal-based tooltip component supporting hover and click triggers, custom placements, and decorated content.',
},
},
},
tags: ['autodocs'],
} satisfies Meta<typeof TooltipGrid>
export default meta
type Story = StoryObj<typeof meta>
export const Playground: Story = {}

View File

@ -1,231 +0,0 @@
'use client'
import type { Placement } from '@langgenius/dify-ui/popover'
/**
* @deprecated Use `@langgenius/dify-ui/tooltip` instead.
* This component will be removed after migration is complete.
* See: https://github.com/langgenius/dify/issues/32767
*/
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@langgenius/dify-ui/popover'
import { RiQuestionLine } from '@remixicon/react'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { tooltipManager } from './TooltipManager'
type TooltipOffset = number | {
mainAxis?: number
crossAxis?: number
}
type TooltipProps = {
position?: Placement
triggerMethod?: 'hover' | 'click'
triggerClassName?: string
triggerTestId?: string
disabled?: boolean
popupContent?: React.ReactNode
children?: React.ReactNode
popupClassName?: string
portalContentClassName?: string
noDecoration?: boolean
offset?: TooltipOffset
needsDelay?: boolean
asChild?: boolean
}
const Tooltip: FC<TooltipProps> = ({
position = 'top',
triggerMethod = 'hover',
triggerClassName,
triggerTestId,
disabled = false,
popupContent,
children,
popupClassName,
portalContentClassName,
noDecoration,
offset,
asChild = true,
needsDelay = true,
}) => {
const [open, setOpen] = useState(false)
const resolvedOffset = offset ?? 8
const sideOffset = typeof resolvedOffset === 'number' ? resolvedOffset : (resolvedOffset.mainAxis ?? 0)
const alignOffset = typeof resolvedOffset === 'number' ? 0 : (resolvedOffset.crossAxis ?? 0)
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 closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const clearCloseTimeout = useCallback(() => {
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current)
closeTimeoutRef.current = null
}
}, [])
useEffect(() => {
return () => {
clearCloseTimeout()
}
}, [clearCloseTimeout])
const close = () => setOpen(false)
const handleOpenChange = (nextOpen: boolean) => {
if (disabled) {
setOpen(false)
return
}
if (triggerMethod === 'click')
setOpen(nextOpen)
else if (!nextOpen)
setOpen(false)
}
const handleLeave = (isTrigger: boolean) => {
if (isTrigger)
setNotHoverTrigger()
else
setNotHoverPopup()
// give time to move to the popup
if (needsDelay) {
clearCloseTimeout()
closeTimeoutRef.current = setTimeout(() => {
closeTimeoutRef.current = null
if (!isHoverPopupRef.current && !isHoverTriggerRef.current) {
setOpen(false)
tooltipManager.clear(close)
}
}, 300)
}
else {
clearCloseTimeout()
setOpen(false)
tooltipManager.clear(close)
}
}
const handleTriggerMouseEnter = () => {
if (triggerMethod === 'hover') {
clearCloseTimeout()
setHoverTrigger()
tooltipManager.register(close)
setOpen(true)
}
}
const handleTriggerMouseLeave = () => {
if (triggerMethod === 'hover')
handleLeave(true)
}
const handlePopupMouseEnter = () => {
if (triggerMethod === 'hover') {
clearCloseTimeout()
setHoverPopup()
}
}
const handlePopupMouseLeave = () => {
if (triggerMethod === 'hover')
handleLeave(false)
}
const fallbackTrigger = (
<div data-testid={triggerTestId} className={triggerClassName || 'h-3.5 w-3.5 shrink-0 p-px'}>
<RiQuestionLine className="h-full w-full text-text-quaternary hover:text-text-tertiary" />
</div>
)
const triggerContent = children || fallbackTrigger
const childElement = React.isValidElement<React.HTMLAttributes<HTMLElement>>(triggerContent)
? triggerContent
: fallbackTrigger
const nativeButton = typeof childElement.type !== 'string' || childElement.type === 'button'
const renderAsChildTrigger = () => {
const childProps = childElement.props
return React.cloneElement(childElement, {
onMouseEnter: (event: React.MouseEvent<HTMLElement>) => {
childProps.onMouseEnter?.(event)
handleTriggerMouseEnter()
},
onMouseLeave: (event: React.MouseEvent<HTMLElement>) => {
childProps.onMouseLeave?.(event)
handleTriggerMouseLeave()
},
})
}
const effectiveOpen = !disabled && open
return (
<Popover
open={effectiveOpen}
onOpenChange={handleOpenChange}
>
{asChild
? (
<PopoverTrigger
nativeButton={nativeButton}
disabled={disabled}
render={renderAsChildTrigger()}
/>
)
: (
<PopoverTrigger
nativeButton={false}
disabled={disabled}
render={(
<div
className={triggerClassName}
onMouseEnter={handleTriggerMouseEnter}
onMouseLeave={handleTriggerMouseLeave}
/>
)}
>
{triggerContent}
</PopoverTrigger>
)}
{effectiveOpen && !!popupContent && (
<PopoverContent
placement={position}
sideOffset={sideOffset}
alignOffset={alignOffset}
className={portalContentClassName}
popupClassName={cn(
noDecoration
? 'border-0 bg-transparent p-0 shadow-none'
: 'relative max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg',
popupClassName,
)}
popupProps={{
onMouseEnter: handlePopupMouseEnter,
onMouseLeave: handlePopupMouseLeave,
}}
>
{popupContent}
</PopoverContent>
)}
</Popover>
)
}
export default React.memo(Tooltip)