From 5456d4d56e5701999bc8da2a2c97f5ae9b3b78d3 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 4 Aug 2026 03:05:49 +0800 Subject: [PATCH] fix(web): improve main navigation responsiveness and tour focus (#39940) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- oxlint-suppressions.json | 4 +- .../solid/development/TerminalSquare.json | 38 ---------- .../solid/development/TerminalSquare.tsx | 18 ----- .../src/vender/solid/development/index.ts | 1 - .../src/vender/solid/education/Beaker02.json | 38 ---------- .../src/vender/solid/education/Beaker02.tsx | 18 ----- .../icons/src/vender/solid/education/index.ts | 2 - web/app/components/header/env-nav/index.tsx | 14 +--- .../main-nav/__tests__/index.spec.tsx | 23 ++---- .../__tests__/support-menu.spec.tsx | 12 +-- .../main-nav/components/help-menu.tsx | 16 +--- .../main-nav/components/support-menu.tsx | 8 +- web/app/components/main-nav/index.tsx | 6 +- .../__tests__/mount.spec.tsx | 76 ++++++++++++++++++- .../step-by-step-tour/floating-widget.tsx | 61 ++++++--------- .../components/step-by-step-tour/mount.tsx | 48 +++++++----- 16 files changed, 153 insertions(+), 230 deletions(-) delete mode 100644 web/app/components/base/icons/src/vender/solid/development/TerminalSquare.json delete mode 100644 web/app/components/base/icons/src/vender/solid/development/TerminalSquare.tsx delete mode 100644 web/app/components/base/icons/src/vender/solid/education/Beaker02.json delete mode 100644 web/app/components/base/icons/src/vender/solid/education/Beaker02.tsx diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 62bf07bc0bb..c10a34f997c 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -1493,7 +1493,7 @@ }, "web/app/components/base/icons/src/vender/solid/development/index.ts": { "no-barrel-files/no-barrel-files": { - "count": 4 + "count": 3 } }, "web/app/components/base/icons/src/vender/solid/editor/index.ts": { @@ -1503,7 +1503,7 @@ }, "web/app/components/base/icons/src/vender/solid/education/index.ts": { "no-barrel-files/no-barrel-files": { - "count": 2 + "count": 1 } }, "web/app/components/base/icons/src/vender/solid/files/index.ts": { diff --git a/web/app/components/base/icons/src/vender/solid/development/TerminalSquare.json b/web/app/components/base/icons/src/vender/solid/development/TerminalSquare.json deleted file mode 100644 index 456b335a943..00000000000 --- a/web/app/components/base/icons/src/vender/solid/development/TerminalSquare.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "icon": { - "type": "element", - "isRootNode": true, - "name": "svg", - "attributes": { - "width": "12", - "height": "12", - "viewBox": "0 0 12 12", - "fill": "none", - "xmlns": "http://www.w3.org/2000/svg" - }, - "children": [ - { - "type": "element", - "name": "g", - "attributes": { - "id": "terminal-square" - }, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "id": "Solid", - "fill-rule": "evenodd", - "clip-rule": "evenodd", - "d": "M8.91927 1H3.08073C2.81716 0.999992 2.58977 0.999984 2.40249 1.01529C2.20481 1.03144 2.00821 1.06709 1.81902 1.16349C1.53677 1.3073 1.3073 1.53677 1.16349 1.81902C1.06709 2.00821 1.03144 2.20481 1.01529 2.40249C0.999984 2.58977 0.999992 2.81714 1 3.08071V8.91927C0.999992 9.18284 0.999984 9.41023 1.01529 9.59752C1.03144 9.79519 1.06709 9.9918 1.16349 10.181C1.3073 10.4632 1.53677 10.6927 1.81902 10.8365C2.00821 10.9329 2.20481 10.9686 2.40249 10.9847C2.58977 11 2.81715 11 3.08072 11H8.91928C9.18285 11 9.41023 11 9.59752 10.9847C9.79519 10.9686 9.9918 10.9329 10.181 10.8365C10.4632 10.6927 10.6927 10.4632 10.8365 10.181C10.9329 9.9918 10.9686 9.79519 10.9847 9.59752C11 9.41023 11 9.18285 11 8.91928V3.08072C11 2.81715 11 2.58977 10.9847 2.40249C10.9686 2.20481 10.9329 2.00821 10.8365 1.81902C10.6927 1.53677 10.4632 1.3073 10.181 1.16349C9.9918 1.06709 9.79519 1.03144 9.59752 1.01529C9.41023 0.999984 9.18284 0.999992 8.91927 1ZM3.85355 4.14645C3.65829 3.95118 3.34171 3.95118 3.14645 4.14645C2.95118 4.34171 2.95118 4.65829 3.14645 4.85355L4.29289 6L3.14645 7.14645C2.95118 7.34171 2.95118 7.65829 3.14645 7.85355C3.34171 8.04882 3.65829 8.04882 3.85355 7.85355L5.35355 6.35355C5.54882 6.15829 5.54882 5.84171 5.35355 5.64645L3.85355 4.14645ZM6.5 7C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H8.5C8.77614 8 9 7.77614 9 7.5C9 7.22386 8.77614 7 8.5 7H6.5Z", - "fill": "currentColor" - }, - "children": [] - } - ] - } - ] - }, - "name": "TerminalSquare" -} diff --git a/web/app/components/base/icons/src/vender/solid/development/TerminalSquare.tsx b/web/app/components/base/icons/src/vender/solid/development/TerminalSquare.tsx deleted file mode 100644 index d4d36792905..00000000000 --- a/web/app/components/base/icons/src/vender/solid/development/TerminalSquare.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// GENERATE BY script -// DON NOT EDIT IT MANUALLY - -import type { IconData } from '@/app/components/base/icons/IconBase' -import * as React from 'react' -import IconBase from '@/app/components/base/icons/IconBase' -import data from './TerminalSquare.json' - -const Icon = ({ - ref, - ...props -}: React.SVGProps & { - ref?: React.RefObject> -}) => - -Icon.displayName = 'TerminalSquare' - -export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/development/index.ts b/web/app/components/base/icons/src/vender/solid/development/index.ts index 25eb3d2736c..b773db8008f 100644 --- a/web/app/components/base/icons/src/vender/solid/development/index.ts +++ b/web/app/components/base/icons/src/vender/solid/development/index.ts @@ -1,5 +1,4 @@ export { default as ApiConnection } from './ApiConnection' export { default as ApiConnectionMod } from './ApiConnectionMod' -export { default as TerminalSquare } from './TerminalSquare' export { default as Variable02 } from './Variable02' diff --git a/web/app/components/base/icons/src/vender/solid/education/Beaker02.json b/web/app/components/base/icons/src/vender/solid/education/Beaker02.json deleted file mode 100644 index fa5e7856d6d..00000000000 --- a/web/app/components/base/icons/src/vender/solid/education/Beaker02.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "icon": { - "type": "element", - "isRootNode": true, - "name": "svg", - "attributes": { - "width": "12", - "height": "12", - "viewBox": "0 0 12 12", - "fill": "none", - "xmlns": "http://www.w3.org/2000/svg" - }, - "children": [ - { - "type": "element", - "name": "g", - "attributes": { - "id": "beaker-02" - }, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "id": "Solid", - "fill-rule": "evenodd", - "clip-rule": "evenodd", - "d": "M4.13856 0.500003H7.8617C7.92126 0.49998 7.99238 0.499953 8.05504 0.505073C8.12765 0.511005 8.23165 0.526227 8.34062 0.581751C8.48174 0.653656 8.59648 0.768392 8.66838 0.909513C8.72391 1.01849 8.73913 1.12248 8.74506 1.19509C8.75018 1.25775 8.75015 1.32888 8.75013 1.38844V2.61157C8.75015 2.67113 8.75018 2.74226 8.74506 2.80492C8.73913 2.87753 8.72391 2.98153 8.66838 3.0905C8.59648 3.23162 8.48174 3.34636 8.34062 3.41826C8.23165 3.47379 8.12765 3.48901 8.05504 3.49494C8.03725 3.49639 8.01877 3.49743 8.00006 3.49817V5.2506C8.00006 5.55312 8.00408 5.61265 8.01723 5.66153C8.03245 5.71807 8.05747 5.7715 8.09117 5.81939C8.1203 5.86078 8.16346 5.90197 8.39586 6.09564L10.2807 7.66627C10.4566 7.81255 10.6116 7.94145 10.7267 8.10509C10.8278 8.24875 10.9029 8.40904 10.9486 8.57867C11.0005 8.7719 11.0003 8.97351 11.0001 9.2023C11.0001 9.39886 11.0002 9.59542 11.0002 9.79198C11.0003 9.98232 11.0005 10.1463 10.9713 10.2927C10.853 10.8877 10.3878 11.3529 9.7928 11.4712C9.64637 11.5003 9.48246 11.5002 9.29211 11.5001H2.70822C2.51787 11.5002 2.35396 11.5003 2.20753 11.4712C1.98473 11.4269 1.78014 11.334 1.60515 11.2038C1.42854 11.0725 1.28221 10.9034 1.17753 10.7077C1.10892 10.5796 1.05831 10.4401 1.02899 10.2927C0.999862 10.1463 0.999992 9.98233 1.00014 9.79199C1.00014 9.59542 1.00006 9.39886 1.00003 9.20229C0.999794 8.97351 0.999584 8.7719 1.05157 8.57867C1.09721 8.40904 1.17229 8.24875 1.27338 8.10509C1.38855 7.94145 1.54356 7.81255 1.71947 7.66627L3.60427 6.09564C3.83667 5.90197 3.87983 5.86078 3.90896 5.81939C3.94266 5.7715 3.96768 5.71807 3.9829 5.66153C3.99605 5.61265 4.00006 5.55312 4.00006 5.2506V3.49817C3.9814 3.49743 3.96297 3.49639 3.94521 3.49494C3.8726 3.48901 3.76861 3.47379 3.65964 3.41826C3.51851 3.34636 3.40378 3.23162 3.33187 3.0905C3.27635 2.98153 3.26113 2.87753 3.25519 2.80492C3.25008 2.74226 3.2501 2.67113 3.25013 2.61158V1.38844C3.2501 1.32888 3.25008 1.25775 3.25519 1.19509C3.26113 1.12248 3.27635 1.01849 3.33187 0.909513C3.40378 0.768392 3.51851 0.653656 3.65964 0.581751C3.76861 0.526227 3.8726 0.511005 3.94521 0.505073C4.00787 0.499953 4.079 0.49998 4.13856 0.500003ZM9.11909 8.00004H2.88104L4.28066 6.83373C4.45657 6.68745 4.61158 6.55855 4.72675 6.39491C4.82784 6.25125 4.90292 6.09096 4.94856 5.92133C5.00054 5.7281 5.00033 5.52649 5.0001 5.29771L5.00006 3.50001H7.00006L7.00003 5.29771C6.99979 5.52649 6.99958 5.7281 7.05157 5.92133C7.09721 6.09096 7.17229 6.25125 7.27338 6.39491C7.38855 6.55855 7.54356 6.68745 7.71947 6.83373L9.11909 8.00004Z", - "fill": "currentColor" - }, - "children": [] - } - ] - } - ] - }, - "name": "Beaker02" -} diff --git a/web/app/components/base/icons/src/vender/solid/education/Beaker02.tsx b/web/app/components/base/icons/src/vender/solid/education/Beaker02.tsx deleted file mode 100644 index 69fd8f088a4..00000000000 --- a/web/app/components/base/icons/src/vender/solid/education/Beaker02.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// GENERATE BY script -// DON NOT EDIT IT MANUALLY - -import type { IconData } from '@/app/components/base/icons/IconBase' -import * as React from 'react' -import IconBase from '@/app/components/base/icons/IconBase' -import data from './Beaker02.json' - -const Icon = ({ - ref, - ...props -}: React.SVGProps & { - ref?: React.RefObject> -}) => - -Icon.displayName = 'Beaker02' - -export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/education/index.ts b/web/app/components/base/icons/src/vender/solid/education/index.ts index 4a8af2253f6..7f4040fd548 100644 --- a/web/app/components/base/icons/src/vender/solid/education/index.ts +++ b/web/app/components/base/icons/src/vender/solid/education/index.ts @@ -1,3 +1 @@ -export { default as Beaker02 } from './Beaker02' - export { default as Unblur } from './Unblur' diff --git a/web/app/components/header/env-nav/index.tsx b/web/app/components/header/env-nav/index.tsx index cdc9f7d2b3c..77b2a4fb0fd 100644 --- a/web/app/components/header/env-nav/index.tsx +++ b/web/app/components/header/env-nav/index.tsx @@ -2,8 +2,6 @@ import { useAtomValue } from 'jotai' import { useTranslation } from 'react-i18next' -import { TerminalSquare } from '@/app/components/base/icons/src/vender/solid/development' -import { Beaker02 } from '@/app/components/base/icons/src/vender/solid/education' import { langGeniusVersionInfoAtom } from '@/context/version-state' const headerEnvClassName: { [k: string]: string } = { @@ -26,18 +24,14 @@ const EnvNav = () => { > {langGeniusVersionInfo.current_env === 'TESTING' && ( <> - -
- {t(($) => $['environment.testing'], { ns: 'common' })} -
+ +
{t(($) => $['environment.testing'], { ns: 'common' })}
)} {langGeniusVersionInfo.current_env === 'DEVELOPMENT' && ( <> - -
- {t(($) => $['environment.development'], { ns: 'common' })} -
+ +
{t(($) => $['environment.development'], { ns: 'common' })}
)} diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx index 9d85546e650..768b0097f98 100644 --- a/web/app/components/main-nav/__tests__/index.spec.tsx +++ b/web/app/components/main-nav/__tests__/index.spec.tsx @@ -715,22 +715,6 @@ describe('MainNav', () => { expect(accountButton).not.toHaveClass('justify-center', 'p-1') }) - it('renders the desktop environment tag from the old header contract', () => { - mockConsoleState.current = { - ...consoleState, - langGeniusVersionInfo: { - ...consoleState.langGeniusVersionInfo, - current_env: 'TESTING', - }, - } - - renderMainNav() - - const environmentTag = screen.getByText('common.environment.testing') - expect(environmentTag).toBeInTheDocument() - expect(environmentTag.closest('.relative.z-30')).toHaveClass('mt-auto', 'shrink-0') - }) - it('does not reserve environment tag space when the environment is not shown', () => { const { container } = renderMainNav() @@ -993,6 +977,7 @@ describe('MainNav', () => { await waitFor(() => { expect(localStorage.getItem(LEARN_DIFY_HIDDEN_STORAGE_KEY)).toBe('false') }) + expect(screen.getByRole('menu')).toBeInTheDocument() expect(mockPush).not.toHaveBeenCalled() }) @@ -1036,7 +1021,7 @@ describe('MainNav', () => { ).toHaveAttribute('aria-checked', 'false') }) - it('lets existing accounts enable Step-by-step Tour from the help menu', async () => { + it('closes the help menu and opens Step-by-step Tour when enabling it', async () => { const user = userEvent.setup() localStorage.setItem(STEP_BY_STEP_TOUR_SHELL_MODE_STORAGE_KEY, 'collapsed') mockStepByStepTour.setState({ @@ -1062,6 +1047,10 @@ describe('MainNav', () => { expect(localStorage.getItem(STEP_BY_STEP_TOUR_SHELL_MODE_STORAGE_KEY)).toBe('expanded') }) expect(mockTrackEvent).toHaveBeenCalledWith('step_tour', { action: 'tour_enabled' }) + await waitFor(() => { + expect(screen.queryByRole('menu')).not.toBeInTheDocument() + }) + expect(await screen.findByRole('dialog', { name: 'Get to know Dify' })).toBeInTheDocument() await user.click(screen.getByRole('button', { name: 'common.mainNav.help.openMenu' })) expect( diff --git a/web/app/components/main-nav/components/__tests__/support-menu.spec.tsx b/web/app/components/main-nav/components/__tests__/support-menu.spec.tsx index 4afbf38f333..e56dfeea16f 100644 --- a/web/app/components/main-nav/components/__tests__/support-menu.spec.tsx +++ b/web/app/components/main-nav/components/__tests__/support-menu.spec.tsx @@ -91,7 +91,7 @@ describe('SupportMenu', () => { ;(mailToSupport as Mock).mockReturnValue('mailto:support@example.com') }) - const renderSupportMenu = (onContactUsClick = vi.fn()) => { + const renderSupportMenu = () => { const { wrapper } = createConsoleQueryWrapper({ systemFeatures: { deployment_edition: deploymentEdition }, }) @@ -99,7 +99,7 @@ describe('SupportMenu', () => { {}}> open - + , { wrapper }, @@ -107,8 +107,7 @@ describe('SupportMenu', () => { } it('renders contact us before community support entries when Zendesk is configured', () => { - const onContactUsClick = vi.fn() - renderSupportMenu(onContactUsClick) + renderSupportMenu() expect(screen.getByText('common.userProfile.contactUs')).toBeInTheDocument() expect(screen.getByText('common.userProfile.forum')).toBeInTheDocument() @@ -126,7 +125,6 @@ describe('SupportMenu', () => { fireEvent.click(screen.getByRole('menuitem', { name: 'common.userProfile.contactUs' })) expect(openZendeskWindow).toHaveBeenCalledWith('CLOUD') - expect(onContactUsClick).toHaveBeenCalled() }) it('renders contact us with upgrade badge for Cloud sandbox plan without dedicated support', () => { @@ -135,8 +133,7 @@ describe('SupportMenu', () => { plan: { type: Plan.sandbox }, }) - const onContactUsClick = vi.fn() - renderSupportMenu(onContactUsClick) + renderSupportMenu() expect(screen.getByText('common.userProfile.contactUs')).toHaveClass('text-text-disabled') expect(screen.getByText('billing.upgradeBtn.encourageShort')).toHaveClass( @@ -156,7 +153,6 @@ describe('SupportMenu', () => { expect(mockSetShowPricingModal).toHaveBeenCalled() expect(openZendeskWindow).not.toHaveBeenCalled() - expect(onContactUsClick).toHaveBeenCalled() }) it('hides upgrade contact for Cloud sandbox plan when billing is disabled', () => { diff --git a/web/app/components/main-nav/components/help-menu.tsx b/web/app/components/main-nav/components/help-menu.tsx index b3bddb5e693..6430e050aca 100644 --- a/web/app/components/main-nav/components/help-menu.tsx +++ b/web/app/components/main-nav/components/help-menu.tsx @@ -100,7 +100,6 @@ const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMe const disableStepByStepTour = useSetAtom(disableStepByStepTourForCurrentWorkspaceAtom) const setStepByStepTourShellMode = useSetStepByStepTourShellMode() const [aboutOpen, setAboutOpen] = useState(false) - const [open, setOpen] = useState(false) const shouldShowLearnDifySwitch = systemFeatures.enable_learn_app const shouldShowStepByStepTourSwitch = systemFeatures.enable_step_by_step_tour const canToggleStepByStepTour = @@ -123,13 +122,9 @@ const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMe onSuccess: trackVisibilityToggled, }) } - - if (checked) setOpen(false) } const handleOpenChange = (nextOpen: boolean) => { - setOpen(nextOpen) - if (nextOpen) setSkipRecoveryVisible(false) } @@ -137,7 +132,7 @@ const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMe return ( <> - + $['mainNav.help.openMenu'], { ns: 'common' })} data-learn-dify-help-target @@ -201,7 +196,7 @@ const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMe {systemFeatures.deployment_edition === 'CLOUD' && shouldShowStepByStepTourSwitch && ( - setOpen(false)} /> + @@ -249,10 +244,7 @@ const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMe {env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && ( { - setOpen(false) - setAboutOpen(true) - }} + onClick={() => setAboutOpen(true)} > void -} - -export default function SupportMenu({ onContactUsClick }: SupportMenuProps) { +export default function SupportMenu() { const { t } = useTranslation() const { data: deploymentEdition } = useSuspenseQuery({ ...systemFeaturesQueryOptions(), @@ -46,7 +42,6 @@ export default function SupportMenu({ onContactUsClick }: SupportMenuProps) { className="mx-0 h-8 gap-1 px-3 py-1" onClick={() => { setShowPricingModal() - onContactUsClick?.() }} > { openZendeskWindow(deploymentEdition) - onContactUsClick?.() }} > {!isCurrentWorkspaceDatasetOperator && } {showEnvTag && ( -
+
)}
-
- +
+
diff --git a/web/app/components/step-by-step-tour/__tests__/mount.spec.tsx b/web/app/components/step-by-step-tour/__tests__/mount.spec.tsx index f8b9624db1f..8fc30e513ba 100644 --- a/web/app/components/step-by-step-tour/__tests__/mount.spec.tsx +++ b/web/app/components/step-by-step-tour/__tests__/mount.spec.tsx @@ -672,6 +672,9 @@ describe('StepByStepTourMount', () => { ).toBeInTheDocument() const dismissButton = screen.getByRole('button', { name: 'Dismiss' }) + await waitFor(() => { + expect(dismissButton).toHaveFocus() + }) await user.click(dismissButton) await waitFor(() => { @@ -761,6 +764,71 @@ describe('StepByStepTourMount', () => { expect(await screen.findByRole('region', { name: 'Get to know Dify' })).toBeInTheDocument() }) + it('exposes dialog semantics and returns focus after minimizing with the keyboard', async () => { + setStepByStepTourTestState({ + manuallyEnabledWorkspaceIds: ['workspace-1'], + manuallyDisabledWorkspaceIds: [], + minimized: false, + completedTaskIds: [], + skipped: false, + }) + + renderStepByStepTourMount() + + expect( + await screen.findByRole('dialog', { + name: 'Get to know Dify', + description: 'A quick tour — about 5 minutes', + }), + ).toBeInTheDocument() + + const minimizeButton = await screen.findByRole('button', { name: 'Minimize tour' }) + minimizeButton.focus() + await user.keyboard('{Enter}') + + const restoreButton = await screen.findByRole('button', { name: 'Open step-by-step tour' }) + + await waitFor(() => { + expect(restoreButton).toHaveFocus() + }) + expect(screen.getAllByRole('button', { name: 'Open step-by-step tour' })).toHaveLength(1) + }) + + it('focuses the minimize button when the checklist opens', async () => { + setStepByStepTourTestState({ + manuallyEnabledWorkspaceIds: ['workspace-1'], + manuallyDisabledWorkspaceIds: [], + minimized: false, + completedTaskIds: ['home'], + skipped: false, + }) + + renderStepByStepTourMount() + + const minimizeButton = await screen.findByRole('button', { name: 'Minimize tour' }) + await waitFor(() => { + expect(minimizeButton).toHaveFocus() + }) + }) + + it('keeps the expanded checklist open after an outside pointer interaction', async () => { + setStepByStepTourTestState({ + manuallyEnabledWorkspaceIds: ['workspace-1'], + manuallyDisabledWorkspaceIds: [], + minimized: false, + completedTaskIds: [], + skipped: false, + }) + + renderStepByStepTourMount() + + const dialog = await screen.findByRole('dialog', { name: 'Get to know Dify' }) + await user.click(document.body) + + expect(dialog).toBeInTheDocument() + expect(localStorage.getItem(STEP_BY_STEP_TOUR_SHELL_MODE_STORAGE_KEY)).toBe('expanded') + }) + it('shows the completion prompt expanded even when the saved shell mode is collapsed', async () => { localStorage.setItem(STEP_BY_STEP_TOUR_SHELL_MODE_STORAGE_KEY, 'collapsed') setStepByStepTourTestState({ @@ -942,13 +1010,13 @@ describe('StepByStepTourMount', () => { } }) - it('completes Knowledge directly when the workspace has no Knowledge walkthrough permissions', async () => { + it('focuses Dismiss after completing Knowledge as the final task without permission', async () => { mockWorkspacePermissionKeys.value = ['app.create_and_management'] setStepByStepTourTestState({ manuallyEnabledWorkspaceIds: ['workspace-1'], manuallyDisabledWorkspaceIds: [], minimized: false, - completedTaskIds: ['home', 'studio'], + completedTaskIds: ['home', 'studio', 'integration'], skipped: false, }) @@ -973,6 +1041,10 @@ describe('StepByStepTourMount', () => { await user.click(screen.getByRole('button', { name: 'Got it' })) await expectStepByStepTourPatch({ action: 'complete_task', task_id: 'knowledge' }) + const dismissButton = await screen.findByRole('button', { name: 'Dismiss' }) + await waitFor(() => { + expect(dismissButton).toHaveFocus() + }) expect(mockRouterPush).not.toHaveBeenCalled() }) diff --git a/web/app/components/step-by-step-tour/floating-widget.tsx b/web/app/components/step-by-step-tour/floating-widget.tsx index 142ef3b1837..69cdc322fd5 100644 --- a/web/app/components/step-by-step-tour/floating-widget.tsx +++ b/web/app/components/step-by-step-tour/floating-widget.tsx @@ -1,15 +1,16 @@ 'use client' +import type { ComponentProps, RefObject } from 'react' import type { StepByStepTourTaskId, StepByStepTourTaskView } from './types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' -import { useEffect, useRef } from 'react' +import { PopoverDescription, PopoverTitle } from '@langgenius/dify-ui/popover' +import { useEffect } from 'react' export type FloatingChecklistProps = { className?: string title: string duration: string - minimized: boolean progress: { ariaValueText: string completed: number @@ -23,13 +24,12 @@ export type FloatingChecklistProps = { title: string } tasks: StepByStepTourTaskView[] + closeButtonRef: RefObject skipLabel: string minimizeLabel: string - restoreLabel: string getTaskCompleteLabel: (taskTitle: string) => string getTaskIncompleteLabel: (taskTitle: string) => string onMinimize: () => void - onRestore: () => void onSkip: () => void onCompleteTask: (taskId: StepByStepTourTaskId) => void onStartTask: (taskId: StepByStepTourTaskId) => void @@ -40,34 +40,20 @@ export function FloatingChecklist({ className, title, duration, - minimized, progress, completionPrompt, tasks, + closeButtonRef, skipLabel, minimizeLabel, - restoreLabel, getTaskCompleteLabel, getTaskIncompleteLabel, onMinimize, - onRestore, onSkip, onCompleteTask, onStartTask, onUncompleteTask, }: FloatingChecklistProps) { - if (minimized && !completionPrompt) { - return ( - - ) - } - return (
-

{title}

-

{duration}

+ {title} + + {duration} +
{!completionPrompt && ( <> @@ -93,6 +81,7 @@ export function FloatingChecklist({ {skipLabel}
) @@ -134,12 +125,13 @@ function TourCompletionPrompt({ label, onDismiss, title, -}: NonNullable) { - const dismissRef = useRef(null) - + closeButtonRef, +}: NonNullable & { + closeButtonRef: RefObject +}) { useEffect(() => { - dismissRef.current?.focus({ preventScroll: true }) - }, []) + closeButtonRef.current?.focus({ preventScroll: true }) + }, [closeButtonRef]) return (
{title}

{description}