UNPKG

@gravityforms/components

Version:

UI components for use in Gravity Forms development. Both React and vanilla js flavors.

377 lines (342 loc) 10.6 kB
import { React, PropTypes, FileDrop, classnames } from '@gravityforms/libraries'; import { useStateWithDep } from '@gravityforms/react-utils'; import { trigger } from '@gravityforms/utils'; import Icon from '../Icon'; import Text from '../Text'; import Button from '../Button'; const { forwardRef, useState, useEffect, useRef } = React; /** * @module FileUpload * @description A file upload component. * * @since 1.1.15 * * @param {object} props Component props. * @param {Array} props.allowedFileTypes The allowed file types. * @param {object} props.customAttributes Custom attributes for the component. * @param {string|Array|object} props.customClasses Custom classes for the component. * @param {boolean} props.disabled Whether this component should be disabled. * @param {string} props.fileURL The url for an already uploaded file. * @param {object} props.i18n Translated strings for the UI. * @param {string} props.id ID of the file input. * @param {string|number} props.maxHeight The maximum height for the image. * @param {string|number} props.maxWidth The maximum width for the image. * @param {string} props.name The name attribute for the file input. * @param {object} props.previewAttributes Attributes for the preview. * @param {Array} props.previewClasses Classes for the preview. * @param {string} props.theme The theme for the component. * @param {string} props.uploadIcon The icon to show for the upload button. * @param {string} props.uploadIconPrefix The prefix to use for the upload button icon. * @param {object} props.wrapperAttributes Custom attributes for the wrapper element. * @param {string|Array|object} props.wrapperClasses Custom classes for the wrapper element. * @param {object|null} ref Ref to the component. * * @return {JSX.Element} The file upload component. * * @example * import FileUpload from '@gravityforms/components/react/admin/elements/FileUpload'; * * return <FileUpload name="file-upload" />; * */ const FileUpload = forwardRef( ( { allowedFileTypes = [], customAttributes = {}, customClasses = [], disabled = false, externalManager = false, fileURL = '', fileId = 0, i18n = {}, id = '', maxHeight = '', maxWidth = '', name = '', previewAttributes = {}, previewClasses = [], theme = 'cosmos', uploadIcon = 'upload-file', uploadIconPrefix = 'gravity-component-icon', wrapperAttributes = {}, wrapperClasses = [], }, ref ) => { const [ selectedFile, setSelectedFile ] = useState( '' ); const [ selectedExternalManagerFile, setSelectedExternalManagerFile ] = useStateWithDep( fileURL ); const [ selectedExternalManagerId, setSelectedExternalManagerId ] = useStateWithDep( fileId ); const [ preview, setPreview ] = useStateWithDep( fileURL ); const fileInputRef = useRef( null ); useEffect( () => { if ( ! externalManager ) { return; } document.addEventListener( 'gform/file_upload/external_manager/file_selected', handleExternalManager ); // Clean up the event listener when component unmounts return () => { document.removeEventListener( 'gform/file_upload/external_manager/file_selected', handleExternalManager ); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [] ); useEffect( () => { if ( ! selectedFile ) { fileInputRef.current.value = null; return; } const objectUrl = URL.createObjectURL( selectedFile ); // eslint-disable-next-line react-hooks/exhaustive-deps setPreview( objectUrl ); // This frees up memory once we're done with the URL. return () => URL.revokeObjectURL( objectUrl ); }, [ selectedFile ] ); const handleExternalManager = ( event ) => { // exit if the id of the event doesn't match this instance id if ( event.detail?.fileUploadId !== id ) { return; } setSelectedExternalManagerFile( event.detail.url ); setSelectedExternalManagerId( event.detail.id ); setPreview( event.detail.url ); }; /** * @function onSelectFile * @description Handler for selecting a file. * * @since 1.1.15 * * @param {Array} files Array of files. * * @return {Function} Function to clear timeout. */ const onSelectFile = ( files ) => { if ( externalManager ) { trigger( { event: 'gform/file_upload/external_manager/save', data: { id, event, file: files[ 0 ] }, native: false } ); } const delayed = setTimeout( () => { if ( ! files || files.length === 0 ) { setSelectedFile( '' ); return; } setSelectedFile( files[ 0 ] ); fileInputRef.current.files = files; }, 0 ); return () => clearTimeout( delayed ); }; /** * @function onFileInputChange * @description Handler for change event on file input. * * @since 1.1.15 * * @param {object} event Event object. * * @return {void} */ const onFileInputChange = ( event ) => { const { files } = event.target; onSelectFile( files ); }; /** * @function onFileInputKeyDown * @description Handler for keydown event on file input. * * @since 2.0.1 * * @param {object} event Event object. * * @return {void} */ const onFileInputKeyDown = ( event ) => { if ( ( event.key === ' ' || event.key === 'Enter' ) && externalManager ) { event.preventDefault(); trigger( { event: 'gform/file_upload/external_manager/open', data: { id, event }, native: false } ); } }; /** * @function onTargetClick * @description Handler for click event on file drop target. * * @since 1.1.15 * * @param {object} event Event object. * * @return {void} */ const onTargetClick = ( event ) => { event.preventDefault(); if ( disabled ) { return; } if ( externalManager ) { trigger( { event: 'gform/file_upload/external_manager/open', data: { id, event }, native: false } ); } else { fileInputRef.current.click(); } }; /** * @function handleRemove * @description Handler for removing file from file upload field. * * @since 1.1.15 * * @return {void} */ const handleRemove = () => { trigger( { event: 'gform/file_upload/external_manager/file_remove', data: { id, event }, native: false } ); setPreview( '' ); setSelectedFile( '' ); setSelectedExternalManagerFile( '' ); setSelectedExternalManagerId( 0 ); }; /** * @function renderFileOptions * @description Renders allowed file type options. * * @since 1.1.15 * * @return {string} String list of allowed file types. */ const renderFileOptions = () => { const fileOptions = allowedFileTypes; return fileOptions.join( ', ' ); }; const removeButtonProps = { onClick: handleRemove, className: 'gform-file-upload__remove', label: i18n.delete || '', type: 'secondary', }; const replaceButtonProps = { onClick: onTargetClick, className: 'gform-file-upload__replace', label: i18n.replace || '', }; const wrapperProps = { ...wrapperAttributes, className: classnames( { 'gform-file-upload__wrapper': true, [ `gform-file-upload__wrapper--theme-${ theme }` ]: true, 'gform-file-upload__wrapper--has-preview': preview, 'gform-file-upload__wrapper--disabled': disabled, }, wrapperClasses ), ref, }; const uploaderProps = { ...customAttributes, className: classnames( { 'gform-file-upload': true, [ `gform-file-upload--theme-${ theme }` ]: true, }, customClasses ), }; const previewProps = { ...previewAttributes, className: classnames( { 'gform-file-upload__preview': true, [ `gform-file-upload__preview--theme-${ theme }` ]: true, }, previewClasses ), }; const previewImgProps = { src: preview, alt: 'Image Preview', }; const fileDropProps = { onTargetClick, onDrop: ( files ) => { if ( disabled ) { return; } onSelectFile( files ); }, }; const fileInputProps = { onChange: onFileInputChange, onKeyDown: onFileInputKeyDown, ref: fileInputRef, type: 'file', id, name, }; const buttonsWrapperProps = { className: classnames( { 'gform-file-upload__buttons-wrapper': true, } ), }; const fileUrlInputProps = { name: `${ name }[file_url]`, type: 'hidden', value: externalManager ? selectedExternalManagerFile : fileURL, }; const fileIdInputProps = { name: `${ name }[attachment_id]`, type: 'hidden', value: externalManager ? selectedExternalManagerId : fileId, }; return ( <div { ...wrapperProps }> { preview && <div { ...previewProps }> { /* eslint-disable-next-line jsx-a11y/alt-text */ } <img { ...previewImgProps } /> </div> } <div { ...uploaderProps }> <FileDrop { ...fileDropProps }> <Icon customClasses={ [ 'gform-file-upload__icon' ] } icon={ uploadIcon } iconPrefix={ uploadIconPrefix } spacing={ 2 } /> <Text customClasses={ [ 'gform-file-upload__message' ] }> <span className="gform-file-upload__bold-text">{ i18n.click_to_upload }</span> { i18n.drag_n_drop } </Text> <Text customClasses={ [ 'gform-file-upload__filetypes' ] }> { renderFileOptions() } ({ i18n.max } { maxWidth }x{ maxHeight }px) </Text> </FileDrop> <input className="gform-file-upload__input" { ...fileInputProps } /> { fileUrlInputProps.value !== '' && <input { ...fileUrlInputProps } /> } { Number( fileIdInputProps.value ) !== 0 && <input { ...fileIdInputProps } /> } </div> { ( selectedFile || preview ) && <div { ...buttonsWrapperProps }> <Button { ...replaceButtonProps } /> <Button { ...removeButtonProps } /> </div> } </div> ); } ); FileUpload.propTypes = { allowedFileTypes: PropTypes.array, customAttributes: PropTypes.object, customClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), disabled: PropTypes.bool, fileURL: PropTypes.string, i18n: PropTypes.object, id: PropTypes.string, maxHeight: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, ] ), maxWidth: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, ] ), name: PropTypes.string, previewAttributes: PropTypes.object, previewClasses: PropTypes.array, theme: PropTypes.string, uploadIcon: PropTypes.string, uploadIconPrefix: PropTypes.string, wrapperAttributes: PropTypes.object, wrapperClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), }; FileUpload.displayName = 'FileUpload'; export default FileUpload;