UNPKG

@carbon/react

Version:

React components for the Carbon Design System

206 lines (201 loc) 6.12 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. */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import React, { useRef, useState } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { Enter, Space } from '../../internal/keyboard/keys.js'; import { matches } from '../../internal/keyboard/match.js'; import { useId } from '../../internal/useId.js'; import { usePrefix } from '../../internal/usePrefix.js'; import { composeEventHandlers } from '../../tools/events.js'; import { deprecate } from '../../prop-types/deprecate.js'; import { noopFn } from '../../internal/noopFn.js'; function FileUploaderDropContainer({ accept = [], className, id, disabled, labelText = 'Add file', multiple = false, name, onAddFiles = noopFn, onClick, pattern = '.[0-9a-z]+$', // eslint-disable-next-line react/prop-types innerRef, ...rest }) { const prefix = usePrefix(); const inputRef = useRef(null); const { current: uid } = useRef(id || useId()); const [isActive, setActive] = 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 restrictions */ function validateFiles(transferredFiles) { if (!accept.length) { return 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 (fileExtension === undefined) { return acc; } if (acceptedTypes.has(mimeType) || acceptedTypes.has(fileExtension.toLowerCase())) { return acc.concat([curr]); } curr.invalidFileType = true; return acc.concat([curr]); }, []); } function handleChange(event) { const files = [...(event.target.files ?? [])]; const filesToValidate = multiple ? files : [files[0]]; const addedFiles = validateFiles(filesToValidate); return onAddFiles(event, { addedFiles }); } function handleDrop(event) { const files = [...event.dataTransfer.files]; const filesToValidate = multiple ? files : [files[0]]; const addedFiles = validateFiles(filesToValidate); return onAddFiles(event, { addedFiles }); } 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", _extends({ type: "button", className: dropareaClasses, ref: innerRef, onKeyDown: evt => { if (matches(evt, [Enter, Space])) { evt.preventDefault(); inputRef.current?.click(); } }, onClick: 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, /** * 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(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(PropTypes.number, 'The `tabIndex` prop for `FileUploaderButton` has ' + 'been deprecated since it now renders a button element by default.') }; export { FileUploaderDropContainer as default };