import * as React from 'react'; import FileTwoTone from '@ant-design/icons/FileTwoTone'; import LoadingOutlined from '@ant-design/icons/LoadingOutlined'; import PaperClipOutlined from '@ant-design/icons/PaperClipOutlined'; import PictureTwoTone from '@ant-design/icons/PictureTwoTone'; import classNames from 'classnames'; import type { CSSMotionListProps } from 'rc-motion'; import CSSMotion, { CSSMotionList } from 'rc-motion'; import useForceUpdate from '../../_util/hooks/useForceUpdate'; import initCollapseMotion from '../../_util/motion'; import { cloneElement } from '../../_util/reactNode'; import type { ButtonProps } from '../../button'; import Button from '../../button'; import { ConfigContext } from '../../config-provider'; import type { UploadFile, UploadListProps } from '../interface'; import { isImageUrl, previewImage } from '../utils'; import ListItem from './ListItem'; interface UploadListRef { handlePreview: (file: UploadFile, e?: React.SyntheticEvent) => void; handleDownload: (file: UploadFile) => void; } const InternalUploadList: React.ForwardRefRenderFunction = ( props, ref, ) => { const { listType = 'text', previewFile = previewImage, onPreview, onDownload, onRemove, locale, iconRender, isImageUrl: isImgUrl = isImageUrl, prefixCls: customizePrefixCls, items = [], showPreviewIcon = true, showRemoveIcon = true, showDownloadIcon = false, removeIcon, previewIcon, downloadIcon, progress = { size: [-1, 2], showInfo: false }, appendAction, appendActionVisible = true, itemRender, disabled, } = props; const forceUpdate = useForceUpdate(); const [motionAppear, setMotionAppear] = React.useState(false); // ============================= Effect ============================= React.useEffect(() => { if (listType !== 'picture' && listType !== 'picture-card' && listType !== 'picture-circle') { return; } (items || []).forEach((file) => { if ( typeof document === 'undefined' || typeof window === 'undefined' || !(window as any).FileReader || !(window as any).File || !(file.originFileObj instanceof File || (file.originFileObj as any) instanceof Blob) || file.thumbUrl !== undefined ) { return; } file.thumbUrl = ''; if (previewFile) { previewFile(file.originFileObj as File).then((previewDataUrl: string) => { // Need append '' to avoid dead loop file.thumbUrl = previewDataUrl || ''; forceUpdate(); }); } }); }, [listType, items, previewFile]); React.useEffect(() => { setMotionAppear(true); }, []); // ============================= Events ============================= const onInternalPreview = (file: UploadFile, e?: React.SyntheticEvent) => { if (!onPreview) { return; } e?.preventDefault(); return onPreview(file); }; const onInternalDownload = (file: UploadFile) => { if (typeof onDownload === 'function') { onDownload(file); } else if (file.url) { window.open(file.url); } }; const onInternalClose = (file: UploadFile) => { onRemove?.(file); }; const internalIconRender = (file: UploadFile) => { if (iconRender) { return iconRender(file, listType); } const isLoading = file.status === 'uploading'; const fileIcon = isImgUrl?.(file) ? : ; let icon: React.ReactNode = isLoading ? : ; if (listType === 'picture') { icon = isLoading ? : fileIcon; } else if (listType === 'picture-card' || listType === 'picture-circle') { icon = isLoading ? locale.uploading : fileIcon; } return icon; }; const actionIconRender = ( customIcon: React.ReactNode, callback: () => void, prefixCls: string, title?: string, acceptUploadDisabled?: boolean, ) => { const btnProps: ButtonProps = { type: 'text', size: 'small', title, onClick: (e: React.MouseEvent) => { callback(); if (React.isValidElement(customIcon)) { customIcon.props.onClick?.(e); } }, className: `${prefixCls}-list-item-action`, }; if (acceptUploadDisabled) { btnProps.disabled = disabled; } if (React.isValidElement(customIcon)) { const btnIcon = cloneElement(customIcon, { ...customIcon.props, onClick: () => {}, }); return ); }; // ============================== Ref =============================== // Test needs React.useImperativeHandle(ref, () => ({ handlePreview: onInternalPreview, handleDownload: onInternalDownload, })); const { getPrefixCls } = React.useContext(ConfigContext); // ============================= Render ============================= const prefixCls = getPrefixCls('upload', customizePrefixCls); const rootPrefixCls = getPrefixCls(); const listClassNames = classNames(`${prefixCls}-list`, `${prefixCls}-list-${listType}`); // >>> Motion config const motionKeyList = [...items.map((file) => ({ key: file.uid, file }))]; const animationDirection = listType === 'picture-card' || listType === 'picture-circle' ? 'animate-inline' : 'animate'; // const transitionName = list.length === 0 ? '' : `${prefixCls}-${animationDirection}`; let motionConfig: Omit = { motionDeadline: 2000, motionName: `${prefixCls}-${animationDirection}`, keys: motionKeyList, motionAppear, }; const listItemMotion = React.useMemo>(() => { const motion = { ...initCollapseMotion(rootPrefixCls), }; delete motion.onAppearEnd; delete motion.onEnterEnd; delete motion.onLeaveEnd; return motion; }, [rootPrefixCls]); if (listType !== 'picture-card' && listType !== 'picture-circle') { motionConfig = { ...listItemMotion, ...motionConfig, }; } return (
{({ key, file, className: motionClassName, style: motionStyle }) => ( )} {/* Append action */} {appendAction && ( {({ className: motionClassName, style: motionStyle }) => cloneElement(appendAction, (oriProps) => ({ className: classNames(oriProps.className, motionClassName), style: { ...motionStyle, // prevent the element has hover css pseudo-class that may cause animation to end prematurely. pointerEvents: motionClassName ? 'none' : undefined, ...oriProps.style, }, })) } )}
); }; const UploadList = React.forwardRef(InternalUploadList); if (process.env.NODE_ENV !== 'production') { UploadList.displayName = 'UploadList'; } export default UploadList;