@razorpay/blade
Version:
The Design System that powers Razorpay
518 lines (510 loc) • 22.6 kB
JavaScript
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
import { useRef, useState, useMemo, useCallback, forwardRef } from 'react';
import { StyledFileUploadWrapper } from './StyledFileUploadWrapper.js';
import { fileUploadColorTokens, getFileUploadInputHoverTokens, fileUploadLinkBorderTokens } from './fileUploadTokens.js';
import { FileUploadItem } from './FileUploadItem.js';
import { isFileAccepted } from './isFileAccepted.js';
import '../Box/BaseBox/index.js';
import '../Box/index.js';
import { SelectorLabel } from '../Form/Selector/SelectorLabel.web.js';
import { SelectorInput } from '../Form/Selector/SelectorInput.web.js';
import '../VisuallyHidden/index.web.js';
import '../Form/index.js';
import { useFormId } from '../Form/useFormId.js';
import '../../utils/assignWithoutSideEffects/index.js';
import '../../utils/metaAttribute/index.js';
import '../Box/styledProps/index.js';
import '../../utils/index.js';
import '../Typography/index.js';
import { getHintType } from '../Input/BaseInput/BaseInput.js';
import '../../utils/makeAccessible/index.js';
import { formHintLeftLabelMarginLeft } from '../Input/BaseInput/baseInputTokens.js';
import { useMergeRefs } from '../../utils/useMergeRefs.js';
import { useControllableState } from '../../utils/useControllable.js';
import { getOuterMotionRef, getInnerMotionRef } from '../../utils/getMotionRefs.js';
import '../../utils/makeAnalyticsAttribute/index.js';
import { jsxs, jsx } from 'react/jsx-runtime';
import useTheme from '../BladeProvider/useTheme.js';
import { makeAccessible } from '../../utils/makeAccessible/makeAccessible.web.js';
import { BaseBox } from '../Box/BaseBox/BaseBox.web.js';
import { metaAttribute } from '../../utils/metaAttribute/metaAttribute.web.js';
import { MetaConstants } from '../../utils/metaAttribute/metaConstants.js';
import { getStyledProps } from '../Box/styledProps/getStyledProps.js';
import { FormLabel } from '../Form/FormLabel.js';
import { screenReaderStyles } from '../VisuallyHidden/ScreenReaderStyles.js';
import { Box } from '../Box/Box.js';
import { makeSize } from '../../utils/makeSize/makeSize.js';
import { Text } from '../Typography/Text/Text.js';
import { makeAnalyticsAttribute } from '../../utils/makeAnalyticsAttribute/makeAnalyticsAttribute.js';
import { FormHint } from '../Form/FormHint.js';
import { assignWithoutSideEffects } from '../../utils/assignWithoutSideEffects/assignWithoutSideEffects.js';
var _excluded = ["name", "accept", "uploadType", "onChange", "onPreview", "onRemove", "onReupload", "onDismiss", "onDrop", "isDisabled", "isRequired", "necessityIndicator", "fileList", "testID", "label", "labelPosition", "accessibilityLabel", "validationState", "helpText", "errorText", "maxCount", "maxSize", "size", "_motionMeta"];
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
var _FileUpload = function _FileUpload(_ref, ref) {
var name = _ref.name,
accept = _ref.accept,
_ref$uploadType = _ref.uploadType,
uploadType = _ref$uploadType === void 0 ? 'single' : _ref$uploadType,
onChange = _ref.onChange,
onPreview = _ref.onPreview,
_onRemove = _ref.onRemove,
_onReupload = _ref.onReupload,
_onDismiss = _ref.onDismiss,
onDrop = _ref.onDrop,
isDisabled = _ref.isDisabled,
isRequired = _ref.isRequired,
necessityIndicator = _ref.necessityIndicator,
fileList = _ref.fileList,
testID = _ref.testID,
label = _ref.label,
_ref$labelPosition = _ref.labelPosition,
labelPosition = _ref$labelPosition === void 0 ? 'top' : _ref$labelPosition,
accessibilityLabel = _ref.accessibilityLabel,
validationState = _ref.validationState,
helpText = _ref.helpText,
errorText = _ref.errorText,
maxCount = _ref.maxCount,
maxSize = _ref.maxSize,
_ref$size = _ref.size,
size = _ref$size === void 0 ? 'medium' : _ref$size,
_motionMeta = _ref._motionMeta,
rest = _objectWithoutProperties(_ref, _excluded);
var inputRef = useRef(null);
var mergedRef = useMergeRefs(ref, inputRef);
var _useTheme = useTheme(),
platform = _useTheme.platform;
var _useControllableState = useControllableState({
value: fileList,
defaultValue: fileList !== null && fileList !== void 0 ? fileList : []
}),
_useControllableState2 = _slicedToArray(_useControllableState, 2),
selectedFiles = _useControllableState2[0],
setSelectedFiles = _useControllableState2[1];
var _useState = useState(null),
_useState2 = _slicedToArray(_useState, 2),
errorMessage = _useState2[0],
setErrorMessage = _useState2[1];
var _useState3 = useState('none'),
_useState4 = _slicedToArray(_useState3, 2),
internalValidationState = _useState4[0],
setInternalValidationState = _useState4[1];
var _useState5 = useState(false),
_useState6 = _slicedToArray(_useState5, 2),
isActive = _useState6[0],
setIsActive = _useState6[1];
var isMultiple = uploadType === 'multiple';
var isOneFileSelectedWithSingleUpload = !isMultiple && selectedFiles.length === 1;
var inputLabelPosition = platform === 'onMobile' ? 'top' : labelPosition;
var isLabelLeftPositioned = inputLabelPosition === 'left';
var willRenderHintText = Boolean(helpText) || Boolean(errorMessage);
var showError = validationState === 'error' || internalValidationState === 'error';
var showHelpText = !showError && helpText;
var accessibilityText = accessibilityLabel !== null && accessibilityLabel !== void 0 ? accessibilityLabel : ",".concat(showError ? errorMessage : '', " ").concat(showHelpText ? helpText : '');
var _useFormId = useFormId('fileuploadinput'),
inputId = _useFormId.inputId,
labelId = _useFormId.labelId,
helpTextId = _useFormId.helpTextId,
errorTextId = _useFormId.errorTextId;
var accessibilityProps = makeAccessible({
required: Boolean(isRequired),
invalid: Boolean(showError),
disabled: Boolean(isDisabled),
describedBy: labelId
});
// In control mode attach a unique id to each file if not provided
useMemo(function () {
var _iterator = _createForOfIteratorHelper(selectedFiles),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var file = _step.value;
if (!file.id) {
file.id = "".concat(new Date().getTime().toString()).concat(Math.floor(Math.random() * 1000000));
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}, [selectedFiles]);
var handleFilesChange = useCallback(function (inputFiles) {
setSelectedFiles(function (prevFiles) {
if (prevFiles.length > 0) {
var allFiles = [].concat(_toConsumableArray(prevFiles), _toConsumableArray(inputFiles));
return allFiles;
}
return inputFiles;
});
}, []);
var validateFiles = function validateFiles(inputFiles, allFiles) {
if (accept && inputFiles.some(function (file) {
return !isFileAccepted(file, accept);
})) {
setErrorMessage("You provided an unsupported file type. Supported file types are: ".concat(accept));
setInternalValidationState('error');
return true;
}
if (uploadType === 'single' && inputFiles.length > 1) {
setErrorMessage('You can upload only one file.');
setInternalValidationState('error');
return true;
}
if (maxCount && allFiles.length > maxCount) {
setErrorMessage("You can't upload more than ".concat(maxCount, " files."));
setInternalValidationState('error');
return true;
}
if (maxSize && inputFiles.some(function (file) {
return file.size > maxSize;
})) {
setErrorMessage('File size exceeded.');
setInternalValidationState('error');
return true;
}
setInternalValidationState('none');
setErrorMessage(null);
return false;
};
var handleDragOver = function handleDragOver(event) {
event.preventDefault();
setIsActive(true);
};
var handleDragLeave = function handleDragLeave(event) {
event.preventDefault();
setIsActive(false);
};
var handleDrop = function handleDrop(event) {
event.preventDefault();
setIsActive(false);
var droppedFiles = Array.from(event.dataTransfer.files);
var allFiles = selectedFiles.length > 0 ? [].concat(_toConsumableArray(selectedFiles), droppedFiles) : droppedFiles;
var hasValidationErrors = validateFiles(droppedFiles, allFiles);
if (!hasValidationErrors) {
handleFilesChange(droppedFiles);
onDrop === null || onDrop === void 0 ? void 0 : onDrop({
name: name,
fileList: allFiles
});
}
};
var handleInputChange = function handleInputChange(event) {
var _event$target$files;
var inputFiles = Array.from((_event$target$files = event.target.files) !== null && _event$target$files !== void 0 ? _event$target$files : []);
var allFiles = selectedFiles.length > 0 ? [].concat(_toConsumableArray(selectedFiles), inputFiles) : inputFiles;
var hasValidationErrors = validateFiles(inputFiles, allFiles);
if (!hasValidationErrors) {
handleFilesChange(inputFiles);
onChange === null || onChange === void 0 ? void 0 : onChange({
name: name,
fileList: allFiles
});
}
// Reset the input value to allow re-selecting the same file
event.target.value = '';
};
return /*#__PURE__*/jsxs(BaseBox, _objectSpread(_objectSpread(_objectSpread({
ref: getOuterMotionRef({
_motionMeta: _motionMeta,
ref: ref
}),
display: "flex",
flexDirection: "column",
width: "100%"
}, metaAttribute({
name: MetaConstants.FileUpload,
testID: testID
})), getStyledProps(rest)), {}, {
children: [/*#__PURE__*/jsxs(BaseBox, {
display: "flex",
flexDirection: isLabelLeftPositioned ? 'row' : 'column',
alignItems: isLabelLeftPositioned ? 'center' : undefined,
position: "relative",
width: "100%",
children: [label ? /*#__PURE__*/jsx(FormLabel, {
size: size,
as: "span",
necessityIndicator: necessityIndicator,
position: labelPosition,
id: labelId,
accessibilityText: accessibilityText,
children: label
}) : null, /*#__PURE__*/jsx(SelectorLabel, {
componentName: MetaConstants.FileUploadLabel,
inputProps: {},
style: {
cursor: isDisabled ? 'not-allowed' : 'pointer',
width: '100%'
},
children: /*#__PURE__*/jsx(BaseBox, {
display: "flex",
flexDirection: "column",
width: "100%",
children: /*#__PURE__*/jsx(StyledFileUploadWrapper, {
size: size,
isDisabled: isDisabled,
isActive: isActive,
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
borderRadius: "medium",
borderWidth: "thin",
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onDrop: handleDrop,
onClick: function onClick() {
return setIsActive(true);
},
"data-comp": "f",
style: _objectSpread({}, isOneFileSelectedWithSingleUpload ? screenReaderStyles : {}),
children: /*#__PURE__*/jsxs(Box, {
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: {
base: 'column',
s: 'row'
},
gap: makeSize(6),
padding: "spacing.3",
children: [/*#__PURE__*/jsxs(Text, {
color: isDisabled ? fileUploadColorTokens.text.disabled : fileUploadColorTokens.text["default"],
children: ["Drag files here or", ' ']
}), /*#__PURE__*/jsx(SelectorInput, _objectSpread({
id: inputId,
hoverTokens: getFileUploadInputHoverTokens(),
isChecked: false,
isDisabled: isDisabled,
inputProps: _objectSpread({
name: name,
type: 'file',
onChange: handleInputChange,
multiple: isMultiple,
required: isRequired,
disabled: isDisabled,
accept: accept,
onBlur: function onBlur() {
return setIsActive(false);
}
}, accessibilityProps),
ref: getInnerMotionRef({
_motionMeta: _motionMeta,
ref: mergedRef
})
}, makeAnalyticsAttribute(rest))), /*#__PURE__*/jsx(Box, {
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: {
base: 'column',
s: 'row'
},
borderRadius: "small",
children: /*#__PURE__*/jsx(Box, {
display: "flex",
flexDirection: "row",
alignItems: "center",
borderBottomColor: fileUploadLinkBorderTokens.color[isDisabled ? 'disabled' : 'default'],
borderBottomWidth: fileUploadLinkBorderTokens.width["default"],
children: /*#__PURE__*/jsx(Text, {
color: isDisabled ? fileUploadColorTokens.link.disabled : fileUploadColorTokens.link["default"],
children: "Upload"
})
})
})]
})
})
})
}), isOneFileSelectedWithSingleUpload && /*#__PURE__*/jsx(FileUploadItem, {
file: selectedFiles[0],
size: size,
onRemove: function onRemove() {
var newFiles = selectedFiles.filter(function (_ref2) {
var id = _ref2.id;
return id !== selectedFiles[0].id;
});
setSelectedFiles(function () {
return newFiles;
});
_onRemove === null || _onRemove === void 0 ? void 0 : _onRemove({
file: selectedFiles[0]
});
},
onReupload: function onReupload() {
var _inputRef$current;
var newFiles = selectedFiles.filter(function (_ref3) {
var id = _ref3.id;
return id !== selectedFiles[0].id;
});
setSelectedFiles(function () {
return newFiles;
});
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.click();
// TODO - Remove this in the next major release
// Fallback to onRemove if onReupload isn't provided to avoid breaking changes in the API
if (_onReupload) {
_onReupload({
file: selectedFiles[0]
});
} else {
_onRemove === null || _onRemove === void 0 ? void 0 : _onRemove({
file: selectedFiles[0]
});
}
setIsActive(false);
},
onDismiss: function onDismiss() {
var newFiles = selectedFiles.filter(function (_ref4) {
var id = _ref4.id;
return id !== selectedFiles[0].id;
});
setSelectedFiles(function () {
return newFiles;
});
_onDismiss === null || _onDismiss === void 0 ? void 0 : _onDismiss({
file: selectedFiles[0]
});
},
onPreview: onPreview
})]
}), willRenderHintText && /*#__PURE__*/jsx(BaseBox, {
marginLeft: makeSize(label && isLabelLeftPositioned ? formHintLeftLabelMarginLeft[size] : 0),
children: /*#__PURE__*/jsx(BaseBox, {
display: "flex",
flexDirection: "row",
justifyContent: "'space-between",
children: /*#__PURE__*/jsx(FormHint, {
size: size,
type: getHintType({
validationState: showError ? 'error' : validationState,
hasHelpText: Boolean(helpText)
}),
helpText: helpText,
errorText: errorMessage !== null && errorMessage !== void 0 ? errorMessage : errorText,
helpTextId: helpTextId,
errorTextId: errorTextId
})
})
}), !isOneFileSelectedWithSingleUpload && selectedFiles.map(function (file, index) {
return /*#__PURE__*/jsx(BaseBox, {
marginLeft: makeSize(label && isLabelLeftPositioned ? formHintLeftLabelMarginLeft[size] : 0),
marginTop: index === 0 ? 'spacing.5' : 'spacing.3',
children: /*#__PURE__*/jsx(FileUploadItem, {
file: file,
size: size,
onRemove: function onRemove() {
var newFiles = selectedFiles.filter(function (_ref5) {
var id = _ref5.id;
return id !== file.id;
});
setSelectedFiles(function () {
return newFiles;
});
_onRemove === null || _onRemove === void 0 ? void 0 : _onRemove({
file: file
});
},
onReupload: function onReupload() {
var _inputRef$current2;
var newFiles = selectedFiles.filter(function (_ref6) {
var id = _ref6.id;
return id !== file.id;
});
setSelectedFiles(function () {
return newFiles;
});
(_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 ? void 0 : _inputRef$current2.click();
// TODO - Remove this in the next major release
// Fallback to onRemove if onReupload isn't provided to avoid breaking changes in the API
if (_onReupload) {
_onReupload({
file: file
});
} else {
_onRemove === null || _onRemove === void 0 ? void 0 : _onRemove({
file: file
});
}
setIsActive(false);
},
onDismiss: function onDismiss() {
var newFiles = selectedFiles.filter(function (_ref7) {
var id = _ref7.id;
return id !== file.id;
});
setSelectedFiles(function () {
return newFiles;
});
_onDismiss === null || _onDismiss === void 0 ? void 0 : _onDismiss({
file: file
});
},
onPreview: onPreview
})
}, file.id);
})]
}));
};
/**
* ### FileUpload Component
*
* The FileUpload component is used to handle file attachments, including the drag-and-drop interaction.
* Primarily, it is used to upload files to a server or to display a list of uploaded files.
*
* ---
*
* #### Usage
*
* ```jsx
const GSTForm = () => {
const [selectedFile, setSelectedFile] = useState<BladeFile>();
const [isLoading, setIsLoading] = useState(false);
return (
<Box>
<Heading marginBottom="spacing.4">Add GST Details</Heading>
<form encType="multipart/form-data" onSubmit={handleSubmit}>
<FileUpload
uploadType="single"
label="Upload GST"
helpText="Upload .jpg, .jpeg, or .png file only"
accept=".jpg, .jpeg, .png"
onChange={({ fileList }) => {
setSelectedFile(fileList[0]);
}}
onDrop={({ fileList }) => {
setSelectedFile(fileList[0]);
}}
isRequired
necessityIndicator="required"
/>
<Button type="submit" variant="primary">
Submit
</Button>
{isLoading && (
<ProgressBar isIndeterminate label="Uploading your GST Certificate..." />
)}
</form>
</Box>
);
}
* ```
*
* ---
*
* Checkout {@link https://blade.razorpay.com/?path=/docs/components-fileupload FileUpload Documentation}
*
*/
var FileUpload = /*#__PURE__*/assignWithoutSideEffects( /*#__PURE__*/forwardRef(_FileUpload), {
displayName: 'FileUpload',
componentId: 'FileUpload'
});
export { FileUpload };
//# sourceMappingURL=FileUpload.web.js.map