[WEB-5524] improvement: update desktop app layout as per new navigation (#4901)

- Removed the DesktopAppProviderRoot component and replaced it with DesktopHeaderProvider for better structure.
- Introduced DesktopSidebarWorkspaceMenu and updated sidebar toggle visibility logic.
- Enhanced WorkspaceMenuRoot to accept a variant prop for better layout control.
- Cleaned up unused imports and adjusted CSS class names for consistency.
- Removed SidebarDropdown component as part of the refactor.
This commit is contained in:
Prateek Shourya
2025-11-28 19:06:31 +05:30
committed by GitHub
parent cebde184b7
commit bf6012df9c
11 changed files with 71 additions and 70 deletions
+1 -3
View File
@@ -15,7 +15,6 @@ import icon180 from "@/app/assets/icons/icon-180x180.png?url";
import icon512 from "@/app/assets/icons/icon-512x512.png?url";
import ogImage from "@/app/assets/og-image.png?url";
import { LogoSpinner } from "@/components/common/logo-spinner";
import { DesktopAppProviderRoot } from "@/plane-web/components/desktop/root";
import { TrialBanner } from "@/plane-web/components/license/banner/trial-banner";
import globalStyles from "@/styles/globals.css?url";
import type { Route } from "./+types/root";
@@ -60,11 +59,10 @@ export function Layout({ children }: { children: ReactNode }) {
<div id="context-menu-portal" />
<div id="editor-portal" />
<AppProvider>
<DesktopAppProviderRoot />
<div
className={cn(
"h-screen w-full overflow-hidden bg-custom-background-100 relative flex flex-col",
"app-container"
"desktop-app-container"
)}
>
{/* free trial banner */}
@@ -6,8 +6,6 @@ import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-butt
// hooks
import { useAppTheme } from "@/hooks/store/use-app-theme";
import { useProjectNavigationPreferences } from "@/hooks/use-navigation-preferences";
// local imports
import { isSidebarToggleVisible } from "../desktop/helper";
export const ExtendedAppHeader = observer((props: { header: ReactNode }) => {
const { header } = props;
@@ -22,7 +20,7 @@ export const ExtendedAppHeader = observer((props: { header: ReactNode }) => {
return (
<>
{isSidebarToggleVisible() && sidebarCollapsed && shouldShowSidebarToggleButton && <AppSidebarToggleButton />}
{sidebarCollapsed && shouldShowSidebarToggleButton && <AppSidebarToggleButton />}
<div className="flex items-center gap-2 divide-x divide-custom-border-100 w-full">
<div className="w-full flex-1">{header}</div>
</div>
@@ -2,12 +2,12 @@
import { createContext, useEffect, useState, useMemo, useCallback } from "react";
// todesktop
import { nativeWindow, webContents } from "@todesktop/client-core";
import { nativeWindow } from "@todesktop/client-core";
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
// mobx
import { observer } from "mobx-react";
export type TDesktopAppContext = {
pageTitle: string;
isFullScreen: boolean;
};
@@ -20,9 +20,8 @@ export type TDesktopAppProviderProps = {
export const DesktopAppProvider = observer((props: TDesktopAppProviderProps) => {
const { children } = props;
// states
const [pageTitle, setPageTitle] = useState<string>(document.title);
const [isFullScreen, setIsFullScreen] = useState<boolean>(false);
// derived
// handle theme change
const handleThemeChange = useCallback(() => {
const currentTheme = localStorage.getItem("theme") || "light";
@@ -30,16 +29,11 @@ export const DesktopAppProvider = observer((props: TDesktopAppProviderProps) =>
}, []);
useEffect(() => {
if (!isDesktopApp()) return;
const cleanupFunctions: (() => Promise<void> | void)[] = [];
// Setup observers
const setupTitleChangeObserver = async () => {
const unsubscribe = await webContents.on("page-title-updated", () => {
setPageTitle(document.title);
});
cleanupFunctions.push(unsubscribe);
};
const setupFullScreenObservers = async () => {
setIsFullScreen(await nativeWindow.isFullscreen());
@@ -60,7 +54,6 @@ export const DesktopAppProvider = observer((props: TDesktopAppProviderProps) =>
// Call observers
handleThemeChange();
setupTitleChangeObserver();
setupFullScreenObservers();
setupThemeObserver();
@@ -70,7 +63,7 @@ export const DesktopAppProvider = observer((props: TDesktopAppProviderProps) =>
};
}, [handleThemeChange]);
const contextValue = useMemo(() => ({ pageTitle, isFullScreen }), [pageTitle, isFullScreen]);
const contextValue = useMemo(() => ({ isFullScreen }), [isFullScreen]);
return <DesktopAppContext.Provider value={contextValue}>{children}</DesktopAppContext.Provider>;
});
+2
View File
@@ -4,3 +4,5 @@ export * from "./sidebar-toggle";
export * from "./navigation";
export * from "./use-desktop-app";
export * from "./helper";
export * from "./sidebar-workspace-menu";
@@ -36,7 +36,8 @@ export const DesktopAppNavigation = observer(() => {
observerNavigationEvents();
return () => {
unsubscribeNavigationEvent && unsubscribeNavigationEvent();
if (!unsubscribeNavigationEvent) return;
unsubscribeNavigationEvent();
};
}, []);
+24 -31
View File
@@ -1,49 +1,42 @@
"use client";
import type { FC } from "react";
// to desktop
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
// mobx
import { useMemo } from "react";
import { isDesktopApp as isDesktopAppFn } from "@todesktop/client-core/platform/todesktop";
import { observer } from "mobx-react";
// helpers
import { cn } from "@plane/utils";
// desktop app components
import { SidebarToggle, DesktopAppNavigation, useDesktopApp, DesktopAppProvider } from "@/plane-web/components/desktop";
import {
DesktopSidebarToggle,
DesktopAppNavigation,
useDesktopApp,
DesktopAppProvider,
} from "@/plane-web/components/desktop";
const DesktopAppRoot: FC = observer(() => {
const DesktopHeaderRoot = observer(function DesktopHeaderRoot() {
// store hooks
const { pageTitle, isFullScreen } = useDesktopApp();
const { isFullScreen } = useDesktopApp();
// derived values
const isDesktopApp = useMemo(() => isDesktopAppFn(), []);
if (!isDesktopApp) return null;
return (
<>
<div className="header fixed top-0 left-0 flex gap-4 items-center w-full h-8">
<div
className={cn(
"flex flex-shrink-0 gap-0.5 items-center justify-end w-[160px] transition-all duration-300 ease-in-out",
{
"pl-3 justify-start": isFullScreen,
}
)}
>
<SidebarToggle />
<DesktopAppNavigation />
</div>
<div className="flex gap-2 grow items-center justify-center">
{/* page title */}
<div className="text-xs font-semibold text-custom-text-300 truncate">{pageTitle}</div>
</div>
<div className="w-[70px] flex flex-shrink-0 items-center justify-start" />
</div>
</>
<div
className={cn("flex flex-shrink-0 gap-0.5 transition-all duration-300 ease-in-out", {
"pl-12": isFullScreen,
"pl-20": !isFullScreen,
})}
>
<DesktopSidebarToggle />
<DesktopAppNavigation />
</div>
);
});
export const DesktopAppProviderRoot: FC = observer(() => {
if (!isDesktopApp()) return null;
export const DesktopHeaderProvider = observer(function DesktopHeaderProvider() {
return (
<DesktopAppProvider>
<DesktopAppRoot />
<DesktopHeaderRoot />
</DesktopAppProvider>
);
});
@@ -11,20 +11,20 @@ import { cn } from "@plane/utils";
// hooks
import { useAppTheme } from "@/hooks/store/use-app-theme";
export const SidebarToggle = observer(() => {
export const DesktopSidebarToggle = observer(function DesktopSidebarToggle() {
// router
const { workspaceSlug } = useParams();
const pathname = usePathname();
// store hooks
const { sidebarCollapsed, toggleSidebar } = useAppTheme();
// derived values
const isSidebarAccessible = !!workspaceSlug?.toString() || pathname.includes("/profile/");
const isSettingsPath = workspaceSlug && pathname.includes(`/${workspaceSlug}/settings`);
const isNotificationsPath = workspaceSlug && pathname.includes(`/${workspaceSlug}/notifications`);
const sidebarInaccessiblePaths = isSettingsPath || isNotificationsPath;
const isSidebarAccessible = workspaceSlug && !sidebarInaccessiblePaths;
const isSidebarCollapsed = useMemo(() => sidebarCollapsed, [sidebarCollapsed]);
if (!isSidebarAccessible) {
return null;
}
if (!isSidebarAccessible) return null;
return (
<>
<Tooltip tooltipContent={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"} position="bottom">
@@ -0,0 +1,12 @@
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
// components
import { WorkspaceMenuRoot } from "@/components/workspace/sidebar/workspace-menu-root";
export function DesktopSidebarWorkspaceMenu() {
if (!isDesktopApp()) return null;
return (
<div className="pb-1.5">
<WorkspaceMenuRoot variant="sidebar" />
</div>
);
}
@@ -1,13 +1,10 @@
import { useContext } from "react";
// todesktop
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
// context
import type { TDesktopAppContext } from "./context";
import { DesktopAppContext } from "./context";
export const useDesktopApp = (): TDesktopAppContext => {
if (!isDesktopApp()) throw new Error("useDesktopApp must be used in desktop app");
const context = useContext(DesktopAppContext);
if (context === undefined) throw new Error("useDesktopApp must be used within IssueModalProvider");
if (context === undefined) throw new Error("useDesktopApp must be used within DesktopAppProvider");
return context;
};
@@ -1,3 +1,5 @@
import { useMemo } from "react";
import { isDesktopApp as isDesktopAppFn } from "@todesktop/client-core/platform/todesktop";
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import useSWR from "swr";
@@ -20,6 +22,7 @@ import { useAppRailPreferences } from "@/hooks/use-navigation-preferences";
import { useFlag, useWorkspaceFeatures } from "@/plane-web/hooks/store";
import { usePiChat } from "@/plane-web/hooks/store/use-pi-chat";
import { EWorkspaceFeatures } from "@/plane-web/types/workspace-feature";
import { DesktopHeaderProvider } from "../desktop/root";
import { TopNavSearch } from "./top-nav-search";
export const TopNavigationRoot = observer(() => {
@@ -29,10 +32,11 @@ export const TopNavigationRoot = observer(() => {
const { isWorkspaceFeatureEnabled } = useWorkspaceFeatures();
const { unreadNotificationsCount, getUnreadNotificationsCount } = useWorkspaceNotifications();
const { preferences } = useAppRailPreferences();
// router
const { workspaceSlug, projectId, workItem } = useParams();
const pathname = usePathname();
// derived
const isDesktopApp = useMemo(() => isDesktopAppFn(), []);
// Fetch notification count
useSWR(
@@ -58,18 +62,21 @@ export const TopNavigationRoot = observer(() => {
return (
<div
className={cn("flex items-center min-h-11 w-full px-3.5 z-[27] transition-all duration-300", {
className={cn("desktop-header flex items-center min-h-11 w-full px-3.5 z-[27] transition-all duration-300", {
"px-2": !showLabel,
})}
>
{/* Workspace Menu */}
<div className="shrink-0 flex-1">
<WorkspaceMenuRoot />
{!isDesktopApp && <WorkspaceMenuRoot variant="top-navigation" />}
{isDesktopApp && <DesktopHeaderProvider />}
</div>
{/* Power K Search */}
<div className="shrink-0">{isAdvancedSearchEnabled && isOpenSearch ? <TopNavSearch /> : <TopNavPowerK />}</div>
<div className="desktop-header-actions shrink-0">
{isAdvancedSearchEnabled && isOpenSearch ? <TopNavSearch /> : <TopNavPowerK />}
</div>
{/* Additional Actions */}
<div className="shrink-0 flex-1 flex items-center justify-end">
<div className="desktop-header-actions shrink-0 flex-1 flex items-center justify-end">
<Tooltip tooltipContent="Inbox" position="bottom">
<AppSidebarItem
variant="link"
+4 -4
View File
@@ -1039,10 +1039,9 @@ div.web-view-spinner div.bar12 {
html.todesktop body {
height: 100vh;
width: 100%;
padding-top: 32px;
}
html.todesktop .app-container {
html.todesktop .desktop-app-container {
height: 100%;
width: 100%;
overflow: unset;
@@ -1051,12 +1050,13 @@ html.todesktop .app-container {
position: relative; /* Ensure it's the containing block */
}
html.todesktop .header {
html.todesktop .desktop-header {
background-color: rgb(var(--color-background-90));
-webkit-app-region: drag;
}
html.todesktop .header button {
html.todesktop .desktop-header button,
html.todesktop .desktop-header .desktop-header-actions {
-webkit-app-region: no-drag;
}