1
0

index.tsx 27 KB

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