Skip to content

Commit 3eb056c

Browse files
committed
fix(organizations): keep the rename draft until it is saved or cancelled
1 parent 18ef6bc commit 3eb056c

3 files changed

Lines changed: 31 additions & 13 deletions

File tree

‎apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-header/organization-header.tsx‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { LOGO_ACCEPT_ATTRIBUTE } from '@/lib/uploads/client/logo-file'
2323
import { InviteModal } from '@/app/workspace/[workspaceId]/components/invite-modal'
2424
import { SidebarRenameRow } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-rename-row'
2525
import { SIDEBAR_RAIL_CHIP_CLASS } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
26+
import { holdRowFocus } from '@/app/workspace/[workspaceId]/w/components/sidebar/utils'
2627
import { useRenameOrganization } from '@/hooks/queries/organization'
2728
import { useUploadOrganizationLogo } from '@/hooks/queries/organization-logo'
2829

@@ -56,12 +57,14 @@ export function OrganizationHeader({
5657
onExpandSidebar,
5758
}: OrganizationHeaderProps) {
5859
const fileInputRef = useRef<HTMLInputElement>(null)
60+
const nameInputRef = useRef<HTMLInputElement>(null)
5961
const router = useRouter()
6062
const { mutate: uploadLogo, isPending: isUploadingLogo } = useUploadOrganizationLogo(
6163
organization.id
6264
)
6365
const renameOrganization = useRenameOrganization()
6466
const [isInviteModalOpen, setIsInviteModalOpen] = useState(false)
67+
const [isMenuOpen, setIsMenuOpen] = useState(false)
6568
const [draftName, setDraftName] = useState<string | null>(null)
6669
const initial = getOrganizationInitial(organization.name)
6770

@@ -104,7 +107,13 @@ export function OrganizationHeader({
104107
stopRenaming()
105108
return
106109
}
107-
renameOrganization.mutate({ orgId: organization.id, name }, { onSuccess: stopRenaming })
110+
renameOrganization.mutate(
111+
{ orgId: organization.id, name },
112+
{
113+
onSuccess: stopRenaming,
114+
onError: () => requestAnimationFrame(() => nameInputRef.current?.focus()),
115+
}
116+
)
108117
}
109118

110119
const logo = (
@@ -133,8 +142,11 @@ export function OrganizationHeader({
133142
/>
134143
)}
135144
<DropdownMenu
145+
open={isMenuOpen}
136146
onOpenChange={(open) => {
137-
if (!open) stopRenaming()
147+
// The input's blur saves the draft; closing now would unmount it and hide a failure.
148+
if (!open && draftName !== null) return
149+
setIsMenuOpen(open)
138150
}}
139151
>
140152
<DropdownMenuTrigger asChild>
@@ -152,6 +164,8 @@ export function OrganizationHeader({
152164
side='bottom'
153165
sideOffset={8}
154166
className='w-64 max-w-[calc(100vw-24px)]'
167+
onPointerMoveCapture={draftName !== null ? holdRowFocus : undefined}
168+
onPointerOutCapture={draftName !== null ? holdRowFocus : undefined}
155169
>
156170
<div className='flex items-center gap-2 px-2 py-1.5'>
157171
{canEditIdentity ? (
@@ -179,6 +193,7 @@ export function OrganizationHeader({
179193
<div className='flex min-w-0 flex-1 flex-col'>
180194
{draftName !== null ? (
181195
<SidebarRenameRow
196+
ref={nameInputRef}
182197
aria-label='Organization name'
183198
aria-invalid={renameOrganization.isError || undefined}
184199
autoFocus

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/collapsed-sidebar-menu/collapsed-sidebar-menu.tsx‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import {
2929
SIDEBAR_RAIL_CHIP_CLASS,
3030
} from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
3131
import type { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
32-
import { interleaveSiblings } from '@/app/workspace/[workspaceId]/w/components/sidebar/utils'
32+
import {
33+
holdRowFocus,
34+
interleaveSiblings,
35+
} from '@/app/workspace/[workspaceId]/w/components/sidebar/utils'
3336
import type { FolderTreeNode } from '@/stores/folders/types'
3437
import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
3538

@@ -217,15 +220,6 @@ interface CollapsedWorkflowFlyoutItemProps {
217220
canRename?: boolean
218221
}
219222

220-
/**
221-
* Prevents Radix's mouse pointer handlers from transferring focus. Row handlers keep
222-
* actions submenus open; content capture handlers preserve inline rename focus,
223-
* including inside portaled folders. Radix skips handlers for defaulted events.
224-
*/
225-
const holdRowFocus = (e: React.PointerEvent) => {
226-
if (e.pointerType === 'mouse') e.preventDefault()
227-
}
228-
229223
const EDIT_ROW_CLASS = cn(
230224
chipVariants({ active: true, fullWidth: true }),
231225
'min-w-0 cursor-default select-none text-small'

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/utils.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
import type { MouseEvent } from 'react'
1+
import type { MouseEvent, PointerEvent } from 'react'
22
import type { MothershipResource } from '@/lib/mothership/resource-types'
33
import { getFolderMap } from '@/hooks/queries/utils/folder-cache'
44
import { getWorkflows } from '@/hooks/queries/utils/workflow-cache'
55
import type { FolderTreeNode } from '@/stores/folders/types'
66
import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
77

8+
/**
9+
* Prevents Radix's mouse pointer handlers from transferring focus. Row handlers keep
10+
* actions submenus open; content capture handlers preserve inline rename focus,
11+
* including inside portaled folders. Radix skips handlers for defaulted events.
12+
*/
13+
export function holdRowFocus(event: PointerEvent) {
14+
if (event.pointerType === 'mouse') event.preventDefault()
15+
}
16+
817
/** Distinguishes sidebar background clicks from controls and portaled overlays. */
918
export function isSidebarBackgroundClick(event: MouseEvent<HTMLElement>): boolean {
1019
return (

0 commit comments

Comments
 (0)