index.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. /* eslint-disable max-len */
  2. import React, { ReactNode, CSSProperties, RefObject, ChangeEvent, DragEvent } from 'react';
  3. import cls from 'classnames';
  4. import PropTypes from 'prop-types';
  5. import { noop, pick } from 'lodash';
  6. import UploadFoundation, { CustomFile, UploadAdapter, BeforeUploadObjectResult, AfterUploadResult } from '@douyinfe/semi-foundation/upload/foundation';
  7. import { strings, cssClasses } from '@douyinfe/semi-foundation/upload/constants';
  8. import FileCard from './fileCard';
  9. import BaseComponent, { ValidateStatus } from '../_base/baseComponent';
  10. import LocaleConsumer from '../locale/localeConsumer';
  11. import { IconUpload } from '@douyinfe/semi-icons';
  12. import { FileItem, RenderFileItemProps, UploadListType, PromptPositionType, BeforeUploadProps, AfterUploadProps, OnChangeProps, customRequestArgs, CustomError } from './interface';
  13. import { Locale } from '../locale/interface';
  14. import '@douyinfe/semi-foundation/upload/upload.scss';
  15. const prefixCls = cssClasses.PREFIX;
  16. export { FileItem, RenderFileItemProps, UploadListType, PromptPositionType, BeforeUploadProps, AfterUploadProps, OnChangeProps, customRequestArgs, CustomError, BeforeUploadObjectResult, AfterUploadResult };
  17. export interface UploadProps {
  18. accept?: string;
  19. action: string;
  20. afterUpload?: (object: AfterUploadProps) => AfterUploadResult;
  21. beforeUpload?: (object: BeforeUploadProps) => BeforeUploadObjectResult | Promise<BeforeUploadObjectResult> | boolean;
  22. beforeClear?: (fileList: Array<FileItem>) => boolean | Promise<boolean>;
  23. beforeRemove?: (file: FileItem, fileList: Array<FileItem>) => boolean | Promise<boolean>;
  24. capture?: boolean | string | undefined;
  25. children?: ReactNode;
  26. className?: string;
  27. customRequest?: (object: customRequestArgs) => void;
  28. data?: Record<string, any> | ((file: File) => Record<string, unknown>);
  29. defaultFileList?: Array<FileItem>;
  30. directory?: boolean;
  31. disabled?: boolean;
  32. dragIcon?: ReactNode;
  33. dragMainText?: ReactNode;
  34. dragSubText?: ReactNode;
  35. draggable?: boolean;
  36. fileList?: Array<FileItem>;
  37. fileName?: string;
  38. headers?: Record<string, any> | ((file: File) => Record<string, string>);
  39. hotSpotLocation?: 'start' | 'end';
  40. itemStyle?: CSSProperties;
  41. limit?: number;
  42. listType?: UploadListType;
  43. maxSize?: number;
  44. minSize?: number;
  45. multiple?: boolean;
  46. name?: string;
  47. onAcceptInvalid?: (files: File[]) => void;
  48. onChange?: (object: OnChangeProps) => void;
  49. onClear?: () => void;
  50. onDrop?: (e: Event, files: Array<File>, fileList: Array<FileItem>) => void;
  51. onError?: (e: CustomError, file: File, fileList: Array<FileItem>, xhr: XMLHttpRequest) => void;
  52. onExceed?: (fileList: Array<File>) => void;
  53. onFileChange?: (files: Array<File>) => void;
  54. onOpenFileDialog?: () => void;
  55. onPreviewClick?: (fileItem: FileItem) => void;
  56. onProgress?: (percent: number, file: File, fileList: Array<FileItem>) => void;
  57. onRemove?: (currentFile: File, fileList: Array<FileItem>, currentFileItem: FileItem) => void;
  58. onRetry?: (fileItem: FileItem) => void;
  59. onSizeError?: (file: File, fileList: Array<FileItem>) => void;
  60. onSuccess?: (responseBody: any, file: File, fileList: Array<FileItem>) => void;
  61. previewFile?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
  62. prompt?: ReactNode;
  63. promptPosition?: PromptPositionType;
  64. renderFileItem?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
  65. renderPicInfo?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
  66. renderThumbnail?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
  67. renderPicPreviewIcon?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
  68. renderFileOperation?: (fileItem: RenderFileItemProps) => ReactNode;
  69. showClear?: boolean;
  70. showPicInfo?: boolean; // Show pic info in picture wall
  71. showReplace?: boolean; // Display replacement function
  72. showRetry?: boolean;
  73. showUploadList?: boolean;
  74. style?: CSSProperties;
  75. timeout?: number;
  76. transformFile?: (file: File) => FileItem;
  77. uploadTrigger?: 'auto' | 'custom';
  78. validateMessage?: ReactNode;
  79. validateStatus?: ValidateStatus;
  80. withCredentials?: boolean;
  81. }
  82. export interface UploadState {
  83. dragAreaStatus: 'default' | 'legal' | 'illegal'; // Status of the drag zone
  84. fileList: Array<FileItem>;
  85. inputKey: number;
  86. localUrls: Array<string>;
  87. replaceIdx: number;
  88. replaceInputKey: number;
  89. }
  90. class Upload extends BaseComponent<UploadProps, UploadState> {
  91. static propTypes = {
  92. accept: PropTypes.string, // Limit allowed file types
  93. action: PropTypes.string.isRequired,
  94. afterUpload: PropTypes.func,
  95. beforeClear: PropTypes.func,
  96. beforeRemove: PropTypes.func,
  97. beforeUpload: PropTypes.func,
  98. children: PropTypes.node,
  99. className: PropTypes.string,
  100. customRequest: PropTypes.func,
  101. data: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), // Extra parameters attached when uploading
  102. defaultFileList: PropTypes.array,
  103. directory: PropTypes.bool, // Support folder upload
  104. disabled: PropTypes.bool,
  105. dragIcon: PropTypes.node,
  106. dragMainText: PropTypes.node,
  107. dragSubText: PropTypes.node,
  108. draggable: PropTypes.bool,
  109. fileList: PropTypes.array, // files had been uploaded
  110. fileName: PropTypes.string, // same as name, to avoid props conflict in Form.Upload
  111. headers: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
  112. hotSpotLocation: PropTypes.oneOf(['start','end']),
  113. itemStyle: PropTypes.object,
  114. limit: PropTypes.number, // 最大允许上传文件个数
  115. listType: PropTypes.oneOf<UploadProps['listType']>(strings.LIST_TYPE),
  116. maxSize: PropTypes.number, // 文件大小限制,单位kb
  117. minSize: PropTypes.number, // 文件大小限制,单位kb
  118. multiple: PropTypes.bool,
  119. name: PropTypes.string, // file name
  120. onAcceptInvalid: PropTypes.func,
  121. onChange: PropTypes.func,
  122. onClear: PropTypes.func,
  123. onDrop: PropTypes.func,
  124. onError: PropTypes.func,
  125. onExceed: PropTypes.func, // Callback exceeding limit
  126. onFileChange: PropTypes.func, // Callback when file is selected
  127. onOpenFileDialog: PropTypes.func,
  128. onPreviewClick: PropTypes.func,
  129. onProgress: PropTypes.func,
  130. onRemove: PropTypes.func,
  131. onRetry: PropTypes.func,
  132. onSizeError: PropTypes.func, // Callback with invalid file size
  133. onSuccess: PropTypes.func,
  134. previewFile: PropTypes.func, // Custom preview
  135. prompt: PropTypes.node,
  136. promptPosition: PropTypes.oneOf<UploadProps['promptPosition']>(strings.PROMPT_POSITION),
  137. renderFileItem: PropTypes.func,
  138. renderPicPreviewIcon: PropTypes.func,
  139. renderFileOperation: PropTypes.func,
  140. renderPicInfo: PropTypes.func,
  141. renderThumbnail: PropTypes.func,
  142. showClear: PropTypes.bool,
  143. showPicInfo: PropTypes.bool,
  144. showReplace: PropTypes.bool,
  145. showRetry: PropTypes.bool,
  146. showUploadList: PropTypes.bool, // whether to show fileList
  147. style: PropTypes.object,
  148. timeout: PropTypes.number,
  149. transformFile: PropTypes.func,
  150. uploadTrigger: PropTypes.oneOf<UploadProps['uploadTrigger']>(strings.UPLOAD_TRIGGER), // auto、custom
  151. validateMessage: PropTypes.node,
  152. validateStatus: PropTypes.oneOf<UploadProps['validateStatus']>(strings.VALIDATE_STATUS),
  153. withCredentials: PropTypes.bool,
  154. };
  155. static defaultProps: Partial<UploadProps> = {
  156. defaultFileList: [],
  157. disabled: false,
  158. listType: 'list' as const,
  159. hotSpotLocation: 'end',
  160. multiple: false,
  161. onAcceptInvalid: noop,
  162. onChange: noop,
  163. beforeRemove: () => true,
  164. beforeClear: () => true,
  165. onClear: noop,
  166. onDrop: noop,
  167. onError: noop,
  168. onExceed: noop,
  169. onFileChange: noop,
  170. onOpenFileDialog: noop,
  171. onProgress: noop,
  172. onRemove: noop,
  173. onRetry: noop,
  174. onSizeError: noop,
  175. onSuccess: noop,
  176. promptPosition: 'right' as const,
  177. showClear: true,
  178. showPicInfo: false,
  179. showReplace: false,
  180. showRetry: true,
  181. showUploadList: true,
  182. uploadTrigger: 'auto' as const,
  183. withCredentials: false,
  184. };
  185. static FileCard = FileCard;
  186. constructor(props: UploadProps) {
  187. super(props);
  188. this.state = {
  189. fileList: props.defaultFileList || [],
  190. replaceIdx: -1,
  191. inputKey: Math.random(),
  192. replaceInputKey: Math.random(),
  193. // Status of the drag zone
  194. dragAreaStatus: 'default',
  195. localUrls: [],
  196. };
  197. this.foundation = new UploadFoundation(this.adapter);
  198. this.inputRef = React.createRef<HTMLInputElement>();
  199. this.replaceInputRef = React.createRef<HTMLInputElement>();
  200. }
  201. /**
  202. * Notes:
  203. * The input parameter and return value here do not declare the type, otherwise tsc may report an error in form/fields.tsx when wrap after withField
  204. * `The types of the parameters "props" and "nextProps" are incompatible.
  205. The attribute "action" is missing in the type "Readonly<any>", but it is required in the type "UploadProps".`
  206. * which seems to be a bug, remove props type declare here
  207. */
  208. static getDerivedStateFromProps(props) {
  209. const { fileList } = props;
  210. if ('fileList' in props) {
  211. return {
  212. fileList: fileList || []
  213. };
  214. }
  215. return null;
  216. }
  217. get adapter(): UploadAdapter<UploadProps, UploadState> {
  218. return {
  219. ...super.adapter,
  220. notifyFileSelect: (files): void => this.props.onFileChange(files),
  221. notifyError: (error, fileInstance, fileList, xhr): void => this.props.onError(error, fileInstance, fileList, xhr),
  222. notifySuccess: (responseBody, file, fileList): void => this.props.onSuccess(responseBody, file, fileList),
  223. notifyProgress: (percent, file, fileList): void => this.props.onProgress(percent, file, fileList),
  224. notifyRemove: (file, fileList, fileItem): void => this.props.onRemove(file, fileList, fileItem),
  225. notifySizeError: (file, fileList): void => this.props.onSizeError(file, fileList),
  226. notifyExceed: (fileList): void => this.props.onExceed(fileList),
  227. updateFileList: (fileList, cb): void => {
  228. if (typeof cb === 'function') {
  229. this.setState({ fileList }, cb);
  230. } else {
  231. this.setState({ fileList });
  232. }
  233. },
  234. notifyBeforeUpload: ({ file, fileList }): boolean | BeforeUploadObjectResult | Promise<BeforeUploadObjectResult> => this.props.beforeUpload({ file, fileList }),
  235. notifyAfterUpload: ({ response, file, fileList }): AfterUploadResult => this.props.afterUpload({ response, file, fileList }),
  236. resetInput: (): void => {
  237. this.setState(prevState => ({
  238. inputKey: Math.random()
  239. }));
  240. },
  241. resetReplaceInput: (): void => {
  242. this.setState(prevState => ({
  243. replaceInputKey: Math.random()
  244. }));
  245. },
  246. updateDragAreaStatus: (dragAreaStatus: string): void => this.setState({ dragAreaStatus } as { dragAreaStatus: 'default' | 'legal' | 'illegal' }),
  247. notifyChange: ({ currentFile, fileList }): void => this.props.onChange({ currentFile, fileList }),
  248. updateLocalUrls: (urls): void => this.setState({ localUrls: urls }),
  249. notifyClear: (): void => this.props.onClear(),
  250. notifyPreviewClick: (file): void => this.props.onPreviewClick(file),
  251. notifyDrop: (e, files, fileList): void => this.props.onDrop(e, files, fileList),
  252. notifyAcceptInvalid: (invalidFiles): void => this.props.onAcceptInvalid(invalidFiles),
  253. notifyBeforeRemove: (file, fileList): boolean | Promise<boolean> => this.props.beforeRemove(file, fileList),
  254. notifyBeforeClear: (fileList): boolean | Promise<boolean> => this.props.beforeClear(fileList),
  255. };
  256. }
  257. foundation: UploadFoundation;
  258. inputRef: RefObject<HTMLInputElement> = null;
  259. replaceInputRef: RefObject<HTMLInputElement> = null;
  260. componentWillUnmount(): void {
  261. this.foundation.destroy();
  262. }
  263. onClick = (): void => {
  264. const { inputRef, props } = this;
  265. const { onOpenFileDialog } = props;
  266. const isDisabled = Boolean(this.props.disabled);
  267. if (isDisabled || !inputRef || !inputRef.current) {
  268. return;
  269. }
  270. inputRef.current.click();
  271. if (onOpenFileDialog && typeof onOpenFileDialog) {
  272. onOpenFileDialog();
  273. }
  274. };
  275. onChange = (e: ChangeEvent<HTMLInputElement>): void => {
  276. const { files } = e.target;
  277. this.foundation.handleChange(files);
  278. };
  279. replace = (index: number): void => {
  280. this.setState({ replaceIdx: index }, () => {
  281. this.replaceInputRef.current.click();
  282. });
  283. };
  284. onReplaceChange = (e: ChangeEvent<HTMLInputElement>): void => {
  285. const { files } = e.target;
  286. this.foundation.handleReplaceChange(files);
  287. };
  288. clear = (): void => {
  289. this.foundation.handleClear();
  290. };
  291. remove = (fileItem: FileItem): void => {
  292. this.foundation.handleRemove(fileItem);
  293. };
  294. /**
  295. * ref method
  296. * insert files at index
  297. * @param files Array<CustomFile>
  298. * @param index number
  299. * @returns
  300. */
  301. insert = (files: Array<CustomFile>, index: number): void => {
  302. return this.foundation.insertFileToList(files, index);
  303. }
  304. /**
  305. * ref method
  306. * manual upload by user
  307. */
  308. upload = (): void => {
  309. const { fileList } = this.state;
  310. this.foundation.startUpload(fileList);
  311. };
  312. renderFile = (file: FileItem, index: number, locale: Locale['Upload']): ReactNode => {
  313. const { name, status, validateMessage, _sizeInvalid, uid } = file;
  314. const { previewFile, listType, itemStyle, showPicInfo, renderPicInfo, renderPicPreviewIcon, renderFileOperation, renderFileItem, renderThumbnail, disabled, onPreviewClick } = this.props;
  315. const onRemove = (): void => this.remove(file);
  316. const onRetry = (): void => {
  317. this.foundation.retry(file);
  318. };
  319. const onReplace = (): void => {
  320. this.replace(index);
  321. };
  322. const fileCardProps = {
  323. ...pick(this.props, ['showRetry', 'showReplace', '']),
  324. ...file,
  325. previewFile,
  326. listType,
  327. onRemove,
  328. onRetry,
  329. index,
  330. key: uid || `${name}${index}`,
  331. style: itemStyle,
  332. disabled,
  333. showPicInfo,
  334. renderPicInfo,
  335. renderPicPreviewIcon,
  336. renderFileOperation,
  337. renderThumbnail,
  338. onReplace,
  339. onPreviewClick: typeof onPreviewClick !== 'undefined' ? (): void => this.foundation.handlePreviewClick(file) : undefined,
  340. };
  341. if (status === strings.FILE_STATUS_UPLOAD_FAIL && !validateMessage) {
  342. fileCardProps.validateMessage = locale.fail;
  343. }
  344. if (_sizeInvalid && !validateMessage) {
  345. fileCardProps.validateMessage = locale.illegalSize;
  346. }
  347. if (typeof renderFileItem === 'undefined') {
  348. return <FileCard {...fileCardProps} />;
  349. } else {
  350. return renderFileItem(fileCardProps);
  351. }
  352. };
  353. renderFileList = (): ReactNode => {
  354. const { listType } = this.props;
  355. if (listType === strings.FILE_LIST_PIC) {
  356. return this.renderFileListPic();
  357. }
  358. if (listType === strings.FILE_LIST_DEFAULT) {
  359. return this.renderFileListDefault();
  360. }
  361. return null;
  362. };
  363. renderFileListPic = () => {
  364. const { showUploadList, limit, disabled, children, draggable, hotSpotLocation } = this.props;
  365. const { fileList: stateFileList, dragAreaStatus } = this.state;
  366. const fileList = this.props.fileList || stateFileList;
  367. const showAddTriggerInList = limit ? limit > fileList.length : true;
  368. const dragAreaBaseCls = `${prefixCls}-drag-area`;
  369. const uploadAddCls = cls(`${prefixCls}-add`, {
  370. [`${prefixCls}-picture-add`]: true,
  371. [`${prefixCls}-picture-add-disabled`]: disabled,
  372. });
  373. const fileListCls = cls(`${prefixCls}-file-list`, {
  374. [`${prefixCls}-picture-file-list`]: true,
  375. });
  376. const dragAreaCls = cls({
  377. [`${dragAreaBaseCls}-legal`]: dragAreaStatus === strings.DRAG_AREA_LEGAL,
  378. [`${dragAreaBaseCls}-illegal`]: dragAreaStatus === strings.DRAG_AREA_ILLEGAL
  379. });
  380. const mainCls = `${prefixCls}-file-list-main`;
  381. const addContentProps = {
  382. role: 'button',
  383. className: uploadAddCls,
  384. onClick: this.onClick,
  385. };
  386. const containerProps = {
  387. className: fileListCls
  388. };
  389. const draggableProps = {
  390. onDrop: this.onDrop,
  391. onDragOver: this.onDragOver,
  392. onDragLeave: this.onDragLeave,
  393. onDragEnter: this.onDragEnter,
  394. };
  395. if (draggable) {
  396. Object.assign(addContentProps, draggableProps, { className: cls(uploadAddCls, dragAreaCls) });
  397. }
  398. const addContent = (
  399. <div {...addContentProps}>
  400. {children}
  401. </div>
  402. );
  403. if (!showUploadList || !fileList.length) {
  404. if (showAddTriggerInList) {
  405. return addContent;
  406. }
  407. return null;
  408. }
  409. return (
  410. <LocaleConsumer componentName="Upload">
  411. {(locale: Locale['Upload']) => (
  412. <div {...containerProps}>
  413. <div className={mainCls} role="list" aria-label="picture list">
  414. {showAddTriggerInList && hotSpotLocation === 'start' ? addContent : null}
  415. {fileList.map((file, index) => this.renderFile(file, index, locale))}
  416. {showAddTriggerInList && hotSpotLocation === 'end' ? addContent : null}
  417. </div>
  418. </div>
  419. )}
  420. </LocaleConsumer>
  421. );
  422. }
  423. renderFileListDefault = () => {
  424. const { showUploadList, limit, disabled } = this.props;
  425. const { fileList: stateFileList } = this.state;
  426. const fileList = this.props.fileList || stateFileList;
  427. const fileListCls = cls(`${prefixCls}-file-list`);
  428. const titleCls = `${prefixCls}-file-list-title`;
  429. const mainCls = `${prefixCls}-file-list-main`;
  430. const showTitle = limit !== 1 && fileList.length;
  431. const showClear = this.props.showClear && !disabled;
  432. const containerProps = {
  433. className: fileListCls
  434. };
  435. if (!showUploadList || !fileList.length) {
  436. return null;
  437. }
  438. return (
  439. <LocaleConsumer componentName="Upload">
  440. {(locale: Locale['Upload']) => (
  441. <div {...containerProps}>
  442. {showTitle ? (
  443. <div className={titleCls}>
  444. <span className={`${titleCls}-choosen`}>{locale.selectedFiles}</span>
  445. {showClear ? (
  446. <span role="button" tabIndex={0} onClick={this.clear} className={`${titleCls}-clear`}>
  447. {locale.clear}
  448. </span>
  449. ) : null}
  450. </div>
  451. ) : null}
  452. <div className={mainCls} role="list" aria-label="file list">
  453. {fileList.map((file, index) => this.renderFile(file, index, locale))}
  454. </div>
  455. </div>
  456. )}
  457. </LocaleConsumer>
  458. );
  459. }
  460. onDrop = (e: DragEvent<HTMLDivElement>): void => {
  461. this.foundation.handleDrop(e);
  462. };
  463. onDragOver = (e: DragEvent<HTMLDivElement>): void => {
  464. // When a drag element moves within the target element
  465. this.foundation.handleDragOver(e);
  466. };
  467. onDragLeave = (e: DragEvent<HTMLDivElement>): void => {
  468. this.foundation.handleDragLeave(e);
  469. };
  470. onDragEnter = (e: DragEvent<HTMLDivElement>): void => {
  471. this.foundation.handleDragEnter(e);
  472. };
  473. renderAddContent = () => {
  474. const { draggable, children, listType, disabled } = this.props;
  475. const uploadAddCls = cls(`${prefixCls}-add`);
  476. if (listType === strings.FILE_LIST_PIC) {
  477. return null;
  478. }
  479. if (draggable) {
  480. return this.renderDragArea();
  481. }
  482. return (
  483. <div role="button" tabIndex={0} aria-disabled={disabled} className={uploadAddCls} onClick={this.onClick}>
  484. {children}
  485. </div>
  486. );
  487. }
  488. renderDragArea = (): ReactNode => {
  489. const { dragAreaStatus } = this.state;
  490. const { children, dragIcon, dragMainText, dragSubText, disabled } = this.props;
  491. const dragAreaBaseCls = `${prefixCls}-drag-area`;
  492. const dragAreaCls = cls(dragAreaBaseCls, {
  493. [`${dragAreaBaseCls}-legal`]: dragAreaStatus === strings.DRAG_AREA_LEGAL,
  494. [`${dragAreaBaseCls}-illegal`]: dragAreaStatus === strings.DRAG_AREA_ILLEGAL,
  495. [`${dragAreaBaseCls}-custom`]: children,
  496. });
  497. return (
  498. <LocaleConsumer componentName="Upload">
  499. {(locale: Locale['Upload']): ReactNode => (
  500. <div
  501. role="button"
  502. tabIndex={0}
  503. aria-disabled={disabled}
  504. className={dragAreaCls}
  505. onDrop={this.onDrop}
  506. onDragOver={this.onDragOver}
  507. onDragLeave={this.onDragLeave}
  508. onDragEnter={this.onDragEnter}
  509. onClick={this.onClick}
  510. >
  511. {children ? (
  512. children
  513. ) : (
  514. <>
  515. <div className={`${dragAreaBaseCls}-icon`}>
  516. {dragIcon || <IconUpload size="extra-large" />}
  517. </div>
  518. <div className={`${dragAreaBaseCls}-text`}>
  519. <div className={`${dragAreaBaseCls}-main-text`}>
  520. {dragMainText || locale.mainText}
  521. </div>
  522. <div className={`${dragAreaBaseCls}-sub-text`}>{dragSubText}</div>
  523. <div className={`${dragAreaBaseCls}-tips`}>
  524. {dragAreaStatus === strings.DRAG_AREA_LEGAL && (
  525. <span className={`${dragAreaBaseCls}-tips-legal`}>{locale.legalTips}</span>
  526. )}
  527. {dragAreaStatus === strings.DRAG_AREA_ILLEGAL && (
  528. <span className={`${dragAreaBaseCls}-tips-illegal`}>
  529. {locale.illegalTips}
  530. </span>
  531. )}
  532. </div>
  533. </div>
  534. </>
  535. )}
  536. </div>
  537. )}
  538. </LocaleConsumer>
  539. );
  540. };
  541. render(): ReactNode {
  542. const {
  543. style,
  544. className,
  545. multiple,
  546. accept,
  547. disabled,
  548. children,
  549. capture,
  550. listType,
  551. prompt,
  552. promptPosition,
  553. draggable,
  554. validateMessage,
  555. validateStatus,
  556. directory,
  557. } = this.props;
  558. const uploadCls = cls(prefixCls, {
  559. [`${prefixCls}-picture`]: listType === strings.FILE_LIST_PIC,
  560. [`${prefixCls}-disabled`]: disabled,
  561. [`${prefixCls}-default`]: validateStatus === 'default',
  562. [`${prefixCls}-error`]: validateStatus === 'error',
  563. [`${prefixCls}-warning`]: validateStatus === 'warning',
  564. [`${prefixCls}-success`]: validateStatus === 'success',
  565. }, className);
  566. const inputCls = cls(`${prefixCls}-hidden-input`);
  567. const inputReplaceCls = cls(`${prefixCls}-hidden-input-replace`);
  568. const promptCls = cls(`${prefixCls}-prompt`);
  569. const validateMsgCls = cls(`${prefixCls}-validate-message`);
  570. const dirProps = directory ? { directory: 'directory', webkitdirectory: 'webkitdirectory' } : {};
  571. return (
  572. <div className={uploadCls} style={style} x-prompt-pos={promptPosition}>
  573. <input
  574. key={this.state.inputKey}
  575. capture={capture}
  576. multiple={multiple}
  577. accept={accept}
  578. onChange={this.onChange}
  579. type="file"
  580. autoComplete="off"
  581. tabIndex={-1}
  582. className={inputCls}
  583. ref={this.inputRef}
  584. {...dirProps}
  585. />
  586. <input
  587. key={this.state.replaceInputKey}
  588. multiple={false}
  589. accept={accept}
  590. onChange={this.onReplaceChange}
  591. type="file"
  592. autoComplete="off"
  593. tabIndex={-1}
  594. className={inputReplaceCls}
  595. ref={this.replaceInputRef}
  596. />
  597. {this.renderAddContent()}
  598. {prompt ? <div className={promptCls}>{prompt}</div> : null}
  599. {validateMessage ? <div className={validateMsgCls}>{validateMessage}</div> : null}
  600. {this.renderFileList()}
  601. </div>
  602. );
  603. }
  604. }
  605. export default Upload;