refactor(skill-editor): simplify code by extracting MenuItem component and removing dead code

- Extract reusable MenuItem component for menu buttons in FileOperationsMenu
- Remove unused handleUploadFileClick/handleUploadFolderClick callbacks
- Remove unused handleDropdownClose callback, inline directly
- Remove unused _fileId parameter from revealFile function
- Simplify toOpensObject using Object.fromEntries
This commit is contained in:
yyh
2026-01-15 15:05:43 +08:00
parent 388ee087c0
commit e6a4a08120
5 changed files with 47 additions and 80 deletions

View File

@ -22,6 +22,30 @@ import { cn } from '@/utils/classnames'
* - Upload Folder: Upload entire folder structure (webkitdirectory)
*/
type MenuItemProps = {
icon: React.ElementType
label: string
onClick: () => void
disabled?: boolean
}
const MenuItem: React.FC<MenuItemProps> = ({ icon: Icon, label, onClick, disabled }) => (
<button
type="button"
onClick={onClick}
disabled={disabled}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-3 py-2',
'hover:bg-state-base-hover disabled:cursor-not-allowed disabled:opacity-50',
)}
>
<Icon className="size-4 text-text-tertiary" />
<span className="system-sm-regular text-text-secondary">
{label}
</span>
</button>
)
type FileOperationsMenuProps = {
/** Target folder ID, or 'root' for root level */
nodeId: string
@ -122,16 +146,6 @@ const FileOperationsMenu: FC<FileOperationsMenuProps> = ({
}
}, [appId, createFolder, onClose, parentId, t])
// Handle Upload File button click
const handleUploadFileClick = useCallback(() => {
fileInputRef.current?.click()
}, [])
// Handle Upload Folder button click
const handleUploadFolderClick = useCallback(() => {
folderInputRef.current?.click()
}, [])
// Handle file input change (single or multiple files)
const handleFileChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || [])
@ -280,72 +294,34 @@ const FileOperationsMenu: FC<FileOperationsMenuProps> = ({
onChange={handleFolderChange}
/>
{/* New File */}
<button
type="button"
<MenuItem
icon={RiFileAddLine}
label={t('skillSidebar.menu.newFile')}
onClick={handleNewFile}
disabled={isLoading}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-3 py-2',
'hover:bg-state-base-hover disabled:cursor-not-allowed disabled:opacity-50',
)}
>
<RiFileAddLine className="size-4 text-text-tertiary" />
<span className="system-sm-regular text-text-secondary">
{t('skillSidebar.menu.newFile')}
</span>
</button>
{/* New Folder */}
<button
type="button"
/>
<MenuItem
icon={RiFolderAddLine}
label={t('skillSidebar.menu.newFolder')}
onClick={handleNewFolder}
disabled={isLoading}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-3 py-2',
'hover:bg-state-base-hover disabled:cursor-not-allowed disabled:opacity-50',
)}
>
<RiFolderAddLine className="size-4 text-text-tertiary" />
<span className="system-sm-regular text-text-secondary">
{t('skillSidebar.menu.newFolder')}
</span>
</button>
/>
{/* Divider */}
<div className="my-1 h-px bg-divider-subtle" />
{/* Upload File */}
<button
type="button"
onClick={handleUploadFileClick}
<MenuItem
icon={RiUploadLine}
label={t('skillSidebar.menu.uploadFile')}
onClick={() => fileInputRef.current?.click()}
disabled={isLoading}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-3 py-2',
'hover:bg-state-base-hover disabled:cursor-not-allowed disabled:opacity-50',
)}
>
<RiUploadLine className="size-4 text-text-tertiary" />
<span className="system-sm-regular text-text-secondary">
{t('skillSidebar.menu.uploadFile')}
</span>
</button>
{/* Upload Folder */}
<button
type="button"
onClick={handleUploadFolderClick}
/>
<MenuItem
icon={RiFolderUploadLine}
label={t('skillSidebar.menu.uploadFolder')}
onClick={() => folderInputRef.current?.click()}
disabled={isLoading}
className={cn(
'flex w-full items-center gap-2 rounded-lg px-3 py-2',
'hover:bg-state-base-hover disabled:cursor-not-allowed disabled:opacity-50',
)}
>
<RiFolderUploadLine className="size-4 text-text-tertiary" />
<span className="system-sm-regular text-text-secondary">
{t('skillSidebar.menu.uploadFolder')}
</span>
</button>
/>
</div>
)
}

View File

@ -77,11 +77,6 @@ const FileTreeNode = ({ node, style, dragHandle }: NodeRendererProps<TreeNodeDat
})
}, [isFolder, node.data.id, storeApi])
// Dropdown close handler
const handleDropdownClose = useCallback(() => {
setShowDropdown(false)
}, [])
// More button click handler
const handleMoreClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
@ -164,7 +159,7 @@ const FileTreeNode = ({ node, style, dragHandle }: NodeRendererProps<TreeNodeDat
<PortalToFollowElemContent className="z-[100]">
<FileOperationsMenu
nodeId={node.data.id}
onClose={handleDropdownClose}
onClose={() => setShowDropdown(false)}
/>
</PortalToFollowElemContent>
</PortalToFollowElem>

View File

@ -92,7 +92,7 @@ const Files: React.FC<FilesProps> = ({ className }) => {
// Update store for state persistence
if (ancestors.length > 0)
storeApi.getState().revealFile(activeTabId, ancestors)
storeApi.getState().revealFile(ancestors)
// Use Tree API for immediate UI update (initialOpenState only applies on first render)
const timeoutId = setTimeout(() => {

View File

@ -98,7 +98,7 @@ export type FileTreeSliceShape = {
/** Toggle a folder's expanded state */
toggleFolder: (folderId: string) => void
/** Reveal a file by expanding all ancestor folders */
revealFile: (fileId: string, ancestorFolderIds: string[]) => void
revealFile: (ancestorFolderIds: string[]) => void
}
export const createFileTreeSlice: StateCreator<FileTreeSliceShape> = (set, get) => ({
@ -119,7 +119,7 @@ export const createFileTreeSlice: StateCreator<FileTreeSliceShape> = (set, get)
set({ expandedFolderIds: newSet })
},
revealFile: (_fileId: string, ancestorFolderIds: string[]) => {
revealFile: (ancestorFolderIds: string[]) => {
const { expandedFolderIds } = get()
const newSet = new Set(expandedFolderIds)
// Expand all ancestors

View File

@ -102,11 +102,7 @@ export function getAncestorIds(nodeId: string, nodes: AppAssetTreeView[]): strin
* @returns Object for react-arborist opens prop
*/
export function toOpensObject(expandedIds: Set<string>): Record<string, boolean> {
const opens: Record<string, boolean> = {}
expandedIds.forEach((id) => {
opens[id] = true
})
return opens
return Object.fromEntries([...expandedIds].map(id => [id, true]))
}
/**