UNPKG

@carbon/ibm-products

Version:
300 lines (298 loc) 11.2 kB
/** * Copyright IBM Corp. 2020, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const require_runtime = require("../../_virtual/_rolldown/runtime.js"); const require_index = require("../../node_modules/classnames/index.js"); const require_settings = require("../../settings.js"); const require_devtools = require("../../global/js/utils/devtools.js"); const require_usePortalTarget = require("../../global/js/hooks/usePortalTarget.js"); const require_uuidv4 = require("../../global/js/utils/uuidv4.js"); let react = require("react"); react = require_runtime.__toESM(react); let prop_types = require("prop-types"); prop_types = require_runtime.__toESM(prop_types); let _carbon_react = require("@carbon/react"); let _carbon_react_icons = require("@carbon/react/icons"); //#region src/components/ImportModal/ImportModal.tsx /** * Copyright IBM Corp. 2021, 2024 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ var import_classnames = /* @__PURE__ */ require_runtime.__toESM(require_index.default); const componentName = "ImportModal"; const ImportModal = (0, react.forwardRef)(({ accept = [], className, defaultErrorBody, defaultErrorHeader, description, fetchErrorBody, fetchErrorHeader, fileDropHeader, fileDropLabel, fileUploadLabel, hideInputLabel, inputButtonIcon, inputButtonText, inputId, inputLabel, inputPlaceholder, invalidFileTypeErrorBody, invalidFileTypeErrorHeader, invalidIconDescription, maxFileSize, maxFileSizeErrorBody, maxFileSizeErrorHeader, onClose, onRequestSubmit, open, portalTarget: portalTargetIn, primaryButtonText, secondaryButtonText, title, ...rest }, ref) => { const carbonPrefix = (0, _carbon_react.usePrefix)(); const [files, setFiles] = (0, react.useState)([]); const [importUrl, setImportUrl] = (0, react.useState)(""); const renderPortalUse = require_usePortalTarget.usePortalTarget(portalTargetIn); const isInvalidFileType = (file) => { const acceptSet = new Set(accept); const name = file.name; const mimeType = file.type; const extension = `.${name.split(".").pop()}`; if (acceptSet.has(mimeType) || acceptSet.has(extension) || accept.length === 0) return false; return true; }; const updateFiles = (newFiles) => { setFiles([...newFiles.map((file) => { const newFile = { uuid: file.uuid || require_uuidv4.default(), status: "edit", iconDescription: invalidIconDescription, name: file.name, fileSize: file.size, invalidFileType: file.invalidFileType, fileData: file, fetchError: file.fetchError }; if (newFile.fetchError) { newFile.errorBody = fetchErrorBody || defaultErrorBody; newFile.errorSubject = fetchErrorHeader || defaultErrorHeader; newFile.invalid = true; } else if (newFile.invalidFileType) { newFile.errorBody = invalidFileTypeErrorBody || defaultErrorBody; newFile.errorSubject = invalidFileTypeErrorHeader || defaultErrorHeader; newFile.invalid = true; } else if (maxFileSize && (newFile?.fileSize ?? 0) > maxFileSize) { newFile.errorBody = maxFileSizeErrorBody || defaultErrorBody; newFile.errorSubject = maxFileSizeErrorHeader || defaultErrorHeader; newFile.invalid = true; } return newFile; })]); }; const fetchFile = async (evt) => { evt.preventDefault(); const fileName = importUrl.substring(importUrl.lastIndexOf("/") + 1).split("?")[0]; const pendingFile = { name: fileName, status: "uploading", uuid: require_uuidv4.default() }; setFiles([pendingFile]); try { const response = await fetch(importUrl); if (!response.ok || response.status !== 200) throw new Error(`${response.status}`); const blob = await response.blob(); const fetchedFile = new File([blob], fileName, { type: blob.type }); fetchedFile.invalidFileType = isInvalidFileType(fetchedFile); fetchedFile.uuid = pendingFile.uuid; updateFiles([fetchedFile]); } catch (err) { updateFiles([{ ...pendingFile, fetchError: true }]); } }; const onAddFile = (evt, { addedFiles }) => { evt.stopPropagation(); updateFiles(addedFiles); }; const onRemoveFile = (uuid) => { setFiles(files.filter((f) => f.uuid !== uuid)); }; const onSubmitHandler = () => { onRequestSubmit(files); }; const inputHandler = (evt) => { setImportUrl(evt.target.value); }; const onCloseHandler = () => { setFiles([]); setImportUrl(""); if (onClose) onClose(); }; const numberOfFiles = files.length; const numberOfValidFiles = files.filter((f) => !f.invalid).length; const hasFiles = numberOfFiles > 0; const primaryButtonDisabled = !hasFiles || !(numberOfValidFiles > 0); const importButtonDisabled = !importUrl || hasFiles; const fileStatusString = `${numberOfValidFiles} / ${numberOfFiles} ${fileUploadLabel}`; const blockClass = `${require_settings.pkg.prefix}--import-modal`; return renderPortalUse(/* @__PURE__ */ react.default.createElement(_carbon_react.ComposedModal, { ...rest, open, ref, ...require_devtools.getDevtoolsProps(componentName), "aria-label": title, className: (0, import_classnames.default)(blockClass, className), size: "sm", preventCloseOnClickOutside: true, onClose: onCloseHandler }, /* @__PURE__ */ react.default.createElement(_carbon_react.ModalHeader, { className: `${blockClass}__header`, title }), /* @__PURE__ */ react.default.createElement(_carbon_react.ModalBody, { className: `${blockClass}__body-container` }, description && /* @__PURE__ */ react.default.createElement("p", { className: `${blockClass}__body` }, description), fileDropHeader && /* @__PURE__ */ react.default.createElement("p", { className: `${blockClass}__file-drop-header` }, fileDropHeader), /* @__PURE__ */ react.default.createElement(_carbon_react.FileUploaderDropContainer, { accept, labelText: fileDropLabel, onAddFiles: onAddFile, disabled: hasFiles, "data-modal-primary-focus": true }), /* @__PURE__ */ react.default.createElement("div", { className: `${blockClass}__input-group` }, /* @__PURE__ */ react.default.createElement(_carbon_react.TextInput, { labelText: /* @__PURE__ */ react.default.createElement("p", { className: `${blockClass}__label` }, inputLabel), id: inputId || "", onChange: inputHandler, placeholder: inputPlaceholder, value: importUrl, disabled: hasFiles, "aria-label": inputLabel, hideLabel: hideInputLabel }), /* @__PURE__ */ react.default.createElement(_carbon_react.Button, { onClick: fetchFile, className: `${blockClass}__import-button`, size: "md", disabled: importButtonDisabled, renderIcon: inputButtonIcon ? (props) => /* @__PURE__ */ react.default.createElement(_carbon_react_icons.Add, { size: 20, ...props }) : void 0 }, inputButtonText)), /* @__PURE__ */ react.default.createElement("div", { className: `${carbonPrefix}--file-container ${blockClass}__file-container` }, hasFiles && /* @__PURE__ */ react.default.createElement("p", { className: `${blockClass}__helper-text` }, fileStatusString), files.map((file) => /* @__PURE__ */ react.default.createElement(_carbon_react.FileUploaderItem, { key: file.uuid, onDelete: () => onRemoveFile(file.uuid), name: file.name, status: file.status, size: "lg", uuid: file.uuid, iconDescription: file.iconDescription, invalid: file.invalid, errorBody: file.errorBody, errorSubject: file.errorSubject, filesize: file.fileSize })))), /* @__PURE__ */ react.default.createElement(_carbon_react.ModalFooter, { className: `${blockClass}__footer` }, /* @__PURE__ */ react.default.createElement(_carbon_react.Button, { type: "button", kind: "secondary", onClick: onCloseHandler }, secondaryButtonText), /* @__PURE__ */ react.default.createElement(_carbon_react.Button, { type: "submit", kind: "primary", onClick: onSubmitHandler, disabled: primaryButtonDisabled }, primaryButtonText)))); }); ImportModal.propTypes = { /** * Specifies the file types that are valid for importing */ accept: prop_types.default.array, /** * Optional class name */ className: prop_types.default.string, /** * The default message shown for an import error */ defaultErrorBody: prop_types.default.string.isRequired, /** * The default header that is displayed to show an error message */ defaultErrorHeader: prop_types.default.string.isRequired, /** * Content that is displayed inside the modal */ description: prop_types.default.string, /** * Optional error body to display specifically for a fetch error */ fetchErrorBody: prop_types.default.string, /** * Optional error header to display specifically for a fetch error */ fetchErrorHeader: prop_types.default.string, /** * Header for the drag and drop box */ fileDropHeader: prop_types.default.string, /** * Label for the drag and drop box */ fileDropLabel: prop_types.default.string, /** * Label that appears when a file is uploaded to show number of files (1 / 1) */ fileUploadLabel: prop_types.default.string, /** * Hide input label */ hideInputLabel: prop_types.default.bool, /** * Button icon for import by url button */ inputButtonIcon: prop_types.default.bool, /** * Button text for import by url button */ inputButtonText: prop_types.default.string.isRequired, /** * ID for text input */ inputId: prop_types.default.string, /** * Header to display above import by url */ inputLabel: prop_types.default.string.isRequired, /** * Placeholder for text input */ inputPlaceholder: prop_types.default.string, /** * Optional error message to display specifically for a invalid file type error */ invalidFileTypeErrorBody: prop_types.default.string, /** * Optional error header to display specifically for a invalid file type error */ invalidFileTypeErrorHeader: prop_types.default.string, /** * Description for delete file icon */ invalidIconDescription: prop_types.default.string, /** * File size limit in bytes */ maxFileSize: prop_types.default.number, /** * Optional error message to display specifically for a max file size error */ maxFileSizeErrorBody: prop_types.default.string, /** * Optional error header to display specifically for a max file size error */ maxFileSizeErrorHeader: prop_types.default.string, /** * Specify a handler for closing modal */ onClose: prop_types.default.func, /** * Specify a handler for "submitting" modal. Access the imported file via `file => {}` */ onRequestSubmit: prop_types.default.func.isRequired, /** * Specify whether the Modal is currently open */ open: prop_types.default.bool.isRequired, /** * The DOM node the tearsheet should be rendered within. Defaults to document.body. */ portalTarget: prop_types.default.node, /** * Specify the text for the primary button */ primaryButtonText: prop_types.default.string.isRequired, /** * Specify the text for the secondary button */ secondaryButtonText: prop_types.default.string.isRequired, /** * The text displayed at the top of the modal */ title: prop_types.default.string.isRequired }; ImportModal.displayName = componentName; //#endregion exports.ImportModal = ImportModal;