dify/web/app/components/workflow/skill/file-tree/file-node-menu.tsx
yyh f1ce933b33
fix(skill): address code review issues for tab management
1. Add confirmation dialog when closing dirty tabs
2. Fix file double-click race condition with useDelayedClick hook
3. Fix previewTabId orphan state in closeTab
4. Remove auto-pin on every keystroke (VS Code behavior)
5. Extract shared MenuItem component to eliminate duplication
6. Make nodeId optional when node is provided (reduce props drilling)
2026-01-16 11:20:49 +08:00

88 lines
2.5 KiB
TypeScript

'use client'
import type { FC } from 'react'
import type { NodeApi, TreeApi } from 'react-arborist'
import type { TreeNodeData } from '../type'
import {
RiDeleteBinLine,
RiEdit2Line,
} from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Confirm from '@/app/components/base/confirm'
import { cn } from '@/utils/classnames'
import { useFileOperations } from '../hooks/use-file-operations'
import MenuItem from './menu-item'
type FileItemMenuProps = {
nodeId?: string
onClose: () => void
className?: string
treeRef?: React.RefObject<TreeApi<TreeNodeData> | null>
node?: NodeApi<TreeNodeData>
}
const FileItemMenu: FC<FileItemMenuProps> = ({
nodeId,
onClose,
className,
treeRef,
node,
}) => {
const { t } = useTranslation('workflow')
const {
showDeleteConfirm,
isLoading,
isDeleting,
handleRename,
handleDeleteClick,
handleDeleteConfirm,
handleDeleteCancel,
} = useFileOperations({ nodeId, onClose, treeRef, node })
return (
<div className={cn(
'min-w-[180px] rounded-xl border-[0.5px] border-components-panel-border',
'bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[5px]',
className,
)}
>
<MenuItem
icon={RiEdit2Line}
label={t('skillSidebar.menu.rename')}
onClick={handleRename}
disabled={isLoading}
/>
<button
type="button"
onClick={handleDeleteClick}
disabled={isLoading}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-3 py-2',
'hover:bg-state-destructive-hover disabled:cursor-not-allowed disabled:opacity-50',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-components-input-border-active',
'group',
)}
>
<RiDeleteBinLine className="size-4 text-text-tertiary group-hover:text-text-destructive" aria-hidden="true" />
<span className="system-sm-regular text-text-secondary group-hover:text-text-destructive">
{t('skillSidebar.menu.delete')}
</span>
</button>
<Confirm
isShow={showDeleteConfirm}
type="danger"
title={t('skillSidebar.menu.fileDeleteConfirmTitle')}
content={t('skillSidebar.menu.fileDeleteConfirmContent')}
onConfirm={handleDeleteConfirm}
onCancel={handleDeleteCancel}
isLoading={isDeleting}
/>
</div>
)
}
export default React.memo(FileItemMenu)