From e4a556d551d082c9f5656509e8b65d0aecff7fdc Mon Sep 17 00:00:00 2001 From: Timothy Carambat <rambat1010@gmail.com> Date: Wed, 11 Dec 2024 17:18:38 -0800 Subject: [PATCH] Add support for hiding sidebar (#2809) * Add support for hiding sidebar Support cmd/ctrl + shift +s for quick hide/show * patch sidebar padding on collapse * update Key for sidebar setting --- .../ThreadContainer/ThreadItem/index.jsx | 8 +- .../Sidebar/SidebarToggle/index.jsx | 105 ++++++++++++++++++ frontend/src/components/Sidebar/index.jsx | 88 +++++++++------ .../WorkspaceChat/ChatContainer/index.jsx | 2 +- 4 files changed, 162 insertions(+), 41 deletions(-) create mode 100644 frontend/src/components/Sidebar/SidebarToggle/index.jsx diff --git a/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx b/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx index 4947de08f..4c12b05b5 100644 --- a/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx +++ b/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx @@ -41,8 +41,8 @@ export default function ThreadItem({ style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }} className={`${ isActive - ? "border-l-2 border-b-2 border-white light:border-theme-sidebar-border z-30" - : "border-l border-b border-[#6F6F71] light:border-theme-sidebar-border z-10" + ? "border-l-2 border-b-2 border-white light:border-theme-sidebar-border z-[2]" + : "border-l border-b border-[#6F6F71] light:border-theme-sidebar-border z-[1]" } h-[50%] absolute top-0 left-2 rounded-bl-lg`} ></div> {/* Downstroke border for next item */} @@ -51,8 +51,8 @@ export default function ThreadItem({ style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }} className={`${ idx <= activeIdx && !isActive - ? "border-l-2 border-white light:border-theme-sidebar-border z-20" - : "border-l border-[#6F6F71] light:border-theme-sidebar-border z-10" + ? "border-l-2 border-white light:border-theme-sidebar-border z-[2]" + : "border-l border-[#6F6F71] light:border-theme-sidebar-border z-[1]" } h-[100%] absolute top-0 left-2`} ></div> )} diff --git a/frontend/src/components/Sidebar/SidebarToggle/index.jsx b/frontend/src/components/Sidebar/SidebarToggle/index.jsx new file mode 100644 index 000000000..a0f47b6b4 --- /dev/null +++ b/frontend/src/components/Sidebar/SidebarToggle/index.jsx @@ -0,0 +1,105 @@ +import React, { useEffect, useState } from "react"; +import { SidebarSimple } from "@phosphor-icons/react"; +import paths from "@/utils/paths"; +import { Tooltip } from "react-tooltip"; +const SIDEBAR_TOGGLE_STORAGE_KEY = "anythingllm_sidebar_toggle"; + +/** + * Returns the previous state of the sidebar from localStorage. + * If the sidebar was closed, returns false. + * If the sidebar was open, returns true. + * If the sidebar state is not set, returns true. + * @returns {boolean} + */ +function previousSidebarState() { + const previousState = window.localStorage.getItem(SIDEBAR_TOGGLE_STORAGE_KEY); + if (previousState === "closed") return false; + return true; +} + +export function useSidebarToggle() { + const [showSidebar, setShowSidebar] = useState(previousSidebarState()); + const [canToggleSidebar, setCanToggleSidebar] = useState(true); + + useEffect(() => { + function checkPath() { + const currentPath = window.location.pathname; + const isVisible = + currentPath === paths.home() || + /^\/workspace\/[^\/]+$/.test(currentPath) || + /^\/workspace\/[^\/]+\/t\/[^\/]+$/.test(currentPath); + setCanToggleSidebar(isVisible); + } + checkPath(); + }, [window.location.pathname]); + + useEffect(() => { + function toggleSidebar(e) { + if (!canToggleSidebar) return; + if ( + (e.ctrlKey || e.metaKey) && + e.shiftKey && + e.key.toLowerCase() === "s" + ) { + setShowSidebar((prev) => { + const newState = !prev; + window.localStorage.setItem( + SIDEBAR_TOGGLE_STORAGE_KEY, + newState ? "open" : "closed" + ); + return newState; + }); + } + } + window.addEventListener("keydown", toggleSidebar); + return () => { + window.removeEventListener("keydown", toggleSidebar); + }; + }, []); + + useEffect(() => { + window.localStorage.setItem( + SIDEBAR_TOGGLE_STORAGE_KEY, + showSidebar ? "open" : "closed" + ); + }, [showSidebar]); + + return { showSidebar, setShowSidebar, canToggleSidebar }; +} + +export function ToggleSidebarButton({ showSidebar, setShowSidebar }) { + const isMac = navigator.userAgent.includes("Mac"); + const shortcut = isMac ? "⌘ + Shift + S" : "Ctrl + Shift + S"; + + return ( + <> + <button + type="button" + className={`hidden md:block border-none bg-transparent outline-none ring-0 transition-left duration-500 ${showSidebar ? "left-[247px]" : "absolute top-[20px] left-[30px] z-10"}`} + onClick={() => setShowSidebar((prev) => !prev)} + data-tooltip-id="sidebar-toggle" + data-tooltip-content={ + showSidebar + ? `Hide Sidebar (${shortcut})` + : `Show Sidebar (${shortcut})` + } + aria-label={ + showSidebar + ? `Hide Sidebar (${shortcut})` + : `Show Sidebar (${shortcut})` + } + > + <SidebarSimple + className="text-theme-text-secondary hover:text-theme-text-primary" + size={24} + /> + </button> + <Tooltip + id="sidebar-toggle" + place="top" + delayShow={300} + className="tooltip !text-xs z-99" + /> + </> + ); +} diff --git a/frontend/src/components/Sidebar/index.jsx b/frontend/src/components/Sidebar/index.jsx index cec1604c0..04a7fe434 100644 --- a/frontend/src/components/Sidebar/index.jsx +++ b/frontend/src/components/Sidebar/index.jsx @@ -11,11 +11,13 @@ import SettingsButton from "../SettingsButton"; import { Link } from "react-router-dom"; import paths from "@/utils/paths"; import { useTranslation } from "react-i18next"; +import { useSidebarToggle, ToggleSidebarButton } from "./SidebarToggle"; export default function Sidebar() { const { user } = useUser(); const { logo } = useLogo(); const sidebarRef = useRef(null); + const { showSidebar, setShowSidebar, canToggleSidebar } = useSidebarToggle(); const { showing: showingNewWsModal, showModal: showNewWsModal, @@ -24,50 +26,64 @@ export default function Sidebar() { const { t } = useTranslation(); return ( - <div> - <Link - to={paths.home()} - className="flex shrink-0 max-w-[55%] items-center justify-start mx-[38px] my-[18px]" - aria-label="Home" - > - <img - src={logo} - alt="Logo" - className="rounded max-h-[24px] object-contain" - /> - </Link> + <> <div - ref={sidebarRef} - className="relative m-[16px] rounded-[16px] bg-theme-bg-sidebar border-[2px] border-theme-sidebar-border light:border-none min-w-[250px] p-[10px] h-[calc(100%-76px)]" + style={{ + width: showSidebar ? "292px" : "0px", + paddingLeft: showSidebar ? "0px" : "16px", + }} + className="transition-all duration-500" > - <div className="flex flex-col h-full overflow-x-hidden"> - <div className="flex-grow flex flex-col min-w-[235px]"> - <div className="relative h-[calc(100%-60px)] flex flex-col w-full justify-between pt-[10px] overflow-y-scroll no-scroll"> - <div className="flex flex-col gap-y-2 pb-[60px] overflow-y-scroll no-scroll"> - <div className="flex gap-x-2 items-center justify-between"> - {(!user || user?.role !== "default") && ( - <button - onClick={showNewWsModal} - className="light:bg-[#C2E7FE] light:hover:bg-[#7CD4FD] flex flex-grow w-[75%] h-[44px] gap-x-2 py-[5px] px-2.5 mb-2 bg-white rounded-[8px] text-sidebar justify-center items-center hover:bg-opacity-80 transition-all duration-300" - > - <Plus size={18} weight="bold" /> - <p className="text-sidebar text-sm font-semibold"> - {t("new-workspace.title")} - </p> - </button> - )} + <div className="flex shrink-0 w-full justify-center my-[18px]"> + <div className="flex justify-between w-[250px] min-w-[250px]"> + <Link to={paths.home()} aria-label="Home"> + <img + src={logo} + alt="Logo" + className={`rounded max-h-[24px] object-contain transition-opacity duration-500 ${showSidebar ? "opacity-100" : "opacity-0"}`} + /> + </Link> + {canToggleSidebar && ( + <ToggleSidebarButton + showSidebar={showSidebar} + setShowSidebar={setShowSidebar} + /> + )} + </div> + </div> + <div + ref={sidebarRef} + className="relative m-[16px] rounded-[16px] bg-theme-bg-sidebar border-[2px] border-theme-sidebar-border light:border-none min-w-[250px] p-[10px] h-[calc(100%-76px)]" + > + <div className="flex flex-col h-full overflow-x-hidden"> + <div className="flex-grow flex flex-col min-w-[235px]"> + <div className="relative h-[calc(100%-60px)] flex flex-col w-full justify-between pt-[10px] overflow-y-scroll no-scroll"> + <div className="flex flex-col gap-y-2 pb-[60px] overflow-y-scroll no-scroll"> + <div className="flex gap-x-2 items-center justify-between"> + {(!user || user?.role !== "default") && ( + <button + onClick={showNewWsModal} + className="light:bg-[#C2E7FE] light:hover:bg-[#7CD4FD] flex flex-grow w-[75%] h-[44px] gap-x-2 py-[5px] px-2.5 mb-2 bg-white rounded-[8px] text-sidebar justify-center items-center hover:bg-opacity-80 transition-all duration-300" + > + <Plus size={18} weight="bold" /> + <p className="text-sidebar text-sm font-semibold"> + {t("new-workspace.title")} + </p> + </button> + )} + </div> + <ActiveWorkspaces /> </div> - <ActiveWorkspaces /> </div> - </div> - <div className="absolute bottom-0 left-0 right-0 pt-4 pb-3 rounded-b-[16px] bg-theme-bg-sidebar bg-opacity-80 backdrop-filter backdrop-blur-md z-10"> - <Footer /> + <div className="absolute bottom-0 left-0 right-0 pt-4 pb-3 rounded-b-[16px] bg-theme-bg-sidebar bg-opacity-80 backdrop-filter backdrop-blur-md z-1"> + <Footer /> + </div> </div> </div> </div> + {showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />} </div> - {showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />} - </div> + </> ); } diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx index ad109e74e..b0ffcbfc8 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx @@ -264,7 +264,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) { return ( <div style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }} - className="transition-all duration-500 relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll no-scroll" + className="transition-all duration-500 relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll no-scroll z-[2]" > {isMobile && <SidebarMobileHeader />} <DnDFileUploaderWrapper>