@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
668 lines (667 loc) • 23.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _pick2 = _interopRequireDefault(require("lodash/pick"));
var _noop2 = _interopRequireDefault(require("lodash/noop"));
var _react = _interopRequireDefault(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/upload/foundation"));
var _constants = require("@douyinfe/semi-foundation/lib/cjs/upload/constants");
var _fileCard = _interopRequireDefault(require("./fileCard"));
var _baseComponent = _interopRequireDefault(require("../_base/baseComponent"));
var _localeConsumer = _interopRequireDefault(require("../locale/localeConsumer"));
var _semiIcons = require("@douyinfe/semi-icons");
require("@douyinfe/semi-foundation/lib/cjs/upload/upload.css");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
var __rest = void 0 && (void 0).__rest || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
};
const prefixCls = _constants.cssClasses.PREFIX;
class Upload extends _baseComponent.default {
constructor(props) {
super(props);
this.inputRef = null;
this.replaceInputRef = null;
this.onClick = () => {
const {
inputRef,
props
} = this;
const {
onOpenFileDialog
} = props;
const isDisabled = Boolean(this.props.disabled);
if (isDisabled || !inputRef || !inputRef.current) {
return;
}
inputRef.current.click();
if (onOpenFileDialog && typeof onOpenFileDialog) {
onOpenFileDialog();
}
};
this.onChange = e => {
const {
files
} = e.target;
this.foundation.handleChange(files);
};
this.replace = index => {
this.setState({
replaceIdx: index
}, () => {
this.replaceInputRef.current.click();
});
};
this.onReplaceChange = e => {
const {
files
} = e.target;
this.foundation.handleReplaceChange(files);
};
this.clear = () => {
this.foundation.handleClear();
};
this.remove = fileItem => {
this.foundation.handleRemove(fileItem);
};
/**
* ref method
* insert files at index
* @param files Array<CustomFile>
* @param index number
* @returns
*/
this.insert = (files, index) => {
return this.foundation.insertFileToList(files, index);
};
/**
* ref method
* manual upload by user
*/
this.upload = () => {
this.foundation.manualUpload();
};
/**
* ref method
* manual open file select dialog
*/
this.openFileDialog = () => {
this.onClick();
};
this.renderFile = (file, index, locale) => {
const {
name,
status,
validateMessage,
_sizeInvalid,
uid
} = file;
const {
previewFile,
listType,
itemStyle,
showPicInfo,
renderPicInfo,
renderPicClose,
renderPicPreviewIcon,
renderFileOperation,
renderFileItem,
renderThumbnail,
disabled,
onPreviewClick,
picWidth,
picHeight,
showTooltip
} = this.props;
const onRemove = () => this.remove(file);
const onRetry = () => {
this.foundation.retry(file);
};
const onReplace = () => {
this.replace(index);
};
const fileCardProps = Object.assign(Object.assign(Object.assign({}, (0, _pick2.default)(this.props, ['showRetry', 'showReplace', ''])), file), {
previewFile,
listType,
onRemove,
onRetry,
index,
key: uid || `${name}${index}`,
style: itemStyle,
disabled,
showPicInfo,
renderPicInfo,
renderPicPreviewIcon,
renderPicClose,
renderFileOperation,
renderThumbnail,
onReplace,
onPreviewClick: typeof onPreviewClick !== 'undefined' ? () => this.foundation.handlePreviewClick(file) : undefined,
picWidth,
picHeight,
showTooltip
});
if (status === _constants.strings.FILE_STATUS_UPLOAD_FAIL && !validateMessage) {
fileCardProps.validateMessage = locale.fail;
}
if (_sizeInvalid && !validateMessage) {
fileCardProps.validateMessage = locale.illegalSize;
}
if (typeof renderFileItem === 'undefined') {
return /*#__PURE__*/_react.default.createElement(_fileCard.default, Object.assign({}, fileCardProps));
} else {
return renderFileItem(fileCardProps);
}
};
this.renderFileList = () => {
const {
listType
} = this.props;
if (listType === _constants.strings.FILE_LIST_PIC) {
return this.renderFileListPic();
}
if (listType === _constants.strings.FILE_LIST_DEFAULT) {
return this.renderFileListDefault();
}
return null;
};
this.renderFileListPic = () => {
const {
showUploadList,
limit,
disabled,
children,
draggable,
hotSpotLocation,
picHeight,
picWidth
} = this.props;
const {
fileList: stateFileList,
dragAreaStatus
} = this.state;
const fileList = this.props.fileList || stateFileList;
const showAddTriggerInList = limit ? limit > fileList.length : true;
const dragAreaBaseCls = `${prefixCls}-drag-area`;
const uploadAddCls = (0, _classnames.default)(`${prefixCls}-add`, {
[`${prefixCls}-picture-add`]: true,
[`${prefixCls}-picture-add-disabled`]: disabled
});
const fileListCls = (0, _classnames.default)(`${prefixCls}-file-list`, {
[`${prefixCls}-picture-file-list`]: true
});
const dragAreaCls = (0, _classnames.default)({
[`${dragAreaBaseCls}-legal`]: dragAreaStatus === _constants.strings.DRAG_AREA_LEGAL,
[`${dragAreaBaseCls}-illegal`]: dragAreaStatus === _constants.strings.DRAG_AREA_ILLEGAL
});
const mainCls = `${prefixCls}-file-list-main`;
const addContentProps = {
role: 'button',
className: uploadAddCls,
onClick: this.onClick,
style: {
height: picHeight,
width: picWidth
}
};
const containerProps = {
className: fileListCls
};
const draggableProps = {
onDrop: this.onDrop,
onDragOver: this.onDragOver,
onDragLeave: this.onDragLeave,
onDragEnter: this.onDragEnter
};
if (draggable) {
Object.assign(addContentProps, draggableProps, {
className: (0, _classnames.default)(uploadAddCls, dragAreaCls)
});
}
const addContent = /*#__PURE__*/_react.default.createElement("div", Object.assign({}, addContentProps, {
"x-semi-prop": "children"
}), children);
if (!showUploadList || !fileList.length) {
if (showAddTriggerInList) {
return addContent;
}
return null;
}
return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, {
componentName: "Upload"
}, locale => (/*#__PURE__*/_react.default.createElement("div", Object.assign({}, containerProps), /*#__PURE__*/_react.default.createElement("div", {
className: mainCls,
role: "list",
"aria-label": "picture list"
}, showAddTriggerInList && hotSpotLocation === 'start' ? addContent : null, fileList.map((file, index) => this.renderFile(file, index, locale)), showAddTriggerInList && hotSpotLocation === 'end' ? addContent : null))));
};
this.renderFileListDefault = () => {
const {
showUploadList,
limit,
disabled
} = this.props;
const {
fileList: stateFileList
} = this.state;
const fileList = this.props.fileList || stateFileList;
const fileListCls = (0, _classnames.default)(`${prefixCls}-file-list`);
const titleCls = `${prefixCls}-file-list-title`;
const mainCls = `${prefixCls}-file-list-main`;
const showTitle = limit !== 1 && fileList.length;
const showClear = this.props.showClear && !disabled;
const containerProps = {
className: fileListCls
};
if (!showUploadList || !fileList.length) {
return null;
}
return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, {
componentName: "Upload"
}, locale => (/*#__PURE__*/_react.default.createElement("div", Object.assign({}, containerProps), showTitle ? (/*#__PURE__*/_react.default.createElement("div", {
className: titleCls
}, /*#__PURE__*/_react.default.createElement("span", {
className: `${titleCls}-choosen`
}, locale.selectedFiles), showClear ? (/*#__PURE__*/_react.default.createElement("span", {
role: "button",
tabIndex: 0,
onClick: this.clear,
className: `${titleCls}-clear`
}, locale.clear)) : null)) : null, /*#__PURE__*/_react.default.createElement("div", {
className: mainCls,
role: "list",
"aria-label": "file list"
}, fileList.map((file, index) => this.renderFile(file, index, locale))))));
};
this.onDrop = e => {
this.foundation.handleDrop(e);
};
this.onDragOver = e => {
// When a drag element moves within the target element
this.foundation.handleDragOver(e);
};
this.onDragLeave = e => {
this.foundation.handleDragLeave(e);
};
this.onDragEnter = e => {
this.foundation.handleDragEnter(e);
};
this.renderAddContent = () => {
const {
draggable,
children,
listType,
disabled
} = this.props;
const uploadAddCls = (0, _classnames.default)(`${prefixCls}-add`);
if (listType === _constants.strings.FILE_LIST_PIC) {
return null;
}
if (draggable) {
return this.renderDragArea();
}
return /*#__PURE__*/_react.default.createElement("div", {
role: "button",
tabIndex: 0,
"aria-disabled": disabled,
className: uploadAddCls,
onClick: this.onClick
}, children);
};
this.renderDragArea = () => {
const {
dragAreaStatus
} = this.state;
const {
children,
dragIcon,
dragMainText,
dragSubText,
disabled
} = this.props;
const dragAreaBaseCls = `${prefixCls}-drag-area`;
const dragAreaCls = (0, _classnames.default)(dragAreaBaseCls, {
[`${dragAreaBaseCls}-legal`]: dragAreaStatus === _constants.strings.DRAG_AREA_LEGAL,
[`${dragAreaBaseCls}-illegal`]: dragAreaStatus === _constants.strings.DRAG_AREA_ILLEGAL,
[`${dragAreaBaseCls}-custom`]: children
});
return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, {
componentName: "Upload"
}, locale => (/*#__PURE__*/_react.default.createElement("div", {
role: "button",
tabIndex: 0,
"aria-disabled": disabled,
className: dragAreaCls,
onDrop: this.onDrop,
onDragOver: this.onDragOver,
onDragLeave: this.onDragLeave,
onDragEnter: this.onDragEnter,
onClick: this.onClick
}, children ? children : (/*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
className: `${dragAreaBaseCls}-icon`,
"x-semi-prop": "dragIcon"
}, dragIcon || /*#__PURE__*/_react.default.createElement(_semiIcons.IconUpload, {
size: "extra-large"
})), /*#__PURE__*/_react.default.createElement("div", {
className: `${dragAreaBaseCls}-text`
}, /*#__PURE__*/_react.default.createElement("div", {
className: `${dragAreaBaseCls}-main-text`,
"x-semi-prop": "dragMainText"
}, dragMainText || locale.mainText), /*#__PURE__*/_react.default.createElement("div", {
className: `${dragAreaBaseCls}-sub-text`,
"x-semi-prop": "dragSubText"
}, dragSubText), /*#__PURE__*/_react.default.createElement("div", {
className: `${dragAreaBaseCls}-tips`
}, dragAreaStatus === _constants.strings.DRAG_AREA_LEGAL && (/*#__PURE__*/_react.default.createElement("span", {
className: `${dragAreaBaseCls}-tips-legal`
}, locale.legalTips)), dragAreaStatus === _constants.strings.DRAG_AREA_ILLEGAL && (/*#__PURE__*/_react.default.createElement("span", {
className: `${dragAreaBaseCls}-tips-illegal`
}, locale.illegalTips)))))))));
};
this.state = {
fileList: props.defaultFileList || [],
replaceIdx: -1,
inputKey: Math.random(),
replaceInputKey: Math.random(),
// Status of the drag zone
dragAreaStatus: 'default',
localUrls: []
};
this.foundation = new _foundation.default(this.adapter);
this.inputRef = /*#__PURE__*/_react.default.createRef();
this.replaceInputRef = /*#__PURE__*/_react.default.createRef();
}
/**
* Notes:
* 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
* `The types of the parameters "props" and "nextProps" are incompatible.
The attribute "action" is missing in the type "Readonly<any>", but it is required in the type "UploadProps".`
* which seems to be a bug, remove props type declare here
*/
static getDerivedStateFromProps(props) {
const {
fileList
} = props;
if ('fileList' in props) {
return {
fileList: fileList || []
};
}
return null;
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
notifyFileSelect: files => this.props.onFileChange(files),
notifyError: (error, fileInstance, fileList, xhr) => this.props.onError(error, fileInstance, fileList, xhr),
notifySuccess: (responseBody, file, fileList) => this.props.onSuccess(responseBody, file, fileList),
notifyProgress: (percent, file, fileList) => this.props.onProgress(percent, file, fileList),
notifyRemove: (file, fileList, fileItem) => this.props.onRemove(file, fileList, fileItem),
notifySizeError: (file, fileList) => this.props.onSizeError(file, fileList),
notifyExceed: fileList => this.props.onExceed(fileList),
updateFileList: (fileList, cb) => {
if (typeof cb === 'function') {
this.setState({
fileList
}, cb);
} else {
this.setState({
fileList
});
}
},
notifyBeforeUpload: _ref => {
let {
file,
fileList
} = _ref;
return this.props.beforeUpload({
file,
fileList
});
},
notifyAfterUpload: _ref2 => {
let {
response,
file,
fileList
} = _ref2;
return this.props.afterUpload({
response,
file,
fileList
});
},
resetInput: () => {
this.setState(prevState => ({
inputKey: Math.random()
}));
},
resetReplaceInput: () => {
this.setState(prevState => ({
replaceInputKey: Math.random()
}));
},
isMac: () => {
return navigator.platform.toUpperCase().indexOf('MAC') >= 0;
},
registerPastingHandler: cb => {
document.body.addEventListener('keydown', cb);
this.pastingCb = cb;
},
unRegisterPastingHandler: () => {
if (this.pastingCb) {
document.body.removeEventListener('keydown', this.pastingCb);
}
},
notifyPastingError: error => this.props.onPastingError(error),
updateDragAreaStatus: dragAreaStatus => this.setState({
dragAreaStatus
}),
notifyChange: _ref3 => {
let {
currentFile,
fileList
} = _ref3;
return this.props.onChange({
currentFile,
fileList
});
},
updateLocalUrls: urls => this.setState({
localUrls: urls
}),
notifyClear: () => this.props.onClear(),
notifyPreviewClick: file => this.props.onPreviewClick(file),
notifyDrop: (e, files, fileList) => this.props.onDrop(e, files, fileList),
notifyAcceptInvalid: invalidFiles => this.props.onAcceptInvalid(invalidFiles),
notifyBeforeRemove: (file, fileList) => this.props.beforeRemove(file, fileList),
notifyBeforeClear: fileList => this.props.beforeClear(fileList)
});
}
componentDidMount() {
this.foundation.init();
}
componentWillUnmount() {
this.foundation.destroy();
}
render() {
const _a = this.props,
{
style,
className,
multiple,
accept,
disabled,
children,
capture,
listType,
prompt,
promptPosition,
draggable,
validateMessage,
validateStatus,
directory
} = _a,
rest = __rest(_a, ["style", "className", "multiple", "accept", "disabled", "children", "capture", "listType", "prompt", "promptPosition", "draggable", "validateMessage", "validateStatus", "directory"]);
const uploadCls = (0, _classnames.default)(prefixCls, {
[`${prefixCls}-picture`]: listType === _constants.strings.FILE_LIST_PIC,
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-default`]: validateStatus === 'default',
[`${prefixCls}-error`]: validateStatus === 'error',
[`${prefixCls}-warning`]: validateStatus === 'warning',
[`${prefixCls}-success`]: validateStatus === 'success'
}, className);
const inputCls = (0, _classnames.default)(`${prefixCls}-hidden-input`);
const inputReplaceCls = (0, _classnames.default)(`${prefixCls}-hidden-input-replace`);
const promptCls = (0, _classnames.default)(`${prefixCls}-prompt`);
const validateMsgCls = (0, _classnames.default)(`${prefixCls}-validate-message`);
const dirProps = directory ? {
directory: 'directory',
webkitdirectory: 'webkitdirectory'
} : {};
return /*#__PURE__*/_react.default.createElement("div", Object.assign({
className: uploadCls,
style: style,
"x-prompt-pos": promptPosition
}, this.getDataAttr(rest)), /*#__PURE__*/_react.default.createElement("input", Object.assign({
key: this.state.inputKey,
capture: capture,
multiple: multiple,
accept: accept,
onChange: this.onChange,
type: "file",
autoComplete: "off",
tabIndex: -1,
className: inputCls,
ref: this.inputRef
}, dirProps)), /*#__PURE__*/_react.default.createElement("input", {
key: this.state.replaceInputKey,
multiple: false,
accept: accept,
onChange: this.onReplaceChange,
type: "file",
autoComplete: "off",
tabIndex: -1,
className: inputReplaceCls,
ref: this.replaceInputRef
}), this.renderAddContent(), prompt ? (/*#__PURE__*/_react.default.createElement("div", {
className: promptCls,
"x-semi-prop": "prompt"
}, prompt)) : null, validateMessage ? (/*#__PURE__*/_react.default.createElement("div", {
className: validateMsgCls,
"x-semi-prop": "validateMessage"
}, validateMessage)) : null, this.renderFileList());
}
}
Upload.propTypes = {
accept: _propTypes.default.string,
action: _propTypes.default.string.isRequired,
addOnPasting: _propTypes.default.bool,
afterUpload: _propTypes.default.func,
beforeClear: _propTypes.default.func,
beforeRemove: _propTypes.default.func,
beforeUpload: _propTypes.default.func,
children: _propTypes.default.node,
className: _propTypes.default.string,
customRequest: _propTypes.default.func,
data: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.func]),
defaultFileList: _propTypes.default.array,
directory: _propTypes.default.bool,
disabled: _propTypes.default.bool,
dragIcon: _propTypes.default.node,
dragMainText: _propTypes.default.node,
dragSubText: _propTypes.default.node,
draggable: _propTypes.default.bool,
fileList: _propTypes.default.array,
fileName: _propTypes.default.string,
headers: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.func]),
hotSpotLocation: _propTypes.default.oneOf(['start', 'end']),
itemStyle: _propTypes.default.object,
limit: _propTypes.default.number,
listType: _propTypes.default.oneOf(_constants.strings.LIST_TYPE),
maxSize: _propTypes.default.number,
minSize: _propTypes.default.number,
multiple: _propTypes.default.bool,
name: _propTypes.default.string,
onAcceptInvalid: _propTypes.default.func,
onChange: _propTypes.default.func,
onClear: _propTypes.default.func,
onDrop: _propTypes.default.func,
onError: _propTypes.default.func,
onExceed: _propTypes.default.func,
onFileChange: _propTypes.default.func,
onOpenFileDialog: _propTypes.default.func,
onPreviewClick: _propTypes.default.func,
onProgress: _propTypes.default.func,
onRemove: _propTypes.default.func,
onRetry: _propTypes.default.func,
onSizeError: _propTypes.default.func,
onSuccess: _propTypes.default.func,
onPastingError: _propTypes.default.func,
previewFile: _propTypes.default.func,
prompt: _propTypes.default.node,
promptPosition: _propTypes.default.oneOf(_constants.strings.PROMPT_POSITION),
picWidth: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
picHeight: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
renderFileItem: _propTypes.default.func,
renderPicPreviewIcon: _propTypes.default.func,
renderFileOperation: _propTypes.default.func,
renderPicClose: _propTypes.default.func,
renderPicInfo: _propTypes.default.func,
renderThumbnail: _propTypes.default.func,
showClear: _propTypes.default.bool,
showPicInfo: _propTypes.default.bool,
showReplace: _propTypes.default.bool,
showRetry: _propTypes.default.bool,
showUploadList: _propTypes.default.bool,
style: _propTypes.default.object,
timeout: _propTypes.default.number,
transformFile: _propTypes.default.func,
uploadTrigger: _propTypes.default.oneOf(_constants.strings.UPLOAD_TRIGGER),
validateMessage: _propTypes.default.node,
validateStatus: _propTypes.default.oneOf(_constants.strings.VALIDATE_STATUS),
withCredentials: _propTypes.default.bool,
showTooltip: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.object])
};
Upload.defaultProps = {
defaultFileList: [],
disabled: false,
listType: 'list',
hotSpotLocation: 'end',
multiple: false,
onAcceptInvalid: _noop2.default,
onChange: _noop2.default,
beforeRemove: () => true,
beforeClear: () => true,
onClear: _noop2.default,
onDrop: _noop2.default,
onError: _noop2.default,
onExceed: _noop2.default,
onFileChange: _noop2.default,
onOpenFileDialog: _noop2.default,
onProgress: _noop2.default,
onRemove: _noop2.default,
onRetry: _noop2.default,
onSizeError: _noop2.default,
onSuccess: _noop2.default,
onPastingError: _noop2.default,
promptPosition: 'right',
showClear: true,
showPicInfo: false,
showReplace: false,
showRetry: true,
showUploadList: true,
uploadTrigger: 'auto',
withCredentials: false,
showTooltip: true
};
Upload.FileCard = _fileCard.default;
var _default = exports.default = Upload;