Skip to content

Commit 1090b3e

Browse files
[WEB-5573] refactor: app rail enhancements (#8239)
* chore: app rail context added * chore: dock/undock app rail implementation * chore: refactor * chore: code refactor * chore: code refactor
1 parent fe86713 commit 1090b3e

File tree

17 files changed

+162
-22
lines changed

17 files changed

+162
-22
lines changed

apps/web/app/(all)/[workspaceSlug]/layout.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { Outlet } from "react-router";
22
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
33
import { WorkspaceContentWrapper } from "@/plane-web/components/workspace/content-wrapper";
4+
import { AppRailVisibilityProvider } from "@/plane-web/hooks/app-rail";
45
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";
56

67
export default function WorkspaceLayout() {
78
return (
89
<AuthenticationWrapper>
910
<WorkspaceAuthWrapper>
10-
<WorkspaceContentWrapper>
11-
<Outlet />
12-
</WorkspaceContentWrapper>
11+
<AppRailVisibilityProvider>
12+
<WorkspaceContentWrapper>
13+
<Outlet />
14+
</WorkspaceContentWrapper>
15+
</AppRailVisibilityProvider>
1316
</WorkspaceAuthWrapper>
1417
</AuthenticationWrapper>
1518
);

apps/web/ce/components/app-rail/app-rail-hoc.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function withDockItems<P extends WithDockItemsProps>(WrappedComponent: Re
2020
const dockItems: (AppSidebarItemData & { shouldRender: boolean })[] = [
2121
{
2222
label: "Projects",
23-
icon: <PlaneNewIcon className="size-4" />,
23+
icon: <PlaneNewIcon className="size-5" />,
2424
href: `/${workspaceSlug}/`,
2525
isActive: isProjectsPath && !isNotificationsPath,
2626
shouldRender: true,

apps/web/ce/components/workspace/content-wrapper.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@ import { observer } from "mobx-react";
33
// plane imports
44
import { cn } from "@plane/utils";
55
import { AppRailRoot } from "@/components/navigation";
6-
// plane web imports
6+
import { useAppRailVisibility } from "@/lib/app-rail";
7+
// local imports
78
import { TopNavigationRoot } from "../navigations";
89

910
export const WorkspaceContentWrapper = observer(function WorkspaceContentWrapper({
1011
children,
1112
}: {
1213
children: React.ReactNode;
1314
}) {
15+
// Use the context to determine if app rail should render
16+
const { shouldRenderAppRail } = useAppRailVisibility();
17+
1418
return (
1519
<div className="flex flex-col relative size-full overflow-hidden bg-custom-background-90 transition-all ease-in-out duration-300">
1620
<TopNavigationRoot />
1721
<div className="relative flex size-full overflow-hidden">
18-
<AppRailRoot />
22+
{/* Conditionally render AppRailRoot based on context */}
23+
{shouldRenderAppRail && <AppRailRoot />}
1924
<div
2025
className={cn(
21-
"relative size-full pb-2 pr-2 flex-grow transition-all ease-in-out duration-300 overflow-hidden"
26+
"relative size-full pl-0 pb-2 pr-2 flex-grow transition-all ease-in-out duration-300 overflow-hidden",
27+
{
28+
"pl-2": shouldRenderAppRail,
29+
}
2230
)}
2331
>
2432
{children}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./provider";
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { observer } from "mobx-react";
5+
import { AppRailVisibilityProvider as CoreProvider } from "@/lib/app-rail";
6+
7+
interface AppRailVisibilityProviderProps {
8+
children: React.ReactNode;
9+
}
10+
11+
/**
12+
* CE AppRailVisibilityProvider
13+
* Wraps core provider with isEnabled hardcoded to false
14+
*/
15+
export const AppRailVisibilityProvider = observer(({ children }: AppRailVisibilityProviderProps) => (
16+
<CoreProvider isEnabled={false}>{children}</CoreProvider>
17+
));

apps/web/core/components/navigation/app-rail-root.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { cn } from "@plane/utils";
88
import { AppSidebarItem } from "@/components/sidebar/sidebar-item";
99
// hooks
1010
import { useAppRailPreferences } from "@/hooks/use-navigation-preferences";
11+
import { useAppRailVisibility } from "@/lib/app-rail/context";
1112
// plane web imports
1213
import { DesktopSidebarWorkspaceMenu } from "@/plane-web/components/desktop";
1314
// local imports
@@ -19,6 +20,7 @@ export const AppRailRoot = observer(() => {
1920
const pathname = usePathname();
2021
// preferences
2122
const { preferences, updateDisplayMode } = useAppRailPreferences();
23+
const { isCollapsed, toggleAppRail } = useAppRailVisibility();
2224

2325
const isSettingsPath = pathname.includes(`/${workspaceSlug}/settings`);
2426
const showLabel = preferences.displayMode === "icon_with_label";
@@ -70,6 +72,10 @@ export const AppRailRoot = observer(() => {
7072
{preferences.displayMode === "icon_with_label" && <Check className="size-3.5" />}
7173
</div>
7274
</ContextMenu.Item>
75+
<ContextMenu.Separator />
76+
<ContextMenu.Item onClick={toggleAppRail}>
77+
<span className="text-xs">{isCollapsed ? "Dock App Rail" : "Undock App Rail"}</span>
78+
</ContextMenu.Item>
7379
</ContextMenu.Content>
7480
</ContextMenu.Portal>
7581
</ContextMenu>

apps/web/core/components/workspace/sidebar/help-section/root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const HelpMenuRoot = observer(function HelpMenuRoot() {
3232
<AppSidebarItem
3333
variant="button"
3434
item={{
35-
icon: <HelpCircle className="size-4" />,
35+
icon: <HelpCircle className="size-5" />,
3636
isActive: isNeedHelpOpen,
3737
}}
3838
/>

apps/web/core/components/workspace/sidebar/user-menu-root.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
33
import Link from "next/link";
44
import { useParams } from "next/navigation";
55
// icons
6-
import { LogOut, Settings } from "lucide-react";
6+
import { LogOut, Settings, Settings2 } from "lucide-react";
77
// plane imports
88
import { GOD_MODE_URL } from "@plane/constants";
99
import { useTranslation } from "@plane/i18n";
@@ -74,7 +74,7 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: Props) {
7474
maxHeight="lg"
7575
closeOnSelect
7676
>
77-
<div className="flex flex-col gap-2.5 pb-2">
77+
<div className="flex flex-col gap-2">
7878
<span className="px-2 text-custom-sidebar-text-200 truncate">{currentUser?.email}</span>
7979
<Link href={`/${workspaceSlug}/settings/account`}>
8080
<CustomMenu.MenuItem>
@@ -84,6 +84,14 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: Props) {
8484
</div>
8585
</CustomMenu.MenuItem>
8686
</Link>
87+
<Link href={`/${workspaceSlug}/settings/account/preferences`}>
88+
<CustomMenu.MenuItem>
89+
<div className="flex w-full items-center gap-2 rounded text-xs">
90+
<Settings2 className="h-4 w-4 stroke-[1.5]" />
91+
<span>Preferences</span>
92+
</div>
93+
</CustomMenu.MenuItem>
94+
</Link>
8795
</div>
8896
<div className="my-1 border-t border-custom-border-200" />
8997
<div className={`${isUserInstanceAdmin ? "pb-2" : ""}`}>

apps/web/core/components/workspace/sidebar/workspace-menu-root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
118118
<WorkspaceLogo
119119
logo={activeWorkspace?.logo_url}
120120
name={activeWorkspace?.name}
121-
classNames="border border-custom-border-200 size-7"
121+
classNames="border border-custom-border-200 rounded-md size-7"
122122
/>
123123
<h4 className="truncate text-base font-medium text-custom-text-100">
124124
{activeWorkspace?.name ?? t("loading")}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use client";
2+
3+
import { createContext, useContext } from "react";
4+
import type { IAppRailVisibilityContext } from "./types";
5+
6+
/**
7+
* Context for app-rail visibility control
8+
* Provides access to app rail enabled state, collapse state, and toggle function
9+
*/
10+
export const AppRailVisibilityContext = createContext<IAppRailVisibilityContext | undefined>(undefined);
11+
12+
/**
13+
* Hook to consume the AppRailVisibilityContext
14+
* Must be used within an AppRailVisibilityProvider
15+
*
16+
* @returns The app rail visibility context
17+
* @throws Error if used outside of AppRailVisibilityProvider
18+
*/
19+
export const useAppRailVisibility = (): IAppRailVisibilityContext => {
20+
const context = useContext(AppRailVisibilityContext);
21+
if (context === undefined) {
22+
throw new Error("useAppRailVisibility must be used within AppRailVisibilityProvider");
23+
}
24+
return context;
25+
};

0 commit comments

Comments
 (0)