From 4573aaa7170910fdc595e77ebc84146acacf0a2b Mon Sep 17 00:00:00 2001 From: Jingyi Date: Tue, 23 Jun 2026 22:46:53 -0700 Subject: [PATCH] fix(web): polish onboarding main nav and preferences tab (#37844) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: yyh --- eslint-suppressions.json | 5 -- packages/dify-ui/src/themes/dark.css | 1 + packages/dify-ui/src/themes/light.css | 1 + packages/dify-ui/src/themes/theme.css | 1 + .../account-dropdown/__tests__/index.spec.tsx | 2 +- .../main-nav-menu-content.tsx | 2 +- .../__tests__/constants.spec.ts | 3 + .../account-setting/__tests__/index.spec.tsx | 11 +++ .../update-setting-dialog-form.spec.tsx | 88 +++++++++++++++++++ .../header/account-setting/constants.ts | 2 + .../header/account-setting/index.tsx | 20 +++-- .../__tests__/index.spec.tsx | 10 +-- .../index.tsx | 2 +- .../update-setting-dialog-form.tsx | 2 +- .../main-nav/__tests__/index.spec.tsx | 23 ++--- .../main-nav/components/nav-link.css | 48 ++++++++++ .../main-nav/components/nav-link.tsx | 25 ++---- web/app/components/main-nav/index.tsx | 2 +- .../__tests__/index.spec.tsx | 31 ++++++- .../auto-update-setting/index.tsx | 2 +- web/app/styles/tailwind-core.css | 1 + web/context/modal-context.test.tsx | 4 +- 22 files changed, 225 insertions(+), 61 deletions(-) create mode 100644 web/app/components/header/account-setting/__tests__/update-setting-dialog-form.spec.tsx rename web/app/components/header/account-setting/{language-page => preference-page}/__tests__/index.spec.tsx (96%) rename web/app/components/header/account-setting/{language-page => preference-page}/index.tsx (99%) create mode 100644 web/app/components/main-nav/components/nav-link.css diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 5c1d04ee120..48f94044d8d 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -3668,11 +3668,6 @@ "count": 1 } }, - "web/app/components/header/account-setting/language-page/__tests__/index.spec.tsx": { - "jsx-a11y/role-has-required-aria-props": { - "count": 1 - } - }, "web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx": { "jsx-a11y/no-autofocus": { "count": 1 diff --git a/packages/dify-ui/src/themes/dark.css b/packages/dify-ui/src/themes/dark.css index 1b24e8fb489..3f4a163a725 100644 --- a/packages/dify-ui/src/themes/dark.css +++ b/packages/dify-ui/src/themes/dark.css @@ -162,6 +162,7 @@ html[data-theme="dark"] { --color-components-main-nav-glass-surface-middle-2: #0033ff1a; --color-components-main-nav-glass-surface-end: #0033ff14; --color-components-main-nav-glass-edge-highlight-first: #fffffffa; + --color-components-main-nav-glass-edge-highlight-middle: #ffffff00; --color-components-main-nav-glass-edge-highlight-end: #ffffff6b; --color-components-main-nav-glass-edge-reflection-first: #0033ff00; --color-components-main-nav-glass-edge-reflection-middle: #0033ff99; diff --git a/packages/dify-ui/src/themes/light.css b/packages/dify-ui/src/themes/light.css index 3feb4afb47f..dd3252f3614 100644 --- a/packages/dify-ui/src/themes/light.css +++ b/packages/dify-ui/src/themes/light.css @@ -162,6 +162,7 @@ html[data-theme="light"] { --color-components-main-nav-glass-surface-middle-2: #0033ff1a; --color-components-main-nav-glass-surface-end: #0033ff14; --color-components-main-nav-glass-edge-highlight-first: #fffffffa; + --color-components-main-nav-glass-edge-highlight-middle: #ffffff00; --color-components-main-nav-glass-edge-highlight-end: #ffffff6b; --color-components-main-nav-glass-edge-reflection-first: #0033ff00; --color-components-main-nav-glass-edge-reflection-middle: #0033ff99; diff --git a/packages/dify-ui/src/themes/theme.css b/packages/dify-ui/src/themes/theme.css index 3e35feb8eb8..c14e54ea549 100644 --- a/packages/dify-ui/src/themes/theme.css +++ b/packages/dify-ui/src/themes/theme.css @@ -169,6 +169,7 @@ --color-components-main-nav-glass-surface-middle-2: var(--color-components-main-nav-glass-surface-middle-2); --color-components-main-nav-glass-surface-end: var(--color-components-main-nav-glass-surface-end); --color-components-main-nav-glass-edge-highlight-first: var(--color-components-main-nav-glass-edge-highlight-first); + --color-components-main-nav-glass-edge-highlight-middle: var(--color-components-main-nav-glass-edge-highlight-middle); --color-components-main-nav-glass-edge-highlight-end: var(--color-components-main-nav-glass-edge-highlight-end); --color-components-main-nav-glass-edge-reflection-first: var(--color-components-main-nav-glass-edge-reflection-first); --color-components-main-nav-glass-edge-reflection-middle: var(--color-components-main-nav-glass-edge-reflection-middle); diff --git a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx index 81b08046a94..741580f6a5c 100644 --- a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx +++ b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx @@ -248,7 +248,7 @@ describe('AccountDropdown', () => { fireEvent.click(screen.getByText('common.settings.preferences')) // Assert - expect(mockSetShowAccountSettingModal).toHaveBeenCalledWith({ payload: ACCOUNT_SETTING_TAB.LANGUAGE }) + expect(mockSetShowAccountSettingModal).toHaveBeenCalledWith({ payload: ACCOUNT_SETTING_TAB.PREFERENCES }) }) it('should show Appearance after Preferences in the main nav account dropdown', () => { diff --git a/web/app/components/header/account-dropdown/main-nav-menu-content.tsx b/web/app/components/header/account-dropdown/main-nav-menu-content.tsx index ee2ebcc981e..1b84397a1ed 100644 --- a/web/app/components/header/account-dropdown/main-nav-menu-content.tsx +++ b/web/app/components/header/account-dropdown/main-nav-menu-content.tsx @@ -127,7 +127,7 @@ export function MainNavMenuContent({ setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.LANGUAGE })} + onClick={() => setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.PREFERENCES })} > { expect(ACCOUNT_SETTING_TAB.DATA_SOURCE).toBe('data-source') expect(ACCOUNT_SETTING_TAB.API_BASED_EXTENSION).toBe('custom-endpoint') expect(ACCOUNT_SETTING_TAB.CUSTOM).toBe('custom') + expect(ACCOUNT_SETTING_TAB.PREFERENCES).toBe('preferences') expect(ACCOUNT_SETTING_TAB.LANGUAGE).toBe('language') }) @@ -42,6 +43,7 @@ describe('AccountSetting Constants', () => { expect(isValidAccountSettingTab('data-source')).toBe(true) expect(isValidAccountSettingTab('custom-endpoint')).toBe(true) expect(isValidAccountSettingTab('custom')).toBe(true) + expect(isValidAccountSettingTab('preferences')).toBe(true) expect(isValidAccountSettingTab('language')).toBe(true) }) @@ -55,6 +57,7 @@ describe('AccountSetting Constants', () => { expect(isValidSettingsTab('permissions')).toBe(true) expect(isValidSettingsTab('access-rules')).toBe(true) expect(isValidSettingsTab('billing')).toBe(true) + expect(isValidSettingsTab('preferences')).toBe(true) expect(isValidSettingsTab('language')).toBe(true) expect(isValidSettingsTab('provider')).toBe(true) expect(isValidSettingsTab('mcp')).toBe(true) diff --git a/web/app/components/header/account-setting/__tests__/index.spec.tsx b/web/app/components/header/account-setting/__tests__/index.spec.tsx index ec1ac9887d2..7113714ac4f 100644 --- a/web/app/components/header/account-setting/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/__tests__/index.spec.tsx @@ -257,6 +257,17 @@ describe('AccountSetting', () => { expect(screen.getByText('common.settings.dataSource'))!.toBeInTheDocument() }) + it('should normalize legacy language tab entries to preferences', () => { + // Act + renderAccountSetting({ initialTab: ACCOUNT_SETTING_TAB.LANGUAGE }) + + // Assert + const preferencesButton = screen.getByRole('button', { name: 'common.settings.preferences' }) + expect(preferencesButton.querySelector('.i-ri-equalizer-2-fill')).toBeInTheDocument() + expect(screen.getByText('common.account.general')).toBeInTheDocument() + expect(screen.getByText('common.account.appearanceLabel')).toBeInTheDocument() + }) + it('should hide sidebar labels on mobile', () => { // Arrange vi.mocked(useBreakpoints).mockReturnValue(MediaType.mobile) diff --git a/web/app/components/header/account-setting/__tests__/update-setting-dialog-form.spec.tsx b/web/app/components/header/account-setting/__tests__/update-setting-dialog-form.spec.tsx new file mode 100644 index 00000000000..7ca34890a35 --- /dev/null +++ b/web/app/components/header/account-setting/__tests__/update-setting-dialog-form.spec.tsx @@ -0,0 +1,88 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import * as React from 'react' +import { AUTO_UPDATE_MODE, AUTO_UPDATE_STRATEGY } from '@/app/components/plugins/reference-setting-modal/auto-update-setting/types' +import { PluginCategoryEnum } from '@/app/components/plugins/types' +import { ACCOUNT_SETTING_TAB } from '../constants' +import UpdateSettingDialogForm from '../update-setting-dialog-form' + +const mockSetShowAccountSettingModal = vi.fn() + +vi.mock('@/context/modal-context', () => ({ + useModalContextSelector: (selector: (s: { setShowAccountSettingModal: typeof mockSetShowAccountSettingModal }) => typeof mockSetShowAccountSettingModal) => { + return selector({ setShowAccountSettingModal: mockSetShowAccountSettingModal }) + }, +})) + +vi.mock('react-i18next', () => ({ + useTranslation: (defaultNs?: string) => ({ + t: (key: string, options?: Record) => { + const ns = (options?.ns as string | undefined) ?? defaultNs + return `${ns ? `${ns}.` : ''}${key}` + }, + i18n: { + language: 'en', + changeLanguage: vi.fn(), + }, + }), + Trans: ({ i18nKey, components }: { + i18nKey: string + components?: Record + }) => { + const setTimezone = components?.setTimezone + if (setTimezone) + return React.cloneElement(setTimezone, undefined, i18nKey) + + return {i18nKey} + }, +})) + +vi.mock('@/app/components/base/date-and-time-picker/time-picker', () => ({ + default: () =>
, +})) + +vi.mock('@/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-picker', () => ({ + default: () =>
, +})) + +describe('UpdateSettingDialogForm', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should open preferences after closing the update setting dialog when timezone link is clicked', () => { + const onRequestClose = vi.fn() + + render( + minutes} + onAutoUpgradeChange={vi.fn()} + onPluginsChange={vi.fn()} + onRequestClose={onRequestClose} + onUpdateTimeChange={vi.fn()} + renderTimePickerTrigger={() => } + />, + ) + + fireEvent.click(screen.getByText('autoUpdate.changeTimezone')) + + expect(onRequestClose).toHaveBeenCalledTimes(1) + expect(mockSetShowAccountSettingModal).toHaveBeenCalledWith({ payload: ACCOUNT_SETTING_TAB.PREFERENCES }) + }) +}) diff --git a/web/app/components/header/account-setting/constants.ts b/web/app/components/header/account-setting/constants.ts index d8128631306..67a89afc0c2 100644 --- a/web/app/components/header/account-setting/constants.ts +++ b/web/app/components/header/account-setting/constants.ts @@ -12,6 +12,7 @@ export const ACCOUNT_SETTING_TAB = { DATA_SOURCE: 'data-source', API_BASED_EXTENSION: 'custom-endpoint', CUSTOM: 'custom', + PREFERENCES: 'preferences', LANGUAGE: 'language', } as const @@ -30,6 +31,7 @@ const WORKSPACE_SETTING_TAB_VALUES = [ export type WorkspaceSettingTab = typeof WORKSPACE_SETTING_TAB_VALUES[number] const USER_SETTING_TAB_VALUES = [ + ACCOUNT_SETTING_TAB.PREFERENCES, ACCOUNT_SETTING_TAB.LANGUAGE, ] as const diff --git a/web/app/components/header/account-setting/index.tsx b/web/app/components/header/account-setting/index.tsx index b04404bde17..a03fb30cea2 100644 --- a/web/app/components/header/account-setting/index.tsx +++ b/web/app/components/header/account-setting/index.tsx @@ -20,11 +20,11 @@ import { BillingPermission, hasPermission } from '@/utils/permission' import AccessRulesPage from './access-rules-page' import { ApiBasedExtensionPage } from './api-based-extension-page' import DataSourcePage from './data-source-page-new' -import LanguagePage from './language-page' import MembersPage from './members-page' import ModelProviderPage from './model-provider-page' import { useResetModelProviderListExpanded } from './model-provider-page/atoms' import PermissionsPage from './permissions-page' +import PreferencePage from './preference-page' const iconClassName = ` w-4 h-4 mr-2 @@ -58,12 +58,14 @@ export default function AccountSetting({ const isRbacEnabled = systemFeatures.rbac_enabled const canManageWorkspaceRoles = isRbacEnabled && hasPermission(workspacePermissionKeys, 'workspace.role.manage') const canViewBilling = enableBilling && hasPermission(workspacePermissionKeys, BillingPermission.View) + // Keep legacy `language` deep links opening Preferences during the tab rename migration. + const normalizedActiveTab = activeTab === ACCOUNT_SETTING_TAB.LANGUAGE ? ACCOUNT_SETTING_TAB.PREFERENCES : activeTab const activeMenu = (() => { - if (activeTab === ACCOUNT_SETTING_TAB.BILLING && !canViewBilling) - return ACCOUNT_SETTING_TAB.LANGUAGE - if ((activeTab === ACCOUNT_SETTING_TAB.PERMISSIONS || activeTab === ACCOUNT_SETTING_TAB.ACCESS_RULES) && !canManageWorkspaceRoles) + if (normalizedActiveTab === ACCOUNT_SETTING_TAB.BILLING && !canViewBilling) + return ACCOUNT_SETTING_TAB.PREFERENCES + if ((normalizedActiveTab === ACCOUNT_SETTING_TAB.PERMISSIONS || normalizedActiveTab === ACCOUNT_SETTING_TAB.ACCESS_RULES) && !canManageWorkspaceRoles) return ACCOUNT_SETTING_TAB.MEMBERS - return activeTab + return normalizedActiveTab })() const scrollContainerRef = useRef(null) @@ -119,7 +121,7 @@ export default function AccountSetting({ activeIcon: , }, { - key: ACCOUNT_SETTING_TAB.LANGUAGE, + key: ACCOUNT_SETTING_TAB.PREFERENCES, name: t('settings.preferences', { ns: 'common' }), title: t('account.general', { ns: 'common' }), icon: , @@ -151,7 +153,7 @@ export default function AccountSetting({ const media = useBreakpoints() const isMobile = media === MediaType.mobile - const languageItem = settingItems.find(item => item.key === ACCOUNT_SETTING_TAB.LANGUAGE) + const preferenceItem = settingItems.find(item => item.key === ACCOUNT_SETTING_TAB.PREFERENCES) const menuItems = [ { @@ -161,7 +163,7 @@ export default function AccountSetting({ }, { key: 'user-group', - items: languageItem ? [languageItem] : [], + items: preferenceItem ? [preferenceItem] : [], }, ] @@ -266,7 +268,7 @@ export default function AccountSetting({ {activeMenu === ACCOUNT_SETTING_TAB.DATA_SOURCE && } {activeMenu === ACCOUNT_SETTING_TAB.API_BASED_EXTENSION && } {activeMenu === ACCOUNT_SETTING_TAB.CUSTOM && } - {activeMenu === ACCOUNT_SETTING_TAB.LANGUAGE && } + {activeMenu === ACCOUNT_SETTING_TAB.PREFERENCES && }
diff --git a/web/app/components/header/account-setting/language-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/preference-page/__tests__/index.spec.tsx similarity index 96% rename from web/app/components/header/account-setting/language-page/__tests__/index.spec.tsx rename to web/app/components/header/account-setting/preference-page/__tests__/index.spec.tsx index edeb14cb1c3..55ee81481ad 100644 --- a/web/app/components/header/account-setting/language-page/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/preference-page/__tests__/index.spec.tsx @@ -4,7 +4,7 @@ import { act, fireEvent, render, screen, waitFor, within } from '@testing-librar import { languages } from '@/i18n-config/language' import { updateUserProfile } from '@/service/common' import { timezones } from '@/utils/timezone' -import LanguagePage from '../index' +import PreferencePage from '../index' const mockRefresh = vi.fn() const mockMutateUserProfile = vi.fn() @@ -54,7 +54,7 @@ vi.mock('@langgenius/dify-ui/select', async () => { SelectItem: ({ children, value }: { children: React.ReactNode, value: string }) => { const context = React.useContext(SelectContext) return ( - ) @@ -104,7 +104,7 @@ const createUserProfile = (overrides: Partial = {}): const renderPage = () => { render( <> - + , ) @@ -150,7 +150,7 @@ beforeEach(() => { }) // Rendering -describe('LanguagePage - Rendering', () => { +describe('PreferencePage - Rendering', () => { it('should render default language and timezone labels', () => { const english = getLanguageOption('en-US') const niueTimezone = getTimezoneOption('Pacific/Niue') @@ -182,7 +182,7 @@ describe('LanguagePage - Rendering', () => { }) // Interactions -describe('LanguagePage - Interactions', () => { +describe('PreferencePage - Interactions', () => { it('should show success toast when language updates', async () => { const chinese = getLanguageOption('zh-Hans') mockUserProfile = createUserProfile({ interface_language: 'en-US' }) diff --git a/web/app/components/header/account-setting/language-page/index.tsx b/web/app/components/header/account-setting/preference-page/index.tsx similarity index 99% rename from web/app/components/header/account-setting/language-page/index.tsx rename to web/app/components/header/account-setting/preference-page/index.tsx index fabd84b4c71..ce59fe59740 100644 --- a/web/app/components/header/account-setting/language-page/index.tsx +++ b/web/app/components/header/account-setting/preference-page/index.tsx @@ -33,7 +33,7 @@ const isThemeOption = (value: string): value is ThemeOption => { return (themes as readonly string[]).includes(value) } -export default function LanguagePage() { +export default function PreferencePage() { const locale = useLocale() const { userProfile, mutateUserProfile } = useAppContext() const [editing, setEditing] = useState(false) diff --git a/web/app/components/header/account-setting/update-setting-dialog-form.tsx b/web/app/components/header/account-setting/update-setting-dialog-form.tsx index 65efcf9c9d1..bdfbd5720ab 100644 --- a/web/app/components/header/account-setting/update-setting-dialog-form.tsx +++ b/web/app/components/header/account-setting/update-setting-dialog-form.tsx @@ -53,7 +53,7 @@ function SettingTimeZone({ className="cursor-pointer border-none bg-transparent p-0 text-left body-xs-regular text-text-accent focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:outline-hidden" onClick={() => { onRequestClose() - setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.LANGUAGE }) + setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.PREFERENCES }) }} > {children} diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx index d0ef0b0b67c..38ea4644ee3 100644 --- a/web/app/components/main-nav/__tests__/index.spec.tsx +++ b/web/app/components/main-nav/__tests__/index.spec.tsx @@ -23,7 +23,8 @@ import { AppModeEnum } from '@/types/app' import MainNav from '../index' import { DETAIL_SIDEBAR_STORAGE_KEY } from '../storage' -const activeEdgeClassName = 'before:pointer-events-none' +const activeGradientMaskClassName = 'aria-[current=page]:main-nav-active-glass' +const activeStackingClassName = 'aria-[current=page]:z-1' const { mockIsAgentV2Enabled, mockSwitchWorkspace, mockToastSuccess, hotkeyRegistrations } = vi.hoisted(() => ({ mockSwitchWorkspace: vi.fn(), @@ -428,7 +429,7 @@ describe('MainNav', () => { expect(logoLink.parentElement).toHaveClass('pt-3', 'pr-2', 'pb-2', 'pl-4') const homeLink = screen.getByRole('link', { name: /common.mainNav.home/ }) - expect(homeLink.closest('nav')).toHaveClass('flex', 'flex-col', 'gap-px', 'p-2') + expect(homeLink.closest('nav')).toHaveClass('isolate', 'flex', 'flex-col', 'gap-px', 'p-2') expect(homeLink).toHaveClass('h-8', 'w-full', 'rounded-[10px]', 'px-2', 'py-1.5') const webAppsButton = screen.getByRole('button', { name: 'explore.sidebar.webApps' }) @@ -566,8 +567,7 @@ describe('MainNav', () => { renderMainNav() const datasetsLink = screen.getByRole('link', { name: /common.menus.datasets/ }) - expect(datasetsLink.className).toContain('bg-[linear-gradient(98.077deg') - expect(datasetsLink).toHaveClass(activeEdgeClassName) + expect(datasetsLink).toHaveClass(activeGradientMaskClassName) expect(datasetsLink).toHaveAttribute('aria-current', 'page') expect(screen.getByRole('link', { name: /common.mainNav.home/ })).not.toHaveAttribute('aria-current') }) @@ -578,7 +578,7 @@ describe('MainNav', () => { renderMainNav() const studioLink = screen.getByRole('link', { name: /common.menus.apps/ }) - expect(studioLink).toHaveClass(activeEdgeClassName) + expect(studioLink).toHaveClass(activeGradientMaskClassName) expect(studioLink).toHaveAttribute('aria-current', 'page') expect(screen.getByRole('link', { name: /common.mainNav.home/ })).not.toHaveAttribute('aria-current') }) @@ -854,7 +854,7 @@ describe('MainNav', () => { renderMainNav() const marketplaceLink = screen.getByRole('link', { name: /common.mainNav.marketplace/ }) - expect(marketplaceLink).toHaveClass(activeEdgeClassName) + expect(marketplaceLink).toHaveClass(activeGradientMaskClassName) }) it('marks roster active on roster routes', () => { @@ -863,7 +863,7 @@ describe('MainNav', () => { renderMainNav() const rosterLink = screen.getByRole('link', { name: /common.menus.roster/ }) - expect(rosterLink).toHaveClass(activeEdgeClassName) + expect(rosterLink).toHaveClass(activeGradientMaskClassName) expect(rosterLink).toHaveAttribute('aria-current', 'page') }) @@ -874,13 +874,8 @@ describe('MainNav', () => { const homeLink = screen.getByRole('link', { name: /common.mainNav.home/ }) - expect(homeLink).toHaveClass( - 'backdrop-blur-[5px]', - 'text-saas-dify-blue-inverted', - activeEdgeClassName, - 'after:border-components-main-nav-glass-edge-highlight-first', - ) - expect(homeLink.className).toContain('var(--color-components-main-nav-glass-surface-first)') + expect(homeLink).toHaveClass(activeGradientMaskClassName) + expect(homeLink).toHaveClass(activeStackingClassName) }) it('keeps Home active on the legacy explore apps route only', () => { diff --git a/web/app/components/main-nav/components/nav-link.css b/web/app/components/main-nav/components/nav-link.css new file mode 100644 index 00000000000..20839c746ea --- /dev/null +++ b/web/app/components/main-nav/components/nav-link.css @@ -0,0 +1,48 @@ +@utility main-nav-active-glass { + @apply overflow-hidden system-md-semibold text-saas-dify-blue-inverted backdrop-blur-[5px]; + + background-image: linear-gradient( + 91.46deg, + var(--color-components-main-nav-glass-surface-first, rgb(0 51 255 / 0.08)) 0%, + var(--color-components-main-nav-glass-surface-middle-1, rgb(0 51 255 / 0.12)) 17.98%, + var(--color-components-main-nav-glass-surface-middle-2, rgb(0 51 255 / 0.1)) 58.75%, + var(--color-components-main-nav-glass-surface-end, rgb(0 51 255 / 0.08)) 101.09% + ); + box-shadow: + 0 4px 8px 0 var(--color-components-main-nav-glass-shadow-reflection-glow), + 0 10px 12px -4px var(--color-shadow-shadow-4), + 0 3px 5px -2px var(--color-shadow-shadow-1), + 0 8px 16px -4px var(--color-components-main-nav-glass-shadow-reflection); + + &::before { + content: ""; + pointer-events: none; + position: absolute; + inset: 0; + border-radius: inherit; + border: 1px solid transparent; + background: linear-gradient( + 0deg, + var(--color-components-main-nav-glass-edge-reflection-first, rgb(0 51 255 / 0)) 0%, + var(--color-components-main-nav-glass-edge-reflection-middle, rgb(0 51 255 / 0.6)) 50%, + var(--color-components-main-nav-glass-edge-reflection-end, rgb(0 51 255 / 0)) 100% + ) border-box; + -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: destination-out; + mask-composite: exclude; + } + + &::after { + content: ""; + pointer-events: none; + position: absolute; + inset: 0; + border-radius: inherit; + border: 1px solid transparent; + background: linear-gradient(180deg, var(--color-components-main-nav-glass-edge-highlight-first, rgb(255 255 255 / 0.98)) 0%, var(--color-components-main-nav-glass-edge-highlight-middle, rgb(255 255 255 / 0)) 18%, var(--color-components-main-nav-glass-edge-highlight-end, rgb(255 255 255 / 0.42)) 100%) border-box; + -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: destination-out; + mask-composite: exclude; + box-shadow: inset 0 0 8px 0 var(--color-components-main-nav-glass-inner-glow); + } +} diff --git a/web/app/components/main-nav/components/nav-link.tsx b/web/app/components/main-nav/components/nav-link.tsx index 10865c94d18..628be088263 100644 --- a/web/app/components/main-nav/components/nav-link.tsx +++ b/web/app/components/main-nav/components/nav-link.tsx @@ -4,21 +4,6 @@ import type { MainNavItem } from '../types' import { cn } from '@langgenius/dify-ui/cn' import Link from '@/next/link' -const navItemClassName = 'group relative flex h-8 w-full items-center gap-2 rounded-[10px] px-2 py-1.5 outline-hidden transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-state-accent-solid' - -const activeNavItemClassName = cn( - 'overflow-hidden', - 'bg-[linear-gradient(98.077deg,var(--color-components-main-nav-glass-surface-first)_0%,var(--color-components-main-nav-glass-surface-middle-1)_17.98%,var(--color-components-main-nav-glass-surface-middle-2)_58.75%,var(--color-components-main-nav-glass-surface-end)_101.09%)]', - 'system-md-semibold text-saas-dify-blue-inverted backdrop-blur-[5px]', - 'shadow-[0px_4px_8px_0px_var(--color-components-main-nav-glass-shadow-reflection-glow),0px_12px_16px_-4px_var(--color-shadow-shadow-4),0px_4px_6px_-2px_var(--color-shadow-shadow-1),0px_10px_16px_-4px_var(--color-components-main-nav-glass-shadow-reflection)]', - 'before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:p-px before:content-[\'\']', - 'before:bg-[linear-gradient(var(--color-components-main-nav-glass-edge-highlight-first),var(--color-components-main-nav-glass-edge-highlight-first))_top/100%_1px_no-repeat,linear-gradient(var(--color-components-main-nav-glass-edge-highlight-end),var(--color-components-main-nav-glass-edge-highlight-end))_bottom/100%_1px_no-repeat,linear-gradient(180deg,var(--color-components-main-nav-glass-edge-reflection-first)_0%,var(--color-components-main-nav-glass-edge-reflection-middle)_50%,var(--color-components-main-nav-glass-edge-reflection-end)_100%)_left/1px_100%_no-repeat,linear-gradient(180deg,var(--color-components-main-nav-glass-edge-reflection-first)_0%,var(--color-components-main-nav-glass-edge-reflection-middle)_50%,var(--color-components-main-nav-glass-edge-reflection-end)_100%)_right/1px_100%_no-repeat]', - 'before:[mask-composite:exclude] before:[-webkit-mask-composite:xor] before:[-webkit-mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] before:[mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)]', - 'after:pointer-events-none after:absolute after:inset-[-1px] after:rounded-[inherit] after:border after:border-components-main-nav-glass-edge-highlight-first after:shadow-[inset_0_0_8px_0_var(--color-components-main-nav-glass-inner-glow)] after:content-[\'\']', -) - -const inactiveNavItemClassName = 'system-md-medium bg-components-main-nav-nav-button-bg text-components-main-nav-nav-button-text hover:bg-components-main-nav-nav-button-bg-hover hover:text-components-main-nav-nav-button-text' - const NavIcon = ({ icon, className, @@ -46,12 +31,14 @@ const MainNavLink = ({ aria-current={activated ? 'page' : undefined} title={item.label} className={cn( - navItemClassName, - activated ? activeNavItemClassName : inactiveNavItemClassName, + 'group relative flex h-8 w-full items-center gap-2 rounded-[10px] px-2 py-1.5 outline-hidden transition-colors focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset', + 'not-aria-[current=page]:bg-components-main-nav-nav-button-bg not-aria-[current=page]:system-md-medium not-aria-[current=page]:text-components-main-nav-nav-button-text not-aria-[current=page]:hover:bg-components-main-nav-nav-button-bg-hover not-aria-[current=page]:hover:text-components-main-nav-nav-button-text', + 'aria-[current=page]:main-nav-active-glass aria-[current=page]:z-1', )} > - - {item.label} + + + {item.label} ) } diff --git a/web/app/components/main-nav/index.tsx b/web/app/components/main-nav/index.tsx index 209f6bb91e8..3d0a83c4af9 100644 --- a/web/app/components/main-nav/index.tsx +++ b/web/app/components/main-nav/index.tsx @@ -297,7 +297,7 @@ const MainNav = ({ ? null : ( <> -