import * as React from 'react'; import CSSMotion from 'rc-motion'; import classNames from 'classnames'; import EyeOutlined from '@ant-design/icons/EyeOutlined'; import DeleteOutlined from '@ant-design/icons/DeleteOutlined'; import DownloadOutlined from '@ant-design/icons/DownloadOutlined'; import Tooltip from '../../tooltip'; import Progress from '../../progress'; import { ConfigContext } from '../../config-provider'; 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); iconRender: (file: UploadFile) => React.ReactNode; actionIconRender: ( customIcon: React.ReactNode, callback: () => void, prefixCls: string, title?: string | undefined, ) => 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, onPreview, onDownload, onClose, }: ListItemProps, ref: React.Ref, ) => { // Delay to show the progress bar const [showProgress, setShowProgress] = React.useState(false); const progressRafRef = React.useRef(); React.useEffect(() => { progressRafRef.current = setTimeout(() => { setShowProgress(true); }, 300); return () => { window.clearTimeout(progressRafRef.current); }; }, []); // This is used for legacy span make scrollHeight the wrong value. // We will force these to be `display: block` with non `picture-card` const spanClassName = `${prefixCls}-span`; const iconNode = iconRender(file); let icon =
{iconNode}
; if (listType === 'picture' || listType === 'picture-card') { if (file.status === 'uploading' || (!file.thumbUrl && !file.url)) { const uploadingClassName = classNames({ [`${prefixCls}-list-item-thumbnail`]: true, [`${prefixCls}-list-item-file`]: file.status !== 'uploading', }); icon =
{iconNode}
; } else { const thumbnail = isImgUrl?.(file) ? ( {file.name} ) : ( iconNode ); const aClassName = classNames({ [`${prefixCls}-list-item-thumbnail`]: true, [`${prefixCls}-list-item-file`]: isImgUrl && !isImgUrl(file), }); icon = ( onPreview(file, e)} href={file.url || file.thumbUrl} target="_blank" rel="noopener noreferrer" > {thumbnail} ); } } const infoUploadingClass = classNames({ [`${prefixCls}-list-item`]: true, [`${prefixCls}-list-item-${file.status}`]: true, [`${prefixCls}-list-item-list-type-${listType}`]: true, }); 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, ) : null; const downloadIcon = showDownloadIcon && file.status === 'done' ? actionIconRender( (typeof customDownloadIcon === 'function' ? customDownloadIcon(file) : customDownloadIcon) || , () => onDownload(file), prefixCls, locale.downloadFile, ) : null; const downloadOrDelete = listType !== 'picture-card' && ( {downloadIcon} {removeIcon} ); const listItemNameClass = classNames(`${prefixCls}-list-item-name`); const preview = file.url ? [ onPreview(file, e)} > {file.name} , downloadOrDelete, ] : [ onPreview(file, e)} title={file.name} > {file.name} , downloadOrDelete, ]; const previewStyle: React.CSSProperties = { pointerEvents: 'none', opacity: 0.5, }; const previewIcon = showPreviewIcon ? ( onPreview(file, e)} title={locale.previewFile} > {typeof customPreviewIcon === 'function' ? customPreviewIcon(file) : customPreviewIcon || } ) : null; const actions = listType === 'picture-card' && file.status !== 'uploading' && ( {previewIcon} {file.status === 'done' && downloadIcon} {removeIcon} ); let message; if (file.response && typeof file.response === 'string') { message = file.response; } else { message = file.error?.statusText || file.error?.message || locale.uploadError; } const iconAndPreview = ( {icon} {preview} ); const { getPrefixCls } = React.useContext(ConfigContext); const rootPrefixCls = getPrefixCls(); const dom = (
{iconAndPreview}
{actions} {showProgress && ( {({ className: motionClassName }) => { // show loading icon if upload progress listener is disabled const loadingProgress = 'percent' in file ? ( ) : null; return (
{loadingProgress}
); }}
)}
); const listContainerNameClass = classNames(`${prefixCls}-list-${listType}-container`, className); const item = file.status === 'error' ? ( node.parentNode as HTMLElement}> {dom} ) : ( dom ); return (
{itemRender ? itemRender(item, file, items, { download: onDownload.bind(null, file), preview: onPreview.bind(null, file), remove: onClose.bind(null, file), }) : item}
); }, ); export default ListItem;