diff --git a/apps/desktop/plans/20260721-sidebar-header-reorg-notes.md b/apps/desktop/plans/20260721-sidebar-header-reorg-notes.md new file mode 100644 index 00000000000..36a84825352 --- /dev/null +++ b/apps/desktop/plans/20260721-sidebar-header-reorg-notes.md @@ -0,0 +1,58 @@ +# Sidebar/header reorg — change log (branch `reorganize-sidebar-header`) + +Revert guide: each item is independent; revert by undoing the listed file(s). + +## Workspace content area (v2) + +- **Presets bar: no border, taller, more chip spacing** — `h-8 gap-0.5 border-b` → `h-10 gap-1.5`, borderless. `v2-workspace/$workspaceId/components/V2PresetsBar/V2PresetsBar.tsx` +- **Run button moved presets bar → tab bar trailing slot** (next to BackgroundTerminalsButton; presets bar `trailing` prop deleted, TopBar portal slot `workspace-topbar-run-slot` deleted). `v2-workspace/$workspaceId/page.tsx`, `V2PresetsBar.tsx`, `_dashboard/components/TopBar/TopBar.tsx` +- **Inverted tab styling** — inactive tabs carry the shaded block/bottom border, active tab blends into pane; bar itself borderless. `packages/panes/.../TabBar/TabBar.tsx`, `TabItem.tsx` + +## Workspace right sidebar (v2) + +- **Tab strip (Files/Changes/Review) border-b removed**; inverted tab button styles. `WorkspaceSidebar/components/SidebarHeader/SidebarHeader.tsx`, `screens/main/.../RightSidebar/headerTabStyles.ts` +- **PR action header: border-b removed; open-in button moved here from TopBar** (renders next to Create PR). `WorkspaceSidebar/components/PRActionHeader/PRActionHeader.tsx`, `TopBar.tsx` +- **Open-in chevron size-3.5 → size-3** to match PR chevron. `TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx` +- **Files tab: search button moved from tab strip into explorer toolbar** (before New File); "EXPLORER" title deleted. `WorkspaceSidebar/components/FilesTab/FilesTab.tsx`, `WorkspaceSidebar.tsx` + +## Dashboard sidebar + +- **New Workspace moved to top** (above Search/Automations/Tasks; also first in collapsed rail). `DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx` +- **Recently-viewed (history) button hidden, not deleted** — two commented lines. `_dashboard/components/NavigationControls/NavigationControls.tsx:7,69` +- **Help menu moved into org dropdown.** `TopBar/components/OrganizationDropdown/components/HelpSubMenu/` (was `DashboardSidebarHelpMenu`) +- **New collapsible "Workspaces" header** above the project list; hover chevron right of label; hosts the Add repository dropdown (moved out of the New Workspace row). Collapse persists via zustand `sidebar-workspaces-collapse`. New: `DashboardSidebar/components/DashboardSidebarWorkspacesHeader/`, `stores/sidebar-workspaces-collapse.ts`; wired in `DashboardSidebar.tsx`, `DashboardSidebarHeader.tsx` +- **Header block border-b removed** (expanded variant only). `DashboardSidebarHeader.tsx` +- **Project header indented** `pl-3` → `pl-5`. `DashboardSidebarProjectSection/components/DashboardSidebarProjectRow/DashboardSidebarProjectRow.tsx` +- **Workspace diff stats (+N −N) only render on the active row.** `DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx` +- **Workspace-count badges removed** from custom section headers and project rows (`totalWorkspaceCount` prop dropped from ProjectRow; collapsed-rail popover count kept). `DashboardSidebarSection/components/DashboardSidebarSectionHeader/DashboardSidebarSectionHeader.tsx`, `DashboardSidebarProjectRow.tsx` + +--- + +# Session 2 additions (same branch, parallel session) + +## Dashboard sidebar + +- **User/org dropdown moved header → footer**; footer is now `[user dropdown | UpdatesPill | ⚙ | ?]` → later `?` removed (help lives in the org dropdown). `DashboardSidebarHeader.tsx`, `DashboardSidebar.tsx` +- **Settings is an icon-only gear** in the footer (was a full-width labeled row); tooltip carries the hotkey. `DashboardSidebar.tsx` +- **Org dropdown (expanded trigger): chevron removed** (topbar variant keeps it); **Settings item removed** from the menu (was the v1 TODO item); **"Switch organization" got an icon** (`HiOutlineArrowsRightLeft`). `TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx` +- **`SubmitPromptDialog` moved** to `OrganizationDropdown/components/`; old `DashboardSidebarHelpMenu/` deleted (git-tracked, restorable). +- **Workspaces tab hidden, not deleted** — `SHOW_WORKSPACES_TAB = false` flag; flip to true to restore. `DashboardSidebarHeader.tsx` +- **New Search row/icon** opens the command palette; clicking while open closes it (state captured on pointerdown because the palette dismisses on outside pointerdown before click). `DashboardSidebarHeader.tsx` +- **Looking-glass button deleted** from `NavigationControls.tsx` (`⌘K` hotkey unaffected). +- **ResourceConsumption moved** from right edge (`ml-auto`) to after back/forward in the top row. `DashboardSidebarHeader.tsx` +- **Section-header hover drag-grip removed** — chevron stays visible; drag still works via row listeners. `DashboardSidebarSectionHeader.tsx` +- **Workspaces under a section: indent `pl-7` → `pl-10`.** `DashboardSidebarExpandedWorkspaceRow.tsx` + +## Terminal pane / changes panel + +- **Pane-title chevron removed** (fetch spinner kept). `v2-workspace/.../TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx` +- **Divider after rich-input ✎ button removed.** `TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx` +- **Duplicate refresh spinner removed** (the refresh icon itself spins). `useChangesTab/components/ChangesToolbar/ChangesToolbar.tsx` +- **Changes header block: `py-1.5` wrapper** around header+toolbar; toolbar `border-b` removed. `useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx`, `ChangesToolbar.tsx` + +## Tabs / top bar (shared surfaces) + +- **`headerTabStyles.ts` gained `inverted` option** used only by v2 `SidebarHeader.tsx` (`inverted: true`) — v1 RightSidebar styling unchanged; active tab framed by l/r/t borders, transparent sides keep 1px stability. +- **`packages/panes` TabItem/TabBar**: inverted colors, active-tab l/r/t frame, close-button hover bgs swapped; ALL `border-b` removed (tabbed + empty-state variants) and inactive-tab `border-r` separators removed; `+` button got `ml-1.5`. Affects every `Workspace` consumer. +- **`TopBar.tsx` `border-b` removed** — NOTE: dashboard-wide; every route loses the line under the top bar, not just the workspace view. +- **TopBar merged into the pane tab bar** (v2 workspace route, expanded sidebar only): `layout.tsx` skips `` when `onV2WorkspaceRoute && !versionMismatch && sidebarOutsideColumn`; the tab bar is now the Electron drag region (`drag` on the bar root, `no-drag` on tabs track / add button / trailing — `packages/panes` `TabBar.tsx`); `RightSidebarToggle` + non-Mac `WindowControls` moved into the tab-bar trailing slot (`v2-workspace/$workspaceId/page.tsx`). Collapsed/closed sidebar still shows the TopBar (its inset keeps content clear of macOS traffic lights). Workspace title breadcrumb no longer renders on this route. diff --git a/apps/desktop/src/renderer/commandPalette/CommandPaletteHost.tsx b/apps/desktop/src/renderer/commandPalette/CommandPaletteHost.tsx index 1dd26424cc6..c8ddbcdcd59 100644 --- a/apps/desktop/src/renderer/commandPalette/CommandPaletteHost.tsx +++ b/apps/desktop/src/renderer/commandPalette/CommandPaletteHost.tsx @@ -5,6 +5,7 @@ import { useFrameStackStore } from "./core/frames"; import { registerAllModules } from "./modules"; import { CommandPalette } from "./ui/CommandPalette/CommandPalette"; import { DeleteWorkspaceMount } from "./ui/DeleteWorkspaceMount/DeleteWorkspaceMount"; +import { FolderImportMount } from "./ui/FolderImportMount/FolderImportMount"; import { RemoveFromSidebarMount } from "./ui/RemoveFromSidebarMount/RemoveFromSidebarMount"; import { SetPreferredOpenInAppMount } from "./ui/SetPreferredOpenInAppMount/SetPreferredOpenInAppMount"; @@ -21,6 +22,7 @@ export function CommandPaletteHost({ children }: { children?: ReactNode }) { + {children} ); diff --git a/apps/desktop/src/renderer/commandPalette/core/sections.ts b/apps/desktop/src/renderer/commandPalette/core/sections.ts index 20db0610467..8ed1305e2f0 100644 --- a/apps/desktop/src/renderer/commandPalette/core/sections.ts +++ b/apps/desktop/src/renderer/commandPalette/core/sections.ts @@ -1,11 +1,12 @@ import type { CommandContext, SectionId } from "./types"; -const BASE: SectionId[] = ["actions", "navigation"]; +const BASE: SectionId[] = ["actions", "navigation", "add-project"]; export const SECTION_LABELS: Record = { workspace: "Workspace actions", actions: "Actions", navigation: "Navigation", + "add-project": "Add project", }; export function resolveSectionOrder(context: CommandContext): SectionId[] { diff --git a/apps/desktop/src/renderer/commandPalette/core/types.ts b/apps/desktop/src/renderer/commandPalette/core/types.ts index 7b80ee5620f..607643957e6 100644 --- a/apps/desktop/src/renderer/commandPalette/core/types.ts +++ b/apps/desktop/src/renderer/commandPalette/core/types.ts @@ -3,7 +3,7 @@ import type { ElementType } from "react"; import type { HotkeyId } from "renderer/hotkeys/registry"; import type { HostServiceAvailabilityStatus } from "renderer/lib/host-service-unavailable"; -export type SectionId = "workspace" | "actions" | "navigation"; +export type SectionId = "workspace" | "actions" | "navigation" | "add-project"; export interface CommandContext { route: { diff --git a/apps/desktop/src/renderer/commandPalette/modules/addProject/commands.tsx b/apps/desktop/src/renderer/commandPalette/modules/addProject/commands.tsx new file mode 100644 index 00000000000..0c28bb43964 --- /dev/null +++ b/apps/desktop/src/renderer/commandPalette/modules/addProject/commands.tsx @@ -0,0 +1,41 @@ +import { FolderInputIcon, LayoutTemplateIcon, PlusIcon } from "lucide-react"; +import { useAddRepositoryModalStore } from "renderer/stores/add-repository-modal"; +import { useFolderImportIntent } from "renderer/stores/folder-import-intent"; +import type { Command, CommandProvider } from "../../core/types"; + +export const addProjectProvider: CommandProvider = { + id: "add-project", + provide: () => { + const commands: Command[] = [ + { + id: "addProject.cloneFromUrl", + title: "Clone from URL", + section: "add-project", + icon: PlusIcon, + keywords: ["add project", "repository", "repo", "git", "clone"], + run: () => { + void useAddRepositoryModalStore.getState().openNewProject(); + }, + }, + { + id: "addProject.openFromFolder", + title: "Open from folder", + section: "add-project", + icon: FolderInputIcon, + keywords: ["add project", "import", "local", "directory"], + run: () => useFolderImportIntent.getState().request(), + }, + { + id: "addProject.startFromTemplate", + title: "Start from a template", + section: "add-project", + icon: LayoutTemplateIcon, + keywords: ["add project", "new", "gallery", "starter"], + run: () => { + void useAddRepositoryModalStore.getState().openTemplateGallery(); + }, + }, + ]; + return commands; + }, +}; diff --git a/apps/desktop/src/renderer/commandPalette/modules/index.ts b/apps/desktop/src/renderer/commandPalette/modules/index.ts index 81b11dad15b..b61685b8ec6 100644 --- a/apps/desktop/src/renderer/commandPalette/modules/index.ts +++ b/apps/desktop/src/renderer/commandPalette/modules/index.ts @@ -1,5 +1,6 @@ import { registerProvider } from "../core/registry"; import { actionsProvider } from "./actions/commands"; +import { addProjectProvider } from "./addProject/commands"; import { navigationProvider } from "./navigation/commands"; import { openInProvider } from "./openIn/commands"; import { workspaceProvider } from "./workspace/commands"; @@ -10,6 +11,7 @@ export function registerAllModules(): () => void { registerProvider(actionsProvider), registerProvider(openInProvider), registerProvider(navigationProvider), + registerProvider(addProjectProvider), ]; return () => { for (const u of unregisters) u(); diff --git a/apps/desktop/src/renderer/commandPalette/ui/FolderImportMount/FolderImportMount.tsx b/apps/desktop/src/renderer/commandPalette/ui/FolderImportMount/FolderImportMount.tsx new file mode 100644 index 00000000000..71ac24c2803 --- /dev/null +++ b/apps/desktop/src/renderer/commandPalette/ui/FolderImportMount/FolderImportMount.tsx @@ -0,0 +1,41 @@ +import { toast } from "@superset/ui/sonner"; +import { useNavigate } from "@tanstack/react-router"; +import { useEffect, useRef } from "react"; +import { useFolderFirstImport } from "renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport"; +import { useFolderImportIntent } from "renderer/stores/folder-import-intent"; + +export function FolderImportMount() { + const tick = useFolderImportIntent((s) => s.tick); + const navigate = useNavigate(); + const folderImport = useFolderFirstImport({ + onError: (message) => { + toast.error(`Import failed: ${message}`); + }, + onMultipleProjects: ({ candidates }) => { + toast.error("Import failed", { + description: `Multiple projects use this repository (${candidates.length}). Choose the project in settings to set it up on this device.`, + action: { + label: "Open Projects", + onClick: () => navigate({ to: "/settings/projects" }), + }, + }); + }, + }); + const folderImportRef = useRef(folderImport); + folderImportRef.current = folderImport; + // Seed with the mount-time tick so a remount doesn't replay an import + // triggered earlier in the session. + const lastTickRef = useRef(tick); + + useEffect(() => { + if (tick === lastTickRef.current) return; + lastTickRef.current = tick; + void folderImportRef.current.start().then((result) => { + if (result) { + toast.success("Project ready — open it from the sidebar."); + } + }); + }, [tick]); + + return null; +} diff --git a/apps/desktop/src/renderer/commandPalette/ui/FolderImportMount/index.ts b/apps/desktop/src/renderer/commandPalette/ui/FolderImportMount/index.ts new file mode 100644 index 00000000000..007f59ca655 --- /dev/null +++ b/apps/desktop/src/renderer/commandPalette/ui/FolderImportMount/index.ts @@ -0,0 +1 @@ +export { FolderImportMount } from "./FolderImportMount"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx index 38938f70224..4ac2718140b 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx @@ -18,6 +18,7 @@ import { verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; +import { OverflowFadeContainer } from "@superset/ui/overflow-fade-container"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { cn } from "@superset/ui/utils"; import { useMatchRoute, useNavigate } from "@tanstack/react-router"; @@ -26,15 +27,17 @@ import { createPortal } from "react-dom"; import { HiOutlineCog6Tooth } from "react-icons/hi2"; import { UpdatesPill } from "renderer/components/UpdatesPill"; import { useHotkeyDisplay } from "renderer/hotkeys"; +import { OrganizationDropdown } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown"; import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { useInlineWorkspacePortsEnabled } from "renderer/stores/inline-workspace-ports"; +import { useSidebarWorkspacesCollapseStore } from "renderer/stores/sidebar-workspaces-collapse"; import { DashboardSidebarHeader } from "./components/DashboardSidebarHeader"; -import { DashboardSidebarHelpMenu } from "./components/DashboardSidebarHelpMenu"; import { DashboardSidebarHoverCardOverlay } from "./components/DashboardSidebarHoverCardOverlay"; import { DashboardSidebarPortsList } from "./components/DashboardSidebarPortsList"; import { DashboardSidebarProjectSection } from "./components/DashboardSidebarProjectSection"; import { DashboardSidebarSectionRenameProvider } from "./components/DashboardSidebarSectionRenameContext"; +import { DashboardSidebarWorkspacesHeader } from "./components/DashboardSidebarWorkspacesHeader"; import { V2SetupScriptCard } from "./components/V2SetupScriptCard"; import { useDashboardSidebarData } from "./hooks/useDashboardSidebarData"; import { useDashboardSidebarShortcuts } from "./hooks/useDashboardSidebarShortcuts"; @@ -109,6 +112,9 @@ export function DashboardSidebar({ const inlineWorkspacePortsEnabled = useInlineWorkspacePortsEnabled(); const v2RouteMatch = matchRoute({ to: "/v2-workspace/$workspaceId" }); const activeV2WorkspaceId = v2RouteMatch ? v2RouteMatch.workspaceId : null; + const workspacesListCollapsed = useSidebarWorkspacesCollapseStore( + (s) => s.isCollapsed, + ); const sensors = useSensors( useSensor(MouseSensor, { activationConstraint: { distance: 8 } }), @@ -184,56 +190,63 @@ export function DashboardSidebar({
-
- { - const project = groups.find((p) => p.id === active.id); - setActiveProject(project ?? null); - }} - onDragEnd={handleDragEnd} - onDragCancel={() => setActiveProject(null)} - > - } + + + {(isCollapsed || !workspacesListCollapsed) && ( + { + const project = groups.find((p) => p.id === active.id); + setActiveProject(project ?? null); + }} + onDragEnd={handleDragEnd} + onDragCancel={() => setActiveProject(null)} > - {orderedGroups.map((project) => ( - - ))} - + + {orderedGroups.map((project) => ( + + ))} + - {createPortal( - - {activeProject && ( -
- {}} - onToggleCollapse={() => {}} - /> -
- )} -
, - document.body, - )} -
-
+ {createPortal( + + {activeProject && ( +
+ {}} + onToggleCollapse={() => {}} + /> +
+ )} +
, + document.body, + )} + + )} + {!isCollapsed && !inlineWorkspacePortsEnabled && ( )} @@ -246,59 +259,42 @@ export function DashboardSidebar({ )}
{isCollapsed ? ( - - - - - Settings - + ) : ( - +
+ +
)} - + + + + + + {settingsHotkey !== "Unassigned" + ? `Settings (${settingsHotkey})` + : "Settings"} + +
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx index a52cd430835..fd051e95d14 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx @@ -8,6 +8,7 @@ import { toast } from "@superset/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { cn } from "@superset/ui/utils"; import { useMatchRoute, useNavigate } from "@tanstack/react-router"; +import { useRef } from "react"; import { HiMiniPlus, HiOutlineClipboardDocumentList } from "react-icons/hi2"; import { LuClock, @@ -16,7 +17,9 @@ import { LuLayers, LuLayoutTemplate, LuPlus, + LuSearch, } from "react-icons/lu"; +import { useFrameStackStore } from "renderer/commandPalette"; import { GATED_FEATURES, usePaywall } from "renderer/components/Paywall"; import { ZoomStable } from "renderer/components/ZoomStable"; import { useZoomFactor } from "renderer/hooks/useZoomFactor"; @@ -25,7 +28,6 @@ import { electronTrpc } from "renderer/lib/electron-trpc"; import { useFolderFirstImport } from "renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport"; import { NavigationControls } from "renderer/routes/_authenticated/_dashboard/components/NavigationControls"; import { SidebarToggle } from "renderer/routes/_authenticated/_dashboard/components/SidebarToggle"; -import { OrganizationDropdown } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown"; import { ResourceConsumption } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption"; import { useFailedAutomations } from "renderer/routes/_authenticated/_dashboard/hooks/useFailedAutomations"; import { @@ -39,6 +41,9 @@ import { } from "renderer/stores/add-repository-modal"; import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal"; +// Temporarily hidden, not removed — flip to true to restore the Workspaces tab. +const SHOW_WORKSPACES_TAB = false; + interface DashboardSidebarHeaderProps { isCollapsed?: boolean; } @@ -73,6 +78,19 @@ export function DashboardSidebarHeader({ }; const shortcutText = useHotkeyDisplay("NEW_WORKSPACE").text; + const searchShortcutText = useHotkeyDisplay("OPEN_COMMAND_PALETTE").text; + const openCommandPalette = useFrameStackStore((s) => s.setOpen); + // The palette dialog dismisses on outside pointerdown before our click fires, + // so a live-state toggle would always reopen it. Capture the state at + // pointerdown to make clicking the button close an open palette. + const paletteWasOpenRef = useRef(false); + const handleSearchPointerDown = () => { + paletteWasOpenRef.current = useFrameStackStore.getState().open; + }; + const handleSearchClick = () => { + openCommandPalette(!paletteWasOpenRef.current); + paletteWasOpenRef.current = false; + }; const { data: platform } = electronTrpc.window.getPlatform.useQuery(); // Default to Mac while loading so we don't briefly cover the traffic lights. const isMac = platform === undefined || platform === "darwin"; @@ -119,27 +137,60 @@ export function DashboardSidebarHeader({ if (isCollapsed) { return ( -
- +
+ + + + + + New Workspace ({shortcutText}) + + - Workspaces + + {searchShortcutText !== "Unassigned" + ? `Search (${searchShortcutText})` + : "Search"} + + {SHOW_WORKSPACES_TAB && ( + + + + + Workspaces + + )} + - - - New Workspace ({shortcutText}) - - - @@ -245,7 +281,7 @@ export function DashboardSidebarHeader({ return (
- -
- + + + {SHOW_WORKSPACES_TAB && ( + + )} + - -
- - - - - - - - - Add repository - - event.preventDefault()} - > - openNewProject()}> - - Clone from URL - - - - Open from folder - - openTemplateGallery()}> - - Start from a template - - - -
); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHelpMenu/DashboardSidebarHelpMenu.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHelpMenu/DashboardSidebarHelpMenu.tsx deleted file mode 100644 index d172cd4327d..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHelpMenu/DashboardSidebarHelpMenu.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { COMPANY } from "@superset/shared/constants"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuShortcut, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuTrigger, -} from "@superset/ui/dropdown-menu"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { useNavigate } from "@tanstack/react-router"; -import { useState } from "react"; -import { FaDiscord, FaGithub, FaXTwitter } from "react-icons/fa6"; -import { - HiOutlineBookOpen, - HiOutlineChatBubbleLeftRight, - HiOutlineEnvelope, - HiOutlineQuestionMarkCircle, -} from "react-icons/hi2"; -import { IoBugOutline } from "react-icons/io5"; -import { LuKeyboard, LuMegaphone } from "react-icons/lu"; -import { useHotkeyDisplay } from "renderer/hotkeys"; -import { electronTrpc } from "renderer/lib/electron-trpc"; -import { SubmitPromptDialog } from "./components/SubmitPromptDialog"; - -interface DashboardSidebarHelpMenuProps { - isCollapsed: boolean; -} - -export function DashboardSidebarHelpMenu({ - isCollapsed, -}: DashboardSidebarHelpMenuProps) { - const navigate = useNavigate(); - const shortcutsHotkey = useHotkeyDisplay("SHOW_HOTKEYS").text; - const [submitPromptOpen, setSubmitPromptOpen] = useState(false); - const openUrlMutation = electronTrpc.external.openUrl.useMutation(); - - const openExternal = (url: string) => { - openUrlMutation.mutate(url); - }; - - const triggerButton = ( - - ); - - return ( - <> - - {isCollapsed ? ( - - - {triggerButton} - - Help - - ) : ( - {triggerButton} - )} - - setSubmitPromptOpen(true)}> - - Submit a prompt - - openExternal(COMPANY.DOCS_URL)}> - - Documentation - - navigate({ to: "/settings/keyboard" })} - > - - Keyboard Shortcuts - {shortcutsHotkey !== "Unassigned" && ( - {shortcutsHotkey} - )} - - openExternal(COMPANY.REPORT_ISSUE_URL)} - > - - Report Issue - - - - - Contact Us - - - openExternal(COMPANY.GITHUB_URL)} - > - - GitHub - - openExternal(COMPANY.DISCORD_URL)} - > - - Discord - - openExternal(COMPANY.X_URL)}> - X - - openExternal(COMPANY.MAIL_TO)}> - - Email Support - - - - - - - - ); -} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHelpMenu/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHelpMenu/index.ts deleted file mode 100644 index e80392612a1..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHelpMenu/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DashboardSidebarHelpMenu } from "./DashboardSidebarHelpMenu"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsx index df2d8c24766..2baee8ed2d3 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsx @@ -97,7 +97,6 @@ export function DashboardSidebarProjectSection({ { projectName: string; iconUrl: string | null; - totalWorkspaceCount: number; isCollapsed: boolean; isRenaming: boolean; renameValue: string; @@ -29,7 +28,6 @@ export const DashboardSidebarProjectRow = forwardRef< { projectName, iconUrl, - totalWorkspaceCount, isCollapsed, isRenaming, renameValue, @@ -63,7 +61,7 @@ export const DashboardSidebarProjectRow = forwardRef< } } className={cn( - "group flex min-h-10 w-full items-center pl-3 pr-2 py-1.5 text-sm font-medium", + "group flex min-h-10 w-full items-center pl-5 pr-2 py-1.5 text-sm font-medium", "hover:bg-muted/50 transition-colors", className, )} @@ -118,9 +116,6 @@ export const DashboardSidebarProjectRow = forwardRef< New workspace - - {totalWorkspaceCount} -
)} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSection/components/DashboardSidebarSectionHeader/DashboardSidebarSectionHeader.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSection/components/DashboardSidebarSectionHeader/DashboardSidebarSectionHeader.tsx index e36fe8c8819..d33d5335b4a 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSection/components/DashboardSidebarSectionHeader/DashboardSidebarSectionHeader.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSection/components/DashboardSidebarSectionHeader/DashboardSidebarSectionHeader.tsx @@ -5,7 +5,6 @@ import { type ReactNode, } from "react"; import { HiChevronRight } from "react-icons/hi2"; -import { LuGripVertical } from "react-icons/lu"; import { RenameInput } from "renderer/screens/main/components/WorkspaceSidebar/RenameInput"; import type { DashboardSidebarSection } from "../../../../types"; @@ -67,11 +66,10 @@ export const DashboardSidebarSectionHeader = forwardRef<
-
@@ -93,22 +91,13 @@ export const DashboardSidebarSectionHeader = forwardRef< {actions ? ( // biome-ignore lint/a11y/noStaticElementInteractions: Nested action controls handle their own semantics; this wrapper only isolates events from the header toggle.
event.stopPropagation()} onKeyDown={(event) => event.stopPropagation()} > {actions}
) : null} - - {section.workspaces.length} -
)} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx index 1b4f9972fb4..977d8fb094f 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx @@ -145,7 +145,7 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef< onDoubleClick={onDoubleClick} className={cn( "group relative flex w-full items-center py-2 pr-2", - isInSection ? "pl-7" : "pl-5", + isInSection ? "pl-10" : "pl-5", onClick && "cursor-pointer", )} > @@ -260,6 +260,7 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef< {creationStatusText} ) : ( + isActive && diffStats && (diffStats.additions > 0 || diffStats.deletions > 0) && ( s.isCollapsed); + const toggleCollapsed = useSidebarWorkspacesCollapseStore((s) => s.toggle); + const openNewProject = useOpenNewProjectModal(); + const openTemplateGallery = useOpenTemplateGalleryModal(); + const navigate = useNavigate(); + const folderImport = useFolderFirstImport({ + onError: (message) => { + toast.error(`Import failed: ${message}`); + }, + onMultipleProjects: ({ candidates }) => { + toast.error("Import failed", { + description: `Multiple projects use this repository (${candidates.length}). Choose the project in settings to set it up on this device.`, + action: { + label: "Open Projects", + onClick: () => navigate({ to: "/settings/projects" }), + }, + }); + }, + }); + + const handleImportFolder = async () => { + const result = await folderImport.start(); + if (result) { + toast.success("Project ready — open it from the sidebar."); + } + }; + + return ( + // biome-ignore lint/a11y/useSemanticElements: can't be a native + + + Add repository + + event.preventDefault()} + > + openNewProject()}> + + Clone from URL + + + + Open from folder + + openTemplateGallery()}> + + Start from a template + + + + + ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/index.ts new file mode 100644 index 00000000000..900df6baddd --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/index.ts @@ -0,0 +1 @@ +export { DashboardSidebarWorkspacesHeader } from "./DashboardSidebarWorkspacesHeader"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/NavigationControls/NavigationControls.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/NavigationControls/NavigationControls.tsx index fb209cf5413..b23a794558c 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/NavigationControls/NavigationControls.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/NavigationControls/NavigationControls.tsx @@ -1,15 +1,13 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useLocation, useRouter } from "@tanstack/react-router"; import { useEffect } from "react"; -import { LuArrowLeft, LuArrowRight, LuSearch } from "react-icons/lu"; -import { useFrameStackStore } from "renderer/commandPalette/core/frames"; +import { LuArrowLeft, LuArrowRight } from "react-icons/lu"; import { HotkeyLabel, useHotkey } from "renderer/hotkeys"; -import { HistoryDropdown } from "./components/HistoryDropdown"; +// Temporarily hidden: import { HistoryDropdown } from "./components/HistoryDropdown"; export function NavigationControls() { const router = useRouter(); const location = useLocation(); - const openCommandPalette = useFrameStackStore((s) => s.setOpen); const canGoBack = router.history.canGoBack(); const canGoForward = location.state.__TSR_index < router.history.length - 1; @@ -66,22 +64,7 @@ export function NavigationControls() { - - - - - - - - - - + {/* Temporarily hidden: */} ); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/TopBar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/TopBar.tsx index f947550f00a..c114e9a5e81 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/TopBar.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/TopBar.tsx @@ -12,7 +12,6 @@ import { OpenInMenuButton } from "./components/OpenInMenuButton"; import { OrganizationDropdown } from "./components/OrganizationDropdown"; import { ResourceConsumption } from "./components/ResourceConsumption"; import { RightSidebarToggle } from "./components/RightSidebarToggle"; -import { V2WorkspaceOpenInButton } from "./components/V2WorkspaceOpenInButton"; import { V2WorkspaceTitle } from "./components/V2WorkspaceTitle"; import { WindowControls } from "./components/WindowControls"; @@ -52,7 +51,7 @@ export function TopBar() { return (
- {!sidebarHostsChrome && isV2CloudEnabled && ( - - )} {!isOnline && (
Offline
)} - {/* Portal target for the v2 run button when the presets bar is - hidden (see v2-workspace page). empty:hidden keeps the flex - gap from doubling while nothing is portaled in. */} - {isV2WorkspaceRoute && ( -
- )} - {isV2WorkspaceRoute ? ( - - ) : workspace?.worktreePath ? ( + {!isV2WorkspaceRoute && workspace?.worktreePath ? ( {displayName} {planBadge} - ) : ( diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx index 94b3bfcf2ca..27d42ac6521 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx @@ -183,12 +183,19 @@ function DashboardLayout() { isWorkspaceSidebarOpen && !isWorkspaceSidebarCollapsed(); + // On the v2 workspace route with an expanded sidebar the TopBar row is + // merged into the pane tab bar (which provides the drag region and hosts + // the right-sidebar toggle). Collapsed/closed sidebars keep the TopBar: + // its inset is what keeps content clear of the macOS traffic lights. + const hideTopBar = + onV2WorkspaceRoute && !versionMismatch && sidebarOutsideColumn; + return (
{sidebarOutsideColumn && sidebarPanel}
- + {!hideTopBar && }
{!sidebarOutsideColumn && sidebarPanel}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/V2PresetsBar/V2PresetsBar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/V2PresetsBar/V2PresetsBar.tsx index 53c68c86b2e..2099b8eee60 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/V2PresetsBar/V2PresetsBar.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/V2PresetsBar/V2PresetsBar.tsx @@ -10,13 +10,7 @@ import { import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useNavigate } from "@tanstack/react-router"; import { Eye, EyeOff, Settings } from "lucide-react"; -import { - type ReactNode, - useCallback, - useEffect, - useMemo, - useState, -} from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { HiMiniCommandLine } from "react-icons/hi2"; import { useIsDarkTheme } from "renderer/assets/app-icons/preset-icons"; import { HotkeyMenuShortcut } from "renderer/components/HotkeyMenuShortcut"; @@ -33,7 +27,6 @@ interface V2PresetsBarProps { executePreset: (preset: V2TerminalPresetRow) => void | Promise; showPresetsBar: boolean; onToggleShowPresetsBar: (enabled: boolean) => void; - trailing?: ReactNode; } // Co-located to keep v2 self-contained. Mirrors the v1 array in @@ -75,7 +68,6 @@ export function V2PresetsBar({ executePreset, showPresetsBar, onToggleShowPresetsBar, - trailing, }: V2PresetsBarProps) { const navigate = useNavigate(); const isDark = useIsDarkTheme(); @@ -202,7 +194,7 @@ export function V2PresetsBar({ return (
@@ -301,9 +293,6 @@ export function V2PresetsBar({ /> ); })} - {trailing ? ( -
{trailing}
- ) : null}
); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx index e09f60987e0..4c7ecfcdf42 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx @@ -1,8 +1,5 @@ -import { Button } from "@superset/ui/button"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; -import { Search } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { LuFile, LuGitCompareArrows } from "react-icons/lu"; import { useWorkspaceGitStatus } from "renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/providers/WorkspaceGitStatusProvider"; @@ -53,32 +50,6 @@ interface WorkspaceSidebarProps { workspaceId: string; } -function IconButton({ - icon: Icon, - tooltip, - onClick, -}: { - icon: React.ComponentType<{ className?: string }>; - tooltip: string; - onClick?: () => void; -}) { - return ( - - - - - {tooltip} - - ); -} - export function WorkspaceSidebar({ onSelectFile, onSelectDiffFile, @@ -162,7 +133,6 @@ export function WorkspaceSidebar({ id: "files", label: "Files", icon: LuFile, - actions: , content: ( ), }; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx index d780fd8d513..b9ea09e2b95 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx @@ -19,6 +19,7 @@ import { FoldVertical, Loader2, RefreshCw, + Search, } from "lucide-react"; import { useCallback, useEffect, useRef } from "react"; import { useGitStatusMap } from "renderer/hooks/host-service/useGitStatusMap"; @@ -46,6 +47,7 @@ import { import { useFilesTabActions } from "./hooks/useFilesTabActions"; import { useFilesTabBridge } from "./hooks/useFilesTabBridge"; import { useFilesTabDrop } from "./hooks/useFilesTabDrop"; +import { useFileTreeScrollFade } from "./hooks/useFileTreeScrollFade"; import { buildPierreGitStatus } from "./utils/buildPierreGitStatus"; import { stripTrailingSlash, toAbs, toRel } from "./utils/treePath"; @@ -66,6 +68,7 @@ interface FilesTabProps { } | null; workspaceId: string; gitStatus: GitStatusData | undefined; + onSearch?: () => void; } export function FilesTab({ @@ -74,6 +77,7 @@ export function FilesTab({ pendingReveal, workspaceId, gitStatus, + onSearch, }: FilesTabProps) { // Shares the query cache with V2WorkspacePage's workspace.get query, so // the first render after a workspace switch typically already has cached @@ -262,6 +266,10 @@ export function FilesTab({ ], ); + const fadeContainerRef = useFileTreeScrollFade( + Boolean(rootPath), + ); + if (!rootPath) { return (
@@ -280,6 +288,7 @@ export function FilesTab({ return ( // biome-ignore lint/a11y/noStaticElementInteractions: Drop zone for external file upload
- Explorer -
+
+ {onSearch && ( + + )} +
{loading ? ( - + ) : ( - + )} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/hooks/useFileTreeScrollFade/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/hooks/useFileTreeScrollFade/index.ts new file mode 100644 index 00000000000..bb55931706e --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/hooks/useFileTreeScrollFade/index.ts @@ -0,0 +1 @@ +export { useFileTreeScrollFade } from "./useFileTreeScrollFade"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/hooks/useFileTreeScrollFade/useFileTreeScrollFade.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/hooks/useFileTreeScrollFade/useFileTreeScrollFade.ts new file mode 100644 index 00000000000..31b89ee8fda --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/hooks/useFileTreeScrollFade/useFileTreeScrollFade.ts @@ -0,0 +1,89 @@ +import { useLayoutEffect, useRef } from "react"; + +const FADE_SIZE = "1.5rem"; +const SCROLLER_SELECTOR = '[data-file-tree-virtualized-scroll="true"]'; + +function buildMask(top: boolean, bottom: boolean): string { + if (!top && !bottom) return ""; + return `linear-gradient(to bottom, transparent, black ${top ? FADE_SIZE : "0px"}, black calc(100% - ${bottom ? FADE_SIZE : "0px"}), transparent)`; +} + +/** + * Edge-fades the Pierre file tree while it has hidden scrollable content. + * The tree's scroller lives inside the `file-tree-container` web component's + * shadow root, where the shared `fade-edge-*` utilities can't reach, so this + * mirrors packages/ui fade-edge.css as an inline mask on that scroller. + */ +export function useFileTreeScrollFade(enabled: boolean) { + const containerRef = useRef(null); + + useLayoutEffect(() => { + const container = containerRef.current; + if (!container || !enabled) return; + + let scroller: HTMLElement | null = null; + + const update = () => { + if (!scroller) return; + const maxScrollTop = scroller.scrollHeight - scroller.clientHeight; + const mask = buildMask( + scroller.scrollTop > 1, + scroller.scrollTop < maxScrollTop - 1, + ); + scroller.style.maskImage = mask; + scroller.style.setProperty("-webkit-mask-image", mask); + }; + + const resizeObserver = new ResizeObserver(update); + + const attach = (next: HTMLElement | null) => { + if (next === scroller) return; + if (scroller) { + scroller.removeEventListener("scroll", update); + resizeObserver.unobserve(scroller); + } + scroller = next; + if (scroller) { + scroller.addEventListener("scroll", update, { passive: true }); + resizeObserver.observe(scroller); + update(); + } + }; + + const mutationObserver = new MutationObserver(() => { + const shadowRoot = container.querySelector( + "file-tree-container", + )?.shadowRoot; + if (!shadowRoot) return; + attach(shadowRoot.querySelector(SCROLLER_SELECTOR)); + update(); + }); + + // The shadow root attaches on connect and the scroller mounts (and can + // remount) as the tree model loads; poll a frame at a time until the + // shadow root exists, then let the observer track scroller swaps. Row + // mutations also change scrollHeight without resizing the scroller. + let raf = 0; + const init = () => { + const shadowRoot = container.querySelector( + "file-tree-container", + )?.shadowRoot; + if (!shadowRoot) { + raf = requestAnimationFrame(init); + return; + } + attach(shadowRoot.querySelector(SCROLLER_SELECTOR)); + mutationObserver.observe(shadowRoot, { childList: true, subtree: true }); + }; + init(); + + return () => { + cancelAnimationFrame(raf); + mutationObserver.disconnect(); + resizeObserver.disconnect(); + scroller?.removeEventListener("scroll", update); + }; + }, [enabled]); + + return containerRef; +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/PRActionHeader/PRActionHeader.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/PRActionHeader/PRActionHeader.tsx index 13a8a4d2639..f21bb7bc114 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/PRActionHeader/PRActionHeader.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/PRActionHeader/PRActionHeader.tsx @@ -1,5 +1,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { VscGitPullRequest, VscLoading } from "react-icons/vsc"; +import { V2WorkspaceOpenInButton } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton"; import type { PRFlowDispatch } from "../../hooks/usePRFlowDispatch"; import { PRStatusGroup } from "./components/PRStatusGroup"; import { @@ -31,8 +32,9 @@ export function PRActionHeader({ const action = selectActionButton(state); return ( -
-
+
+
+ - + {/* Icon-only when the nearest @container is narrow (resizable + sidebar); the number and indicators come back with room. */} + #{pr.number} - {showIndicators && } + {showIndicators && ( + + + + )} t.id === activeTab)?.actions; return ( -
+
{tabs.map((tab) => { const isActive = activeTab === tab.id; @@ -38,6 +38,7 @@ export function SidebarHeader({ getSidebarHeaderTabButtonClassName({ isActive, compact, + inverted: true, }), "relative flex-1 justify-center", )} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx index 55b3973ac05..58077a6f823 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/ChangesFileList.tsx @@ -1,3 +1,4 @@ +import { OverflowFadeContainer } from "@superset/ui/overflow-fade-container"; import { memo, useMemo } from "react"; import type { ChangesetFile } from "renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useChangeset"; import type { ChangesViewMode } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema"; @@ -86,7 +87,8 @@ export const ChangesFileList = memo(function ChangesFileList({ } return ( -
@@ -132,6 +134,6 @@ export const ChangesFileList = memo(function ChangesFileList({ ); })} -
+ ); }); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/components/ViewModeToggle/ViewModeToggle.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/components/ViewModeToggle/ViewModeToggle.tsx index 0ca41b1022b..fcfdd888f38 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/components/ViewModeToggle/ViewModeToggle.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/components/ViewModeToggle/ViewModeToggle.tsx @@ -1,5 +1,5 @@ +import { Button } from "@superset/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { cn } from "@superset/ui/utils"; import { Folder, ListTree } from "lucide-react"; import type { ChangesViewMode } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema"; @@ -9,58 +9,25 @@ interface ViewModeToggleProps { } /** - * Two-button segmented toggle: folders (flat by parent folder) vs tree - * (full directory hierarchy). + * Single toggle between folders (flat by parent folder) and tree (full + * directory hierarchy) views. Shows the mode it will switch to. */ export function ViewModeToggle({ viewMode, onChange }: ViewModeToggleProps) { - return ( -
- onChange("folders")} - /> - onChange("tree")} - /> -
- ); -} - -interface ToggleButtonProps { - icon: React.ComponentType<{ className?: string }>; - label: string; - active: boolean; - onClick: () => void; -} - -function ToggleButton({ - icon: Icon, - label, - active, - onClick, -}: ToggleButtonProps) { + const next: ChangesViewMode = viewMode === "folders" ? "tree" : "folders"; + const label = next === "tree" ? "Tree view" : "Folder view"; + const Icon = next === "tree" ? ListTree : Folder; return ( - + + {label} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx index e9f18624a26..aa7018c0a5d 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx @@ -111,32 +111,34 @@ export const ChangesTabContent = memo(function ChangesTabContent({ return (
- - +
+ + +
-
+ <> +
)} - {isRefreshing && ( - - - - )}
-
- - - - - - Refresh changes - - - - - - {label} - +
+
+ + + + + + Refresh changes + + + + + + {label} + +
-
+ ); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx index 57f8b6f6dfa..6c730cc1a27 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalPaneHeaderExtras/TerminalPaneHeaderExtras.tsx @@ -55,10 +55,6 @@ export function TerminalPaneHeaderExtras({ {label} - ); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx index 2a79526ab5e..34d4f35ff93 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/TerminalSessionDropdown/TerminalSessionDropdown.tsx @@ -11,7 +11,7 @@ import { toast } from "@superset/ui/sonner"; import { workspaceTrpc } from "@superset/workspace-client"; import { eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; -import { Check, ChevronDown, LoaderCircle, Plus, Trash2 } from "lucide-react"; +import { Check, LoaderCircle, Plus, Trash2 } from "lucide-react"; import { useCallback, useMemo, useState, useSyncExternalStore } from "react"; import { useRenderStressInstrumentation } from "renderer/lib/performance/stress-instrumentation"; import { markTerminalForBackground } from "renderer/lib/terminal/terminal-background-intents"; @@ -310,10 +310,8 @@ export function TerminalSessionDropdown({ {triggerTitle} - {sessionsQuery.isFetching && isOpen ? ( + {sessionsQuery.isFetching && isOpen && ( - ) : ( - )} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx index f9f3e0c3e5b..dde404e0765 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx @@ -6,6 +6,9 @@ import { createPortal } from "react-dom"; import { useQuickOpenStore } from "renderer/commandPalette/ui/QuickOpen/quickOpenStore"; import { useV2UserPreferences } from "renderer/hooks/useV2UserPreferences"; import { useHotkey } from "renderer/hotkeys"; +import { electronTrpc } from "renderer/lib/electron-trpc"; +import { RightSidebarToggle } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/RightSidebarToggle"; +import { WindowControls } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/WindowControls"; import { CommandPalette } from "renderer/screens/main/components/CommandPalette"; import { ResizablePanel } from "renderer/screens/main/components/ResizablePanel"; import { getV2NotificationSourcesForTab } from "renderer/stores/v2-notifications"; @@ -241,10 +244,6 @@ function V2WorkspaceContent() { // The sidebar slot lives at the dashboard layout level (next to TopBar) so // the sidebar runs full-height. const sidebarSlotEl = useSlotElement("workspace-right-sidebar-slot"); - // TopBar slot for the run button when the presets bar (its usual home) is - // hidden. The button renders here via portal so it keeps this page's - // context (pane store, workspace providers) while appearing in the TopBar. - const runButtonSlotEl = useSlotElement("workspace-topbar-run-slot"); useWorkspaceHotkeys({ store, @@ -260,6 +259,10 @@ function V2WorkspaceContent() { void workspaceRun.toggleWorkspaceRun(); }); + const { data: platform } = electronTrpc.window.getPlatform.useQuery(); + // Default to Mac while loading so window controls don't flash in. + const isMac = platform === undefined || platform === "darwin"; + const workspaceRunButton = ( ) : null } @@ -316,10 +318,15 @@ function V2WorkspaceContent() { /> )} renderTabBarTrailing={() => ( - +
+ + {workspaceRunButton} + + {!isMac && } +
)} renderEmptyState={() => (
- {!showPresetsBar && - runButtonSlotEl && - createPortal(workspaceRunButton, runButtonSlotEl)} {sidebarOpen && sidebarSlotEl && createPortal( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles.ts index cf06bacf244..6f0261fc115 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/headerTabStyles.ts @@ -3,22 +3,35 @@ import { cn } from "@superset/ui/utils"; const SIDEBAR_HEADER_TAB_ACTIVE_CLASS_NAME = "text-foreground bg-border/30"; const SIDEBAR_HEADER_TAB_INACTIVE_CLASS_NAME = "text-muted-foreground/70 hover:text-muted-foreground hover:bg-tertiary/20"; +// Inverted scheme: the shaded block marks the inactive tabs while the active +// tab blends into the panel below it. Every state keeps a 1px border on all +// sides (transparent where hidden) so tabs don't shift when switching. +const SIDEBAR_HEADER_TAB_ACTIVE_INVERTED_CLASS_NAME = + "text-foreground border border-border border-b-transparent"; +const SIDEBAR_HEADER_TAB_INACTIVE_INVERTED_CLASS_NAME = + "bg-border/30 text-muted-foreground/70 hover:text-muted-foreground hover:bg-border/20 border border-transparent border-b-border"; export function getSidebarHeaderTabButtonClassName({ isActive, compact = false, + inverted = false, }: { isActive: boolean; compact?: boolean; + inverted?: boolean; }) { + const activeClassName = inverted + ? SIDEBAR_HEADER_TAB_ACTIVE_INVERTED_CLASS_NAME + : SIDEBAR_HEADER_TAB_ACTIVE_CLASS_NAME; + const inactiveClassName = inverted + ? SIDEBAR_HEADER_TAB_INACTIVE_INVERTED_CLASS_NAME + : SIDEBAR_HEADER_TAB_INACTIVE_CLASS_NAME; return cn( "h-full shrink-0 transition-all", compact ? "flex w-10 items-center justify-center" : "flex items-center gap-1.5 px-3 text-xs", - isActive - ? SIDEBAR_HEADER_TAB_ACTIVE_CLASS_NAME - : SIDEBAR_HEADER_TAB_INACTIVE_CLASS_NAME, + isActive ? activeClassName : inactiveClassName, ); } diff --git a/apps/desktop/src/renderer/stores/folder-import-intent.ts b/apps/desktop/src/renderer/stores/folder-import-intent.ts new file mode 100644 index 00000000000..835c3be2a27 --- /dev/null +++ b/apps/desktop/src/renderer/stores/folder-import-intent.ts @@ -0,0 +1,16 @@ +import { create } from "zustand"; + +// Palette commands run outside React, but the folder-import flow is a hook +// (dialogs, host service, react-query). This intent store bridges the two: +// commands call request(), FolderImportMount runs the flow. +interface FolderImportIntentState { + tick: number; + request: () => void; +} + +export const useFolderImportIntent = create( + (set, get) => ({ + tick: 0, + request: () => set({ tick: get().tick + 1 }), + }), +); diff --git a/apps/desktop/src/renderer/stores/sidebar-workspaces-collapse.ts b/apps/desktop/src/renderer/stores/sidebar-workspaces-collapse.ts new file mode 100644 index 00000000000..1484d7b1737 --- /dev/null +++ b/apps/desktop/src/renderer/stores/sidebar-workspaces-collapse.ts @@ -0,0 +1,20 @@ +import { create } from "zustand"; +import { devtools, persist } from "zustand/middleware"; + +interface SidebarWorkspacesCollapseState { + isCollapsed: boolean; + toggle: () => void; +} + +export const useSidebarWorkspacesCollapseStore = + create()( + devtools( + persist( + (set) => ({ + isCollapsed: false, + toggle: () => set((state) => ({ isCollapsed: !state.isCollapsed })), + }), + { name: "sidebar-workspaces-collapse" }, + ), + ), + ); diff --git a/packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx b/packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx index 47c0a407eca..6693da7ae26 100644 --- a/packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx +++ b/packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx @@ -245,7 +245,13 @@ export function Pane({ {/* biome-ignore lint/a11y/noStaticElementInteractions: clicking anywhere in a pane focuses it (standard IDE behavior) */}
-
+ {/* font-semibold on the wrapper so custom titleContent inherits the + active bolding too, not just the default title span. */} +
{titleContent ?? ( <> {icon && {icon}} diff --git a/packages/panes/src/react/components/Workspace/components/TabBar/TabBar.tsx b/packages/panes/src/react/components/Workspace/components/TabBar/TabBar.tsx index 4d3c117d869..06370298d58 100644 --- a/packages/panes/src/react/components/Workspace/components/TabBar/TabBar.tsx +++ b/packages/panes/src/react/components/Workspace/components/TabBar/TabBar.tsx @@ -47,7 +47,7 @@ function AddTabButton<_TData>({ }) { const button = (