mirror of
https://github.com/langgenius/dify.git
synced 2026-04-19 18:27:27 +08:00
refactor(ui): compose tooltip primitives and dedupe menu popup
This commit is contained in:
@ -7,6 +7,7 @@ import { parsePlacement } from '@/app/components/base/ui/placement'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export const DropdownMenu = Menu.Root
|
||||
export const DropdownMenuPortal = Menu.Portal
|
||||
export const DropdownMenuTrigger = Menu.Trigger
|
||||
export const DropdownMenuSub = Menu.SubmenuRoot
|
||||
export const DropdownMenuGroup = Menu.Group
|
||||
@ -26,14 +27,22 @@ type DropdownMenuContentProps = {
|
||||
popupClassName?: string
|
||||
}
|
||||
|
||||
export function DropdownMenuContent({
|
||||
type DropdownMenuPopupProps = Required<Pick<DropdownMenuContentProps, 'children'>> & {
|
||||
placement: Placement
|
||||
sideOffset: number
|
||||
alignOffset: number
|
||||
className?: string
|
||||
popupClassName?: string
|
||||
}
|
||||
|
||||
function DropdownMenuPopup({
|
||||
children,
|
||||
placement = 'bottom-end',
|
||||
sideOffset = 4,
|
||||
alignOffset = 0,
|
||||
placement,
|
||||
sideOffset,
|
||||
alignOffset,
|
||||
className,
|
||||
popupClassName,
|
||||
}: DropdownMenuContentProps) {
|
||||
}: DropdownMenuPopupProps) {
|
||||
const { side, align } = parsePlacement(placement)
|
||||
|
||||
return (
|
||||
@ -43,7 +52,7 @@ export function DropdownMenuContent({
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('outline-none', className)}
|
||||
className={cn('isolate outline-none', className)}
|
||||
>
|
||||
<Menu.Popup
|
||||
className={cn(
|
||||
@ -59,6 +68,27 @@ export function DropdownMenuContent({
|
||||
)
|
||||
}
|
||||
|
||||
export function DropdownMenuContent({
|
||||
children,
|
||||
placement = 'bottom-end',
|
||||
sideOffset = 4,
|
||||
alignOffset = 0,
|
||||
className,
|
||||
popupClassName,
|
||||
}: DropdownMenuContentProps) {
|
||||
return (
|
||||
<DropdownMenuPopup
|
||||
placement={placement}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={className}
|
||||
popupClassName={popupClassName}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuPopup>
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuSubTriggerProps = React.ComponentPropsWithoutRef<typeof Menu.SubmenuTrigger> & {
|
||||
destructive?: boolean
|
||||
}
|
||||
@ -98,28 +128,16 @@ export function DropdownMenuSubContent({
|
||||
className,
|
||||
popupClassName,
|
||||
}: DropdownMenuSubContentProps) {
|
||||
const { side, align } = parsePlacement(placement)
|
||||
|
||||
return (
|
||||
<Menu.Portal>
|
||||
<Menu.Positioner
|
||||
side={side}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('outline-none', className)}
|
||||
>
|
||||
<Menu.Popup
|
||||
className={cn(
|
||||
'rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg py-1 text-sm text-text-secondary shadow-lg',
|
||||
'origin-[var(--transform-origin)] transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0',
|
||||
popupClassName,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Menu.Popup>
|
||||
</Menu.Positioner>
|
||||
</Menu.Portal>
|
||||
<DropdownMenuPopup
|
||||
placement={placement}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={className}
|
||||
popupClassName={popupClassName}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuPopup>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -6,61 +6,53 @@ import * as React from 'react'
|
||||
import { parsePlacement } from '@/app/components/base/ui/placement'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export type TooltipProps = {
|
||||
position?: Placement
|
||||
disabled?: boolean
|
||||
popupContent?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
type TooltipContentVariant = 'default' | 'plain'
|
||||
|
||||
export type TooltipContentProps = {
|
||||
children: React.ReactNode
|
||||
placement?: Placement
|
||||
sideOffset?: number
|
||||
alignOffset?: number
|
||||
className?: string
|
||||
popupClassName?: string
|
||||
noDecoration?: boolean
|
||||
offset?: number
|
||||
delay?: number
|
||||
closeDelay?: number
|
||||
}
|
||||
variant?: TooltipContentVariant
|
||||
} & Omit<React.ComponentPropsWithoutRef<typeof BaseTooltip.Popup>, 'children'>
|
||||
|
||||
const Tooltip = React.memo(({
|
||||
position = 'top',
|
||||
disabled = false,
|
||||
popupContent,
|
||||
export function TooltipContent({
|
||||
children,
|
||||
placement = 'top',
|
||||
sideOffset = 8,
|
||||
alignOffset = 0,
|
||||
className,
|
||||
popupClassName,
|
||||
noDecoration,
|
||||
offset = 8,
|
||||
delay,
|
||||
closeDelay,
|
||||
}: TooltipProps) => {
|
||||
const { side, align } = parsePlacement(position)
|
||||
|
||||
if (!popupContent || disabled)
|
||||
return <>{children}</>
|
||||
variant = 'default',
|
||||
...props
|
||||
}: TooltipContentProps) {
|
||||
const { side, align } = parsePlacement(placement)
|
||||
|
||||
return (
|
||||
<BaseTooltip.Root>
|
||||
{React.isValidElement(children)
|
||||
? <BaseTooltip.Trigger delay={delay} closeDelay={closeDelay} render={children} />
|
||||
: <BaseTooltip.Trigger delay={delay} closeDelay={closeDelay} render={<span className="inline-flex" />}>{children}</BaseTooltip.Trigger>}
|
||||
<BaseTooltip.Portal>
|
||||
<BaseTooltip.Positioner
|
||||
side={side}
|
||||
align={align}
|
||||
sideOffset={offset}
|
||||
className="outline-none"
|
||||
<BaseTooltip.Portal>
|
||||
<BaseTooltip.Positioner
|
||||
side={side}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('isolate outline-none', className)}
|
||||
>
|
||||
<BaseTooltip.Popup
|
||||
className={cn(
|
||||
variant === 'default' && 'max-w-[300px] break-words rounded-md bg-components-panel-bg px-3 py-2 text-left text-text-tertiary shadow-lg system-xs-regular',
|
||||
popupClassName,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<BaseTooltip.Popup
|
||||
className={cn(
|
||||
!noDecoration && 'max-w-[300px] break-words rounded-md bg-components-panel-bg px-3 py-2 text-left text-text-tertiary shadow-lg system-xs-regular',
|
||||
popupClassName,
|
||||
)}
|
||||
>
|
||||
{popupContent}
|
||||
</BaseTooltip.Popup>
|
||||
</BaseTooltip.Positioner>
|
||||
</BaseTooltip.Portal>
|
||||
</BaseTooltip.Root>
|
||||
{children}
|
||||
</BaseTooltip.Popup>
|
||||
</BaseTooltip.Positioner>
|
||||
</BaseTooltip.Portal>
|
||||
)
|
||||
})
|
||||
|
||||
Tooltip.displayName = 'Tooltip'
|
||||
}
|
||||
|
||||
export const TooltipProvider = BaseTooltip.Provider
|
||||
export default Tooltip
|
||||
export const Tooltip = BaseTooltip.Root
|
||||
export const TooltipTrigger = BaseTooltip.Trigger
|
||||
|
||||
@ -3,7 +3,7 @@ import { useMutation } from '@tanstack/react-query'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu'
|
||||
import Tooltip from '@/app/components/base/ui/tooltip'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
@ -83,14 +83,27 @@ function ComplianceDocActionVisual({
|
||||
)
|
||||
}
|
||||
|
||||
const canShowUpgradeTooltip = tooltipText.length > 0
|
||||
|
||||
return (
|
||||
<Tooltip popupContent={tooltipText} delay={0}>
|
||||
<PremiumBadge color="blue" allowHover={true}>
|
||||
<SparklesSoft className="flex h-3.5 w-3.5 items-center py-[1px] pl-[3px] text-components-premium-badge-indigo-text-stop-0" />
|
||||
<div className="px-1 system-xs-medium">
|
||||
{upgradeText}
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
delay={0}
|
||||
disabled={!canShowUpgradeTooltip}
|
||||
render={(
|
||||
<PremiumBadge color="blue" allowHover={true}>
|
||||
<SparklesSoft className="flex h-3.5 w-3.5 items-center py-[1px] pl-[3px] text-components-premium-badge-indigo-text-stop-0" />
|
||||
<div className="px-1 system-xs-medium">
|
||||
{upgradeText}
|
||||
</div>
|
||||
</PremiumBadge>
|
||||
)}
|
||||
/>
|
||||
{canShowUpgradeTooltip && (
|
||||
<TooltipContent>
|
||||
{tooltipText}
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user