From 489e77658e9823a1ca0269dab020bb5566a6d630 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:37:36 +0800 Subject: [PATCH] fix(web): simplify infotip icon variants (#38644) --- .../specific-groups-or-members.tsx | 3 +- .../params-config/config-content.tsx | 1 - .../chat-input-area/__tests__/index.spec.tsx | 5 +- .../base/chat/chat/chat-input-area/index.tsx | 39 +++++--------- .../__tests__/modal.spec.tsx | 8 ++- .../conversation-opener/modal.tsx | 1 - .../base/infotip/__tests__/index.spec.tsx | 53 +++++++++++++++++++ web/app/components/base/infotip/index.tsx | 53 ++++++++++--------- .../components/general-chunking-options.tsx | 1 - .../metadata/edit-metadata-batch/modal.tsx | 3 +- .../model-load-balancing-configs.tsx | 2 +- .../subscription-list/create/index.tsx | 1 - .../subscription-list/list-view.tsx | 1 - .../subscription-list/selector-view.tsx | 1 - .../components/tools/workflow-tool/index.tsx | 1 - .../search-method-option.tsx | 1 - .../top-k-and-score-threshold.tsx | 2 - .../json-schema-generator/prompt-editor.tsx | 1 - .../nodes/parameter-extractor/panel.tsx | 1 - .../components/advanced-setting.tsx | 1 - .../components/monthly-days-selector.tsx | 2 +- .../panel/env-panel/variable-modal.tsx | 1 - .../components/orchestrate/files/index.tsx | 4 +- 23 files changed, 108 insertions(+), 78 deletions(-) create mode 100644 web/app/components/base/infotip/__tests__/index.spec.tsx diff --git a/web/app/components/app/app-access-control/specific-groups-or-members.tsx b/web/app/components/app/app-access-control/specific-groups-or-members.tsx index 982a018930c..64cf628f87c 100644 --- a/web/app/components/app/app-access-control/specific-groups-or-members.tsx +++ b/web/app/components/app/app-access-control/specific-groups-or-members.tsx @@ -149,7 +149,8 @@ export function WebAppSSONotEnabledTip() { return ( {tip} diff --git a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx index 0d0a56e5845..4f9e3088d54 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx @@ -247,7 +247,6 @@ const ConfigContent: FC = ({ {option.tips} diff --git a/web/app/components/base/chat/chat/chat-input-area/__tests__/index.spec.tsx b/web/app/components/base/chat/chat/chat-input-area/__tests__/index.spec.tsx index 7db45d3f9b4..d51a810751a 100644 --- a/web/app/components/base/chat/chat/chat-input-area/__tests__/index.spec.tsx +++ b/web/app/components/base/chat/chat/chat-input-area/__tests__/index.spec.tsx @@ -756,16 +756,17 @@ describe('ChatInputArea', () => { // ------------------------------------------------------------------------- describe('Feature Bar', () => { - it('should render footer notice with tooltip when provided', async () => { + it('should render footer notice with an accessible infotip', async () => { const user = userEvent.setup({ delay: null }) const footerNotice = 'Agent runs in a Linux sandbox.' const footerNoticeTooltip = 'For Dify Community Edition, each of your agents runs in a Linux 7.0.0-10060-aws sandbox environment within your docker. Your edits to the environment via Build Chats are persistent.' + const accessibleName = `common.operation.learnMore: ${footerNotice}` render() expect(screen.getByText(footerNotice)).toBeInTheDocument() expect(screen.queryByText(footerNoticeTooltip)).not.toBeInTheDocument() - await user.hover(screen.getByRole('button', { name: footerNoticeTooltip })) + await user.click(screen.getByRole('button', { name: accessibleName })) expect(await screen.findByText(footerNoticeTooltip)).toBeInTheDocument() }) diff --git a/web/app/components/base/chat/chat/chat-input-area/index.tsx b/web/app/components/base/chat/chat/chat-input-area/index.tsx index e79368a61bd..4b7261657e7 100644 --- a/web/app/components/base/chat/chat/chat-input-area/index.tsx +++ b/web/app/components/base/chat/chat/chat-input-area/index.tsx @@ -4,7 +4,6 @@ import type { EnableType, OnSend } from '../../types' import type { InputForm } from '../type' import type { FileUpload } from '@/app/components/base/features/types' import { cn } from '@langgenius/dify-ui/cn' -import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { toast } from '@langgenius/dify-ui/toast' import { noop } from 'es-toolkit/function' import { decode } from 'html-entities' @@ -16,6 +15,7 @@ import FeatureBar from '@/app/components/base/features/new-feature-panel/feature import { FileListInChatInput } from '@/app/components/base/file-uploader' import { useFile } from '@/app/components/base/file-uploader/hooks' import { FileContextProvider, useFileStore } from '@/app/components/base/file-uploader/store' +import { Infotip } from '@/app/components/base/infotip' import VoiceInput from '@/app/components/base/voice-input' import { TransferMethod } from '@/types/app' import { useCheckInputsForms } from '../check-input-forms-hooks' @@ -170,6 +170,10 @@ const ChatInputArea = ({ readonly, botName, customPlaceholder, showFeatureBar, s const operation = () const shouldShowFooterNotice = footerNotice !== undefined && footerNotice !== null const shouldShowFooterNoticeTooltip = footerNoticeTooltip !== undefined && footerNoticeTooltip !== null + const footerNoticeText = typeof footerNotice === 'string' ? footerNotice.trim() : '' + const footerNoticeAriaLabel = footerNoticeText + ? `${t('operation.learnMore', { ns: 'common' })}: ${footerNoticeText}` + : t('operation.learnMore', { ns: 'common' }) return ( <>
@@ -212,31 +216,16 @@ const ChatInputArea = ({ readonly, botName, customPlaceholder, showFeatureBar, s {shouldShowFooterNotice && (
- -
{footerNotice}
+
{footerNotice}
{shouldShowFooterNoticeTooltip && ( - - - - - )} - /> - - {footerNoticeTooltip} - - + + {footerNoticeTooltip} + )}
diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx index e41079e66a5..e309c253104 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx @@ -373,7 +373,7 @@ describe('OpeningSettingModal', () => { expect(screen.queryByText(/openingStatement\.openingQuestionDescription/)).not.toBeInTheDocument() }) - it('should show the opening questions description in a tooltip', async () => { + it('should show the opening questions description in an infotip', async () => { await render( { />, ) - act(() => { - fireEvent.mouseEnter(screen.getByRole('button', { name: /openingStatement\.openingQuestionDescription/ })) - }) + await userEvent.hover(screen.getByRole('button', { name: /openingStatement\.openingQuestionDescription/ })) - expect(screen.getByText(/openingStatement\.openingQuestionDescription/)).toBeInTheDocument() + expect(await screen.findByText(/openingStatement\.openingQuestionDescription/)).toBeInTheDocument() }) it('should call onAutoAddPromptVariable when confirm add is clicked', async () => { diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx index 1fcdbd922e6..5fac2ef2b86 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx @@ -121,7 +121,6 @@ const OpeningSettingModal = ({ aria-label={t('openingStatement.openingQuestionDescription', { ns: 'appDebug' })} className="size-3.5" popupClassName="max-w-[220px] system-sm-regular text-text-secondary" - delay={0} > {t('openingStatement.openingQuestionDescription', { ns: 'appDebug' })} diff --git a/web/app/components/base/infotip/__tests__/index.spec.tsx b/web/app/components/base/infotip/__tests__/index.spec.tsx new file mode 100644 index 00000000000..31b035b5692 --- /dev/null +++ b/web/app/components/base/infotip/__tests__/index.spec.tsx @@ -0,0 +1,53 @@ +import type { ReactNode } from 'react' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { createPortal } from 'react-dom' +import { Infotip } from '../index' + +function ClickBoundary({ children, onClick }: { children: ReactNode, onClick: () => void }) { + return ( + + ) +} + +describe('Infotip', () => { + it('should open with keyboard activation', async () => { + const user = userEvent.setup() + render(Rate limit details) + + screen.getByRole('button', { name: 'Rate limits' }).focus() + await user.keyboard('{Enter}') + + expect(await screen.findByRole('dialog')).toHaveTextContent('Rate limit details') + }) + + it('should close the dialog with Escape', async () => { + const user = userEvent.setup() + render(Rate limit details) + + await user.click(screen.getByRole('button', { name: 'Rate limits' })) + expect(await screen.findByRole('dialog')).toBeInTheDocument() + + await user.keyboard('{Escape}') + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() + }) + }) + + it('should not propagate trigger clicks', async () => { + const user = userEvent.setup() + const parentClick = vi.fn() + render( + + Rate limit details + , + ) + + await user.click(screen.getByRole('button', { name: 'Rate limits' })) + + expect(parentClick).not.toHaveBeenCalled() + }) +}) diff --git a/web/app/components/base/infotip/index.tsx b/web/app/components/base/infotip/index.tsx index 3a80d1f5c94..3b5927ea83c 100644 --- a/web/app/components/base/infotip/index.tsx +++ b/web/app/components/base/infotip/index.tsx @@ -1,12 +1,25 @@ 'use client' -import type { Placement } from '@langgenius/dify-ui/popover' import type { MouseEvent, ReactNode } from 'react' import { cn } from '@langgenius/dify-ui/cn' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' +const iconClassNames = { + question: 'i-ri-question-line', + information: 'i-ri-information-line', +} as const + +const iconSizeClassNames = { + small: 'size-3', + medium: 'size-3.5', + large: 'size-4', +} as const + +type InfotipIconVariant = keyof typeof iconClassNames +type InfotipIconSize = keyof typeof iconSizeClassNames + /** - * Infotip — a `?` icon that reveals a long-form explanation on hover / focus / tap. + * Infotip — an info glyph that reveals a long-form explanation on hover / keyboard activation / tap. * * Implements the pattern Base UI calls an "infotip": * https://base-ui.com/react/components/tooltip#infotips @@ -32,34 +45,25 @@ import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/pop type InfotipProps = { /** Popup content. Rich nodes are allowed. */ 'children': ReactNode - /** Accessible name for the trigger. Required; should match the popup text. */ + /** Accessible name for the icon-only trigger. */ 'aria-label': string - /** Placement of the popup relative to the trigger. Defaults to `top`. */ - 'placement'?: Placement - /** Distance between the trigger and popup. Defaults to the popover primitive spacing. */ - 'sideOffset'?: number - /** Extra classes on the outer trigger wrapper (layout / margin). */ + /** Extra classes on the trigger for contextual layout and color. */ 'className'?: string - /** Extra classes on the `?` icon itself (size / color overrides). */ - 'iconClassName'?: string + /** Icon glyph. Defaults to `question`. */ + 'iconVariant'?: InfotipIconVariant + /** Icon size. Defaults to `medium` (14px). */ + 'iconSize'?: InfotipIconSize /** Extra classes on the popup body (width / padding / whitespace overrides). */ 'popupClassName'?: string - /** Hover open delay in ms. Defaults to 300 to match the app-wide Tooltip delay. */ - 'delay'?: number - /** Hover close delay in ms. Defaults to 200 to match the app-wide Tooltip delay. */ - 'closeDelay'?: number } export function Infotip({ children, 'aria-label': ariaLabel, - placement = 'top', - sideOffset, className, - iconClassName, + iconVariant = 'question', + iconSize = 'medium', popupClassName, - delay = 300, - closeDelay = 200, }: InfotipProps) { const handleClick = (event: MouseEvent) => { event.stopPropagation() @@ -69,20 +73,19 @@ export function Infotip({ - + {children} diff --git a/web/app/components/datasets/create/step-two/components/general-chunking-options.tsx b/web/app/components/datasets/create/step-two/components/general-chunking-options.tsx index ad217d5b9be..eacece64773 100644 --- a/web/app/components/datasets/create/step-two/components/general-chunking-options.tsx +++ b/web/app/components/datasets/create/step-two/components/general-chunking-options.tsx @@ -196,7 +196,6 @@ export const GeneralChunkingOptions: FC = ({ {t('stepTwo.QATip', { ns: 'datasetCreation' })} diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx index b13f4978031..97610096ee6 100644 --- a/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx +++ b/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx @@ -142,8 +142,7 @@ const EditMetadataBatchModal: FC = ({ datasetId, documentNum, list, onSav {t(`${i18nPrefix}.applyToAllSelectDocumentTip`, { ns: 'dataset' })} diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx index c3558ac968b..9b8509d3998 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx @@ -156,7 +156,7 @@ const ModelLoadBalancingConfigs = ({ {t('modelProvider.loadBalancingInfo', { ns: 'common' })} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx index cbfc68e4b14..a5db2c37e28 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx @@ -128,7 +128,6 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU {t('subscription.addType.options.manual.tip', { ns: 'pluginTrigger' })} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx index 2af0bde981f..121964e4cb2 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx @@ -34,7 +34,6 @@ export const SubscriptionListView: React.FC = ({ {t('subscription.list.tip', { ns: 'pluginTrigger' })} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx index 1a254e99a0f..8547d9647c4 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx @@ -37,7 +37,6 @@ export const SubscriptionSelectorView: React.FC = ({ {t('subscription.list.tip', { ns: 'pluginTrigger' })} diff --git a/web/app/components/tools/workflow-tool/index.tsx b/web/app/components/tools/workflow-tool/index.tsx index e51e3fe1655..f8ed8ad5478 100644 --- a/web/app/components/tools/workflow-tool/index.tsx +++ b/web/app/components/tools/workflow-tool/index.tsx @@ -75,7 +75,6 @@ const InfoTooltip = ({ children }: { children: string }) => { {children} diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx index 5303abd38b8..6ab59952704 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx @@ -303,7 +303,6 @@ export function SearchMethodOption({ {rerankModelTip} diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx index 8de280eb570..cd009e409ae 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx @@ -72,7 +72,6 @@ export function TopKAndScoreThreshold({ {topKTip} @@ -115,7 +114,6 @@ export function TopKAndScoreThreshold({ {scoreThresholdTip} diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx index 651ac8518fa..f4a1870967f 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx @@ -83,7 +83,6 @@ const PromptEditor: FC = ({ {t('nodes.llm.jsonSchema.promptTooltip', { ns: 'workflow' })} diff --git a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx index 698222af757..e2088051982 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx @@ -134,7 +134,6 @@ const Panel: FC> = ({ {t(`${i18nPrefix}.instructionTip`, { ns: 'workflow' })} diff --git a/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx b/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx index 752a73270bd..291216bdd58 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx @@ -51,7 +51,6 @@ const AdvancedSetting: FC = ({ {t(`${i18nPrefix}.instructionTip`, { ns: 'workflow' })} diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx index 5c47b9a9e7a..d9a9d7e9d1a 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx @@ -60,7 +60,7 @@ const MonthlyDaysSelector = ({ selectedDays, onChange }: MonthlyDaysSelectorProp {t('nodes.triggerSchedule.lastDayTooltip', { ns: 'workflow' })} diff --git a/web/app/components/workflow/panel/env-panel/variable-modal.tsx b/web/app/components/workflow/panel/env-panel/variable-modal.tsx index 14b5d94404d..7f848f981bc 100644 --- a/web/app/components/workflow/panel/env-panel/variable-modal.tsx +++ b/web/app/components/workflow/panel/env-panel/variable-modal.tsx @@ -132,7 +132,6 @@ const VariableModal = ({ {t('env.modal.secretTip', { ns: 'workflow' })} diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx index 1c13730d78b..9f7e9ee8031 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx @@ -321,8 +321,8 @@ function AgentBuildNoteInfotip() { return (