dify/web/app/components/workflow/nodes/_base/node-sections.tsx
yyh 49b9b6baef
chore(web): migrate infotip-style tooltips to popover
Switch 13 infotip / static-trigger usages of @langgenius/dify-ui/tooltip
over to @langgenius/dify-ui/popover with openOnHover, so the descriptive
content is reachable on touch and via screen readers. Visual presentation
is preserved by setting popupClassName to match the original tooltip
styling.

Triggers that already own a primary click action (model selector rows,
variable chips, online-user avatars, etc.) are intentionally left on
Tooltip pending a separate selection-with-preview redesign, since
swapping in Popover would have click both fire that action and toggle
the popup.

Made-with: Cursor
2026-04-19 16:07:06 +08:00

103 lines
3.7 KiB
TypeScript

import type { TFunction } from 'i18next'
import type { ReactElement } from 'react'
import type { IterationNodeType } from '@/app/components/workflow/nodes/iteration/types'
import type { NodeProps } from '@/app/components/workflow/types'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types'
type HeaderMetaProps = {
data: NodeProps['data']
hasVarValue: boolean
isLoading: boolean
loopIndex: ReactElement | null
t: TFunction
}
export const NodeHeaderMeta = ({
data,
hasVarValue,
isLoading,
loopIndex,
t,
}: HeaderMetaProps) => {
return (
<>
{data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && (
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('nodes.iteration.parallelModeEnableTitle', { ns: 'workflow' })}
render={(
<div className="ml-1 flex items-center justify-center rounded-[5px] border border-text-warning px-[5px] py-[3px] system-2xs-medium-uppercase text-text-warning">
{t('nodes.iteration.parallelModeUpper', { ns: 'workflow' })}
</div>
)}
/>
<PopoverContent
placement="top"
popupClassName="w-[180px] 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"
>
<div className="font-extrabold">
{t('nodes.iteration.parallelModeEnableTitle', { ns: 'workflow' })}
</div>
{t('nodes.iteration.parallelModeEnableDesc', { ns: 'workflow' })}
</PopoverContent>
</Popover>
)}
{!!(data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running) && (
<div className="mr-1.5 text-xs font-medium text-text-accent">
{data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}
/
{data._iterationLength}
</div>
)}
{!!(data.type === BlockEnum.Loop && data._loopIndex) && loopIndex}
{isLoading && <span className="i-ri-loader-2-line h-3.5 w-3.5 animate-spin text-text-accent" />}
{!isLoading && data._runningStatus === NodeRunningStatus.Failed && (
<span className="i-ri-error-warning-fill h-3.5 w-3.5 text-text-destructive" />
)}
{!isLoading && data._runningStatus === NodeRunningStatus.Exception && (
<span className="i-ri-alert-fill h-3.5 w-3.5 text-text-warning-secondary" />
)}
{!isLoading && (data._runningStatus === NodeRunningStatus.Succeeded || (!data._runningStatus && hasVarValue)) && (
<span className="i-ri-checkbox-circle-fill h-3.5 w-3.5 text-text-success" />
)}
{!isLoading && data._runningStatus === NodeRunningStatus.Paused && (
<span className="i-ri-pause-circle-fill h-3.5 w-3.5 text-text-warning-secondary" />
)}
</>
)
}
type NodeBodyProps = {
data: NodeProps['data']
child: ReactElement
}
export const NodeBody = ({
data,
child,
}: NodeBodyProps) => {
if (data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop) {
return (
<div className="grow pr-1 pb-1 pl-1">
{child}
</div>
)
}
return child
}
export const NodeDescription = ({ data }: { data: NodeProps['data'] }) => {
if (!data.desc || data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop)
return null
return (
<div className="px-3 pt-1 pb-2 system-xs-regular wrap-break-word whitespace-pre-line text-text-tertiary">
{data.desc}
</div>
)
}