1
0

index.tsx 27 KB

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