refactor: consolidate sidebar item interactions
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Folder } from '@phosphor-icons/react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useSidebarInlineRenameInput } from '../sidebar/sidebarHooks'
|
||||
|
||||
interface FolderNameInputProps {
|
||||
ariaLabel: string
|
||||
@@ -25,26 +25,18 @@ export function FolderNameInput({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}: FolderNameInputProps) {
|
||||
const [value, setValue] = useState(initialValue)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const submittingRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const input = inputRef.current
|
||||
if (!input) return
|
||||
input.focus()
|
||||
if (selectTextOnFocus) input.select()
|
||||
}, [selectTextOnFocus])
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (submittingRef.current) return false
|
||||
submittingRef.current = true
|
||||
try {
|
||||
return await onSubmit(value)
|
||||
} finally {
|
||||
submittingRef.current = false
|
||||
}
|
||||
}, [onSubmit, value])
|
||||
const {
|
||||
handleKeyDown,
|
||||
inputRef,
|
||||
setValue,
|
||||
submitValue,
|
||||
value,
|
||||
} = useSidebarInlineRenameInput({
|
||||
initialValue,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
selectTextOnFocus,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded" style={{ paddingTop: 6, paddingBottom: 6, paddingRight: 16, paddingLeft: leftInset, borderRadius: 4 }}>
|
||||
@@ -55,17 +47,8 @@ export function FolderNameInput({
|
||||
className="h-auto min-h-0 flex-1 rounded-sm px-2 py-[3px] text-[13px] font-medium"
|
||||
value={value}
|
||||
onChange={(event) => setValue(event.target.value)}
|
||||
onBlur={submitOnBlur ? () => { void handleSubmit() } : undefined}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
void handleSubmit()
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
onCancel()
|
||||
}
|
||||
}}
|
||||
onBlur={submitOnBlur ? () => { void submitValue() } : undefined}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
data-testid={testId}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState, type MouseEvent as ReactMouseEvent, type RefObject } from 'react'
|
||||
import { useCallback, type MouseEvent as ReactMouseEvent } from 'react'
|
||||
import type { FolderNode } from '../../types'
|
||||
import type { FolderFileActions } from '../../hooks/useFileActions'
|
||||
import type { FolderContextMenuState } from './FolderContextMenu'
|
||||
import { useSidebarContextMenu } from '../sidebar/sidebarHooks'
|
||||
|
||||
interface UseFolderContextMenuInput {
|
||||
onDeleteFolder?: (folderPath: string) => void
|
||||
@@ -9,50 +9,21 @@ interface UseFolderContextMenuInput {
|
||||
onStartRenameFolder?: (folderPath: string) => void
|
||||
}
|
||||
|
||||
function useContextMenuDismiss(
|
||||
contextMenu: FolderContextMenuState | null,
|
||||
menuRef: RefObject<HTMLDivElement | null>,
|
||||
closeContextMenu: () => void,
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (!contextMenu) return
|
||||
|
||||
const handleOutsideClick = (event: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) closeContextMenu()
|
||||
}
|
||||
const handleEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') closeContextMenu()
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleOutsideClick)
|
||||
document.addEventListener('keydown', handleEscape)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleOutsideClick)
|
||||
document.removeEventListener('keydown', handleEscape)
|
||||
}
|
||||
}, [closeContextMenu, contextMenu, menuRef])
|
||||
}
|
||||
|
||||
export function useFolderContextMenu({
|
||||
onDeleteFolder,
|
||||
folderFileActions,
|
||||
onStartRenameFolder,
|
||||
}: UseFolderContextMenuInput) {
|
||||
const [contextMenu, setContextMenu] = useState<FolderContextMenuState | null>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const closeContextMenu = useCallback(() => setContextMenu(null), [])
|
||||
useContextMenuDismiss(contextMenu, menuRef, closeContextMenu)
|
||||
const {
|
||||
closeContextMenu,
|
||||
contextMenu,
|
||||
contextMenuRef,
|
||||
openContextMenuFromPointer,
|
||||
} = useSidebarContextMenu<string>()
|
||||
|
||||
const handleOpenMenu = useCallback((node: FolderNode, event: ReactMouseEvent<HTMLDivElement>) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
setContextMenu({
|
||||
path: node.path,
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
})
|
||||
}, [])
|
||||
openContextMenuFromPointer(node.path, event)
|
||||
}, [openContextMenuFromPointer])
|
||||
|
||||
const handleRenameFromMenu = useCallback((folderPath: string) => {
|
||||
closeContextMenu()
|
||||
@@ -73,15 +44,20 @@ export function useFolderContextMenu({
|
||||
closeContextMenu()
|
||||
folderFileActions?.copyFolderPath(folderPath)
|
||||
}, [closeContextMenu, folderFileActions])
|
||||
const menu = contextMenu ? {
|
||||
path: contextMenu.target,
|
||||
x: contextMenu.pos.x,
|
||||
y: contextMenu.pos.y,
|
||||
} : null
|
||||
|
||||
return {
|
||||
closeContextMenu,
|
||||
contextMenu,
|
||||
contextMenu: menu,
|
||||
handleCopyPathFromMenu,
|
||||
handleDeleteFromMenu,
|
||||
handleOpenMenu,
|
||||
handleRevealFromMenu,
|
||||
handleRenameFromMenu,
|
||||
menuRef,
|
||||
menuRef: contextMenuRef,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user