diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index f3f163b017d..28fb67864e3 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -284,11 +284,7 @@ interface SidebarThreadRowProps { isActive: boolean; jumpLabel: string | null; appSettingsConfirmThreadArchive: boolean; - renamingThreadKey: string | null; - renamingTitle: string; - setRenamingTitle: (title: string) => void; - renamingInputRef: React.RefObject; - renamingCommittedRef: React.RefObject; + isRenaming: boolean; confirmingArchiveThreadKey: string | null; setConfirmingArchiveThreadKey: React.Dispatch>; confirmArchiveButtonRefs: React.RefObject>; @@ -314,17 +310,77 @@ interface SidebarThreadRowProps { openPrLink: (event: React.MouseEvent, prUrl: string) => void; } +interface SidebarThreadRenameInputProps { + thread: SidebarThreadSummary; + threadRef: ScopedThreadRef; + commitRename: ( + threadRef: ScopedThreadRef, + newTitle: string, + originalTitle: string, + ) => Promise; + cancelRename: () => void; +} + +const SidebarThreadRenameInput = memo(function SidebarThreadRenameInput({ + thread, + threadRef, + commitRename, + cancelRename, +}: SidebarThreadRenameInputProps) { + const [title, setTitle] = useState(thread.title); + const committedRef = useRef(false); + const handleInputRef = useCallback((element: HTMLInputElement | null) => { + if (!element) return; + element.focus(); + element.select(); + }, []); + const handleChange = useCallback((event: React.ChangeEvent) => { + setTitle(event.target.value); + }, []); + const handleKeyDown = useCallback( + (event: React.KeyboardEvent) => { + event.stopPropagation(); + if (event.key === "Enter") { + event.preventDefault(); + committedRef.current = true; + void commitRename(threadRef, title, thread.title); + } else if (event.key === "Escape") { + event.preventDefault(); + committedRef.current = true; + cancelRename(); + } + }, + [cancelRename, commitRename, thread.title, threadRef, title], + ); + const handleBlur = useCallback(() => { + if (!committedRef.current) { + void commitRename(threadRef, title, thread.title); + } + }, [commitRename, thread.title, threadRef, title]); + const handleClick = useCallback((event: React.MouseEvent) => { + event.stopPropagation(); + }, []); + + return ( + + ); +}); + const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowProps) { const { orderedProjectThreadKeys, isActive, jumpLabel, appSettingsConfirmThreadArchive, - renamingThreadKey, - renamingTitle, - setRenamingTitle, - renamingInputRef, - renamingCommittedRef, + isRenaming, confirmingArchiveThreadKey, setConfirmingArchiveThreadKey, confirmArchiveButtonRefs, @@ -454,45 +510,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP }, [openPrLink, prStatus], ); - const handleRenameInputRef = useCallback( - (element: HTMLInputElement | null) => { - if (element && renamingInputRef.current !== element) { - renamingInputRef.current = element; - element.focus(); - element.select(); - } - }, - [renamingInputRef], - ); - const handleRenameInputChange = useCallback( - (event: React.ChangeEvent) => { - setRenamingTitle(event.target.value); - }, - [setRenamingTitle], - ); - const handleRenameInputKeyDown = useCallback( - (event: React.KeyboardEvent) => { - event.stopPropagation(); - if (event.key === "Enter") { - event.preventDefault(); - renamingCommittedRef.current = true; - void commitRename(threadRef, renamingTitle, thread.title); - } else if (event.key === "Escape") { - event.preventDefault(); - renamingCommittedRef.current = true; - cancelRename(); - } - }, - [cancelRename, commitRename, renamingCommittedRef, renamingTitle, thread.title, threadRef], - ); - const handleRenameInputBlur = useCallback(() => { - if (!renamingCommittedRef.current) { - void commitRename(threadRef, renamingTitle, thread.title); - } - }, [commitRename, renamingCommittedRef, renamingTitle, thread.title, threadRef]); - const handleRenameInputClick = useCallback((event: React.MouseEvent) => { - event.stopPropagation(); - }, []); const handleConfirmArchiveRef = useCallback( (element: HTMLButtonElement | null) => { if (element) { @@ -578,15 +595,12 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP )} {threadStatus && } - {renamingThreadKey === threadKey ? ( - ) : ( @@ -734,10 +748,6 @@ interface SidebarProjectThreadListProps { threadJumpLabelByKey: ReadonlyMap; appSettingsConfirmThreadArchive: boolean; renamingThreadKey: string | null; - renamingTitle: string; - setRenamingTitle: (title: string) => void; - renamingInputRef: React.RefObject; - renamingCommittedRef: React.RefObject; confirmingArchiveThreadKey: string | null; setConfirmingArchiveThreadKey: React.Dispatch>; confirmArchiveButtonRefs: React.RefObject>; @@ -784,10 +794,6 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( threadJumpLabelByKey, appSettingsConfirmThreadArchive, renamingThreadKey, - renamingTitle, - setRenamingTitle, - renamingInputRef, - renamingCommittedRef, confirmingArchiveThreadKey, setConfirmingArchiveThreadKey, confirmArchiveButtonRefs, @@ -834,11 +840,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( isActive={activeRouteThreadKey === threadKey} jumpLabel={threadJumpLabelByKey.get(threadKey) ?? null} appSettingsConfirmThreadArchive={appSettingsConfirmThreadArchive} - renamingThreadKey={renamingThreadKey} - renamingTitle={renamingTitle} - setRenamingTitle={setRenamingTitle} - renamingInputRef={renamingInputRef} - renamingCommittedRef={renamingCommittedRef} + isRenaming={renamingThreadKey === threadKey} confirmingArchiveThreadKey={confirmingArchiveThreadKey} setConfirmingArchiveThreadKey={setConfirmingArchiveThreadKey} confirmArchiveButtonRefs={confirmArchiveButtonRefs} @@ -1058,7 +1060,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec ), ); const [renamingThreadKey, setRenamingThreadKey] = useState(null); - const [renamingTitle, setRenamingTitle] = useState(""); const [confirmingArchiveThreadKey, setConfirmingArchiveThreadKey] = useState(null); const [projectRenameTarget, setProjectRenameTarget] = useState( null, @@ -1069,8 +1070,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec const [projectGroupingSelection, setProjectGroupingSelection] = useState< SidebarProjectGroupingMode | "inherit" >("inherit"); - const renamingCommittedRef = useRef(false); - const renamingInputRef = useRef(null); const confirmArchiveButtonRefs = useRef(new Map()); const memberProjectByScopedKey = useMemo( () => @@ -1762,7 +1761,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec const cancelRename = useCallback(() => { setRenamingThreadKey(null); - renamingInputRef.current = null; }, []); const commitRename = useCallback( @@ -1771,7 +1769,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec const finishRename = () => { setRenamingThreadKey((current) => { if (current !== threadKey) return current; - renamingInputRef.current = null; return null; }); }; @@ -1925,8 +1922,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec if (clicked === "rename") { setRenamingThreadKey(threadKey); - setRenamingTitle(thread.title); - renamingCommittedRef.current = false; return; } @@ -2088,10 +2083,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec threadJumpLabelByKey={threadJumpLabelByKey} appSettingsConfirmThreadArchive={appSettingsConfirmThreadArchive} renamingThreadKey={renamingThreadKey} - renamingTitle={renamingTitle} - setRenamingTitle={setRenamingTitle} - renamingInputRef={renamingInputRef} - renamingCommittedRef={renamingCommittedRef} confirmingArchiveThreadKey={confirmingArchiveThreadKey} setConfirmingArchiveThreadKey={setConfirmingArchiveThreadKey} confirmArchiveButtonRefs={confirmArchiveButtonRefs}