import * as React from 'react'; import DeleteOutlined from '@ant-design/icons/DeleteOutlined'; import DownloadOutlined from '@ant-design/icons/DownloadOutlined'; import EyeOutlined from '@ant-design/icons/EyeOutlined'; import classNames from 'classnames'; import CSSMotion from 'rc-motion'; import { ConfigContext } from '../../config-provider'; import Progress from '../../progress'; import Tooltip from '../../tooltip'; import type { ItemRender, UploadFile, UploadListProgressProps, UploadListType, UploadLocale, } from '../interface'; export interface ListItemProps { prefixCls: string; className?: string; style?: React.CSSProperties; locale: UploadLocale; file: UploadFile; items: UploadFile[]; listType?: UploadListType; isImgUrl?: (file: UploadFile) => boolean; showRemoveIcon?: boolean; showDownloadIcon?: boolean; showPreviewIcon?: boolean; removeIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode); downloadIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode); previewIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode); extra?: React.ReactNode | ((file: UploadFile) => React.ReactNode); iconRender: (file: UploadFile) => React.ReactNode; actionIconRender: ( customIcon: React.ReactNode, callback: () => void, prefixCls: string, title?: string, acceptUploadDisabled?: boolean, ) => React.ReactNode; itemRender?: ItemRender; onPreview: (file: UploadFile, e: React.SyntheticEvent) => void; onClose: (file: UploadFile) => void; onDownload: (file: UploadFile) => void; progress?: UploadListProgressProps; } const ListItem = React.forwardRef( ( { prefixCls, className, style, locale, listType, file, items, progress: progressProps, iconRender, actionIconRender, itemRender, isImgUrl, showPreviewIcon, showRemoveIcon, showDownloadIcon, previewIcon: customPreviewIcon, removeIcon: customRemoveIcon, downloadIcon: customDownloadIcon, extra: customExtra, onPreview, onDownload, onClose, }, ref, ) => { // Status: which will ignore `removed` status const { status } = file; const [mergedStatus, setMergedStatus] = React.useState(status); React.useEffect(() => { if (status !== 'removed') { setMergedStatus(status); } }, [status]); // Delay to show the progress bar const [showProgress, setShowProgress] = React.useState(false); React.useEffect(() => { const timer = setTimeout(() => { setShowProgress(true); }, 300); return () => { clearTimeout(timer); }; }, []); const iconNode = iconRender(file); let icon =
{iconNode}
; if (listType === 'picture' || listType === 'picture-card' || listType === 'picture-circle') { if (mergedStatus === 'uploading' || (!file.thumbUrl && !file.url)) { const uploadingClassName = classNames(`${prefixCls}-list-item-thumbnail`, { [`${prefixCls}-list-item-file`]: mergedStatus !== 'uploading', }); icon =
{iconNode}
; } else { const thumbnail = isImgUrl?.(file) ? ( {file.name} ) : ( iconNode ); const aClassName = classNames(`${prefixCls}-list-item-thumbnail`, { [`${prefixCls}-list-item-file`]: isImgUrl && !isImgUrl(file), }); icon = ( onPreview(file, e)} href={file.url || file.thumbUrl} target="_blank" rel="noopener noreferrer" > {thumbnail} ); } } const listItemClassName = classNames( `${prefixCls}-list-item`, `${prefixCls}-list-item-${mergedStatus}`, ); const linkProps = typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps; const removeIcon = showRemoveIcon ? actionIconRender( (typeof customRemoveIcon === 'function' ? customRemoveIcon(file) : customRemoveIcon) || ( ), () => onClose(file), prefixCls, locale.removeFile, // acceptUploadDisabled is true, only remove icon will follow Upload disabled prop // https://github.com/ant-design/ant-design/issues/46171 true, ) : null; const downloadIcon = showDownloadIcon && mergedStatus === 'done' ? actionIconRender( (typeof customDownloadIcon === 'function' ? customDownloadIcon(file) : customDownloadIcon) || , () => onDownload(file), prefixCls, locale.downloadFile, ) : null; const downloadOrDelete = listType !== 'picture-card' && listType !== 'picture-circle' && ( {downloadIcon} {removeIcon} ); const extraContent = typeof customExtra === 'function' ? customExtra(file) : customExtra; const extra = extraContent && ( {extraContent} ); const listItemNameClass = classNames(`${prefixCls}-list-item-name`); const fileName = file.url ? ( onPreview(file, e)} > {file.name} {extra} ) : ( onPreview(file, e)} title={file.name} > {file.name} {extra} ); const previewIcon = showPreviewIcon && (file.url || file.thumbUrl) ? ( onPreview(file, e)} title={locale.previewFile} > {typeof customPreviewIcon === 'function' ? customPreviewIcon(file) : customPreviewIcon || } ) : null; const pictureCardActions = (listType === 'picture-card' || listType === 'picture-circle') && mergedStatus !== 'uploading' && ( {previewIcon} {mergedStatus === 'done' && downloadIcon} {removeIcon} ); const { getPrefixCls } = React.useContext(ConfigContext); const rootPrefixCls = getPrefixCls(); const dom = (
{icon} {fileName} {downloadOrDelete} {pictureCardActions} {showProgress && ( {({ className: motionClassName }) => { // show loading icon if upload progress listener is disabled const loadingProgress = 'percent' in file ? ( ) : null; return (
{loadingProgress}
); }}
)}
); const message = file.response && typeof file.response === 'string' ? file.response : file.error?.statusText || file.error?.message || locale.uploadError; const item = mergedStatus === 'error' ? ( node.parentNode as HTMLElement}> {dom} ) : ( dom ); return (
{itemRender ? itemRender(item, file, items, { download: onDownload.bind(null, file), preview: onPreview.bind(null, file) as any, remove: onClose.bind(null, file), }) : item}
); }, ); export default ListItem;