mirror of
https://github.com/langgenius/dify.git
synced 2026-03-01 04:45:09 +00:00
Rename components and reorganize directory structure: - skill-doc-editor.tsx → file-content-panel.tsx (handles edit/preview/download) - editor-area.tsx → content-area.tsx - editor-body.tsx → content-body.tsx - editor-tabs.tsx → file-tabs.tsx - editor-tab-item.tsx → file-tab-item.tsx Create viewer/ directory for non-editor components: - Move media-file-preview.tsx from editor/ to viewer/ - Move unsupported-file-download.tsx from editor/ to viewer/ This clarifies the distinction between: - editor/: actual file editors (code, markdown) - viewer/: preview and download components (media, unsupported files)
104 lines
3.1 KiB
TypeScript
104 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
import type { FC } from 'react'
|
|
import type { FileAppearanceType } from '@/app/components/base/file-uploader/types'
|
|
import { RiCloseLine } from '@remixicon/react'
|
|
import * as React from 'react'
|
|
import { useCallback } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon'
|
|
import { cn } from '@/utils/classnames'
|
|
import { getFileIconType } from './utils/file-utils'
|
|
|
|
type FileTabItemProps = {
|
|
fileId: string
|
|
name: string
|
|
isActive: boolean
|
|
isDirty: boolean
|
|
isPreview: boolean
|
|
onClick: (fileId: string) => void
|
|
onClose: (fileId: string) => void
|
|
onDoubleClick: (fileId: string) => void
|
|
}
|
|
|
|
const FileTabItem: FC<FileTabItemProps> = ({
|
|
fileId,
|
|
name,
|
|
isActive,
|
|
isDirty,
|
|
isPreview,
|
|
onClick,
|
|
onClose,
|
|
onDoubleClick,
|
|
}) => {
|
|
const { t } = useTranslation()
|
|
const iconType = getFileIconType(name)
|
|
|
|
const handleClick = useCallback(() => {
|
|
onClick(fileId)
|
|
}, [onClick, fileId])
|
|
|
|
const handleDoubleClick = useCallback(() => {
|
|
if (isPreview)
|
|
onDoubleClick(fileId)
|
|
}, [onDoubleClick, fileId, isPreview])
|
|
|
|
const handleClose = useCallback((e: React.MouseEvent) => {
|
|
e.stopPropagation()
|
|
onClose(fileId)
|
|
}, [onClose, fileId])
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'group relative flex shrink-0 items-center border-r border-components-panel-border-subtle',
|
|
isActive ? 'bg-components-panel-bg' : 'bg-transparent hover:bg-state-base-hover',
|
|
)}
|
|
>
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
'flex items-center gap-1.5 px-2.5 pb-2 pt-2.5',
|
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-components-input-border-active',
|
|
)}
|
|
onClick={handleClick}
|
|
onDoubleClick={handleDoubleClick}
|
|
>
|
|
<div className="relative flex size-5 shrink-0 items-center justify-center">
|
|
<FileTypeIcon type={iconType as FileAppearanceType} size="sm" />
|
|
{isDirty && (
|
|
<span className="absolute -bottom-px -right-px size-[7px] rounded-full border border-white bg-text-warning-secondary" />
|
|
)}
|
|
</div>
|
|
|
|
<span
|
|
className={cn(
|
|
'max-w-40 truncate text-[13px] font-normal leading-4',
|
|
isPreview && 'italic',
|
|
isActive
|
|
? 'text-text-primary'
|
|
: 'text-text-tertiary',
|
|
)}
|
|
>
|
|
{name}
|
|
</span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
'mr-1 flex size-4 items-center justify-center rounded-[6px] text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
|
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-components-input-border-active',
|
|
isActive ? 'opacity-100' : 'opacity-0 focus-visible:opacity-100 group-hover:opacity-100',
|
|
)}
|
|
aria-label={t('operation.close', { ns: 'common' })}
|
|
onClick={handleClose}
|
|
>
|
|
<RiCloseLine className="size-4" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default React.memo(FileTabItem)
|