diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/CreateWorkspaceButton.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/CreateWorkspaceButton.tsx index d173dbe2196..b654288c8c1 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/CreateWorkspaceButton.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/CreateWorkspaceButton.tsx @@ -1,9 +1,16 @@ -import { Button } from "@superset/ui/button"; -import { ButtonGroup, ButtonGroupSeparator } from "@superset/ui/button-group"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@superset/ui/dropdown-menu"; import { toast } from "@superset/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { useRef } from "react"; -import { HiMiniPlus, HiOutlineBolt } from "react-icons/hi2"; +import { useCallback, useState } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; +import { HiFolderOpen, HiMiniPlus, HiOutlineBolt } from "react-icons/hi2"; import { trpc } from "renderer/lib/trpc"; import { useOpenNew } from "renderer/react-query/projects"; import { @@ -11,6 +18,7 @@ import { useCreateWorkspace, } from "renderer/react-query/workspaces"; import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal"; +import { HOTKEYS } from "shared/hotkeys"; export interface CreateWorkspaceButtonProps { className?: string; @@ -19,8 +27,7 @@ export interface CreateWorkspaceButtonProps { export function CreateWorkspaceButton({ className, }: CreateWorkspaceButtonProps) { - const modalButtonRef = useRef(null); - const quickCreateButtonRef = useRef(null); + const [open, setOpen] = useState(false); const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); const { data: recentProjects = [] } = trpc.projects.getRecents.useQuery(); @@ -33,29 +40,18 @@ export function CreateWorkspaceButton({ (p) => p.id === activeWorkspace?.projectId, ); - const handleModalCreate = () => { - modalButtonRef.current?.blur(); - openModal(); - }; + const isLoading = + createWorkspace.isPending || + createBranchWorkspace.isPending || + openNew.isPending; - const handleQuickCreate = () => { - quickCreateButtonRef.current?.blur(); - if (currentProject) { - toast.promise( - createWorkspace.mutateAsync({ projectId: currentProject.id }), - { - loading: "Creating workspace...", - success: "Workspace created", - error: (err) => - err instanceof Error ? err.message : "Failed to create workspace", - }, - ); - } else { - handleOpenNewProject(); - } - }; + const handleModalCreate = useCallback(() => { + setOpen(false); + openModal(); + }, [openModal]); - const handleOpenNewProject = async () => { + const handleOpenNewProject = useCallback(async () => { + setOpen(false); try { const result = await openNew.mutateAsync(undefined); if (result.canceled) { @@ -90,55 +86,94 @@ export function CreateWorkspaceButton({ error instanceof Error ? error.message : "An unknown error occurred", }); } - }; + }, [openNew, createBranchWorkspace]); + + const handleQuickCreate = useCallback(() => { + setOpen(false); + if (currentProject) { + toast.promise( + createWorkspace.mutateAsync({ projectId: currentProject.id }), + { + loading: "Creating workspace...", + success: "Workspace created", + error: (err) => + err instanceof Error ? err.message : "Failed to create workspace", + }, + ); + } else { + handleOpenNewProject(); + } + }, [currentProject, createWorkspace, handleOpenNewProject]); + + // Keyboard shortcuts + const handleQuickCreateHotkey = useCallback(() => { + if (!isLoading) handleQuickCreate(); + }, [isLoading, handleQuickCreate]); + + const handleOpenProjectHotkey = useCallback(() => { + if (!isLoading) handleOpenNewProject(); + }, [isLoading, handleOpenNewProject]); + + useHotkeys(HOTKEYS.NEW_WORKSPACE.keys, handleModalCreate); + useHotkeys(HOTKEYS.QUICK_CREATE_WORKSPACE.keys, handleQuickCreateHotkey); + useHotkeys(HOTKEYS.OPEN_PROJECT.keys, handleOpenProjectHotkey); return ( - + - + + + Create workspace or project - - - - - - - {currentProject - ? `Quick create in ${currentProject.name}` - : "Quick create workspace"} - - - + + + + New Workspace + ⌘N + + + + Quick Create + + ⌘⇧N + + + + + + Open Project + + ⌘⇧O + + + + ); } diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx index 8f06bc49e37..fedaaba8267 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/index.tsx @@ -1,4 +1,4 @@ -import { Fragment, useEffect, useRef, useState } from "react"; +import { Fragment, useCallback, useEffect, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { trpc } from "renderer/lib/trpc"; import { @@ -16,7 +16,7 @@ import { WorkspaceGroup } from "./WorkspaceGroup"; const MIN_WORKSPACE_WIDTH = 60; const MAX_WORKSPACE_WIDTH = 160; -const ADD_BUTTON_WIDTH = 48; +const ADD_BUTTON_WIDTH = 40; export function WorkspacesTabs() { const { data: groups = [] } = trpc.workspaces.getAllGrouped.useQuery(); @@ -74,7 +74,7 @@ export function WorkspacesTabs() { // Only create one at a time break; } - }, [groups, isCreating, createBranchWorkspace.mutate]); + }, [groups, isCreating, createBranchWorkspace]); // Flatten workspaces for keyboard navigation const allWorkspaces = groups.flatMap((group) => group.workspaces); @@ -84,9 +84,9 @@ export function WorkspacesTabs() { { length: 9 }, (_, i) => `meta+${i + 1}`, ).join(", "); - useHotkeys( - workspaceKeys, - (event) => { + + const handleWorkspaceSwitch = useCallback( + (event: KeyboardEvent) => { const num = Number(event.key); if (num >= 1 && num <= 9) { const workspace = allWorkspaces[num - 1]; @@ -98,8 +98,7 @@ export function WorkspacesTabs() { [allWorkspaces, setActiveWorkspace], ); - // Navigate to previous workspace (⌘+←) - useHotkeys(HOTKEYS.PREV_WORKSPACE.keys, () => { + const handlePrevWorkspace = useCallback(() => { if (!activeWorkspaceId) return; const currentIndex = allWorkspaces.findIndex( (w) => w.id === activeWorkspaceId, @@ -109,8 +108,7 @@ export function WorkspacesTabs() { } }, [activeWorkspaceId, allWorkspaces, setActiveWorkspace]); - // Navigate to next workspace (⌘+→) - useHotkeys(HOTKEYS.NEXT_WORKSPACE.keys, () => { + const handleNextWorkspace = useCallback(() => { if (!activeWorkspaceId) return; const currentIndex = allWorkspaces.findIndex( (w) => w.id === activeWorkspaceId, @@ -120,6 +118,10 @@ export function WorkspacesTabs() { } }, [activeWorkspaceId, allWorkspaces, setActiveWorkspace]); + useHotkeys(workspaceKeys, handleWorkspaceSwitch); + useHotkeys(HOTKEYS.PREV_WORKSPACE.keys, handlePrevWorkspace); + useHotkeys(HOTKEYS.NEXT_WORKSPACE.keys, handleNextWorkspace); + useEffect(() => { const checkScroll = () => { if (!scrollRef.current) return; @@ -163,58 +165,59 @@ export function WorkspacesTabs() { return (
-
-
-
- {groups.map((group, groupIndex) => ( - - - {groupIndex < groups.length - 1 && ( -
-
-
- )} - - ))} - {isSettingsTabOpen && ( - <> - {groups.length > 0 && ( -
-
-
- )} - - - )} -
- - {/* Fade effects for scroll indication */} - {showStartFade && ( -
+
+
+ {groups.map((group, groupIndex) => ( + + + {groupIndex < groups.length - 1 && ( +
+
+
+ )} + + ))} + {isSettingsTabOpen && ( + <> + {groups.length > 0 && ( +
+
+
+ )} + + )} +
+ + {/* Left fade for scroll indication */} + {showStartFade && ( +
+ )} + + {/* Right side: gradient fade + button container */} +
+ {/* Gradient fade - only show when content overflows */} {showEndFade && ( -
+
)} + {/* Button with solid background */} +
+ +
-
); diff --git a/apps/desktop/src/shared/hotkeys.ts b/apps/desktop/src/shared/hotkeys.ts index 355639b6d20..f9d07eee468 100644 --- a/apps/desktop/src/shared/hotkeys.ts +++ b/apps/desktop/src/shared/hotkeys.ts @@ -190,9 +190,29 @@ export const HOTKEYS = { description: "Focus the next pane in the current tab", }), + // Workspace creation + NEW_WORKSPACE: hotkey({ + keys: "meta+n", + label: "New Workspace", + category: "Workspace", + description: "Open the new workspace modal", + }), + QUICK_CREATE_WORKSPACE: hotkey({ + keys: "meta+shift+n", + label: "Quick Create Workspace", + category: "Workspace", + description: "Quickly create a workspace in the current project", + }), + OPEN_PROJECT: hotkey({ + keys: "meta+shift+o", + label: "Open Project", + category: "Workspace", + description: "Open an existing project folder", + }), + // Window NEW_WINDOW: hotkey({ - keys: "meta+shift+n", + keys: "meta+alt+n", label: "New Window", category: "Window", }),