@carbon/react
Version:
React components for the Carbon Design System
154 lines (152 loc) • 5.31 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 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.
*/
import { usePrefix } from "../../internal/usePrefix.js";
import { Enter, Space } from "../../internal/keyboard/keys.js";
import { matches } from "../../internal/keyboard/match.js";
import { useId } from "../../internal/useId.js";
import { noopFn } from "../../internal/noopFn.js";
import { deprecate } from "../../prop-types/deprecate.js";
import { composeEventHandlers } from "../../tools/events.js";
import classNames from "classnames";
import { useRef, useState } from "react";
import PropTypes from "prop-types";
import { jsx, jsxs } from "react/jsx-runtime";
//#region src/components/FileUploader/FileUploaderDropContainer.tsx
/**
* Copyright IBM Corp. 2016, 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.
*/
function FileUploaderDropContainer({ accept = [], className, id, disabled, labelText = "Add file", maxFileSize, multiple = false, name, onAddFiles = noopFn, onClick, pattern = ".[0-9a-z]+$", innerRef, ...rest }) {
const prefix = usePrefix();
const inputRef = useRef(null);
const generatedId = useId();
const { current: uid } = useRef(id || generatedId);
const [isActive, setActive] = useState(false);
const dropareaClasses = classNames(`${prefix}--file__drop-container`, `${prefix}--file-browse-btn`, {
[`${prefix}--file__drop-container--drag-over`]: isActive,
[`${prefix}--file-browse-btn--disabled`]: disabled
}, className);
/**
* Filters the array of added files based on file type and size restrictions
*/
function validateFiles(transferredFiles) {
const acceptedTypes = new Set(accept);
return transferredFiles.reduce((acc, curr) => {
const { name, type: mimeType = "" } = curr;
const fileExtensionRegExp = new RegExp(pattern, "i");
const [fileExtension] = name.match(fileExtensionRegExp) ?? [];
if (maxFileSize && curr.size > maxFileSize) {
curr.invalidFileType = true;
return acc.concat([curr]);
}
if (!accept.length) return acc.concat([curr]);
if (fileExtension === void 0) return acc;
if (acceptedTypes.has(mimeType) || acceptedTypes.has(fileExtension.toLowerCase())) return acc.concat([curr]);
curr.invalidFileType = true;
return acc.concat([curr]);
}, []);
}
const handleFiles = (event, files) => {
if (!files.length) return onAddFiles(event, { addedFiles: [] });
return onAddFiles(event, { addedFiles: validateFiles(multiple ? files : [files[0]]) });
};
const handleChange = (event) => {
return handleFiles(event, [...event.target.files ?? []]);
};
const handleDrop = (event) => {
const items = [...event.dataTransfer.items ?? []];
return handleFiles(event, items.length ? items.reduce((acc, item) => {
if (item.kind !== "file") return acc;
if (item.webkitGetAsEntry()?.isDirectory) return acc;
const file = item.getAsFile();
if (file) acc.push(file);
return acc;
}, []) : [...event.dataTransfer.files]);
};
const handleClick = () => {
if (!disabled) inputRef.current?.click();
};
return /* @__PURE__ */ jsxs("div", {
className: `${prefix}--file`,
onDragOver: (evt) => {
evt.stopPropagation();
evt.preventDefault();
if (disabled) return;
setActive(true);
evt.dataTransfer.dropEffect = "copy";
},
onDragLeave: (evt) => {
evt.stopPropagation();
evt.preventDefault();
if (disabled) return;
setActive(false);
evt.dataTransfer.dropEffect = "move";
},
onDrop: (evt) => {
evt.stopPropagation();
evt.preventDefault();
if (disabled) return;
setActive(false);
handleDrop(evt);
},
children: [
/* @__PURE__ */ jsx("button", {
type: "button",
className: dropareaClasses,
ref: innerRef,
onKeyDown: (evt) => {
if (matches(evt, [Enter, Space])) {
evt.preventDefault();
inputRef.current?.click();
}
},
onClick: composeEventHandlers([onClick, handleClick]),
...rest,
children: labelText
}),
/* @__PURE__ */ jsx("label", {
htmlFor: uid,
className: `${prefix}--visually-hidden`,
children: labelText
}),
/* @__PURE__ */ jsx("input", {
type: "file",
id: uid,
className: `${prefix}--file-input`,
ref: inputRef,
tabIndex: -1,
disabled,
accept: accept.join(","),
name,
multiple,
onChange: handleChange,
onClick: (evt) => {
evt.target.value = "";
}
})
]
});
}
FileUploaderDropContainer.propTypes = {
accept: PropTypes.arrayOf(PropTypes.string),
className: PropTypes.string,
disabled: PropTypes.bool,
id: PropTypes.string,
labelText: PropTypes.string.isRequired,
maxFileSize: PropTypes.number,
multiple: PropTypes.bool,
name: PropTypes.string,
onAddFiles: PropTypes.func,
onClick: PropTypes.func,
pattern: PropTypes.string,
role: deprecate(PropTypes.number, "The `role` prop for `FileUploaderButton` has been deprecated since it now renders a button element by default, and has an implicit role of button."),
tabIndex: deprecate(PropTypes.number, "The `tabIndex` prop for `FileUploaderButton` has been deprecated since it now renders a button element by default.")
};
//#endregion
export { FileUploaderDropContainer as default };