diff --git a/packages/iconify-collections/assets/vender/main-nav/workspace-settings.svg b/packages/iconify-collections/assets/vender/main-nav/workspace-settings.svg
new file mode 100644
index 0000000000..0eba74b874
--- /dev/null
+++ b/packages/iconify-collections/assets/vender/main-nav/workspace-settings.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/iconify-collections/custom-vender/icons.json b/packages/iconify-collections/custom-vender/icons.json
index a6dcd536b9..bf6b4bb8a7 100644
--- a/packages/iconify-collections/custom-vender/icons.json
+++ b/packages/iconify-collections/custom-vender/icons.json
@@ -1158,6 +1158,11 @@
"body": "",
"width": 20,
"height": 20
+ },
+ "main-nav-workspace-settings": {
+ "body": "",
+ "width": 16,
+ "height": 16
}
}
}
diff --git a/packages/iconify-collections/custom-vender/info.json b/packages/iconify-collections/custom-vender/info.json
index fd25449385..27ee20eb64 100644
--- a/packages/iconify-collections/custom-vender/info.json
+++ b/packages/iconify-collections/custom-vender/info.json
@@ -1,7 +1,7 @@
{
"prefix": "custom-vender",
"name": "Dify Custom Vender",
- "total": 290,
+ "total": 291,
"version": "0.0.0-private",
"author": {
"name": "LangGenius, Inc.",
diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx
index e908b474ed..8c84a6cf0d 100644
--- a/web/app/components/main-nav/__tests__/index.spec.tsx
+++ b/web/app/components/main-nav/__tests__/index.spec.tsx
@@ -161,6 +161,7 @@ describe('MainNav', () => {
;(useProviderContext as Mock).mockReturnValue({
enableBilling: true,
isEducationAccount: false,
+ isFetchedPlan: true,
plan: { type: Plan.sandbox },
} as ProviderContextState)
;(useModalContext as Mock).mockReturnValue({
@@ -169,8 +170,8 @@ describe('MainNav', () => {
} as unknown as ModalContextState)
;(useWorkspacesContext as Mock).mockReturnValue({
workspaces: [
- { id: 'workspace-1', name: 'Solar Studio', current: true },
- { id: 'workspace-2', name: 'Evan Workspace', current: false },
+ { id: 'workspace-1', name: 'Solar Studio', plan: Plan.team, status: 'normal', created_at: 0, current: true },
+ { id: 'workspace-2', name: 'Evan Workspace', plan: Plan.sandbox, status: 'normal', created_at: 0, current: false },
],
})
;(useGetInstalledApps as Mock).mockImplementation(() => ({
@@ -190,6 +191,7 @@ describe('MainNav', () => {
it('renders primary navigation with the planned routes', () => {
renderMainNav()
+ expect(screen.getByText(Plan.team)).toBeInTheDocument()
expect(screen.getByRole('link', { name: /common.mainNav.home/ })).toHaveAttribute('href', '/explore/apps')
expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps')
expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveAttribute('href', '/datasets')
@@ -202,7 +204,24 @@ describe('MainNav', () => {
renderMainNav()
- expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveClass('bg-components-main-nav-nav-button-bg-active')
+ const datasetsLink = screen.getByRole('link', { name: /common.menus.datasets/ })
+ expect(datasetsLink.className).toContain('bg-[linear-gradient(98.077deg')
+ expect(datasetsLink).toHaveClass('main-nav-active-edge')
+ })
+
+ it('applies the Figma glass active state to the Home route', () => {
+ mockPathname = '/explore/apps'
+
+ renderMainNav()
+
+ const homeLink = screen.getByRole('link', { name: /common.mainNav.home/ })
+
+ expect(homeLink).toHaveClass(
+ 'border-transparent',
+ 'backdrop-blur-[5px]',
+ 'main-nav-active-edge',
+ )
+ expect(homeLink.className).toContain('bg-[linear-gradient(98.077deg')
})
it('dispatches the goto anything open event from the search button', () => {
@@ -219,7 +238,7 @@ describe('MainNav', () => {
it('opens workspace settings, members, provider credits, upgrade, and workspace switching actions', async () => {
renderMainNav()
- fireEvent.click(screen.getByText(/common\.mainNav\.workspace\.credits|7,500 credits/))
+ 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'))
diff --git a/web/app/components/main-nav/index.css b/web/app/components/main-nav/index.css
new file mode 100644
index 0000000000..599791c7a1
--- /dev/null
+++ b/web/app/components/main-nav/index.css
@@ -0,0 +1,41 @@
+.main-nav-active-edge::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ padding: 1px;
+ pointer-events: none;
+ background:
+ 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;
+ -webkit-mask:
+ linear-gradient(#000 0 0) content-box,
+ linear-gradient(#000 0 0);
+ mask:
+ linear-gradient(#000 0 0) content-box,
+ linear-gradient(#000 0 0);
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+}
+
+.main-nav-active-edge::after {
+ content: "";
+ position: absolute;
+ inset: -1px;
+ border: 1px solid var(--color-components-main-nav-glass-edge-highlight-first);
+ border-radius: inherit;
+ pointer-events: none;
+ box-shadow: inset 0 0 8px 0 var(--color-components-main-nav-glass-inner-glow);
+}
diff --git a/web/app/components/main-nav/index.tsx b/web/app/components/main-nav/index.tsx
index c3d07521af..3a8efcb9ea 100644
--- a/web/app/components/main-nav/index.tsx
+++ b/web/app/components/main-nav/index.tsx
@@ -68,11 +68,11 @@ type MainNavItem = {
const navItemClassName = 'group relative flex h-9 items-center gap-2 rounded-xl p-2 transition-colors'
const activeNavItemClassName = [
- 'overflow-hidden border border-components-main-nav-glass-edge-reflection-first bg-components-main-nav-nav-button-bg-active',
- 'bg-[linear-gradient(98deg,var(--color-components-main-nav-glass-surface-first)_0%,var(--color-components-main-nav-glass-surface-middle-1)_18%,var(--color-components-main-nav-glass-surface-middle-2)_59%,var(--color-components-main-nav-glass-surface-end)_100%)]',
+ 'overflow-hidden border border-transparent',
+ '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-components-main-nav-text-active 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-5),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-[-1px] before:rounded-xl before:border before:border-components-main-nav-glass-edge-highlight-first before:shadow-[inset_0px_0px_8px_0px_var(--color-components-main-nav-glass-inner-glow)] before:content-[""]',
+ 'main-nav-active-edge',
].join(' ')
const inactiveNavItemClassName = 'system-md-medium bg-components-main-nav-nav-button-bg text-components-main-nav-text hover:bg-state-base-hover hover:text-components-main-nav-text'
@@ -113,6 +113,23 @@ const NavIcon = ({
)
+const WorkspacePlanBadge = ({
+ plan,
+}: {
+ plan: Plan
+}) => {
+ if (plan !== Plan.sandbox)
+ return
+
+ return (
+
+
+ {plan}
+
+
+ )
+}
+
const WorkspaceMenuItemContent = ({
icon,
label,
@@ -124,7 +141,7 @@ const WorkspaceMenuItemContent = ({
}) => (
<>
{icon}
- {label}
+ {label}
{trailing}
>
)
@@ -135,7 +152,10 @@ const WorkspaceCard = () => {
const { workspaces } = useWorkspacesContext()
const { enableBilling, plan } = useProviderContext()
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
+ const [open, setOpen] = useState(false)
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 handlePlanClick = () => {
@@ -160,43 +180,55 @@ const WorkspaceCard = () => {
}
return (
-
-
+
+
-
-
+
-
+
+
+
{enableBilling && (
)}
-
-
-
-
-
-
-
{currentWorkspace.name}
-
-
+
+ {open && (
+
+
+
+
+
-
setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.BILLING })}
- >
- } label={t('mainNav.workspace.settings', { ns: 'common' })} />
-
-
setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.MEMBERS })}
- >
- } label={t('mainNav.workspace.inviteMembers', { ns: 'common' })} />
-
-
- {workspaces.length > 0 && (
- <>
-
-
-
+ {workspaces.length > 0 && (
+
+
{t('mainNav.workspace.switchWorkspace', { ns: 'common' })}
{workspaces.map(workspace => (
-
{
+ setOpen(false)
void handleSwitchWorkspace(workspace.id)
}}
>
@@ -253,13 +299,13 @@ const WorkspaceCard = () => {
label={workspace.name}
trailing={workspace.current ? : undefined}
/>
-
+
))}
-
- >
- )}
-
-
+
+ )}
+
+ )}
+
)
}
@@ -583,7 +629,7 @@ const MainNav = ({
const renderLogo = () => (
-
+
{systemFeatures.branding.enabled && systemFeatures.branding.application_title ? systemFeatures.branding.application_title : 'Dify'}
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
? (
@@ -599,20 +645,23 @@ const MainNav = ({
)
return (
-