fix: use infotip for help glyphs (#36008)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yyh 2026-05-11 12:23:04 +08:00 committed by GitHub
parent 2162ea6a68
commit bd0d10ac5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 193 additions and 267 deletions

View File

@ -2527,11 +2527,6 @@
"count": 1
}
},
"web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx": {
"ts/no-explicit-any": {
"count": 1
}
},
"web/app/components/plugins/plugin-detail-panel/strategy-detail.tsx": {
"ts/no-explicit-any": {
"count": 2

View File

@ -5,11 +5,6 @@ import type { PromptRole, PromptVariable } from '@/models/debug'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@langgenius/dify-ui/tooltip'
import {
RiDeleteBinLine,
RiErrorWarningFill,
@ -25,6 +20,7 @@ import {
Copy,
CopyCheck,
} from '@/app/components/base/icons/src/vender/line/files'
import { Infotip } from '@/app/components/base/infotip'
import PromptEditor from '@/app/components/base/prompt-editor'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
import ConfigContext from '@/context/debug-configuration'
@ -183,18 +179,13 @@ const AdvancedPromptInput: FC<Props> = ({
<div className="text-sm font-semibold text-indigo-800 uppercase">
{t('pageTitle.line1', { ns: 'appDebug' })}
</div>
<Tooltip>
<TooltipTrigger
render={(
<span className="ml-1 i-ri-question-line h-4 w-4 shrink-0 text-text-quaternary" />
)}
/>
<TooltipContent>
<div className="w-[180px]">
{t('promptTip', { ns: 'appDebug' })}
</div>
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('promptTip', { ns: 'appDebug' })}
className="ml-1"
popupClassName="w-[180px]"
>
{t('promptTip', { ns: 'appDebug' })}
</Infotip>
</div>
)}
<div className={cn(s.optionWrap, 'items-center space-x-1')}>

View File

@ -5,11 +5,6 @@ import type { PromptVariable } from '@/models/debug'
import type { GenRes } from '@/service/debug'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@langgenius/dify-ui/tooltip'
import { useBoolean } from 'ahooks'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
@ -21,6 +16,7 @@ import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/confi
import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn'
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { Infotip } from '@/app/components/base/infotip'
import PromptEditor from '@/app/components/base/prompt-editor'
import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '@/app/components/base/prompt-editor/plugins/update-block'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
@ -183,18 +179,13 @@ const Prompt: FC<ISimplePromptInput> = ({
<div className="flex items-center space-x-1">
<div className="system-sm-semibold-uppercase text-text-secondary">{mode !== AppModeEnum.COMPLETION ? t('chatSubTitle', { ns: 'appDebug' }) : t('completionSubTitle', { ns: 'appDebug' })}</div>
{!readonly && (
<Tooltip>
<TooltipTrigger
render={(
<span className="ml-1 i-ri-question-line h-4 w-4 shrink-0 text-text-quaternary" />
)}
/>
<TooltipContent>
<div className="w-[180px]">
{t('promptTip', { ns: 'appDebug' })}
</div>
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('promptTip', { ns: 'appDebug' })}
className="ml-1"
popupClassName="w-[180px]"
>
{t('promptTip', { ns: 'appDebug' })}
</Infotip>
)}
</div>
<div className="flex items-center">

View File

@ -369,7 +369,7 @@ describe('OpeningSettingModal', () => {
expect(screen.getByTestId('opener-input-section')).toBeInTheDocument()
expect(screen.getByTestId('opener-questions-section')).toBeInTheDocument()
expect(screen.getByText(/openingStatement\.editorTitle/)).toBeInTheDocument()
expect(screen.getByTestId('opening-questions-tooltip')).toBeInTheDocument()
expect(screen.getByRole('button', { name: /openingStatement\.openingQuestionDescription/ })).toBeInTheDocument()
expect(screen.queryByText(/openingStatement\.openingQuestionDescription/)).not.toBeInTheDocument()
})
@ -383,7 +383,7 @@ describe('OpeningSettingModal', () => {
)
act(() => {
fireEvent.mouseEnter(screen.getByTestId('opening-questions-tooltip'))
fireEvent.mouseEnter(screen.getByRole('button', { name: /openingStatement\.openingQuestionDescription/ }))
})
expect(screen.getByText(/openingStatement\.openingQuestionDescription/)).toBeInTheDocument()

View File

@ -4,7 +4,6 @@ import type { PromptVariable } from '@/models/debug'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useBoolean } from 'ahooks'
import { produce } from 'immer'
import * as React from 'react'
@ -14,6 +13,7 @@ import { ReactSortable } from 'react-sortablejs'
import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var'
import { getInputKeys } from '@/app/components/base/block-input'
import Divider from '@/app/components/base/divider'
import { Infotip } from '@/app/components/base/infotip'
import PromptEditor from '@/app/components/base/prompt-editor'
import { checkKeys, getNewVar } from '@/utils/var'
@ -117,24 +117,14 @@ const OpeningSettingModal = ({
<div className="text-sm font-medium text-text-primary">
{t('openingStatement.openingQuestion', { ns: 'appDebug' })}
</div>
<Tooltip>
<TooltipTrigger
delay={0}
render={(
<button
type="button"
className="flex items-center rounded-sm p-px text-text-quaternary hover:text-text-tertiary"
data-testid="opening-questions-tooltip"
aria-label={t('openingStatement.openingQuestionDescription', { ns: 'appDebug' })}
>
<span className="i-ri-question-line h-3.5 w-3.5" />
</button>
)}
/>
<TooltipContent className="max-w-[220px] system-sm-regular text-text-secondary">
{t('openingStatement.openingQuestionDescription', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('openingStatement.openingQuestionDescription', { ns: 'appDebug' })}
className="h-3.5 w-3.5"
popupClassName="max-w-[220px] system-sm-regular text-text-secondary"
delay={0}
>
{t('openingStatement.openingQuestionDescription', { ns: 'appDebug' })}
</Infotip>
</div>
<div className="text-xs leading-[18px] font-medium text-text-tertiary">
{tempSuggestedQuestions.length}

View File

@ -1,9 +1,6 @@
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiQuestionLine,
} from '@remixicon/react'
import * as React from 'react'
import { Infotip } from '@/app/components/base/infotip'
type Props = {
icon: any
@ -41,16 +38,12 @@ const FeatureCard = ({
<div className="flex grow items-center system-sm-semibold text-text-secondary">
{title}
{tooltip && (
<Tooltip>
<TooltipTrigger
render={(
<div className="ml-0.5 p-px"><RiQuestionLine className="h-3.5 w-3.5 text-text-quaternary" /></div>
)}
/>
<TooltipContent>
{tooltip}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={typeof tooltip === 'string' ? tooltip : String(title)}
className="ml-0.5 h-3.5 w-3.5"
>
{tooltip}
</Infotip>
)}
</div>
<Switch disabled={disabled} className="shrink-0" onCheckedChange={state => onChange?.(state)} checked={value} />

View File

@ -1,7 +1,7 @@
'use client'
import type { Placement } from '@langgenius/dify-ui/popover'
import type { ReactNode } from 'react'
import type { MouseEvent, ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
@ -58,6 +58,10 @@ export function Infotip({
delay = 300,
closeDelay = 200,
}: InfotipProps) {
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
event.stopPropagation()
}
return (
<Popover>
<PopoverTrigger
@ -65,6 +69,7 @@ export function Infotip({
delay={delay}
closeDelay={closeDelay}
aria-label={ariaLabel}
onClick={handleClick}
className={cn(
'inline-flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center border-0 bg-transparent p-0 focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:outline-hidden',
className,

View File

@ -3,7 +3,6 @@
import type { FC } from 'react'
import type { PreProcessingRule, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
import { Button } from '@langgenius/dify-ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiAlertFill,
RiSearchEyeLine,
@ -11,6 +10,7 @@ import {
import { useTranslation } from 'react-i18next'
import Checkbox from '@/app/components/base/checkbox'
import Divider from '@/app/components/base/divider'
import { Infotip } from '@/app/components/base/infotip'
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
import { IS_CE_EDITION } from '@/config'
import { ChunkingMode } from '@/models/datasets'
@ -191,18 +191,13 @@ export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
onSelect={onDocLanguageChange}
disabled={currentDocForm !== ChunkingMode.qa}
/>
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('stepTwo.QATip', { ns: 'datasetCreation' })}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('stepTwo.QATip', { ns: 'datasetCreation' })}
className="h-3.5 w-3.5"
iconClassName="h-full w-full"
>
{t('stepTwo.QATip', { ns: 'datasetCreation' })}
</Infotip>
</div>
{currentDocForm === ChunkingMode.qa && (
<div

View File

@ -14,7 +14,9 @@ describe('IndexMethod', () => {
vi.clearAllMocks()
})
const getKeywordSlider = () => screen.getByLabelText('datasetSettings.form.numberOfKeywords')
const getKeywordSlider = () => screen.getByLabelText('datasetSettings.form.numberOfKeywords', {
selector: 'input[type="range"]',
})
describe('Rendering', () => {
it('should render without crashing', () => {

View File

@ -11,7 +11,9 @@ describe('KeyWordNumber', () => {
vi.clearAllMocks()
})
const getSlider = () => screen.getByLabelText('datasetSettings.form.numberOfKeywords')
const getSlider = () => screen.getByLabelText('datasetSettings.form.numberOfKeywords', {
selector: 'input[type="range"]',
})
describe('Rendering', () => {
it('should render without crashing', () => {
@ -24,9 +26,10 @@ describe('KeyWordNumber', () => {
expect(screen.getByText(/form\.numberOfKeywords/)).toBeInTheDocument()
})
it('should render tooltip with question icon', () => {
it('should render infotip with question icon', () => {
render(<KeyWordNumber {...defaultProps} />)
const container = screen.getByText(/form\.numberOfKeywords/).closest('div')?.parentElement
const trigger = screen.getByRole('button', { name: 'datasetSettings.form.numberOfKeywords' })
const container = trigger.parentElement
const questionIcon = container?.querySelector('.i-ri-question-line')
expect(questionIcon).toBeInTheDocument()
})

View File

@ -7,10 +7,10 @@ import {
NumberFieldInput,
} from '@langgenius/dify-ui/number-field'
import { Slider } from '@langgenius/dify-ui/slider'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
const MIN_KEYWORD_NUMBER = 0
const MAX_KEYWORD_NUMBER = 50
@ -36,16 +36,12 @@ const KeyWordNumber = ({
<div className="truncate system-xs-medium text-text-secondary">
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</div>
<Tooltip>
<TooltipTrigger
render={(
<span className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary" />
)}
/>
<TooltipContent>
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('form.numberOfKeywords', { ns: 'datasetSettings' })}
className="h-3.5 w-3.5"
>
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</Infotip>
</div>
<Slider
className="mr-3 w-[206px] shrink-0"

View File

@ -15,6 +15,7 @@ import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge/index'
import GridMask from '@/app/components/base/grid-mask'
import { Infotip } from '@/app/components/base/infotip'
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
import s from '@/app/components/custom/style.module.css'
import { AddCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
@ -152,18 +153,14 @@ const ModelLoadBalancingConfigs = ({
<div className="grow">
<div className="flex items-center gap-1 text-sm text-text-primary">
{t('modelProvider.loadBalancing', { ns: 'common' })}
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3 w-3 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent className="max-w-[300px]">
{t('modelProvider.loadBalancingInfo', { ns: 'common' })}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('modelProvider.loadBalancingInfo', { ns: 'common' })}
className="h-3 w-3"
iconClassName="h-full w-full"
popupClassName="max-w-[300px]"
>
{t('modelProvider.loadBalancingInfo', { ns: 'common' })}
</Infotip>
</div>
<div className="text-xs text-text-tertiary">{t('modelProvider.loadBalancingDescription', { ns: 'common' })}</div>
</div>

View File

@ -2,16 +2,15 @@ import type { Node } from 'reactflow'
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
import type { NodeOutPutVar } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiAddLine,
RiQuestionLine,
} from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Divider from '@/app/components/base/divider'
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
import { Infotip } from '@/app/components/base/infotip'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
import { useAllMCPTools } from '@/service/use-tools'
@ -21,7 +20,7 @@ type Props = {
value: ToolValue[]
label: string
required?: boolean
tooltip?: any
tooltip?: React.ReactNode
supportCollapse?: boolean
scope?: string
onChange: (value: ToolValue[]) => void
@ -111,18 +110,16 @@ const MultipleToolSelector = ({
>
<div className="flex h-6 items-center system-sm-semibold-uppercase text-text-secondary">{label}</div>
{required && <div className="text-red-500">*</div>}
{tooltip && (
<Tooltip>
<TooltipTrigger
render={(
<div><RiQuestionLine className="h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" /></div>
)}
/>
<TooltipContent>
{tooltip}
</TooltipContent>
</Tooltip>
)}
{tooltip
? (
<Infotip
aria-label={typeof tooltip === 'string' ? tooltip : label}
className="h-3.5 w-3.5"
>
{tooltip}
</Infotip>
)
: null}
{supportCollapse && (
<ArrowDownRoundFill
className={cn(

View File

@ -257,6 +257,10 @@ const createDefaultProps = (overrides: Partial<Parameters<typeof CreateSubscript
...overrides,
})
const getCreateButton = () => screen.getByRole('button', {
name: /pluginTrigger\.subscription\.(createButton|empty\.button)/,
})
const setupMocks = (config: {
providerInfo?: TriggerProviderApiEntity
oauthConfig?: TriggerOAuthConfig
@ -353,7 +357,7 @@ describe('CreateSubscriptionButton', () => {
// Assert
// Assert
expect(screen.getByRole('button'))!.toBeInTheDocument()
expect(getCreateButton()).toBeInTheDocument()
})
it('should render icon button when buttonType is ICON_BUTTON', () => {
@ -387,7 +391,7 @@ describe('CreateSubscriptionButton', () => {
// Assert
// Assert
expect(screen.getByRole('button'))!.toBeInTheDocument()
expect(getCreateButton()).toBeInTheDocument()
})
it('should apply shape prop correctly', () => {
@ -592,7 +596,7 @@ describe('CreateSubscriptionButton', () => {
// Assert
// Assert
expect(screen.getByRole('button'))!.toHaveTextContent('pluginTrigger.subscription.createButton.apiKey')
expect(getCreateButton()).toHaveTextContent('pluginTrigger.subscription.createButton.apiKey')
})
it('should display correct button text for MANUAL method', () => {
@ -610,7 +614,7 @@ describe('CreateSubscriptionButton', () => {
// Assert
// Assert
expect(screen.getByRole('button'))!.toHaveTextContent('pluginTrigger.subscription.createButton.manual')
expect(getCreateButton()).toHaveTextContent('pluginTrigger.subscription.createButton.manual')
})
it('should display default button text when multiple methods are supported', () => {
@ -628,7 +632,7 @@ describe('CreateSubscriptionButton', () => {
// Assert
// Assert
expect(screen.getByRole('button'))!.toHaveTextContent('pluginTrigger.subscription.empty.button')
expect(getCreateButton()).toHaveTextContent('pluginTrigger.subscription.empty.button')
})
})
@ -780,7 +784,7 @@ describe('CreateSubscriptionButton', () => {
// Act
render(<CreateSubscriptionButton {...props} />)
const button = screen.getByRole('button')
const button = getCreateButton()
fireEvent.click(button)
// Assert - modal should not open
@ -830,7 +834,7 @@ describe('CreateSubscriptionButton', () => {
// Act
render(<CreateSubscriptionButton {...props} />)
const button = screen.getByRole('button')
const button = getCreateButton()
fireEvent.click(button)
// Assert - modal should open
@ -1328,7 +1332,7 @@ describe('CreateSubscriptionButton', () => {
render(<CreateSubscriptionButton {...props} />)
// Assert - should not have settings divider
const button = screen.getByRole('button')
const button = getCreateButton()
const divider = button.querySelector('.bg-text-primary-on-surface')
expect(divider).not.toBeInTheDocument()
})
@ -1447,7 +1451,7 @@ describe('CreateSubscriptionButton', () => {
render(<CreateSubscriptionButton {...props} />)
// Assert
const button = screen.getByRole('button')
const button = getCreateButton()
expect(button)!.toHaveClass('w-full')
})

View File

@ -9,6 +9,7 @@ import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ActionButton, ActionButtonState } from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import { Infotip } from '@/app/components/base/infotip'
import { openOAuthPopup } from '@/hooks/use-oauth'
import { useInitiateTriggerOAuth, useTriggerOAuthConfig, useTriggerProviderInfo } from '@/service/use-triggers'
import { SupportedCreationMethods } from '../../../types'
@ -124,18 +125,13 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU
value: SupportedCreationMethods.MANUAL,
label: t('subscription.addType.options.manual.description', { ns: 'pluginTrigger' }),
extra: (
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('subscription.addType.options.manual.tip', { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('subscription.addType.options.manual.tip', { ns: 'pluginTrigger' })}
className="h-3.5 w-3.5"
iconClassName="h-full w-full"
>
{t('subscription.addType.options.manual.tip', { ns: 'pluginTrigger' })}
</Infotip>
),
show: supportedMethods.includes(SupportedCreationMethods.MANUAL),
},

View File

@ -1,9 +1,9 @@
'use client'
import type { PluginDetail } from '@/app/components/plugins/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { CreateSubscriptionButton } from './create'
import { CreateButtonType } from './create/types'
import SubscriptionCard from './subscription-card'
@ -31,18 +31,13 @@ export const SubscriptionListView: React.FC<SubscriptionListViewProps> = ({
<span className="system-sm-semibold-uppercase text-text-secondary">
{t('subscription.listNum', { ns: 'pluginTrigger', num: subscriptionCount })}
</span>
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('subscription.list.tip', { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('subscription.list.tip', { ns: 'pluginTrigger' })}
className="h-3.5 w-3.5"
iconClassName="h-full w-full"
>
{t('subscription.list.tip', { ns: 'pluginTrigger' })}
</Infotip>
</div>
)}
<CreateSubscriptionButton

View File

@ -1,12 +1,12 @@
'use client'
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCheckLine, RiDeleteBinLine, RiWebhookLine } from '@remixicon/react'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { Infotip } from '@/app/components/base/infotip'
import { CreateSubscriptionButton } from './create'
import { CreateButtonType } from './create/types'
import { DeleteConfirm } from './delete-confirm'
@ -34,18 +34,13 @@ export const SubscriptionSelectorView: React.FC<SubscriptionSelectorProps> = ({
<span className="system-sm-semibold-uppercase text-text-secondary">
{t('subscription.listNum', { ns: 'pluginTrigger', num: subscriptionCount })}
</span>
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('subscription.list.tip', { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('subscription.list.tip', { ns: 'pluginTrigger' })}
className="h-3.5 w-3.5"
iconClassName="h-full w-full"
>
{t('subscription.list.tip', { ns: 'pluginTrigger' })}
</Infotip>
</div>
<CreateSubscriptionButton
buttonType={CreateButtonType.ICON_BUTTON}

View File

@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import Divider from '@/app/components/base/divider'
import EmojiPickerInner from '@/app/components/base/emoji-picker/Inner'
import { Infotip } from '@/app/components/base/infotip'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import LabelSelector from '@/app/components/tools/labels/selector'
@ -71,20 +72,16 @@ type WorkflowToolDrawerFrameProps = {
children: React.ReactNode
}
const InfoTooltip = ({ children }: { children: React.ReactNode }) => {
const InfoTooltip = ({ children }: { children: string }) => {
return (
<Tooltip>
<TooltipTrigger
render={(
<span className="i-ri-question-line h-3.5 w-3.5 shrink-0 cursor-help text-text-quaternary hover:text-text-tertiary" />
)}
/>
<TooltipContent>
<div className="w-[180px]">
{children}
</div>
</TooltipContent>
</Tooltip>
<Infotip
aria-label={children}
className="ml-1 h-3.5 w-3.5"
iconClassName="h-3.5 w-3.5"
popupClassName="w-[180px]"
>
{children}
</Infotip>
)
}

View File

@ -1,12 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { FieldTitle } from '../field-title'
vi.mock('@langgenius/dify-ui/tooltip', () => ({
Tooltip: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
TooltipTrigger: ({ render }: { render: React.ReactNode }) => <>{render}</>,
TooltipContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}))
describe('FieldTitle', () => {
it('should render title, subtitle, operation, tooltip and warning dot', () => {
render(
@ -21,7 +15,7 @@ describe('FieldTitle', () => {
expect(screen.getByText('Embedding')).toBeInTheDocument()
expect(screen.getByText('subtitle')).toBeInTheDocument()
expect(screen.getByText('Tooltip copy')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Tooltip copy' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'action' })).toBeInTheDocument()
expect(document.querySelector('.bg-text-warning-secondary')).not.toBeNull()
})

View File

@ -1,10 +1,10 @@
import type { ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
memo,
useState,
} from 'react'
import { Infotip } from '@/app/components/base/infotip'
export type FieldTitleProps = {
title?: string
@ -62,18 +62,9 @@ export const FieldTitle = memo(({
}
{
tooltip && (
<Tooltip>
<TooltipTrigger
render={(
<span className="ml-1 flex h-4 w-4 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{tooltip}
</TooltipContent>
</Tooltip>
<Infotip aria-label={tooltip} className="ml-1">
{tooltip}
</Infotip>
)
}
</div>

View File

@ -1,7 +1,5 @@
import { cn } from '@langgenius/dify-ui/cn'
import { Slider } from '@langgenius/dify-ui/slider'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiQuestionLine } from '@remixicon/react'
import {
memo,
useCallback,
@ -11,6 +9,7 @@ import {
Economic,
HighQuality,
} from '@/app/components/base/icons/src/vender/knowledge'
import { Infotip } from '@/app/components/base/infotip'
import Input from '@/app/components/base/input'
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
import {
@ -97,14 +96,12 @@ const IndexMethod = ({
<div className="truncate system-xs-medium text-text-secondary">
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</div>
<Tooltip>
<TooltipTrigger
render={<RiQuestionLine className="ml-0.5 h-3.5 w-3.5 text-text-quaternary" />}
/>
<TooltipContent>
number of keywords
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('form.numberOfKeywords', { ns: 'datasetSettings' })}
className="ml-0.5 h-3.5 w-3.5"
>
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</Infotip>
</div>
<Slider
disabled={readonly}

View File

@ -2,11 +2,11 @@ import type { FC } from 'react'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Model } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCloseLine, RiSparklingFill } from '@remixicon/react'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import Textarea from '@/app/components/base/textarea'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
@ -80,18 +80,13 @@ const PromptEditor: FC<PromptEditorProps> = ({
<div className="flex flex-col gap-y-1 px-4 py-2">
<div className="flex h-6 items-center system-sm-semibold-uppercase text-text-secondary">
<span>{t('nodes.llm.jsonSchema.instruction', { ns: 'workflow' })}</span>
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('nodes.llm.jsonSchema.promptTooltip', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
<Infotip
aria-label={t('nodes.llm.jsonSchema.promptTooltip', { ns: 'workflow' })}
className="h-3.5 w-3.5"
iconClassName="h-full w-full"
>
{t('nodes.llm.jsonSchema.promptTooltip', { ns: 'workflow' })}
</Infotip>
</div>
<div className="flex items-center">
<Textarea

View File

@ -1,7 +1,6 @@
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiQuestionLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
type MonthlyDaysSelectorProps = {
selectedDays: (number | 'last')[]
@ -41,38 +40,46 @@ const MonthlyDaysSelector = ({ selectedDays, onChange }: MonthlyDaysSelectorProp
{rows.map((row, rowIndex) => (
<div key={rowIndex} className="grid grid-cols-7 gap-1.5">
{row.map(day => (
<button
key={day}
type="button"
onClick={() => handleDayClick(day)}
className={`rounded-lg border bg-components-option-card-option-bg py-1 text-xs transition-colors ${
day === 'last' ? 'col-span-2 min-w-0' : ''
} ${
isDaySelected(day)
? 'border-util-colors-blue-brand-blue-brand-600 text-text-secondary'
: 'border-divider-subtle text-text-tertiary hover:border-divider-regular hover:text-text-secondary'
}`}
>
{day === 'last'
? (
<div className="flex items-center justify-center gap-1">
<span>{t('nodes.triggerSchedule.lastDay', { ns: 'workflow' })}</span>
<Tooltip>
<TooltipTrigger
render={(
<RiQuestionLine className="h-3 w-3 text-text-quaternary" />
)}
/>
<TooltipContent>
{t('nodes.triggerSchedule.lastDayTooltip', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
</div>
)
: (
day
)}
</button>
day === 'last'
? (
<div
key={day}
className={`col-span-2 flex min-w-0 items-center rounded-lg border bg-components-option-card-option-bg text-xs transition-colors ${
isDaySelected(day)
? 'border-util-colors-blue-brand-blue-brand-600 text-text-secondary'
: 'border-divider-subtle text-text-tertiary hover:border-divider-regular hover:text-text-secondary'
}`}
>
<button
type="button"
onClick={() => handleDayClick(day)}
className="min-w-0 flex-1 py-1"
>
{t('nodes.triggerSchedule.lastDay', { ns: 'workflow' })}
</button>
<Infotip
aria-label={t('nodes.triggerSchedule.lastDayTooltip', { ns: 'workflow' })}
className="mr-1 h-3 w-3"
iconClassName="h-3 w-3"
>
{t('nodes.triggerSchedule.lastDayTooltip', { ns: 'workflow' })}
</Infotip>
</div>
)
: (
<button
key={day}
type="button"
onClick={() => handleDayClick(day)}
className={`rounded-lg border bg-components-option-card-option-bg py-1 text-xs transition-colors ${
isDaySelected(day)
? 'border-util-colors-blue-brand-blue-brand-600 text-text-secondary'
: 'border-divider-subtle text-text-tertiary hover:border-divider-regular hover:text-text-secondary'
}`}
>
{day}
</button>
)
))}
{/* Fill empty cells in the last row (Last day takes 2 cols, so need 1 less) */}
{rowIndex === rows.length - 1 && Array.from({ length: 7 - row.length - 1 }, (_, i) => (