From 15292ba52779299fcf4768c2564546390465fcc4 Mon Sep 17 00:00:00 2001 From: Jingyi-Dify Date: Mon, 11 May 2026 15:18:17 -0700 Subject: [PATCH] fix: align main nav interactions Update active main nav icon positioning from the refreshed Figma assets, remove the transparent active border that caused nav item jitter, and route mobile common layout through the new MainNav instead of the legacy Header. Also align workspace plan actions with the new UI contract by showing Upgrade for sandbox workspaces and View Plan for paid workspaces, both opening the pricing modal. --- .../assets/vender/main-nav/home-active.svg | 2 +- .../vender/main-nav/integrations-active.svg | 2 +- .../assets/vender/main-nav/studio-active.svg | 2 +- .../custom-vender/icons.json | 8 +- .../main-nav/__tests__/index.spec.tsx | 35 ++++- .../main-nav/__tests__/layout.spec.tsx | 8 +- .../main-nav/components/help-menu.tsx | 131 +++++++++--------- .../main-nav/components/nav-link.tsx | 2 +- .../main-nav/components/workspace-card.tsx | 16 +-- web/app/components/main-nav/layout.tsx | 16 --- 10 files changed, 113 insertions(+), 109 deletions(-) diff --git a/packages/iconify-collections/assets/vender/main-nav/home-active.svg b/packages/iconify-collections/assets/vender/main-nav/home-active.svg index ce1204492c..13dc13e094 100644 --- a/packages/iconify-collections/assets/vender/main-nav/home-active.svg +++ b/packages/iconify-collections/assets/vender/main-nav/home-active.svg @@ -1,5 +1,5 @@ - + diff --git a/packages/iconify-collections/assets/vender/main-nav/integrations-active.svg b/packages/iconify-collections/assets/vender/main-nav/integrations-active.svg index a7367871d1..351802502f 100644 --- a/packages/iconify-collections/assets/vender/main-nav/integrations-active.svg +++ b/packages/iconify-collections/assets/vender/main-nav/integrations-active.svg @@ -1,5 +1,5 @@ - + diff --git a/packages/iconify-collections/assets/vender/main-nav/studio-active.svg b/packages/iconify-collections/assets/vender/main-nav/studio-active.svg index 59826145ab..2441d4760c 100644 --- a/packages/iconify-collections/assets/vender/main-nav/studio-active.svg +++ b/packages/iconify-collections/assets/vender/main-nav/studio-active.svg @@ -1,5 +1,5 @@ - + diff --git a/packages/iconify-collections/custom-vender/icons.json b/packages/iconify-collections/custom-vender/icons.json index 5293600cfc..55d525a7d8 100644 --- a/packages/iconify-collections/custom-vender/icons.json +++ b/packages/iconify-collections/custom-vender/icons.json @@ -1,6 +1,6 @@ { "prefix": "custom-vender", - "lastModified": 1778201271, + "lastModified": 1778535177, "icons": { "features-citations": { "body": "", @@ -697,7 +697,7 @@ "height": 20 }, "main-nav-home-active": { - "body": "", + "body": "", "width": 20, "height": 20 }, @@ -707,7 +707,7 @@ "height": 20 }, "main-nav-integrations-active": { - "body": "", + "body": "", "width": 20, "height": 20 }, @@ -742,7 +742,7 @@ "height": 20 }, "main-nav-studio-active": { - "body": "", + "body": "", "width": 20, "height": 20 }, diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx index c1f16fe396..71c1a00a2c 100644 --- a/web/app/components/main-nav/__tests__/index.spec.tsx +++ b/web/app/components/main-nav/__tests__/index.spec.tsx @@ -154,9 +154,9 @@ const appContextValue: AppContextValue = { isValidatingCurrentWorkspace: false, } -const renderMainNav = () => renderWithSystemFeatures(, { - systemFeatures: { branding: { enabled: false } }, -}) +const renderMainNav = ( + systemFeatures = { branding: { enabled: false } }, +) => renderWithSystemFeatures(, { systemFeatures }) describe('MainNav', () => { beforeEach(() => { @@ -321,7 +321,6 @@ describe('MainNav', () => { const homeLink = screen.getByRole('link', { name: /common.mainNav.home/ }) expect(homeLink).toHaveClass( - 'border-transparent', 'backdrop-blur-[5px]', 'text-saas-dify-blue-inverted', activeEdgeClassName, @@ -357,13 +356,19 @@ describe('MainNav', () => { expect(mockPush).not.toHaveBeenCalled() }) - it('opens workspace settings, members, provider credits, upgrade, and workspace switching actions', async () => { + it('hides the help menu when branding is enabled', () => { + renderMainNav({ branding: { enabled: true } }) + + expect(screen.queryByRole('button', { name: 'common.mainNav.help.openMenu' })).not.toBeInTheDocument() + }) + + it('opens workspace settings, members, provider credits, plan, and workspace switching actions', async () => { renderMainNav() fireEvent.click(screen.getByRole('button', { name: /common\.mainNav\.workspace\.credits|7,500 credits/ })) expect(mockSetShowAccountSettingModal).toHaveBeenCalledWith({ payload: ACCOUNT_SETTING_TAB.PROVIDER }) - fireEvent.click(screen.getByText('billing.upgradeBtn.encourageShort')) + fireEvent.click(screen.getByText('billing.upgradeBtn.plain')) expect(mockSetShowPricingModal).toHaveBeenCalled() fireEvent.click(screen.getByRole('button', { name: 'common.mainNav.workspace.openMenu' })) @@ -381,7 +386,20 @@ describe('MainNav', () => { }) }) - it('hides the upgrade shortcut for paid plans', () => { + it('shows the upgrade shortcut for sandbox workspaces', () => { + ;(useWorkspacesContext as Mock).mockReturnValue({ + workspaces: [ + { id: 'workspace-1', name: 'Solar Studio', plan: Plan.sandbox, status: 'normal', created_at: 0, current: true }, + ], + }) + + renderMainNav() + + expect(screen.getByText('billing.upgradeBtn.encourageShort')).toBeInTheDocument() + expect(screen.queryByText('billing.upgradeBtn.plain')).not.toBeInTheDocument() + }) + + it('shows the view plan shortcut for paid workspaces', () => { ;(useProviderContext as Mock).mockReturnValue({ enableBilling: true, isEducationAccount: false, @@ -393,6 +411,9 @@ describe('MainNav', () => { renderMainNav() expect(screen.queryByText('billing.upgradeBtn.encourageShort')).not.toBeInTheDocument() + fireEvent.click(screen.getByText('billing.upgradeBtn.plain')) + expect(mockSetShowPricingModal).toHaveBeenCalled() + expect(mockSetShowAccountSettingModal).not.toHaveBeenCalledWith({ payload: ACCOUNT_SETTING_TAB.BILLING }) }) it('limits workspace settings and invite actions by role', async () => { diff --git a/web/app/components/main-nav/__tests__/layout.spec.tsx b/web/app/components/main-nav/__tests__/layout.spec.tsx index f74d016da9..41b4a7a928 100644 --- a/web/app/components/main-nav/__tests__/layout.spec.tsx +++ b/web/app/components/main-nav/__tests__/layout.spec.tsx @@ -58,14 +58,14 @@ describe('MainNavLayout', () => { expect(screen.getByText('content')).toBeInTheDocument() }) - it('keeps the current header on mobile', () => { + it('uses the main nav on mobile too', () => { mockMediaType = MediaType.mobile render(
content
) - expect(screen.getByTestId('header-wrapper')).toBeInTheDocument() - expect(screen.getByTestId('desktop-header')).toBeInTheDocument() - expect(screen.queryByTestId('main-nav')).not.toBeInTheDocument() + expect(screen.getByTestId('main-nav')).toBeInTheDocument() + expect(screen.queryByTestId('header-wrapper')).not.toBeInTheDocument() + expect(screen.queryByTestId('desktop-header')).not.toBeInTheDocument() }) it('hides the desktop main nav on fullscreen workflow canvases', () => { diff --git a/web/app/components/main-nav/components/help-menu.tsx b/web/app/components/main-nav/components/help-menu.tsx index 644eb8bc17..a508bc22a7 100644 --- a/web/app/components/main-nav/components/help-menu.tsx +++ b/web/app/components/main-nav/components/help-menu.tsx @@ -36,6 +36,9 @@ const HelpMenu = () => { const [aboutVisible, setAboutVisible] = useState(false) const [open, setOpen] = useState(false) + if (systemFeatures.branding.enabled) + return null + return ( <> @@ -54,76 +57,74 @@ const HelpMenu = () => { sideOffset={8} popupClassName="w-60 overflow-hidden bg-components-panel-bg-blur! p-0! backdrop-blur-[5px]" > - {!systemFeatures.branding.enabled && ( - <> - - + <> + + + } + /> + +
+ + + {t('mainNav.help.learnDify', { ns: 'common' })} + + event.stopPropagation()} + onCheckedChange={checked => setLearnDifyHidden(!checked)} + /> +
+ setOpen(false)} /> + {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && } +
+ + + + } + /> + + + + + + + )} + /> + + {env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && ( + { + setAboutVisible(true) + setOpen(false) + }} + > } - /> -
-
- - - {t('mainNav.help.learnDify', { ns: 'common' })} - - event.stopPropagation()} - onCheckedChange={checked => setLearnDifyHidden(!checked)} - /> -
- setOpen(false)} /> - {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && } -
- - - - } - /> - - - - - +
+
{t('about.version', { ns: 'common', version: langGeniusVersionInfo.current_version })}
+
)} /> -
- {env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && ( - { - setAboutVisible(true) - setOpen(false) - }} - > - -
{t('about.version', { ns: 'common', version: langGeniusVersionInfo.current_version })}
- - - )} - /> -
- )} -
- - )} + + )} + +
{aboutVisible && setAboutVisible(false)} langGeniusVersionInfo={langGeniusVersionInfo} />} diff --git a/web/app/components/main-nav/components/nav-link.tsx b/web/app/components/main-nav/components/nav-link.tsx index 0e4098e8c6..39a3b16b92 100644 --- a/web/app/components/main-nav/components/nav-link.tsx +++ b/web/app/components/main-nav/components/nav-link.tsx @@ -7,7 +7,7 @@ import Link from '@/next/link' const navItemClassName = 'group relative flex h-9 items-center gap-2 rounded-xl p-2 transition-colors' const activeNavItemClassName = cn( - 'overflow-hidden border border-transparent', + 'overflow-hidden', 'bg-[linear-gradient(98.077deg,rgba(0,51,255,0.08)_0%,rgba(0,51,255,0.12)_17.98%,rgba(0,51,255,0.1)_58.75%,rgba(0,51,255,0.08)_101.09%)]', 'system-md-semibold text-saas-dify-blue-inverted backdrop-blur-[5px]', 'shadow-[0px_4px_8px_0px_rgba(255,255,255,0),0px_12px_16px_-4px_rgba(9,9,11,0.08),0px_4px_6px_-2px_rgba(9,9,11,0.03),0px_10px_16px_-4px_rgba(0,51,255,0.06)]', diff --git a/web/app/components/main-nav/components/workspace-card.tsx b/web/app/components/main-nav/components/workspace-card.tsx index d627670a0a..a81d663280 100644 --- a/web/app/components/main-nav/components/workspace-card.tsx +++ b/web/app/components/main-nav/components/workspace-card.tsx @@ -60,18 +60,16 @@ const WorkspaceCard = () => { const credits = getRemainingCredits(currentWorkspace.trial_credits, currentWorkspace.trial_credits_used) const formattedCredits = formatCredits(credits) const workspacePlan = (workspaces.find(workspace => workspace.current)?.plan || currentWorkspace.plan || plan.type) as Plan - const isFreePlan = plan.type === Plan.sandbox + const isFreePlan = workspacePlan === Plan.sandbox const showCloudBilling = IS_CLOUD_EDITION && enableBilling - const showUpgradeAction = showCloudBilling && isFreePlan + const showPlanAction = showCloudBilling + const planActionLabel = t(isFreePlan ? 'upgradeBtn.encourageShort' : 'upgradeBtn.plain', { ns: 'billing' }) const showWorkspaceSettings = !isCurrentWorkspaceDatasetOperator const showInviteMembers = showWorkspaceSettings && isCurrentWorkspaceManager const renderWorkspaceStatus = () => enableBilling ? : const handlePlanClick = () => { - if (isFreePlan) - setShowPricingModal() - else - setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.BILLING }) + setShowPricingModal() } const handleSwitchWorkspace = async (tenant_id: string) => { @@ -135,17 +133,17 @@ const WorkspaceCard = () => { {formattedCredits} {t('mainNav.workspace.creditsUnit', { ns: 'common' })} - {showUpgradeAction && ( + {showPlanAction && ( )} diff --git a/web/app/components/main-nav/layout.tsx b/web/app/components/main-nav/layout.tsx index df8e046c97..c6f081aa7a 100644 --- a/web/app/components/main-nav/layout.tsx +++ b/web/app/components/main-nav/layout.tsx @@ -4,11 +4,8 @@ import type { ReactNode } from 'react' import type { EventEmitterValue } from '@/context/event-emitter' import { cn } from '@langgenius/dify-ui/cn' import { useState } from 'react' -import Header from '@/app/components/header' -import HeaderWrapper from '@/app/components/header/header-wrapper' import { useEventEmitterContextContext } from '@/context/event-emitter' import { WorkspaceProvider } from '@/context/workspace-context-provider' -import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { usePathname } from '@/next/navigation' import MainNav from './index' @@ -19,8 +16,6 @@ type MainNavLayoutProps = { const MainNavLayout = ({ children, }: MainNavLayoutProps) => { - const media = useBreakpoints() - const isMobile = media === MediaType.mobile const pathname = usePathname() const inWorkflowCanvas = pathname.endsWith('/workflow') const isPipelineCanvas = pathname.endsWith('/pipeline') @@ -34,17 +29,6 @@ const MainNavLayout = ({ setHideMainNav(v.payload) }) - if (isMobile) { - return ( - <> - -
- - {children} - - ) - } - return (