UNPKG

@drivy/cobalt

Version:

Opinionated design system for Drivy's projects.

171 lines (168 loc) 8.72 kB
import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; import cx from 'classnames'; import { useRef, useState, useEffect } from 'react'; import useBreakpoint from '../../hooks/useBreakpoint.js'; import '../Icon/index.js'; import Modal from '../Modal/index.js'; import Popover from '../Popover/index.js'; import { validateFile } from '../utils/validateFile.js'; import BinIcon from '../Icon/__generated__/BinIcon.js'; import ContextualWarningCircleFilledIcon from '../Icon/__generated__/ContextualWarningCircleFilledIcon.js'; import LoadingIcon from '../Icon/__generated__/LoadingIcon.js'; import PlusIcon from '../Icon/__generated__/PlusIcon.js'; const ACCEPTED_MAX_SIZE_MB = 10; const ACCEPTED_PHOTOS_TYPES = ["jpg", "jpeg", "png", "gif"]; const ERROR_DISPLAY_TIME = 6000; const ACCEPTED_TYPES_LOCALE_STRING = ACCEPTED_PHOTOS_TYPES.join(", "); const preventEventDefaults = (e) => { var _a, _b; (_a = e.preventDefault) === null || _a === void 0 ? void 0 : _a.call(e); (_b = e.stopPropagation) === null || _b === void 0 ? void 0 : _b.call(e); }; const isEnterOrSpaceKey = (event) => event.key === "Enter" || event.key === " "; const PhotoDropzone = ({ className, description, deleteContent, errorContent, onDropped, onPhotoDelete, deleteContentMode = "popover", initialImageUrl = "", }) => { const { isMobile } = useBreakpoint(); const fileInputRef = useRef(null); const deleteButtonRef = useRef(null); const [isDragging, setIsDragging] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isErrored, setIsErrored] = useState(false); const [imagePreviewUrl, setImagePreviewUrl] = useState(initialImageUrl); const [displayDeletion, setDisplayDeletion] = useState(false); const [showDeletePopover, setShowDeletePopover] = useState(false); const [isImageLoaded, setIsImageLoaded] = useState(false); const openDeletePopover = (event) => { event && preventEventDefaults(event); setShowDeletePopover(true); }; const closeDeletePopover = (event) => { event && preventEventDefaults(event); setShowDeletePopover(false); }; const onDropzoneMouseEnter = (event) => { event && preventEventDefaults(event); if (imagePreviewUrl) setDisplayDeletion(true); }; const onDropzoneMouseLeave = (event) => { event && preventEventDefaults(event); if (displayDeletion && !showDeletePopover) setDisplayDeletion(false); }; const onDropzoneClick = (event) => { var _a; event && preventEventDefaults(event); if (isMobile && showDeletePopover) return; if (isLoading) return; if (isErrored) setIsErrored(false); if (imagePreviewUrl) { if (showDeletePopover) closeDeletePopover(); return setDisplayDeletion(!displayDeletion); } (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }; const onDropzoneKey = (event) => { if (showDeletePopover) return; if (isEnterOrSpaceKey(event)) { onDropzoneClick(); } }; const onDeleteKey = (event) => { if (isEnterOrSpaceKey(event)) { openDeletePopover(event); } }; const onDelete = () => { if (fileInputRef.current) fileInputRef.current.value = ""; closeDeletePopover(); setImagePreviewUrl(""); onPhotoDelete === null || onPhotoDelete === void 0 ? void 0 : onPhotoDelete(); }; const onDragEnter = (event) => { preventEventDefaults(event); if (isErrored) setIsErrored(false); !imagePreviewUrl && setIsDragging(true); }; const onDragLeave = (event) => { preventEventDefaults(event); setIsDragging(false); }; const processFile = async (fileToProcess) => { setIsDragging(false); setIsErrored(false); setIsLoading(true); const isValidFile = await validateFile(fileToProcess, (fileToValidate, extension) => { const maxFileSize = ACCEPTED_MAX_SIZE_MB * 1000 * 1000; const acceptedFileTypes = ACCEPTED_PHOTOS_TYPES; if (fileToValidate.size > maxFileSize) { return false; } else if (!extension || !acceptedFileTypes.includes(extension)) { return false; } else { return true; } }); if (isValidFile) { const imageSrc = URL.createObjectURL(fileToProcess); setImagePreviewUrl(imageSrc); await onDropped(fileToProcess); setIsLoading(false); } else { setIsLoading(false); setIsErrored(true); setTimeout(() => { setIsErrored(false); }, ERROR_DISPLAY_TIME); } }; const onFileInputChanged = (event) => { var _a; if ((_a = event.target.files) === null || _a === void 0 ? void 0 : _a.length) { processFile(event.target.files[0]); } }; const onDrop = (event) => { var _a, _b; preventEventDefaults(event); if (((_b = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b.length) && !imagePreviewUrl) { processFile(event.dataTransfer.files[0]); } }; useEffect(() => { if (imagePreviewUrl) setIsImageLoaded(false); }, [imagePreviewUrl]); const onDeleteButtonClick = showDeletePopover ? closeDeletePopover : openDeletePopover; return (jsxs("label", { className: cx("cobalt-photo-dropzone", className, { "cobalt-photo-dropzone--filled": imagePreviewUrl, "cobalt-photo-dropzone--dragging": isDragging, "cobalt-photo-dropzone--loading": isLoading, "cobalt-photo-dropzone--errored": isErrored, "cobalt-photo-dropzone--imageVisible": isImageLoaded, }), onMouseEnter: onDropzoneMouseEnter, onMouseLeave: onDropzoneMouseLeave, onDragEnter: onDragEnter, onDragLeave: onDragLeave, onClick: onDropzoneClick, onKeyUp: onDropzoneKey, onDrop: onDrop, // Need to reset those listeners to avoid default browser behaviour onDragStart: preventEventDefaults, onDragEnd: preventEventDefaults, onDragOver: preventEventDefaults, children: [isErrored && (jsxs("div", { className: "cobalt-photo-dropzone__description", children: [jsx(ContextualWarningCircleFilledIcon, { color: "error" }), errorContent(ACCEPTED_TYPES_LOCALE_STRING, ACCEPTED_MAX_SIZE_MB)] })), isLoading && (jsx("div", { className: "cobalt-photo-dropzone__description", children: jsx(LoadingIcon, {}) })), !isLoading && !isErrored && (imagePreviewUrl ? (jsxs(Fragment, { children: [jsx("img", { className: "cobalt-photo-dropzone__preview", src: imagePreviewUrl, alt: "Uploaded preview", onLoad: () => { setIsImageLoaded(true); } }), jsxs("div", { children: [jsx("button", { type: "button", className: cx("cobalt-photo-dropzone__delete-button", { "cobalt-photo-dropzone__delete-button--triggered": displayDeletion, }), ref: deleteButtonRef, onClick: onDeleteButtonClick, onKeyUp: onDeleteKey, children: jsx(BinIcon, { color: "onSurface" }) }), deleteContentMode === "modal" && (jsx(Modal, { isOpen: showDeletePopover, "aria-label": "delete", bodySpacing: false, children: deleteContent(onDelete, closeDeletePopover) })), deleteContentMode === "popover" && (jsx(Popover, { targetRef: deleteButtonRef, isOpen: showDeletePopover, onOpenChange: setShowDeletePopover, placement: "left-start", offset: [0, 12], bodySpacing: false, arrow: true, children: deleteContent(onDelete, closeDeletePopover) }))] })] })) : (jsxs("div", { className: "cobalt-photo-dropzone__description cobalt-photo-dropzone__description--strong", children: [description && jsx("div", { children: description }), jsx(PlusIcon, {})] }))), jsx("input", { ref: fileInputRef, className: "cobalt-photo-dropzone__hidden-input", type: "file", onChange: onFileInputChanged, // Prevent the bubbled click from re-entering the label's onClick, // which would cancel the file dialog via preventDefault. onClick: (event) => event.stopPropagation(), accept: ACCEPTED_PHOTOS_TYPES.map((ext) => `.${ext}`).join(","), multiple: false })] })); }; export { PhotoDropzone as default }; //# sourceMappingURL=index.js.map