Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions apps/web/app/(all)/[workspaceSlug]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Outlet } from "react-router";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
import { WorkspaceContentWrapper } from "@/plane-web/components/workspace/content-wrapper";
import { AppRailVisibilityProvider } from "@/plane-web/hooks/app-rail";
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";

export default function WorkspaceLayout() {
return (
<AuthenticationWrapper>
<WorkspaceAuthWrapper>
<WorkspaceContentWrapper>
<Outlet />
</WorkspaceContentWrapper>
<AppRailVisibilityProvider>
<WorkspaceContentWrapper>
<Outlet />
</WorkspaceContentWrapper>
</AppRailVisibilityProvider>
</WorkspaceAuthWrapper>
</AuthenticationWrapper>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/ce/components/app-rail/app-rail-hoc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function withDockItems<P extends WithDockItemsProps>(WrappedComponent: Re
const dockItems: (AppSidebarItemData & { shouldRender: boolean })[] = [
{
label: "Projects",
icon: <PlaneNewIcon className="size-4" />,
icon: <PlaneNewIcon className="size-5" />,
href: `/${workspaceSlug}/`,
isActive: isProjectsPath && !isNotificationsPath,
shouldRender: true,
Expand Down
14 changes: 11 additions & 3 deletions apps/web/ce/components/workspace/content-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,30 @@ import { observer } from "mobx-react";
// plane imports
import { cn } from "@plane/utils";
import { AppRailRoot } from "@/components/navigation";
// plane web imports
import { useAppRailVisibility } from "@/lib/app-rail";
// local imports
import { TopNavigationRoot } from "../navigations";

export const WorkspaceContentWrapper = observer(function WorkspaceContentWrapper({
children,
}: {
children: React.ReactNode;
}) {
// Use the context to determine if app rail should render
const { shouldRenderAppRail } = useAppRailVisibility();

return (
<div className="flex flex-col relative size-full overflow-hidden bg-custom-background-90 transition-all ease-in-out duration-300">
<TopNavigationRoot />
<div className="relative flex size-full overflow-hidden">
<AppRailRoot />
{/* Conditionally render AppRailRoot based on context */}
{shouldRenderAppRail && <AppRailRoot />}
<div
className={cn(
"relative size-full pb-2 pr-2 flex-grow transition-all ease-in-out duration-300 overflow-hidden"
"relative size-full pl-0 pb-2 pr-2 flex-grow transition-all ease-in-out duration-300 overflow-hidden",
{
"pl-2": shouldRenderAppRail,
}
)}
>
{children}
Expand Down
1 change: 1 addition & 0 deletions apps/web/ce/hooks/app-rail/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./provider";
17 changes: 17 additions & 0 deletions apps/web/ce/hooks/app-rail/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use client";

import React from "react";
import { observer } from "mobx-react";
import { AppRailVisibilityProvider as CoreProvider } from "@/lib/app-rail";

interface AppRailVisibilityProviderProps {
children: React.ReactNode;
}

/**
* CE AppRailVisibilityProvider
* Wraps core provider with isEnabled hardcoded to false
*/
export const AppRailVisibilityProvider = observer(({ children }: AppRailVisibilityProviderProps) => (
<CoreProvider isEnabled={false}>{children}</CoreProvider>
));
6 changes: 6 additions & 0 deletions apps/web/core/components/navigation/app-rail-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { cn } from "@plane/utils";
import { AppSidebarItem } from "@/components/sidebar/sidebar-item";
// hooks
import { useAppRailPreferences } from "@/hooks/use-navigation-preferences";
import { useAppRailVisibility } from "@/lib/app-rail/context";
// plane web imports
import { DesktopSidebarWorkspaceMenu } from "@/plane-web/components/desktop";
// local imports
Expand All @@ -19,6 +20,7 @@ export const AppRailRoot = observer(() => {
const pathname = usePathname();
// preferences
const { preferences, updateDisplayMode } = useAppRailPreferences();
const { isCollapsed, toggleAppRail } = useAppRailVisibility();

const isSettingsPath = pathname.includes(`/${workspaceSlug}/settings`);
const showLabel = preferences.displayMode === "icon_with_label";
Expand Down Expand Up @@ -70,6 +72,10 @@ export const AppRailRoot = observer(() => {
{preferences.displayMode === "icon_with_label" && <Check className="size-3.5" />}
</div>
</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item onClick={toggleAppRail}>
<span className="text-xs">{isCollapsed ? "Dock App Rail" : "Undock App Rail"}</span>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const HelpMenuRoot = observer(function HelpMenuRoot() {
<AppSidebarItem
variant="button"
item={{
icon: <HelpCircle className="size-4" />,
icon: <HelpCircle className="size-5" />,
isActive: isNeedHelpOpen,
}}
/>
Expand Down
12 changes: 10 additions & 2 deletions apps/web/core/components/workspace/sidebar/user-menu-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import Link from "next/link";
import { useParams } from "next/navigation";
// icons
import { LogOut, Settings } from "lucide-react";
import { LogOut, Settings, Settings2 } from "lucide-react";
// plane imports
import { GOD_MODE_URL } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
Expand Down Expand Up @@ -74,7 +74,7 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: Props) {
maxHeight="lg"
closeOnSelect
>
<div className="flex flex-col gap-2.5 pb-2">
<div className="flex flex-col gap-2">
<span className="px-2 text-custom-sidebar-text-200 truncate">{currentUser?.email}</span>
<Link href={`/${workspaceSlug}/settings/account`}>
<CustomMenu.MenuItem>
Expand All @@ -84,6 +84,14 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: Props) {
</div>
</CustomMenu.MenuItem>
</Link>
<Link href={`/${workspaceSlug}/settings/account/preferences`}>
<CustomMenu.MenuItem>
<div className="flex w-full items-center gap-2 rounded text-xs">
<Settings2 className="h-4 w-4 stroke-[1.5]" />
<span>Preferences</span>
</div>
</CustomMenu.MenuItem>
</Link>
</div>
<div className="my-1 border-t border-custom-border-200" />
<div className={`${isUserInstanceAdmin ? "pb-2" : ""}`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
<WorkspaceLogo
logo={activeWorkspace?.logo_url}
name={activeWorkspace?.name}
classNames="border border-custom-border-200 size-7"
classNames="border border-custom-border-200 rounded-md size-7"
/>
<h4 className="truncate text-base font-medium text-custom-text-100">
{activeWorkspace?.name ?? t("loading")}
Expand Down
25 changes: 25 additions & 0 deletions apps/web/core/lib/app-rail/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import { createContext, useContext } from "react";
import type { IAppRailVisibilityContext } from "./types";

/**
* Context for app-rail visibility control
* Provides access to app rail enabled state, collapse state, and toggle function
*/
export const AppRailVisibilityContext = createContext<IAppRailVisibilityContext | undefined>(undefined);

/**
* Hook to consume the AppRailVisibilityContext
* Must be used within an AppRailVisibilityProvider
*
* @returns The app rail visibility context
* @throws Error if used outside of AppRailVisibilityProvider
*/
export const useAppRailVisibility = (): IAppRailVisibilityContext => {
const context = useContext(AppRailVisibilityContext);
if (context === undefined) {
throw new Error("useAppRailVisibility must be used within AppRailVisibilityProvider");
}
return context;
};
3 changes: 3 additions & 0 deletions apps/web/core/lib/app-rail/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./context";
export * from "./provider";
export * from "./types";
46 changes: 46 additions & 0 deletions apps/web/core/lib/app-rail/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use client";

import React, { useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useLocalStorage from "@/hooks/use-local-storage";
import { AppRailVisibilityContext } from "./context";
import type { IAppRailVisibilityContext } from "./types";

interface AppRailVisibilityProviderProps {
children: React.ReactNode;
isEnabled?: boolean; // Allow override, default false
}

/**
* AppRailVisibilityProvider - manages app rail visibility state
* Base provider that accepts isEnabled as a prop
*/
export const AppRailVisibilityProvider = observer(({ children, isEnabled = false }: AppRailVisibilityProviderProps) => {
const { workspaceSlug } = useParams();

// User preference from localStorage
const { storedValue: isCollapsed, setValue: setIsCollapsed } = useLocalStorage<boolean>(
`APP_RAIL_${workspaceSlug}`,
false // Default: not collapsed (app rail visible)
);

const toggleAppRail = useCallback(() => {
setIsCollapsed(!isCollapsed);
}, [isCollapsed, setIsCollapsed]);

// Compute final visibility: enabled and not collapsed
const shouldRenderAppRail = isEnabled && !isCollapsed;

const value: IAppRailVisibilityContext = useMemo(
() => ({
isEnabled,
isCollapsed: isCollapsed ?? false,
shouldRenderAppRail,
toggleAppRail,
}),
[isEnabled, isCollapsed, shouldRenderAppRail, toggleAppRail]
);

return <AppRailVisibilityContext.Provider value={value}>{children}</AppRailVisibilityContext.Provider>;
});
26 changes: 26 additions & 0 deletions apps/web/core/lib/app-rail/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Type definitions for app-rail visibility context
*/

export interface IAppRailVisibilityContext {
/**
* Whether the app rail is enabled
*/
isEnabled: boolean;

/**
* Whether the app rail is collapsed (user preference from localStorage)
*/
isCollapsed: boolean;

/**
* Computed property: whether the app rail should actually render
* True only if isEnabled && !isCollapsed
*/
shouldRenderAppRail: boolean;

/**
* Toggle the collapse state of the app rail
*/
toggleAppRail: () => void;
}
1 change: 1 addition & 0 deletions apps/web/ee/hooks/app-rail/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "ce/hooks/app-rail";
4 changes: 2 additions & 2 deletions packages/i18n/src/locales/en/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2699,8 +2699,8 @@ export default {
// Navigation customization
customize_navigation: "Customize navigation",
personal: "Personal",
accordion_navigation_control: "Accordion navigation control",
horizontal_navigation_bar: "Horizontal navigation bar",
accordion_navigation_control: "Accordion sidebar navigation",
horizontal_navigation_bar: "Tabbed Navigation",
show_limited_projects_on_sidebar: "Show limited projects on sidebar",
enter_number_of_projects: "Enter number of projects",
pin: "Pin",
Expand Down
8 changes: 3 additions & 5 deletions packages/propel/src/icons/sub-brand/plane-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ import { IconWrapper } from "../icon-wrapper";
import type { ISvgIcons } from "../type";

export function PlaneNewIcon({ color = "currentColor", ...rest }: ISvgIcons) {
const clipPathId = React.useId();

return (
<IconWrapper color={color} clipPathId={clipPathId} {...rest}>
<IconWrapper color={color} {...rest}>
<path
d="M5.15383 9.50566V5.15381H1.34152C0.601228 5.15381 0 5.75399 0 6.49533V14.6595C0 15.3998 0.600183 16.001 1.34152 16.001H9.50568C10.246 16.001 10.8472 15.4008 10.8472 14.6595V10.8461H6.49536C5.75506 10.8461 5.15383 10.246 5.15383 9.50461V9.50566Z"
d="M10.3617 10.3629V12.8365C10.3617 13.8272 9.55787 14.6303 8.56797 14.6303H3.17365C2.18298 14.6303 1.37988 13.8272 1.37988 12.8365V7.44221C1.37988 6.45077 2.18298 5.64844 3.17365 5.64844H5.64726V8.56915C5.64726 9.55982 6.45036 10.3629 7.44103 10.3629H10.3617Z"
fill={color}
/>
<path
d="M14.66 0H6.49582C5.75553 0 5.1543 0.600183 5.1543 1.34152V5.15488H9.50615C10.2464 5.15488 10.8477 5.75506 10.8477 6.49641V10.8483H14.661C15.4013 10.8483 16.0026 10.2481 16.0026 9.50673V1.34152C16.0026 0.601229 15.4024 0 14.661 0H14.66Z"
d="M14.6291 3.17365V8.56797C14.6291 9.55864 13.826 10.3617 12.8353 10.3617H10.3625V7.44103C10.3625 6.44959 9.55864 5.64726 8.56874 5.64726H5.64803V3.17365C5.64803 2.18298 6.45113 1.37988 7.44179 1.37988H12.8361C13.8275 1.37988 14.6291 2.18375 14.6291 3.17365Z"
fill={color}
/>
</IconWrapper>
Expand Down
6 changes: 2 additions & 4 deletions packages/propel/src/icons/sub-brand/wiki-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import { IconWrapper } from "../icon-wrapper";
import type { ISvgIcons } from "../type";

export function WikiIcon({ color = "currentColor", ...rest }: ISvgIcons) {
const clipPathId = React.useId();

return (
<IconWrapper color={color} clipPathId={clipPathId} {...rest}>
<IconWrapper color={color} {...rest}>
<path
d="M15.558 6.93332L9.06623 0.441504C8.47755 -0.147168 7.5229 -0.147168 6.93332 0.441504L0.441504 6.93332C-0.147168 7.52199 -0.147168 8.47664 0.441504 9.06623L6.93332 15.558C7.52199 16.1467 8.47664 16.1467 9.06623 15.558L15.558 9.06623C16.1467 8.47755 16.1467 7.5229 15.558 6.93332ZM10.7629 9.65855C10.7629 10.2682 10.2691 10.762 9.65946 10.762H6.341C5.73133 10.762 5.23758 10.2682 5.23758 9.65855V6.34008C5.23758 5.73042 5.73133 5.23667 6.341 5.23667H9.65946C10.2691 5.23667 10.7629 5.73042 10.7629 6.34008V9.65855Z"
d="M14.1062 6.74052L9.26925 1.90354C8.57104 1.20533 7.43873 1.20533 6.74052 1.90354L1.90354 6.74052C1.20533 7.43873 1.20533 8.57103 1.90354 9.26925L6.74052 14.1062C7.43873 14.8044 8.57104 14.8044 9.26925 14.1062L14.1062 9.26925C14.8044 8.57103 14.8044 7.43873 14.1062 6.74052ZM10.3648 9.74697C10.3648 10.0877 10.0884 10.364 9.74697 10.364H6.26279C5.92211 10.364 5.64496 10.0877 5.64496 9.74697V6.26203C5.64496 5.92134 5.92134 5.6442 6.26279 5.6442H9.74697C10.0884 5.6442 10.3648 5.92057 10.3648 6.26203V9.74697Z"
fill={color}
/>
</IconWrapper>
Expand Down
Loading