From ff89d2201a92cc671cdbaf0a9f2f4d2f9e2450fd Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 12 Jun 2025 17:59:06 +0530 Subject: [PATCH 01/47] chore: sidebar peek state added to theme store --- web/core/store/theme.store.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/web/core/store/theme.store.ts b/web/core/store/theme.store.ts index b37fb0ef5f3..331c3ea322d 100644 --- a/web/core/store/theme.store.ts +++ b/web/core/store/theme.store.ts @@ -3,6 +3,7 @@ import { action, observable, makeObservable, runInAction } from "mobx"; export interface IThemeStore { // observables sidebarCollapsed: boolean | undefined; + sidebarPeek: boolean | undefined; extendedSidebarCollapsed: boolean | undefined; extendedProjectSidebarCollapsed: boolean | undefined; profileSidebarCollapsed: boolean | undefined; @@ -13,6 +14,7 @@ export interface IThemeStore { projectOverviewSidebarCollapsed: boolean | undefined; // actions toggleSidebar: (collapsed?: boolean) => void; + toggleSidebarPeek: (peek?: boolean) => void; toggleExtendedSidebar: (collapsed?: boolean) => void; toggleExtendedProjectSidebar: (collapsed?: boolean) => void; toggleProfileSidebar: (collapsed?: boolean) => void; @@ -26,7 +28,8 @@ export interface IThemeStore { export class ThemeStore implements IThemeStore { // observables sidebarCollapsed: boolean | undefined = undefined; - extendedSidebarCollapsed: boolean | undefined = true; + sidebarPeek: boolean | undefined = undefined; + extendedSidebarCollapsed: boolean | undefined = undefined; extendedProjectSidebarCollapsed: boolean | undefined = undefined; profileSidebarCollapsed: boolean | undefined = undefined; workspaceAnalyticsSidebarCollapsed: boolean | undefined = undefined; @@ -39,6 +42,7 @@ export class ThemeStore implements IThemeStore { makeObservable(this, { // observable sidebarCollapsed: observable.ref, + sidebarPeek: observable.ref, extendedSidebarCollapsed: observable.ref, extendedProjectSidebarCollapsed: observable.ref, profileSidebarCollapsed: observable.ref, @@ -49,6 +53,7 @@ export class ThemeStore implements IThemeStore { projectOverviewSidebarCollapsed: observable.ref, // action toggleSidebar: action, + toggleSidebarPeek: action, toggleExtendedSidebar: action, toggleExtendedProjectSidebar: action, toggleProfileSidebar: action, @@ -73,6 +78,18 @@ export class ThemeStore implements IThemeStore { localStorage.setItem("app_sidebar_collapsed", this.sidebarCollapsed.toString()); }; + /** + * Toggle the sidebar peek state + * @param peek + */ + toggleSidebarPeek = (peek?: boolean) => { + if (peek === undefined) { + this.sidebarPeek = !this.sidebarPeek; + } else { + this.sidebarPeek = peek; + } + }; + /** * Toggle the extended sidebar collapsed state * @param collapsed From 1fa543050fa5b787f921994aec0736032003cee5 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 15:29:42 +0530 Subject: [PATCH 02/47] chore: extended sidebar wrapper added --- .../(projects)/extended-sidebar-wrapper.tsx | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar-wrapper.tsx diff --git a/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar-wrapper.tsx b/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar-wrapper.tsx new file mode 100644 index 00000000000..dba9fb2ed17 --- /dev/null +++ b/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar-wrapper.tsx @@ -0,0 +1,50 @@ +"use client"; + +import React, { FC } from "react"; +import { observer } from "mobx-react"; +// plane imports +import { useLocalStorage } from "@plane/hooks"; +import { cn } from "@plane/utils"; +// hooks +import { useAppTheme } from "@/hooks/store"; +import useExtendedSidebarOutsideClickDetector from "@/hooks/use-extended-sidebar-overview-outside-click"; + +type Props = { + children: React.ReactNode; + extendedSidebarRef: React.RefObject; + extendedSidebarCollapsed: boolean; + handleClose: () => void; + excludedElementId: string; +}; + +export const ExtendedSidebarWrapper: FC = observer((props) => { + const { children, extendedSidebarRef, extendedSidebarCollapsed, handleClose, excludedElementId } = props; + // store hooks + const { sidebarPeek } = useAppTheme(); + const { storedValue } = useLocalStorage("sidebarWidth", 250); + + useExtendedSidebarOutsideClickDetector(extendedSidebarRef, handleClose, excludedElementId); + + return ( +
+ {children} +
+ ); +}); From bbab8f3c514c26089e2158762e4dd937f12544d7 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 15:30:35 +0530 Subject: [PATCH 03/47] chore: resizeable sidebar component added --- .../(projects)/resizeable-sidebar.tsx | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx diff --git a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx new file mode 100644 index 00000000000..e6a998a3d63 --- /dev/null +++ b/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx @@ -0,0 +1,294 @@ +"use client"; + +import React, { Dispatch, ReactElement, SetStateAction, useCallback, useEffect, useState, useRef } from "react"; +// helpers +import { cn } from "@/helpers/common.helper"; + +interface ResizableSidebarProps { + showPeek?: boolean; + togglePeek: (value?: boolean) => void; + isCollapsed?: boolean; + width: number; + setWidth: Dispatch>; + defaultWidth?: number; + minWidth?: number; + maxWidth?: number; + defaultCollapsed?: boolean; + peekDuration?: number; + toggleCollapsed: (value?: boolean) => void; + onWidthChange?: (width: number) => void; + onCollapsedChange?: (collapsed: boolean) => void; + className?: string; + children?: ReactElement; + extendedSidebar?: ReactElement; + isAnyExtendedSidebarExpanded?: boolean; +} + +export function ResizableSidebar({ + showPeek = false, + togglePeek, + peekDuration = 1000, + isCollapsed = false, + toggleCollapsed: toggleCollapsedProp, + onCollapsedChange, + width, + setWidth, + onWidthChange, + minWidth = 236, + maxWidth = 350, + className = "", + children, + extendedSidebar, + isAnyExtendedSidebarExpanded = false, +}: ResizableSidebarProps) { + // states + const [isResizing, setIsResizing] = useState(false); + const [isHoveringTrigger, setIsHoveringTrigger] = useState(false); + // refs + const peekTimeoutRef = useRef>(); + + // handlers + const setShowPeek = useCallback( + (value: boolean) => { + togglePeek(value); + }, + [togglePeek] + ); + + const handleResize = useCallback( + (e: MouseEvent) => { + if (!isResizing) return; + const newWidth = Math.min(Math.max(e.clientX, minWidth), maxWidth); + setWidth(newWidth); + }, + [isResizing, minWidth, maxWidth, setWidth] + ); + + const startResizing = useCallback(() => { + setIsResizing(true); + }, []); + + const stopResizing = useCallback(() => { + setIsResizing(false); + }, []); + + const toggleCollapsed = useCallback(() => { + toggleCollapsedProp(); + setShowPeek(false); + setIsHoveringTrigger(false); + if (peekTimeoutRef.current) { + clearTimeout(peekTimeoutRef.current); + } + }, [toggleCollapsedProp, setShowPeek]); + + const handleTriggerEnter = useCallback(() => { + if (isCollapsed) { + setIsHoveringTrigger(true); + setShowPeek(true); + if (peekTimeoutRef.current) { + clearTimeout(peekTimeoutRef.current); + } + } + }, [isCollapsed, setShowPeek]); + + const handleTriggerLeave = useCallback(() => { + if (isCollapsed && !isAnyExtendedSidebarExpanded) { + setIsHoveringTrigger(false); + peekTimeoutRef.current = setTimeout(() => { + setShowPeek(false); + }, peekDuration); + } + }, [isCollapsed, peekDuration, setShowPeek, isAnyExtendedSidebarExpanded]); + + const handlePeekEnter = useCallback(() => { + if (isCollapsed && showPeek) { + if (peekTimeoutRef.current) { + clearTimeout(peekTimeoutRef.current); + } + } + }, [isCollapsed, showPeek]); + + const handlePeekLeave = useCallback(() => { + if (isCollapsed && !isAnyExtendedSidebarExpanded) { + peekTimeoutRef.current = setTimeout(() => { + setShowPeek(false); + }, peekDuration); + } + }, [isCollapsed, peekDuration, setShowPeek, isAnyExtendedSidebarExpanded]); + + // Set up event listeners for resizing + useEffect(() => { + if (isResizing) { + document.addEventListener("mousemove", handleResize); + document.addEventListener("mouseup", stopResizing); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + } + + return () => { + document.removeEventListener("mousemove", handleResize); + document.removeEventListener("mouseup", stopResizing); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }; + }, [isResizing, handleResize, stopResizing]); + + // Clean up timeout on unmount + useEffect( + () => () => { + if (peekTimeoutRef.current) { + clearTimeout(peekTimeoutRef.current); + } + }, + [] + ); + + // Reset peek when sidebar is expanded + useEffect(() => { + if (!isCollapsed) { + setShowPeek(false); + setIsHoveringTrigger(false); + if (peekTimeoutRef.current) { + clearTimeout(peekTimeoutRef.current); + } + } + }, [isCollapsed, setShowPeek]); + + // Call external handlers when state changes + useEffect(() => { + onWidthChange?.(width); + }, [width, onWidthChange]); + + useEffect(() => { + onCollapsedChange?.(isCollapsed); + }, [isCollapsed, onCollapsedChange]); + + return ( + <> + {/* Main Sidebar */} +
+ +
+ + {/* Peek Trigger Area */} + {isCollapsed && ( +
+ )} + + {/* Peek View */} +
+ +
+ + {/* Toggle Button */} + + + {/* Extended Sidebar */} + {extendedSidebar && extendedSidebar} + + ); +} From 12c1ae6eadf9c05fb69d8ae5019b6629b15deabc Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 15:31:18 +0530 Subject: [PATCH 04/47] chore: appsidebar root component --- .../[workspaceSlug]/(projects)/_sidebar.tsx | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx diff --git a/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx new file mode 100644 index 00000000000..06be475cd6c --- /dev/null +++ b/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx @@ -0,0 +1,59 @@ +"use client"; +import { FC, useState } from "react"; +import { observer } from "mobx-react"; +// plane imports +import { useLocalStorage } from "@plane/hooks"; +// hooks +import { useAppTheme } from "@/hooks/store"; +// local imports +import { ExtendedProjectSidebar } from "./extended-project-sidebar"; +import { ExtendedAppSidebar } from "./extended-sidebar"; +import { ResizableSidebar } from "./resizeable-sidebar"; +import { AppSidebar } from "./sidebar"; + +export const ProjectAppSidebar: FC = observer(() => { + // store hooks + const { + sidebarCollapsed, + toggleSidebar, + sidebarPeek, + toggleSidebarPeek, + extendedSidebarCollapsed, + extendedProjectSidebarCollapsed, + } = useAppTheme(); + const { storedValue, setValue } = useLocalStorage("sidebarWidth", 250); + // states + const [sidebarWidth, setSidebarWidth] = useState(storedValue ?? 250); + // derived values + const anyExtendedSidebarCollapsed = extendedSidebarCollapsed || extendedProjectSidebarCollapsed; + + // handlers + const handleWidthChange = (width: number) => setValue(width); + + return ( + <> + + + + + } + isAnyExtendedSidebarExpanded={anyExtendedSidebarCollapsed} + > + + + + ); +}); From ed86b800c9daffcd4c0c751c0b0754a10f82c5cd Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 15:34:34 +0530 Subject: [PATCH 05/47] chore: updated sidebar and applied necessary changes across codebase --- .../(projects)/extended-project-sidebar.tsx | 40 ++- .../(projects)/extended-sidebar.tsx | 32 +-- .../[workspaceSlug]/(projects)/header.tsx | 25 +- .../[workspaceSlug]/(projects)/layout.tsx | 5 +- .../[workspaceSlug]/(projects)/sidebar.tsx | 76 ++---- .../sidebar/project-navigation-root.tsx | 7 +- .../workspace/sidebar/app-search.tsx | 12 +- .../sidebar/extended-sidebar-item.tsx | 3 - .../workspace/sidebar/sidebar-item.tsx | 39 +-- .../notification-app-sidebar-option.tsx | 6 +- .../components/workspace/sidebar/dropdown.tsx | 44 +--- .../sidebar/favorites/favorite-folder.tsx | 194 ++++++-------- .../common/favorite-item-title.tsx | 14 +- .../common/favorite-item-wrapper.tsx | 29 +-- .../sidebar/favorites/favorite-items/root.tsx | 31 +-- .../sidebar/favorites/favorites-menu.tsx | 169 +++++------- .../workspace/sidebar/help-section.tsx | 40 +-- .../workspace/sidebar/project-navigation.tsx | 26 +- .../workspace/sidebar/projects-list-item.tsx | 240 ++++++++---------- .../workspace/sidebar/projects-list.tsx | 119 +++------ .../workspace/sidebar/quick-actions.tsx | 17 +- .../workspace/sidebar/sidebar-menu-items.tsx | 17 +- .../workspace/sidebar/user-menu-item.tsx | 39 +-- .../workspace/sidebar/user-menu.tsx | 11 +- .../sidebar/workspace-menu-header.tsx | 16 +- .../workspace/sidebar/workspace-menu-item.tsx | 48 ++-- .../workspace/sidebar/workspace-menu.tsx | 19 +- 27 files changed, 472 insertions(+), 846 deletions(-) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx index b96f008ab69..fb92c5d3aba 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx @@ -8,15 +8,15 @@ import { Plus, Search } from "lucide-react"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui"; -import { cn, copyUrlToClipboard } from "@plane/utils"; +import { copyUrlToClipboard } from "@plane/utils"; // components import { CreateProjectModal } from "@/components/project"; import { SidebarProjectsListItem } from "@/components/workspace"; // hooks import { orderJoinedProjects } from "@/helpers/project.helper"; import { useAppTheme, useProject, useUserPermissions } from "@/hooks/store"; -import useExtendedSidebarOutsideClickDetector from "@/hooks/use-extended-sidebar-overview-outside-click"; import { TProject } from "@/plane-web/types"; +import { ExtendedSidebarWrapper } from "./extended-sidebar-wrapper"; export const ExtendedProjectSidebar = observer(() => { // refs @@ -28,7 +28,8 @@ export const ExtendedProjectSidebar = observer(() => { const { workspaceSlug } = useParams(); // store hooks const { t } = useTranslation(); - const { sidebarCollapsed, extendedProjectSidebarCollapsed, toggleExtendedProjectSidebar } = useAppTheme(); + const { sidebarPeek, toggleSidebarPeek, extendedProjectSidebarCollapsed, toggleExtendedProjectSidebar } = + useAppTheme(); const { getPartialProjectById, joinedProjectIds: joinedProjects, updateProjectView } = useProject(); const { allowPermissions } = useUserPermissions(); @@ -75,15 +76,12 @@ export const ExtendedProjectSidebar = observer(() => { EUserPermissionsLevel.WORKSPACE ); - useExtendedSidebarOutsideClickDetector( - extendedProjectSidebarRef, - () => { - if (!isProjectModalOpen) { - toggleExtendedProjectSidebar(false); - } - }, - "extended-project-sidebar-toggle" - ); + const handleClose = () => { + if (sidebarPeek) toggleSidebarPeek(false); + if (!isProjectModalOpen) { + toggleExtendedProjectSidebar(false); + } + }; const handleCopyText = (projectId: string) => { copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/issues`).then(() => { @@ -104,17 +102,11 @@ export const ExtendedProjectSidebar = observer(() => { workspaceSlug={workspaceSlug.toString()} /> )} -
@@ -159,7 +151,7 @@ export const ExtendedProjectSidebar = observer(() => { /> ))}
-
+ ); }); diff --git a/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar.tsx index baa41eb9fb9..a1eaf4a34e6 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar.tsx @@ -5,12 +5,11 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // plane imports import { EUserWorkspaceRoles, WORKSPACE_SIDEBAR_DYNAMIC_NAVIGATION_ITEMS_LINKS } from "@plane/constants"; -import { cn } from "@plane/utils"; // hooks import { useAppTheme, useWorkspace } from "@/hooks/store"; -import useExtendedSidebarOutsideClickDetector from "@/hooks/use-extended-sidebar-overview-outside-click"; // plane-web imports import { ExtendedSidebarItem } from "@/plane-web/components/workspace/sidebar"; +import { ExtendedSidebarWrapper } from "./extended-sidebar-wrapper"; export const ExtendedAppSidebar = observer(() => { // refs @@ -18,7 +17,7 @@ export const ExtendedAppSidebar = observer(() => { // routers const { workspaceSlug } = useParams(); // store hooks - const { sidebarCollapsed, extendedSidebarCollapsed, toggleExtendedSidebar } = useAppTheme(); + const { sidebarPeek, toggleSidebarPeek, extendedSidebarCollapsed, toggleExtendedSidebar } = useAppTheme(); const { updateSidebarPreference, getNavigationPreferences } = useWorkspace(); // derived values @@ -94,24 +93,17 @@ export const ExtendedAppSidebar = observer(() => { }); }; - useExtendedSidebarOutsideClickDetector( - extendedSidebarRef, - () => toggleExtendedSidebar(true), - "extended-sidebar-toggle" - ); + const handleClose = () => { + if (sidebarPeek) toggleSidebarPeek(false); + toggleExtendedSidebar(false); + }; return ( -
{sortedNavigationItems.map((item, index) => ( { handleOnNavigationItemDrop={handleOnNavigationItemDrop} /> ))} -
+ ); }); diff --git a/web/app/(all)/[workspaceSlug]/(projects)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/header.tsx index 16c106dad4a..da1007447dd 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/header.tsx @@ -1,8 +1,9 @@ "use client"; +import { observer } from "mobx-react"; import Image from "next/image"; import { useTheme } from "next-themes"; -import { Home } from "lucide-react"; +import { Home, MenuIcon } from "lucide-react"; // images import githubBlackImage from "/public/logos/github-black.png"; import githubWhiteImage from "/public/logos/github-white.png"; @@ -14,19 +15,33 @@ import { Breadcrumbs, Header } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; // constants // hooks -import { useEventTracker } from "@/hooks/store"; +import { useAppTheme, useEventTracker } from "@/hooks/store"; -export const WorkspaceDashboardHeader = () => { +export const WorkspaceDashboardHeader = observer(() => { // hooks const { captureEvent } = useEventTracker(); const { resolvedTheme } = useTheme(); + const { sidebarCollapsed, toggleSidebar, sidebarPeek, toggleSidebarPeek } = useAppTheme(); + const { t } = useTranslation(); return ( <>
-
+
+ {sidebarCollapsed && ( + + )} {
); -}; +}); diff --git a/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx b/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx index 340ec57d0d0..1e09ef39027 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx @@ -4,7 +4,8 @@ import { CommandPalette } from "@/components/command-palette"; import { AuthenticationWrapper } from "@/lib/wrappers"; // plane web components import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper"; -import { AppSidebar } from "./sidebar"; +// local imports +import { ProjectAppSidebar } from "./_sidebar"; export default function WorkspaceLayout({ children }: { children: React.ReactNode }) { return ( @@ -12,7 +13,7 @@ export default function WorkspaceLayout({ children }: { children: React.ReactNod
- +
{children}
diff --git a/web/app/(all)/[workspaceSlug]/(projects)/sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/sidebar.tsx index 27a73137e47..dbf99334507 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/sidebar.tsx @@ -8,8 +8,6 @@ import { useOutsideClickDetector } from "@plane/hooks"; import { SidebarDropdown, SidebarHelpSection, SidebarProjectsList, SidebarQuickActions } from "@/components/workspace"; import { SidebarFavoritesMenu } from "@/components/workspace/sidebar/favorites/favorites-menu"; import { SidebarMenuItems } from "@/components/workspace/sidebar/sidebar-menu-items"; -// helpers -import { cn } from "@/helpers/common.helper"; // hooks import { useAppTheme, useUserPermissions } from "@/hooks/store"; import { useFavorite } from "@/hooks/store/use-favorite"; @@ -17,8 +15,6 @@ import useSize from "@/hooks/use-window-size"; // plane web components import { SidebarAppSwitcher } from "@/plane-web/components/sidebar"; import { SidebarTeamsList } from "@/plane-web/components/workspace/sidebar/teams-sidebar-list"; -import { ExtendedProjectSidebar } from "./extended-project-sidebar"; -import { ExtendedAppSidebar } from "./extended-sidebar"; export const AppSidebar: FC = observer(() => { // store hooks @@ -52,60 +48,26 @@ export const AppSidebar: FC = observer(() => { return ( <> -
-
-
- {/* Workspace switcher and settings */} - -
- {/* App switcher */} - {canPerformWorkspaceMemberActions && } - {/* Quick actions */} - -
-
-
- - {sidebarCollapsed && ( -
- )} - {/* Favorites Menu */} - {canPerformWorkspaceMemberActions && !isFavoriteEmpty && } - {/* Teams List */} - - {/* Projects List */} - -
- {/* Help Section */} - -
+
+ {/* Workspace switcher and settings */} + +
+ {/* App switcher */} + {canPerformWorkspaceMemberActions && } + {/* Quick actions */} +
- - +
+ + {/* Favorites Menu */} + {canPerformWorkspaceMemberActions && !isFavoriteEmpty && } + {/* Teams List */} + + {/* Projects List */} + +
+ {/* Help Section */} + ); }); diff --git a/web/ce/components/sidebar/project-navigation-root.tsx b/web/ce/components/sidebar/project-navigation-root.tsx index 6b269a0a014..25a0dd9d8ca 100644 --- a/web/ce/components/sidebar/project-navigation-root.tsx +++ b/web/ce/components/sidebar/project-navigation-root.tsx @@ -7,12 +7,9 @@ import { ProjectNavigation } from "@/components/workspace"; type TProjectItemsRootProps = { workspaceSlug: string; projectId: string; - isSidebarCollapsed: boolean; }; export const ProjectNavigationRoot: FC = (props) => { - const { workspaceSlug, projectId, isSidebarCollapsed } = props; - return ( - - ); + const { workspaceSlug, projectId } = props; + return ; }; diff --git a/web/ce/components/workspace/sidebar/app-search.tsx b/web/ce/components/workspace/sidebar/app-search.tsx index 1ba0ea72c2a..6b8a94f6ba2 100644 --- a/web/ce/components/workspace/sidebar/app-search.tsx +++ b/web/ce/components/workspace/sidebar/app-search.tsx @@ -2,14 +2,11 @@ import { observer } from "mobx-react"; import { Search } from "lucide-react"; // plane imports import { useTranslation } from "@plane/i18n"; -// helpers -import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme, useCommandPalette } from "@/hooks/store"; +import { useCommandPalette } from "@/hooks/store"; export const AppSearch = observer(() => { // store hooks - const { sidebarCollapsed } = useAppTheme(); const { toggleCommandPaletteModal } = useCommandPalette(); // translation const { t } = useTranslation(); @@ -17,12 +14,7 @@ export const AppSearch = observer(() => { return ( + +
+

{favorite.name}

- -
- ) : ( - <> - -
- - - - -
- -
-

{favorite.name}

-
+
+
+ + + + } + menuButtonOnClick={() => setIsMenuActive(!isMenuActive)} + className={cn( + "opacity-0 pointer-events-none flex-shrink-0 group-hover/project-item:opacity-100 group-hover/project-item:pointer-events-auto", + { + "opacity-100 pointer-events-auto": isMenuActive, + } + )} + customButtonClassName="grid place-items-center" + placement="bottom-start" + ariaLabel={t("aria_labels.projects_sidebar.toggle_quick_actions_menu")} + > + handleRemoveFromFavorites(favorite)}> + + + Remove from favorites + + + setFolderToRename(favorite.id)}> +
+ + Rename Folder
- - - - +
+
+ setIsMenuActive(!isMenuActive)} - className={cn( - "opacity-0 pointer-events-none flex-shrink-0 group-hover/project-item:opacity-100 group-hover/project-item:pointer-events-auto", - { - "opacity-100 pointer-events-auto": isMenuActive, - } - )} - customButtonClassName="grid place-items-center" - placement="bottom-start" - ariaLabel={t("aria_labels.projects_sidebar.toggle_quick_actions_menu")} - > - handleRemoveFromFavorites(favorite)}> - - - Remove from favorites - - - setFolderToRename(favorite.id)}> -
- - Rename Folder -
-
- - - - - - )} + )} + aria-label={t( + open ? "aria_labels.projects_sidebar.close_folder" : "aria_labels.projects_sidebar.open_folder" + )} + > + +
+
{favorite.children && favorite.children.length > 0 && ( = (props) => { leaveFrom="transform scale-100 opacity-100" leaveTo="transform scale-95 opacity-0" > - + {orderBy(favorite.children, "sequence", "desc").map((child, index) => ( = observer((props) => { - const { href, title, icon, isSidebarCollapsed } = props; + const { href, title, icon } = props; // store hooks const { toggleSidebar } = useAppTheme(); const { isMobile } = usePlatformOS(); - const linkClass = "flex items-center gap-1.5 truncate w-full"; - const collapsedClass = - "group/project-item cursor-pointer relative group w-full flex items-center justify-center gap-1.5 rounded px-2 py-1 outline-none text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90 truncate p-0 size-8 aspect-square mx-auto"; - const handleOnClick = () => { if (isMobile) toggleSidebar(); }; return ( - - + + {icon} - {!isSidebarCollapsed && {title}} + {title} ); diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-wrapper.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-wrapper.tsx index f1ffde408ca..e97b2b95ed1 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-wrapper.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/common/favorite-item-wrapper.tsx @@ -7,28 +7,23 @@ type Props = { children: React.ReactNode; elementRef: React.RefObject; isMenuActive?: boolean; - sidebarCollapsed?: boolean; }; export const FavoriteItemWrapper: FC = (props) => { - const { children, elementRef, isMenuActive = false, sidebarCollapsed = false } = props; + const { children, elementRef, isMenuActive = false } = props; return ( <> - {sidebarCollapsed ? ( -
{children}
- ) : ( -
- {children} -
- )} +
+ {children} +
); }; diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index 49931802e84..1b5c9436e4c 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -27,7 +27,6 @@ import { FavoriteItemTitle, } from "@/components/workspace/sidebar/favorites"; // hooks -import { useAppTheme } from "@/hooks/store"; import { useFavoriteItemDetails } from "@/hooks/use-favorite-item-details"; //helpers import { getCanDrop, getInstructionFromPayload } from "../favorites.helpers"; @@ -45,7 +44,6 @@ export const FavoriteRoot: FC = observer((props) => { // props const { isLastChild, parentId, workspaceSlug, favorite, handleRemoveFromFavorites, handleDrop } = props; // store hooks - const { sidebarCollapsed } = useAppTheme(); const { itemLink, itemIcon, itemTitle } = useFavoriteItemDetails(workspaceSlug, favorite); //state const [isDragging, setIsDragging] = useState(false); @@ -82,12 +80,7 @@ export const FavoriteRoot: FC = observer((props) => { const root = createRoot(container); root.render(
- +
); return () => root.unmount(); @@ -138,18 +131,16 @@ export const FavoriteRoot: FC = observer((props) => { return ( <> - - {!sidebarCollapsed && } - - {!sidebarCollapsed && ( - - )} + + + + {isLastChild && } diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index f83f0ed061b..de2194cdf3d 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -23,10 +23,8 @@ import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme } from "@/hooks/store"; import { useFavorite } from "@/hooks/store/use-favorite"; import useLocalStorage from "@/hooks/use-local-storage"; -import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { FavoriteFolder } from "./favorite-folder"; import { FavoriteRoot } from "./favorite-items"; @@ -40,19 +38,10 @@ export const SidebarFavoritesMenu = observer(() => { // navigation const { workspaceSlug } = useParams(); // store hooks - const { sidebarCollapsed } = useAppTheme(); - const { - favoriteIds, - groupedFavorites, - deleteFavorite, - removeFromFavoriteFolder, - reOrderFavorite, - moveFavoriteToFolder, - } = useFavorite(); + const { groupedFavorites, deleteFavorite, removeFromFavoriteFolder, reOrderFavorite, moveFavoriteToFolder } = + useFavorite(); // translation const { t } = useTranslation(); - // platform hooks - const { isMobile } = usePlatformOS(); // local storage const { setValue: toggleFavoriteMenu, storedValue } = useLocalStorage(IS_FAVORITE_MENU_OPEN, false); // derived values @@ -154,10 +143,6 @@ export const SidebarFavoritesMenu = observer(() => { [workspaceSlug, reOrderFavorite, t] ); - useEffect(() => { - if (sidebarCollapsed) toggleFavoriteMenu(true); - }, [sidebarCollapsed, toggleFavoriteMenu]); - useEffect(() => { const element = elementRef.current; @@ -189,27 +174,48 @@ export const SidebarFavoritesMenu = observer(() => { return ( <> - {!sidebarCollapsed && ( -
+ toggleFavoriteMenu(!isFavoriteMenuOpen)} + aria-label={t( + isFavoriteMenuOpen + ? "aria_labels.projects_sidebar.close_favorites_menu" + : "aria_labels.projects_sidebar.open_favorites_menu" + )} > + {t("favorites")} + +
+ + + toggleFavoriteMenu(!isFavoriteMenuOpen)} aria-label={t( isFavoriteMenuOpen @@ -217,42 +223,14 @@ export const SidebarFavoritesMenu = observer(() => { : "aria_labels.projects_sidebar.open_favorites_menu" )} > - {t("favorites")} + -
- - - - toggleFavoriteMenu(!isFavoriteMenuOpen)} - aria-label={t( - isFavoriteMenuOpen - ? "aria_labels.projects_sidebar.close_favorites_menu" - : "aria_labels.projects_sidebar.open_favorites_menu" - )} - > - - -
- )} +
{ leaveTo="transform scale-95 opacity-0" > {isFavoriteMenuOpen && ( - + {createNewFolder && } {Object.keys(groupedFavorites).length === 0 ? ( <> - {!sidebarCollapsed && ( - - {t("no_favorites_yet")} - - )} + {t("no_favorites_yet")} ) : ( orderBy(Object.values(groupedFavorites), "sequence", "desc") .filter((fav) => !fav.parent) .map((fav, index, { length }) => ( <> - {fav?.id && ( - - {fav?.is_folder ? ( - - ) : ( - - )} - + {fav?.is_folder ? ( + + ) : ( + )} )) @@ -320,10 +277,6 @@ export const SidebarFavoritesMenu = observer(() => { )}
- - {sidebarCollapsed && favoriteIds.length > 0 && ( -
- )} ); }); diff --git a/web/core/components/workspace/sidebar/help-section.tsx b/web/core/components/workspace/sidebar/help-section.tsx index 9c1b98fe8be..b4673dad5b9 100644 --- a/web/core/components/workspace/sidebar/help-section.tsx +++ b/web/core/components/workspace/sidebar/help-section.tsx @@ -26,7 +26,7 @@ export const SidebarHelpSection: React.FC = observer( const { workspaceSlug, projectId } = useParams(); // store hooks const { t } = useTranslation(); - const { sidebarCollapsed, toggleSidebar } = useAppTheme(); + const { sidebarCollapsed: isCollapsed, toggleSidebar, sidebarPeek, toggleSidebarPeek } = useAppTheme(); const { toggleShortcutModal } = useCommandPalette(); const { isMobile } = usePlatformOS(); const { config } = useInstance(); @@ -40,22 +40,11 @@ export const SidebarHelpSection: React.FC = observer( toggleIntercom(!isIntercomToggle); }; - const isCollapsed = sidebarCollapsed || false; - return ( <> setProductUpdatesModalOpen(false)} /> -
-
+
+
= observer(
} - customButtonClassName={`relative grid place-items-center rounded-md p-1.5 outline-none ${isCollapsed ? "w-full" : ""}`} + customButtonClassName="relative grid place-items-center rounded-md p-1.5 outline-none" menuButtonOnClick={() => !isNeedHelpOpen && setIsNeedHelpOpen(true)} onMenuClose={() => setIsNeedHelpOpen(false)} - placement={isCollapsed ? "left-end" : "top-end"} + placement="top-end" maxHeight="lg" closeOnSelect > @@ -158,23 +147,18 @@ export const SidebarHelpSection: React.FC = observer(
-
+
-
+
)} - {isSidebarCollapsed ? ( + <> - +
+

{project.name}

- ) : ( - <> - - setIsMenuActive(!isMenuActive)} > - -
- -
-

{project.name}

-
-
-
- setIsMenuActive(!isMenuActive)} - > - - + + + } + className={cn( + "opacity-0 pointer-events-none flex-shrink-0 group-hover/project-item:opacity-100 group-hover/project-item:pointer-events-auto", + { + "opacity-100 pointer-events-auto": isMenuActive, } - className={cn( - "opacity-0 pointer-events-none flex-shrink-0 group-hover/project-item:opacity-100 group-hover/project-item:pointer-events-auto", - { - "opacity-100 pointer-events-auto": isMenuActive, - } - )} - customButtonClassName="grid place-items-center" - placement="bottom-start" - ariaLabel={t("aria_labels.projects_sidebar.toggle_quick_actions_menu")} - useCaptureForOutsideClick - closeOnSelect - > - {/* TODO: Removed is_favorite logic due to the optimization in projects API */} - {/* {isAuthorized && ( + )} + customButtonClassName="grid place-items-center" + placement="bottom-start" + ariaLabel={t("aria_labels.projects_sidebar.toggle_quick_actions_menu")} + useCaptureForOutsideClick + closeOnSelect + > + {/* TODO: Removed is_favorite logic due to the optimization in projects API */} + {/* {isAuthorized && ( @@ -335,79 +306,78 @@ export const SidebarProjectsListItem: React.FC = observer((props) => { )} */} - {/* publish project settings */} - {isAdmin && ( - setPublishModal(true)}> -
-
- -
-
{t("publish_project")}
+ {/* publish project settings */} + {isAdmin && ( + setPublishModal(true)}> +
+
+
- - )} - - - - {t("copy_link")} - +
{t("publish_project")}
+
- {isAuthorized && ( - - -
- - {t("archives")} -
- -
- )} - { - setIsMenuActive(false); - }} - > - + )} + + + + {t("copy_link")} + + + {isAuthorized && ( + +
- - {t("settings")} + + {t("archives")}
- {/* leave project */} - {!isAuthorized && ( - -
- - {t("leave_project")} -
-
- )} - - setIsProjectListOpen(!isProjectListOpen)} - aria-label={t( - isProjectListOpen - ? "aria_labels.projects_sidebar.close_project_menu" - : "aria_labels.projects_sidebar.open_project_menu" - )} + )} + { + setIsMenuActive(false); + }} > - - - - )} + +
+ + {t("settings")} +
+ +
+ {/* leave project */} + {!isAuthorized && ( + +
+ + {t("leave_project")} +
+
+ )} + + setIsProjectListOpen(!isProjectListOpen)} + aria-label={t( + isProjectListOpen + ? "aria_labels.projects_sidebar.close_project_menu" + : "aria_labels.projects_sidebar.open_project_menu" + )} + > + + +
= observer((props) => { > {isProjectListOpen && ( - + )} diff --git a/web/core/components/workspace/sidebar/projects-list.tsx b/web/core/components/workspace/sidebar/projects-list.tsx index bea70b0d61b..829c9a30bbc 100644 --- a/web/core/components/workspace/sidebar/projects-list.tsx +++ b/web/core/components/workspace/sidebar/projects-list.tsx @@ -5,7 +5,7 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; -import { Briefcase, ChevronRight, Plus } from "lucide-react"; +import { ChevronRight, Plus } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; @@ -19,7 +19,7 @@ import { SidebarProjectsListItem } from "@/components/workspace"; import { cn } from "@/helpers/common.helper"; import { orderJoinedProjects } from "@/helpers/project.helper"; // hooks -import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; +import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; // plane web types import { TProject } from "@/plane-web/types"; @@ -34,7 +34,6 @@ export const SidebarProjectsList: FC = observer(() => { // store hooks const { t } = useTranslation(); const { toggleCreateProjectModal } = useCommandPalette(); - const { sidebarCollapsed } = useAppTheme(); const { setTrackElement } = useEventTracker(); const { allowPermissions } = useUserPermissions(); @@ -89,8 +88,6 @@ export const SidebarProjectsList: FC = observer(() => { }); }; - const isCollapsed = sidebarCollapsed || false; - /** * Implementing scroll animation styles based on the scroll length of the container */ @@ -154,24 +151,11 @@ export const SidebarProjectsList: FC = observer(() => { > <> -
+
toggleListDisclosure(!isAllProjectsListOpen)} aria-label={t( isAllProjectsListOpen @@ -179,52 +163,42 @@ export const SidebarProjectsList: FC = observer(() => { : "aria_labels.projects_sidebar.open_projects_menu" )} > - - <> - {isCollapsed ? ( - - ) : ( - {t("projects")} - )} - - + {t("projects")} - {!isCollapsed && ( -
- {isAuthorizedUser && ( - - - +
+ {isAuthorizedUser && ( + + + + )} + toggleListDisclosure(!isAllProjectsListOpen)} + aria-label={t( + isAllProjectsListOpen + ? "aria_labels.projects_sidebar.close_projects_menu" + : "aria_labels.projects_sidebar.open_projects_menu" )} - toggleListDisclosure(!isAllProjectsListOpen)} - aria-label={t( - isAllProjectsListOpen - ? "aria_labels.projects_sidebar.close_projects_menu" - : "aria_labels.projects_sidebar.open_projects_menu" - )} - > - - -
- )} + > + + +
{ )} {isAllProjectsListOpen && ( - + <> {joinedProjects.map((projectId, index) => ( { {isAuthorizedUser && joinedProjects?.length === 0 && ( )}
diff --git a/web/core/components/workspace/sidebar/quick-actions.tsx b/web/core/components/workspace/sidebar/quick-actions.tsx index 86ab8960300..b8cb159e13a 100644 --- a/web/core/components/workspace/sidebar/quick-actions.tsx +++ b/web/core/components/workspace/sidebar/quick-actions.tsx @@ -12,7 +12,7 @@ import { CreateUpdateIssueModal } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; +import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; import useLocalStorage from "@/hooks/use-local-storage"; // plane web components import { AppSearch } from "@/plane-web/components/workspace"; @@ -30,7 +30,6 @@ export const SidebarQuickActions = observer(() => { const workspaceSlug = routerWorkspaceSlug?.toString(); // store hooks const { toggleCreateIssueModal } = useCommandPalette(); - const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme(); const { setTrackElement } = useEventTracker(); const { joinedProjectIds } = useProject(); const { allowPermissions } = useUserPermissions(); @@ -74,19 +73,13 @@ export const SidebarQuickActions = observer(() => { onSubmit={() => removeWorkspaceDraftIssue()} isDraft /> -
+
diff --git a/web/core/components/workspace/sidebar/sidebar-menu-items.tsx b/web/core/components/workspace/sidebar/sidebar-menu-items.tsx index 89c0f150eaa..f23cadb382c 100644 --- a/web/core/components/workspace/sidebar/sidebar-menu-items.tsx +++ b/web/core/components/workspace/sidebar/sidebar-menu-items.tsx @@ -9,7 +9,6 @@ import { WORKSPACE_SIDEBAR_STATIC_NAVIGATION_ITEMS_LINKS, } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import { cn } from "@plane/utils"; // components import { SidebarNavItem } from "@/components/sidebar"; // store hooks @@ -21,7 +20,7 @@ export const SidebarMenuItems = observer(() => { // routers const { workspaceSlug } = useParams(); // store hooks - const { sidebarCollapsed, extendedSidebarCollapsed, toggleExtendedSidebar } = useAppTheme(); + const { extendedSidebarCollapsed, toggleExtendedSidebar } = useAppTheme(); const { getNavigationPreferences } = useWorkspace(); // translation const { t } = useTranslation(); @@ -41,24 +40,18 @@ export const SidebarMenuItems = observer(() => { ); return ( -
+
{WORKSPACE_SIDEBAR_STATIC_NAVIGATION_ITEMS_LINKS.map((item, _index) => ( ))} {sortedNavigationItems.map((item, _index) => ( ))} - +
diff --git a/web/core/components/workspace/sidebar/user-menu-item.tsx b/web/core/components/workspace/sidebar/user-menu-item.tsx index f8f0f120a3a..919ed87d520 100644 --- a/web/core/components/workspace/sidebar/user-menu-item.tsx +++ b/web/core/components/workspace/sidebar/user-menu-item.tsx @@ -4,9 +4,7 @@ import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; // plane imports import { EUserPermissionsLevel, SIDEBAR_CLICKED, EUserWorkspaceRoles } from "@plane/constants"; -import { usePlatformOS } from "@plane/hooks"; import { useTranslation } from "@plane/i18n"; -import { Tooltip } from "@plane/ui"; // components import { SidebarNavItem } from "@/components/sidebar"; import { NotificationAppSidebarOption } from "@/components/workspace-notifications"; @@ -35,8 +33,7 @@ export const SidebarUserMenuItem: FC = observer((props // store hooks const { captureEvent } = useEventTracker(); const { allowPermissions } = useUserPermissions(); - const { toggleSidebar, sidebarCollapsed } = useAppTheme(); - const { isMobile } = usePlatformOS(); + const { toggleSidebar } = useAppTheme(); const isActive = pathname === item.href; @@ -55,30 +52,14 @@ export const SidebarUserMenuItem: FC = observer((props }; return ( - - handleLinkClick(item.key)}> - -
- - {!sidebarCollapsed &&

{t(item.labelTranslationKey)}

} -
- {item.key === "notifications" && ( - - )} -
- -
+ handleLinkClick(item.key)}> + +
+ +

{t(item.labelTranslationKey)}

+
+ {item.key === "notifications" && } +
+ ); }); diff --git a/web/core/components/workspace/sidebar/user-menu.tsx b/web/core/components/workspace/sidebar/user-menu.tsx index 2d625ae6b69..4e0ed5a0682 100644 --- a/web/core/components/workspace/sidebar/user-menu.tsx +++ b/web/core/components/workspace/sidebar/user-menu.tsx @@ -9,14 +9,11 @@ import { EUserWorkspaceRoles } from "@plane/constants"; import { UserActivityIcon } from "@plane/ui"; // components import { SidebarUserMenuItem } from "@/components/workspace/sidebar"; -// helpers -import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme, useUserPermissions, useUser } from "@/hooks/store"; +import { useUserPermissions, useUser } from "@/hooks/store"; export const SidebarUserMenu = observer(() => { const { workspaceSlug } = useParams(); - const { sidebarCollapsed } = useAppTheme(); const { workspaceUserInfo } = useUserPermissions(); const { data: currentUser } = useUser(); @@ -54,11 +51,7 @@ export const SidebarUserMenu = observer(() => { const draftIssueCount = workspaceUserInfo[workspaceSlug.toString()]?.draft_issue_count; return ( -
+
{SIDEBAR_USER_MENU_ITEMS.map((item) => ( ))} diff --git a/web/core/components/workspace/sidebar/workspace-menu-header.tsx b/web/core/components/workspace/sidebar/workspace-menu-header.tsx index 3367a2b347c..5cc4d244870 100644 --- a/web/core/components/workspace/sidebar/workspace-menu-header.tsx +++ b/web/core/components/workspace/sidebar/workspace-menu-header.tsx @@ -11,7 +11,7 @@ import { useTranslation } from "@plane/i18n"; import { CustomMenu } from "@plane/ui"; import { cn } from "@plane/utils"; // store hooks -import { useAppTheme, useUserPermissions } from "@/hooks/store"; +import { useUserPermissions } from "@/hooks/store"; export type SidebarWorkspaceMenuHeaderProps = { isWorkspaceMenuOpen: boolean; @@ -26,7 +26,6 @@ export const SidebarWorkspaceMenuHeader: FC = o const actionSectionRef = useRef(null); // hooks const { workspaceSlug } = useParams(); - const { sidebarCollapsed } = useAppTheme(); const { allowPermissions } = useUserPermissions(); const { t } = useTranslation(); @@ -36,19 +35,8 @@ export const SidebarWorkspaceMenuHeader: FC = o // eslint-disable-next-line @typescript-eslint/no-explicit-any const isAdmin = allowPermissions([EUserWorkspaceRoles.ADMIN] as any, EUserPermissionsLevel.WORKSPACE); - if (sidebarCollapsed) { - return <>; - } - return ( -
+
= obser const { workspaceSlug } = useParams(); const { allowPermissions } = useUserPermissions(); // store hooks - const { toggleSidebar, sidebarCollapsed } = useAppTheme(); - const { isMobile } = usePlatformOS(); + const { toggleSidebar } = useAppTheme(); const handleLinkClick = () => { if (window.innerWidth < 768) { @@ -50,33 +47,20 @@ export const SidebarWorkspaceMenuItem: FC = obser const isActive = item.href === pathname; return ( - - handleLinkClick()}> - -
- - {!sidebarCollapsed &&

{t(item.labelTranslationKey)}

} -
- {!sidebarCollapsed && item.key === "active_cycles" && ( -
- -
- )} -
- -
+ handleLinkClick()}> + +
+ +

{t(item.labelTranslationKey)}

+
+
+ +
+
+ ); }); diff --git a/web/core/components/workspace/sidebar/workspace-menu.tsx b/web/core/components/workspace/sidebar/workspace-menu.tsx index 2b2fa90b317..d2c6ad37563 100644 --- a/web/core/components/workspace/sidebar/workspace-menu.tsx +++ b/web/core/components/workspace/sidebar/workspace-menu.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect } from "react"; +import React from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { BarChart2, Briefcase, Layers } from "lucide-react"; @@ -10,26 +10,17 @@ import { EUserWorkspaceRoles } from "@plane/constants"; import { ContrastIcon } from "@plane/ui"; // components import { SidebarWorkspaceMenuHeader, SidebarWorkspaceMenuItem } from "@/components/workspace/sidebar"; -// helpers -import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme } from "@/hooks/store"; import useLocalStorage from "@/hooks/use-local-storage"; export const SidebarWorkspaceMenu = observer(() => { // router params const { workspaceSlug } = useParams(); - // store hooks - const { sidebarCollapsed } = useAppTheme(); // local storage const { setValue: toggleWorkspaceMenu, storedValue } = useLocalStorage("is_workspace_menu_open", true); // derived values const isWorkspaceMenuOpen = !!storedValue; - useEffect(() => { - if (sidebarCollapsed) toggleWorkspaceMenu(true); - }, [sidebarCollapsed, toggleWorkspaceMenu]); - const SIDEBAR_WORKSPACE_MENU_ITEMS = [ { key: "projects", @@ -74,13 +65,7 @@ export const SidebarWorkspaceMenu = observer(() => { leaveTo="transform scale-95 opacity-0" > {isWorkspaceMenuOpen && ( - + {SIDEBAR_WORKSPACE_MENU_ITEMS.map((item) => ( ))} From a07cff02c25ee5b186c8c96c54f7dece25fc40ca Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 15:36:41 +0530 Subject: [PATCH 06/47] chore: code refactor --- .../(projects)/resizeable-sidebar.tsx | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx index e6a998a3d63..41015300669 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx @@ -264,29 +264,6 @@ export function ResizableSidebar({
- {/* Toggle Button */} - - {/* Extended Sidebar */} {extendedSidebar && extendedSidebar} From 7205b72c8e9eafda3f67cb9a84fcdc2d7b56b482 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 17:36:38 +0530 Subject: [PATCH 07/47] chore: code refactor --- .../(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx index 41015300669..deaa7c40021 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx @@ -173,7 +173,11 @@ export function ResizableSidebar({ isCollapsed ? "translate-x-[-100%] opacity-0 w-0" : "translate-x-0 opacity-100", className )} - style={{ width: `${isCollapsed ? 0 : width}px` }} + style={{ + width: `${isCollapsed ? 0 : width}px`, + minWidth: `${isCollapsed ? 0 : width}px`, + maxWidth: `${isCollapsed ? 0 : width}px`, + }} role="complementary" aria-label="Main sidebar" > From 3cdfac257593ab28b3146668f0693dac785a828d Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 17:41:03 +0530 Subject: [PATCH 08/47] chore: code refactor --- .../(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx index deaa7c40021..dc5ba9db74d 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx @@ -2,7 +2,8 @@ import React, { Dispatch, ReactElement, SetStateAction, useCallback, useEffect, useState, useRef } from "react"; // helpers -import { cn } from "@/helpers/common.helper"; +import { cn } from "@plane/utils"; + interface ResizableSidebarProps { showPeek?: boolean; From 8e270781e8b0611c6f697ad9fcce338c7e34b830 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 19:45:47 +0530 Subject: [PATCH 09/47] chore: breadcrumb changes --- .../(all)/[workspaceSlug]/(projects)/header.tsx | 17 ++--------------- web/core/components/core/app-header.tsx | 16 ++++++++++------ .../sidebar/sidebar-menu-hamburger-toggle.tsx | 13 ++++++++++--- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/header.tsx index da1007447dd..27e9470222c 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/header.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import Image from "next/image"; import { useTheme } from "next-themes"; -import { Home, MenuIcon } from "lucide-react"; +import { Home } from "lucide-react"; // images import githubBlackImage from "/public/logos/github-black.png"; import githubWhiteImage from "/public/logos/github-white.png"; @@ -15,13 +15,12 @@ import { Breadcrumbs, Header } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; // constants // hooks -import { useAppTheme, useEventTracker } from "@/hooks/store"; +import { useEventTracker } from "@/hooks/store"; export const WorkspaceDashboardHeader = observer(() => { // hooks const { captureEvent } = useEventTracker(); const { resolvedTheme } = useTheme(); - const { sidebarCollapsed, toggleSidebar, sidebarPeek, toggleSidebarPeek } = useAppTheme(); const { t } = useTranslation(); @@ -30,18 +29,6 @@ export const WorkspaceDashboardHeader = observer(() => {
- {sidebarCollapsed && ( - - )} { +export const AppHeader = observer((props: AppHeaderProps) => { const { header, mobileHeader } = props; + // store hooks + const { sidebarCollapsed } = useAppTheme(); return (
-
- -
+ {sidebarCollapsed && }
{header}
{mobileHeader && mobileHeader}
); -}; +}); diff --git a/web/core/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx b/web/core/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx index 7dc39881299..7e773131c8d 100644 --- a/web/core/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx +++ b/web/core/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx @@ -6,13 +6,20 @@ import { useAppTheme } from "@/hooks/store"; export const SidebarHamburgerToggle = observer(() => { // store hooks - const { toggleSidebar } = useAppTheme(); + const { toggleSidebar, sidebarPeek, toggleSidebarPeek } = useAppTheme(); + + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (sidebarPeek) toggleSidebarPeek(false); + toggleSidebar(false); + }; return ( From 71c5616096c1b1c81bc5f3cda38cd48550d02fcd Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 17 Jun 2025 20:47:30 +0530 Subject: [PATCH 10/47] chore: sidebar improvements and fixes --- .../[workspaceSlug]/(projects)/_sidebar.tsx | 17 ++++---------- .../(projects)/extended-project-sidebar.tsx | 12 +++------- .../(projects)/extended-sidebar-wrapper.tsx | 8 +++---- .../(projects)/extended-sidebar.tsx | 9 +++----- .../workspace/sidebar/sidebar-item.tsx | 4 ++-- .../sidebar/sidebar-menu-hamburger-toggle.tsx | 5 ++--- web/core/components/sidebar/index.ts | 3 ++- .../components/sidebar/resizable-sidebar.tsx} | 3 +-- .../components/workspace/sidebar/dropdown.tsx | 2 +- .../workspace/sidebar/sidebar-menu-items.tsx | 4 ++-- web/core/store/theme.store.ts | 22 +++++++++---------- 11 files changed, 35 insertions(+), 54 deletions(-) rename web/{app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx => core/components/sidebar/resizable-sidebar.tsx} (98%) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx index 06be475cd6c..3380f1b1130 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx @@ -4,28 +4,20 @@ import { observer } from "mobx-react"; // plane imports import { useLocalStorage } from "@plane/hooks"; // hooks +import { ResizableSidebar } from "@/components/sidebar"; import { useAppTheme } from "@/hooks/store"; // local imports -import { ExtendedProjectSidebar } from "./extended-project-sidebar"; import { ExtendedAppSidebar } from "./extended-sidebar"; -import { ResizableSidebar } from "./resizeable-sidebar"; import { AppSidebar } from "./sidebar"; export const ProjectAppSidebar: FC = observer(() => { // store hooks - const { - sidebarCollapsed, - toggleSidebar, - sidebarPeek, - toggleSidebarPeek, - extendedSidebarCollapsed, - extendedProjectSidebarCollapsed, - } = useAppTheme(); + const { sidebarCollapsed, toggleSidebar, sidebarPeek, toggleSidebarPeek, isExtendedSidebarOpened } = useAppTheme(); const { storedValue, setValue } = useLocalStorage("sidebarWidth", 250); // states const [sidebarWidth, setSidebarWidth] = useState(storedValue ?? 250); // derived values - const anyExtendedSidebarCollapsed = extendedSidebarCollapsed || extendedProjectSidebarCollapsed; + const isAnyExtendedSidebarOpen = isExtendedSidebarOpened; // handlers const handleWidthChange = (width: number) => setValue(width); @@ -47,10 +39,9 @@ export const ProjectAppSidebar: FC = observer(() => { extendedSidebar={ <> - } - isAnyExtendedSidebarExpanded={anyExtendedSidebarCollapsed} + isAnyExtendedSidebarExpanded={isAnyExtendedSidebarOpen} > diff --git a/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx index 7fa1bbaea36..85de64569d7 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/extended-project-sidebar.tsx @@ -27,8 +27,7 @@ export const ExtendedProjectSidebar = observer(() => { const { workspaceSlug } = useParams(); // store hooks const { t } = useTranslation(); - const { sidebarPeek, toggleSidebarPeek, extendedProjectSidebarCollapsed, toggleExtendedProjectSidebar } = - useAppTheme(); + const { isExtendedProjectSidebarOpened, toggleExtendedProjectSidebar } = useAppTheme(); const { getPartialProjectById, joinedProjectIds: joinedProjects, updateProjectView } = useProject(); const { allowPermissions } = useUserPermissions(); @@ -75,12 +74,7 @@ export const ExtendedProjectSidebar = observer(() => { EUserPermissionsLevel.WORKSPACE ); - const handleClose = () => { - if (sidebarPeek) toggleSidebarPeek(false); - if (!isProjectModalOpen) { - toggleExtendedProjectSidebar(false); - } - }; + const handleClose = () => toggleExtendedProjectSidebar(false); const handleCopyText = (projectId: string) => { copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/issues`).then(() => { @@ -102,7 +96,7 @@ export const ExtendedProjectSidebar = observer(() => { /> )} ; - extendedSidebarCollapsed: boolean; + isExtendedSidebarOpened: boolean; handleClose: () => void; excludedElementId: string; }; export const ExtendedSidebarWrapper: FC = observer((props) => { - const { children, extendedSidebarRef, extendedSidebarCollapsed, handleClose, excludedElementId } = props; + const { children, extendedSidebarRef, isExtendedSidebarOpened, handleClose, excludedElementId } = props; // store hooks const { sidebarPeek } = useAppTheme(); const { storedValue } = useLocalStorage("sidebarWidth", 250); @@ -32,8 +32,8 @@ export const ExtendedSidebarWrapper: FC = observer((props) => { className={cn( `fixed top-0 h-full z-[19] flex flex-col w-[300px] transform transition-all duration-300 ease-in-out bg-custom-background-100 border-r border-custom-sidebar-border-200 p-4 shadow-md`, { - "translate-x-0 opacity-100": extendedSidebarCollapsed, - "-translate-x-[600px] opacity-0": !extendedSidebarCollapsed, + "translate-x-0 opacity-100": isExtendedSidebarOpened, + "-translate-x-[600px] opacity-0 w-0": !isExtendedSidebarOpened, "border border-l-none shadow-lg rounded-tr-md rounded-br-md": sidebarPeek, } )} diff --git a/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar.tsx index a1eaf4a34e6..d4c764d8387 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar.tsx @@ -17,7 +17,7 @@ export const ExtendedAppSidebar = observer(() => { // routers const { workspaceSlug } = useParams(); // store hooks - const { sidebarPeek, toggleSidebarPeek, extendedSidebarCollapsed, toggleExtendedSidebar } = useAppTheme(); + const { isExtendedSidebarOpened, toggleExtendedSidebar } = useAppTheme(); const { updateSidebarPreference, getNavigationPreferences } = useWorkspace(); // derived values @@ -93,14 +93,11 @@ export const ExtendedAppSidebar = observer(() => { }); }; - const handleClose = () => { - if (sidebarPeek) toggleSidebarPeek(false); - toggleExtendedSidebar(false); - }; + const handleClose = () => toggleExtendedSidebar(false); return ( = observer((props) => { const { data } = useUser(); // store hooks - const { toggleSidebar, extendedSidebarCollapsed, toggleExtendedSidebar } = useAppTheme(); + const { toggleSidebar, isExtendedSidebarOpened, toggleExtendedSidebar } = useAppTheme(); const handleLinkClick = () => { if (window.innerWidth < 768) { toggleSidebar(); } - if (!extendedSidebarCollapsed) toggleExtendedSidebar(); + if (isExtendedSidebarOpened) toggleExtendedSidebar(false); }; const staticItems = ["home", "inbox", "pi-chat", "projects"]; diff --git a/web/core/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx b/web/core/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx index 7e773131c8d..314f661f9d2 100644 --- a/web/core/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx +++ b/web/core/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx @@ -6,13 +6,12 @@ import { useAppTheme } from "@/hooks/store"; export const SidebarHamburgerToggle = observer(() => { // store hooks - const { toggleSidebar, sidebarPeek, toggleSidebarPeek } = useAppTheme(); + const { toggleSidebar } = useAppTheme(); const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); - if (sidebarPeek) toggleSidebarPeek(false); - toggleSidebar(false); + toggleSidebar(); }; return ( diff --git a/web/core/components/sidebar/index.ts b/web/core/components/sidebar/index.ts index b2e6f7602d8..b64ab385e78 100644 --- a/web/core/components/sidebar/index.ts +++ b/web/core/components/sidebar/index.ts @@ -1 +1,2 @@ -export * from "./sidebar-navigation"; \ No newline at end of file +export * from "./sidebar-navigation"; +export * from "./resizable-sidebar"; diff --git a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx b/web/core/components/sidebar/resizable-sidebar.tsx similarity index 98% rename from web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx rename to web/core/components/sidebar/resizable-sidebar.tsx index dc5ba9db74d..991589ad119 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/resizeable-sidebar.tsx +++ b/web/core/components/sidebar/resizable-sidebar.tsx @@ -4,7 +4,6 @@ import React, { Dispatch, ReactElement, SetStateAction, useCallback, useEffect, // helpers import { cn } from "@plane/utils"; - interface ResizableSidebarProps { showPeek?: boolean; togglePeek: (value?: boolean) => void; @@ -169,7 +168,7 @@ export function ResizableSidebar({ {/* Main Sidebar */}
{ leaveTo="transform opacity-0 scale-95" > -
+
{currentUser?.email} diff --git a/web/core/components/workspace/sidebar/sidebar-menu-items.tsx b/web/core/components/workspace/sidebar/sidebar-menu-items.tsx index f23cadb382c..f67eef724b3 100644 --- a/web/core/components/workspace/sidebar/sidebar-menu-items.tsx +++ b/web/core/components/workspace/sidebar/sidebar-menu-items.tsx @@ -20,7 +20,7 @@ export const SidebarMenuItems = observer(() => { // routers const { workspaceSlug } = useParams(); // store hooks - const { extendedSidebarCollapsed, toggleExtendedSidebar } = useAppTheme(); + const { isExtendedSidebarOpened, toggleExtendedSidebar } = useAppTheme(); const { getNavigationPreferences } = useWorkspace(); // translation const { t } = useTranslation(); @@ -54,7 +54,7 @@ export const SidebarMenuItems = observer(() => { className="flex items-center gap-1.5 text-sm font-medium flex-grow text-custom-text-350" id="extended-sidebar-toggle" aria-label={t( - extendedSidebarCollapsed + isExtendedSidebarOpened ? "aria_labels.projects_sidebar.open_extended_sidebar" : "aria_labels.projects_sidebar.close_extended_sidebar" )} diff --git a/web/core/store/theme.store.ts b/web/core/store/theme.store.ts index 331c3ea322d..b141f7f076e 100644 --- a/web/core/store/theme.store.ts +++ b/web/core/store/theme.store.ts @@ -4,8 +4,8 @@ export interface IThemeStore { // observables sidebarCollapsed: boolean | undefined; sidebarPeek: boolean | undefined; - extendedSidebarCollapsed: boolean | undefined; - extendedProjectSidebarCollapsed: boolean | undefined; + isExtendedSidebarOpened: boolean | undefined; + isExtendedProjectSidebarOpened: boolean | undefined; profileSidebarCollapsed: boolean | undefined; workspaceAnalyticsSidebarCollapsed: boolean | undefined; issueDetailSidebarCollapsed: boolean | undefined; @@ -29,8 +29,8 @@ export class ThemeStore implements IThemeStore { // observables sidebarCollapsed: boolean | undefined = undefined; sidebarPeek: boolean | undefined = undefined; - extendedSidebarCollapsed: boolean | undefined = undefined; - extendedProjectSidebarCollapsed: boolean | undefined = undefined; + isExtendedSidebarOpened: boolean | undefined = undefined; + isExtendedProjectSidebarOpened: boolean | undefined = undefined; profileSidebarCollapsed: boolean | undefined = undefined; workspaceAnalyticsSidebarCollapsed: boolean | undefined = undefined; issueDetailSidebarCollapsed: boolean | undefined = undefined; @@ -43,8 +43,8 @@ export class ThemeStore implements IThemeStore { // observable sidebarCollapsed: observable.ref, sidebarPeek: observable.ref, - extendedSidebarCollapsed: observable.ref, - extendedProjectSidebarCollapsed: observable.ref, + isExtendedSidebarOpened: observable.ref, + isExtendedProjectSidebarOpened: observable.ref, profileSidebarCollapsed: observable.ref, workspaceAnalyticsSidebarCollapsed: observable.ref, issueDetailSidebarCollapsed: observable.ref, @@ -95,9 +95,9 @@ export class ThemeStore implements IThemeStore { * @param collapsed */ toggleExtendedSidebar = (collapsed?: boolean) => { - const updatedState = collapsed ?? !this.extendedSidebarCollapsed; + const updatedState = collapsed ?? !this.isExtendedSidebarOpened; runInAction(() => { - this.extendedSidebarCollapsed = updatedState; + this.isExtendedSidebarOpened = updatedState; }); localStorage.setItem("extended_sidebar_collapsed", updatedState.toString()); }; @@ -108,11 +108,11 @@ export class ThemeStore implements IThemeStore { */ toggleExtendedProjectSidebar = (collapsed?: boolean) => { if (collapsed === undefined) { - this.extendedProjectSidebarCollapsed = !this.extendedProjectSidebarCollapsed; + this.isExtendedProjectSidebarOpened = !this.isExtendedProjectSidebarOpened; } else { - this.extendedProjectSidebarCollapsed = collapsed; + this.isExtendedProjectSidebarOpened = collapsed; } - localStorage.setItem("extended_project_sidebar_collapsed", this.extendedProjectSidebarCollapsed.toString()); + localStorage.setItem("extended_project_sidebar_collapsed", this.isExtendedProjectSidebarOpened.toString()); }; /** From 322cdaf13fc1738f7ed4c99569525102da9beca2 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Wed, 18 Jun 2025 17:08:19 +0530 Subject: [PATCH 11/47] chore: enhancements and fixes --- .../[workspaceSlug]/(projects)/_sidebar.tsx | 10 +- .../(projects)/extended-sidebar-wrapper.tsx | 4 +- .../components/sidebar/resizable-sidebar.tsx | 33 +- .../sidebar/header/root.tsx | 15 +- .../components/workspace/sidebar/dropdown.tsx | 344 ++++++++++-------- .../workspace/sidebar/projects-list-item.tsx | 41 ++- web/core/store/theme.store.ts | 13 + 7 files changed, 269 insertions(+), 191 deletions(-) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx b/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx index 3380f1b1130..8a1f2526c90 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx @@ -12,7 +12,14 @@ import { AppSidebar } from "./sidebar"; export const ProjectAppSidebar: FC = observer(() => { // store hooks - const { sidebarCollapsed, toggleSidebar, sidebarPeek, toggleSidebarPeek, isExtendedSidebarOpened } = useAppTheme(); + const { + sidebarCollapsed, + toggleSidebar, + sidebarPeek, + toggleSidebarPeek, + isExtendedSidebarOpened, + isAnySidebarDropdownOpen, + } = useAppTheme(); const { storedValue, setValue } = useLocalStorage("sidebarWidth", 250); // states const [sidebarWidth, setSidebarWidth] = useState(storedValue ?? 250); @@ -42,6 +49,7 @@ export const ProjectAppSidebar: FC = observer(() => { } isAnyExtendedSidebarExpanded={isAnyExtendedSidebarOpen} + isAnySidebarDropdownOpen={isAnySidebarDropdownOpen} > diff --git a/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar-wrapper.tsx b/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar-wrapper.tsx index ed398d87ad2..b89fbedfee5 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar-wrapper.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/extended-sidebar-wrapper.tsx @@ -30,11 +30,11 @@ export const ExtendedSidebarWrapper: FC = observer((props) => { id={excludedElementId} ref={extendedSidebarRef} className={cn( - `fixed top-0 h-full z-[19] flex flex-col w-[300px] transform transition-all duration-300 ease-in-out bg-custom-background-100 border-r border-custom-sidebar-border-200 p-4 shadow-md`, + `fixed top-0 h-full z-[19] flex flex-col w-[300px] transform transition-all duration-300 ease-in-out bg-custom-background-100 border-r border-custom-sidebar-border-200 p-4 shadow-sm`, { "translate-x-0 opacity-100": isExtendedSidebarOpened, "-translate-x-[600px] opacity-0 w-0": !isExtendedSidebarOpened, - "border border-l-none shadow-lg rounded-tr-md rounded-br-md": sidebarPeek, + "border border-l-none rounded-tr-md rounded-br-md": sidebarPeek, } )} style={{ diff --git a/web/core/components/sidebar/resizable-sidebar.tsx b/web/core/components/sidebar/resizable-sidebar.tsx index 991589ad119..373b59571ac 100644 --- a/web/core/components/sidebar/resizable-sidebar.tsx +++ b/web/core/components/sidebar/resizable-sidebar.tsx @@ -22,12 +22,13 @@ interface ResizableSidebarProps { children?: ReactElement; extendedSidebar?: ReactElement; isAnyExtendedSidebarExpanded?: boolean; + isAnySidebarDropdownOpen?: boolean; } export function ResizableSidebar({ showPeek = false, togglePeek, - peekDuration = 1000, + peekDuration = 500, isCollapsed = false, toggleCollapsed: toggleCollapsedProp, onCollapsedChange, @@ -40,6 +41,7 @@ export function ResizableSidebar({ children, extendedSidebar, isAnyExtendedSidebarExpanded = false, + isAnySidebarDropdownOpen = false, }: ResizableSidebarProps) { // states const [isResizing, setIsResizing] = useState(false); @@ -109,12 +111,12 @@ export function ResizableSidebar({ }, [isCollapsed, showPeek]); const handlePeekLeave = useCallback(() => { - if (isCollapsed && !isAnyExtendedSidebarExpanded) { + if (isCollapsed && !isAnyExtendedSidebarExpanded && !isAnySidebarDropdownOpen) { peekTimeoutRef.current = setTimeout(() => { setShowPeek(false); }, peekDuration); } - }, [isCollapsed, peekDuration, setShowPeek, isAnyExtendedSidebarExpanded]); + }, [isCollapsed, peekDuration, setShowPeek, isAnyExtendedSidebarExpanded, isAnySidebarDropdownOpen]); // Set up event listeners for resizing useEffect(() => { @@ -143,6 +145,18 @@ export function ResizableSidebar({ [] ); + useEffect(() => { + if (!isAnySidebarDropdownOpen) { + handlePeekLeave(); + } + }, [isAnySidebarDropdownOpen]); + + useEffect(() => { + if (!isAnyExtendedSidebarExpanded) { + handlePeekLeave(); + } + }, [isAnyExtendedSidebarExpanded]); + // Reset peek when sidebar is expanded useEffect(() => { if (!isCollapsed) { @@ -184,8 +198,7 @@ export function ResizableSidebar({