From 9a89a3ecb9282e76a03a9eca4381920f78c0644c Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Mon, 13 Jul 2026 20:20:15 -0700 Subject: [PATCH] fix(sidebar): use emcn tooltip for workspace switcher disabled reasons --- .../workspace-header/workspace-header.tsx | 121 +++++++++++------- 1 file changed, 74 insertions(+), 47 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx index abd4a77a566..3de09fe074e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx @@ -1,6 +1,6 @@ 'use client' -import { memo, useEffect, useRef, useState } from 'react' +import { memo, type ReactElement, useEffect, useRef, useState } from 'react' import { ChevronDown, Chip, @@ -14,6 +14,7 @@ import { Plus, Send, Skeleton, + Tooltip, } from '@sim/emcn' import { ManageWorkspace, PanelLeft } from '@sim/emcn/icons' import { createLogger } from '@sim/logger' @@ -33,6 +34,27 @@ import { useSettingsNavigation } from '@/hooks/use-settings-navigation' const logger = createLogger('WorkspaceHeader') +interface DisabledReasonTooltipProps { + reason: string | null + children: ReactElement +} + +/** + * Wraps a menu item in a tooltip explaining why the action is unavailable. + * Renders the child as-is when there is no reason to show. + */ +function DisabledReasonTooltip({ reason, children }: DisabledReasonTooltipProps) { + if (!reason) return children + return ( + + {children} + +

{reason}

+
+
+ ) +} + interface WorkspaceHeaderProps { /** The active workspace object */ activeWorkspace?: { name: string } | null @@ -548,62 +570,67 @@ function WorkspaceHeaderImpl({
+ + { + e.stopPropagation() + if (!canCreateWorkspace) return + setIsWorkspaceMenuOpen(false) + setIsCreateModalOpen(true) + }} + disabled={isCreatingWorkspace} + aria-disabled={!canCreateWorkspace || undefined} + fullWidth + flush + className={cn( + 'select-none', + !canCreateWorkspace && + 'cursor-not-allowed opacity-60 hover-hover:bg-transparent' + )} + > + New workspace + + +
+ + + { - e.stopPropagation() + leftIcon={Send} + onClick={() => { setIsWorkspaceMenuOpen(false) - if (!canCreateWorkspace) { + if (isInvitationsDisabled) { if (isBillingEnabled) navigateToSettings({ section: 'billing' }) return } - setIsCreateModalOpen(true) + setIsInviteModalOpen(true) }} - disabled={isCreatingWorkspace} - title={createWorkspaceDisabledReason ?? undefined} fullWidth flush - className='w-full select-none disabled:pointer-events-none disabled:opacity-50' + className='select-none' > - New workspace + Invite teammates - - - - { - setIsWorkspaceMenuOpen(false) - if (isInvitationsDisabled) { - if (isBillingEnabled) navigateToSettings({ section: 'billing' }) - return - } - setIsInviteModalOpen(true) - }} - title={inviteDisabledReason ?? undefined} - fullWidth - flush - className='w-full select-none' - > - Invite teammates - - { - setIsWorkspaceMenuOpen(false) - if (isInvitationsDisabled) { - if (isBillingEnabled) navigateToSettings({ section: 'billing' }) - return - } - navigateToSettings({ section: 'teammates' }) - }} - title={inviteDisabledReason ?? undefined} - fullWidth - flush - className='w-full select-none' - > - Manage workspace - + + + { + setIsWorkspaceMenuOpen(false) + if (isInvitationsDisabled) { + if (isBillingEnabled) navigateToSettings({ section: 'billing' }) + return + } + navigateToSettings({ section: 'teammates' }) + }} + fullWidth + flush + className='select-none' + > + Manage workspace + + )}