UNPKG

@carbon/react

Version:

React components for the Carbon Design System

237 lines (230 loc) 7.05 kB
/** * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var React = require('react'); var PropTypes = require('prop-types'); var cx = require('classnames'); var keys = require('../../internal/keyboard/keys.js'); var match = require('../../internal/keyboard/match.js'); var useId = require('../../internal/useId.js'); var usePrefix = require('../../internal/usePrefix.js'); var events = require('../../tools/events.js'); var deprecate = require('../../prop-types/deprecate.js'); var noopFn = require('../../internal/noopFn.js'); function FileUploaderDropContainer({ accept = [], className, id, disabled, labelText = 'Add file', maxFileSize, multiple = false, name, onAddFiles = noopFn.noopFn, onClick, pattern = '.[0-9a-z]+$', innerRef, ...rest }) { const prefix = usePrefix.usePrefix(); const inputRef = React.useRef(null); const generatedId = useId.useId(); const { current: uid } = React.useRef(id || generatedId); const [isActive, setActive] = React.useState(false); const dropareaClasses = cx(`${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 === undefined) { 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: [] }); const filesToValidate = multiple ? files : [files[0]]; const addedFiles = validateFiles(filesToValidate); return onAddFiles(event, { addedFiles }); }; const handleChange = event => { const files = [...(event.target.files ?? [])]; return handleFiles(event, files); }; const handleDrop = event => { const items = [...(event.dataTransfer.items ?? [])]; const files = items.length ? // Normalize dropped items to files. Skip directories and non-file items. items.reduce((acc, item) => { if (item.kind !== 'file') { return acc; } const entry = item.webkitGetAsEntry(); if (entry?.isDirectory) { return acc; } const file = item.getAsFile(); if (file) { acc.push(file); } return acc; }, []) : [...event.dataTransfer.files]; return handleFiles(event, files); }; const handleClick = () => { if (!disabled) { inputRef.current?.click(); } }; return /*#__PURE__*/React.createElement("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); } }, /*#__PURE__*/React.createElement("button", _rollupPluginBabelHelpers.extends({ type: "button", className: dropareaClasses, ref: innerRef, onKeyDown: evt => { if (match.matches(evt, [keys.Enter, keys.Space])) { evt.preventDefault(); inputRef.current?.click(); } }, onClick: events.composeEventHandlers([onClick, handleClick]) }, rest), labelText), /*#__PURE__*/React.createElement("label", { htmlFor: uid, className: `${prefix}--visually-hidden` }, labelText), /*#__PURE__*/React.createElement("input", { type: "file", id: uid, className: `${prefix}--file-input`, ref: inputRef, tabIndex: -1, disabled: disabled, accept: accept.join(','), name: name, multiple: multiple, onChange: handleChange, onClick: evt => { evt.target.value = ''; } })); } FileUploaderDropContainer.propTypes = { /** * Specify the types of files that this input should be able to receive */ accept: PropTypes.arrayOf(PropTypes.string), /** * Provide a custom className to be applied to the container node */ className: PropTypes.string, /** * Specify whether file input is disabled */ disabled: PropTypes.bool, /** * Provide a unique id for the underlying `<input>` node */ id: PropTypes.string, /** * Provide the label text to be read by screen readers when interacting with * this control */ labelText: PropTypes.string.isRequired, /** * Maximum file size allowed in bytes. Files larger than this will be marked invalid */ maxFileSize: PropTypes.number, /** * Specify if the component should accept multiple files to upload */ multiple: PropTypes.bool, /** * Provide a name for the underlying `<input>` node */ name: PropTypes.string, /** * Event handler that is called after files are added to the uploader * The event handler signature looks like `onAddFiles(evt, { addedFiles })` */ onAddFiles: PropTypes.func, /** * Provide an optional function to be called when the button element * is clicked */ onClick: PropTypes.func, /** * Provide a custom regex pattern for the acceptedTypes */ pattern: PropTypes.string, /** * Provide an accessibility role for the `<FileUploaderButton>` */ role: deprecate.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.'), /** * Provide a custom tabIndex value for the `<FileUploaderButton>` */ tabIndex: deprecate.deprecate(PropTypes.number, 'The `tabIndex` prop for `FileUploaderButton` has ' + 'been deprecated since it now renders a button element by default.') }; exports.default = FileUploaderDropContainer;