diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx index b14e662b5b2..6d97698391a 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx @@ -7,8 +7,9 @@ import { useReorderWorkspaces, useSetActiveWorkspace, } from "renderer/react-query/workspaces"; -import { DeleteWorkspaceDialog } from "./DeleteWorkspaceDialog"; import { useTabs } from "renderer/stores"; +import { DeleteWorkspaceDialog } from "./DeleteWorkspaceDialog"; +import { useWorkspaceRename } from "./useWorkspaceRename"; const WORKSPACE_TYPE = "WORKSPACE"; @@ -37,6 +38,7 @@ export function WorkspaceItem({ const reorderWorkspaces = useReorderWorkspaces(); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const tabs = useTabs(); + const rename = useWorkspaceRename(id, title); const needsAttention = tabs .filter((t) => t.workspaceId === id) @@ -80,7 +82,8 @@ export function WorkspaceItem({ ref={(node) => { drag(drop(node)); }} - onMouseDown={() => setActive.mutate({ id })} + onMouseDown={() => !rename.isRenaming && setActive.mutate({ id })} + onDoubleClick={rename.startRename} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} className={` @@ -94,14 +97,30 @@ export function WorkspaceItem({ `} style={{ cursor: isDragging ? "grabbing" : "pointer" }} > - - {title} - - {needsAttention && ( - - - - + {rename.isRenaming ? ( + rename.setRenameValue(e.target.value)} + onBlur={rename.submitRename} + onKeyDown={rename.handleKeyDown} + onClick={(e) => e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + className="flex-1 min-w-0 bg-muted border border-primary rounded px-1 py-0.5 text-sm outline-none" + /> + ) : ( + <> + + {title} + + {needsAttention && ( + + + + + )} + )} diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/useWorkspaceRename.ts b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/useWorkspaceRename.ts new file mode 100644 index 00000000000..dc2e0b1c43d --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/useWorkspaceRename.ts @@ -0,0 +1,64 @@ +import { useEffect, useRef, useState } from "react"; +import { useUpdateWorkspace } from "renderer/react-query/workspaces/useUpdateWorkspace"; + +export function useWorkspaceRename(workspaceId: string, workspaceName: string) { + const [isRenaming, setIsRenaming] = useState(false); + const [renameValue, setRenameValue] = useState(workspaceName); + const inputRef = useRef(null); + const updateWorkspace = useUpdateWorkspace(); + + // Select input text when rename mode is activated + useEffect(() => { + if (isRenaming && inputRef.current) { + inputRef.current.select(); + } + }, [isRenaming]); + + // Sync rename value when workspace name changes + useEffect(() => { + setRenameValue(workspaceName); + }, [workspaceName]); + + const startRename = () => { + setIsRenaming(true); + }; + + const submitRename = () => { + const trimmedValue = renameValue.trim(); + if (trimmedValue && trimmedValue !== workspaceName) { + updateWorkspace.mutate({ + id: workspaceId, + patch: { name: trimmedValue }, + }); + } else { + setRenameValue(workspaceName); + } + setIsRenaming(false); + }; + + const cancelRename = () => { + setRenameValue(workspaceName); + setIsRenaming(false); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + submitRename(); + } else if (e.key === "Escape") { + e.preventDefault(); + cancelRename(); + } + }; + + return { + isRenaming, + renameValue, + inputRef, + setRenameValue, + startRename, + submitRename, + cancelRename, + handleKeyDown, + }; +}