From dcf8833a45797a196fb77de6edf073777520a421 Mon Sep 17 00:00:00 2001 From: klopez4212 Date: Tue, 23 Jun 2026 19:24:45 +0100 Subject: [PATCH] Move workspace switcher to sidebar header --- .../features/profile/ui/ProfilePopover.tsx | 17 ---- .../src/features/sidebar/ui/AppSidebar.tsx | 18 ++-- .../sidebar/ui/SidebarProfileCard.tsx | 94 ++++--------------- .../workspaces/ui/WorkspaceSwitcher.tsx | 93 ++++++++++++++++-- 4 files changed, 117 insertions(+), 105 deletions(-) diff --git a/desktop/src/features/profile/ui/ProfilePopover.tsx b/desktop/src/features/profile/ui/ProfilePopover.tsx index 88fcffe4d..b6d371ed7 100644 --- a/desktop/src/features/profile/ui/ProfilePopover.tsx +++ b/desktop/src/features/profile/ui/ProfilePopover.tsx @@ -33,10 +33,6 @@ interface ProfilePopoverProps { // Used when auxiliary triggers (avatar, status text) live alongside the // primary PopoverTrigger and toggle the popover via controlled `open`. triggerContainerRef?: React.RefObject; - // Optional slot rendered between the identity block and the menu items. - // Used by the sidebar to surface the workspace/relay selector inside the - // profile menu instead of on the sidebar card. - workspaceSwitcherSlot?: React.ReactNode; } // --------------------------------------------------------------------------- @@ -68,7 +64,6 @@ export function ProfilePopover({ onOpenSettings, children, triggerContainerRef, - workspaceSwitcherSlot, }: ProfilePopoverProps) { const [statusDialogOpen, setStatusDialogOpen] = React.useState(false); const [presenceMenuOpen, setPresenceMenuOpen] = React.useState(false); @@ -250,8 +245,6 @@ export function ProfilePopover({ -
- {/* ── Settings ───────────────────────────────────────── */} - - {workspaceSwitcherSlot ? ( - <> -
- {/* ── Workspace / relay selector ─────────────────── */} -
- {workspaceSwitcherSlot} -
- - ) : null} diff --git a/desktop/src/features/sidebar/ui/AppSidebar.tsx b/desktop/src/features/sidebar/ui/AppSidebar.tsx index 9b925f07f..c256202dc 100644 --- a/desktop/src/features/sidebar/ui/AppSidebar.tsx +++ b/desktop/src/features/sidebar/ui/AppSidebar.tsx @@ -15,6 +15,7 @@ import { TopbarSearch } from "@/features/search/ui/TopbarSearch"; import type { Workspace } from "@/features/workspaces/types"; import { AddWorkspaceDialog } from "@/features/workspaces/ui/AddWorkspaceDialog"; +import { WorkspaceSwitcher } from "@/features/workspaces/ui/WorkspaceSwitcher"; import { useDeferredLoad } from "@/shared/hooks/useDeferredStartup"; import { useChannelSections, @@ -529,6 +530,17 @@ export function AppSidebar({ className="mt-(--buzz-top-chrome-height,2.5rem) shrink-0 px-2 pt-2" data-testid="sidebar-pinned-header" > +
+ +
diff --git a/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx b/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx index 759ef4775..486cfbf11 100644 --- a/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx +++ b/desktop/src/features/sidebar/ui/SidebarProfileCard.tsx @@ -6,48 +6,30 @@ import { useSelfProfileCache } from "@/features/profile/hooks"; import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar"; import { ProfilePopover } from "@/features/profile/ui/ProfilePopover"; import { StatusEmoji } from "@/features/user-status/ui/StatusEmoji"; -import type { Workspace } from "@/features/workspaces/types"; -import { WorkspaceSwitcher } from "@/features/workspaces/ui/WorkspaceSwitcher"; import type { PresenceStatus, Profile, UserStatus } from "@/shared/api/types"; -import { cn } from "@/shared/lib/cn"; type SidebarProfileCardProps = { - activeWorkspace: Workspace | null; isPresencePending?: boolean; - onOpenAddWorkspace: () => void; onOpenSettings: (section?: "profile" | "appearance") => void; - onRemoveWorkspace: (id: string) => void; onSetPresenceStatus?: (status: PresenceStatus) => void; onSetUserStatus: (text: string, emoji: string) => void; onClearUserStatus: () => void; - onSwitchWorkspace: (id: string) => void; - onUpdateWorkspace: ( - id: string, - updates: Partial>, - ) => void; profile?: Profile; resolvedDisplayName: string; selfPresenceStatus: PresenceStatus; selfUserStatus?: UserStatus; - workspaces: Workspace[]; }; export function SidebarProfileCard({ - activeWorkspace, isPresencePending, - onOpenAddWorkspace, onOpenSettings, - onRemoveWorkspace, onSetPresenceStatus, onSetUserStatus, onClearUserStatus, - onSwitchWorkspace, - onUpdateWorkspace, profile, resolvedDisplayName, selfPresenceStatus, selfUserStatus, - workspaces, }: SidebarProfileCardProps) { const selfProfileCache = useSelfProfileCache(); const [profilePopoverOpen, setProfilePopoverOpen] = React.useState(false); @@ -70,18 +52,6 @@ export function SidebarProfileCard({ [toggleProfilePopover], ); const hasStatus = Boolean(selfUserStatus?.text || selfUserStatus?.emoji); - const workspaceLabel = activeWorkspace?.name ?? "No workspace"; - const readonlyWorkspaceLabel = ( - - - {workspaceLabel} - - ); return ( // biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: child buttons provide keyboard access; wrapper fills pointer gaps between them. @@ -136,17 +106,6 @@ export function SidebarProfileCard({ triggerContainerRef={profileCardRef} userStatusEmoji={selfUserStatus?.emoji} userStatusText={selfUserStatus?.text} - workspaceSwitcherSlot={ - - } > -
- {readonlyWorkspaceLabel} -
- - ) : ( -
{readonlyWorkspaceLabel}
- )} + + ) : null} diff --git a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx index a2655ea04..8434c0a59 100644 --- a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx +++ b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx @@ -28,6 +28,7 @@ import { isRelayConnectionDegraded, useRelayConnection, } from "@/shared/api/useRelayConnection"; +import { cn } from "@/shared/lib/cn"; import { EditWorkspaceDialog } from "./EditWorkspaceDialog"; const CONNECTION_STATE_LABEL: Record = { @@ -42,7 +43,7 @@ const CONNECTION_STATE_LABEL: Record = { type WorkspaceSwitcherProps = { activeWorkspace: Workspace | null; workspaces: Workspace[]; - variant?: "sidebar" | "profile" | "profile-menu"; + variant?: "sidebar" | "sidebar-card" | "profile" | "profile-menu"; onSwitchWorkspace: (id: string) => void; onAddWorkspace: () => void; onUpdateWorkspace: ( @@ -52,10 +53,43 @@ type WorkspaceSwitcherProps = { onRemoveWorkspace: (id: string) => void; }; -function WorkspaceEmojiIcon({ className }: { className: string }) { +function getWorkspaceInitial(name: string): string { + return name.trim().charAt(0).toUpperCase() || "W"; +} + +function WorkspaceAvatar({ + className, + emoji, + imageUrl, + name, +}: { + className: string; + emoji?: string; + imageUrl?: string | null; + name: string; +}) { return ( -