@drivy/cobalt
Version:
Opinionated design system for Drivy's projects.
222 lines (221 loc) • 9.51 kB
JavaScript
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import classnames from "classnames";
import { useEffect, useRef, useState } from "react";
import useBreakpoint from "../../hooks/useBreakpoint.js";
import { BinIcon, ContextualWarningCircleFilledIcon, LoadingIcon, PlusIcon } from "../Icon/index.js";
import Modal from "../Modal/index.js";
import Popover from "../Popover/index.js";
import { validateFile } from "../utils/validateFile.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)=>{
e.preventDefault?.();
e.stopPropagation?.();
};
const isEnterOrSpaceKey = (event)=>"Enter" === event.key || " " === 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)=>{
event && preventEventDefaults(event);
if (isMobile && showDeletePopover) return;
if (isLoading) return;
if (isErrored) setIsErrored(false);
if (imagePreviewUrl) {
if (showDeletePopover) closeDeletePopover();
return setDisplayDeletion(!displayDeletion);
}
fileInputRef.current?.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?.();
};
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 = 1000 * ACCEPTED_MAX_SIZE_MB * 1000;
const acceptedFileTypes = ACCEPTED_PHOTOS_TYPES;
if (fileToValidate.size > maxFileSize) return false;
if (!extension || !acceptedFileTypes.includes(extension)) return false;
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)=>{
if (event.target.files?.length) processFile(event.target.files[0]);
};
const onDrop = (event)=>{
preventEventDefaults(event);
if (event.dataTransfer?.files?.length && !imagePreviewUrl) processFile(event.dataTransfer.files[0]);
};
useEffect(()=>{
if (imagePreviewUrl) setIsImageLoaded(false);
}, [
imagePreviewUrl
]);
const onDeleteButtonClick = showDeletePopover ? closeDeletePopover : openDeletePopover;
return /*#__PURE__*/ jsxs("label", {
className: classnames("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,
onDragStart: preventEventDefaults,
onDragEnd: preventEventDefaults,
onDragOver: preventEventDefaults,
children: [
isErrored && /*#__PURE__*/ jsxs("div", {
className: "cobalt-photo-dropzone__description",
children: [
/*#__PURE__*/ jsx(ContextualWarningCircleFilledIcon, {
color: "error"
}),
errorContent(ACCEPTED_TYPES_LOCALE_STRING, ACCEPTED_MAX_SIZE_MB)
]
}),
isLoading && /*#__PURE__*/ jsx("div", {
className: "cobalt-photo-dropzone__description",
children: /*#__PURE__*/ jsx(LoadingIcon, {})
}),
!isLoading && !isErrored && (imagePreviewUrl ? /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx("img", {
className: "cobalt-photo-dropzone__preview",
src: imagePreviewUrl,
alt: "Uploaded preview",
onLoad: ()=>{
setIsImageLoaded(true);
}
}),
/*#__PURE__*/ jsxs("div", {
children: [
/*#__PURE__*/ jsx("button", {
type: "button",
className: classnames("cobalt-photo-dropzone__delete-button", {
"cobalt-photo-dropzone__delete-button--triggered": displayDeletion
}),
ref: deleteButtonRef,
onClick: onDeleteButtonClick,
onKeyUp: onDeleteKey,
children: /*#__PURE__*/ jsx(BinIcon, {
color: "onSurface"
})
}),
"modal" === deleteContentMode && /*#__PURE__*/ jsx(Modal, {
isOpen: showDeletePopover,
"aria-label": "delete",
bodySpacing: false,
children: deleteContent(onDelete, closeDeletePopover)
}),
"popover" === deleteContentMode && /*#__PURE__*/ jsx(Popover, {
targetRef: deleteButtonRef,
isOpen: showDeletePopover,
onOpenChange: setShowDeletePopover,
placement: "left-start",
offset: [
0,
12
],
bodySpacing: false,
arrow: true,
children: deleteContent(onDelete, closeDeletePopover)
})
]
})
]
}) : /*#__PURE__*/ jsxs("div", {
className: "cobalt-photo-dropzone__description cobalt-photo-dropzone__description--strong",
children: [
description && /*#__PURE__*/ jsx("div", {
children: description
}),
/*#__PURE__*/ jsx(PlusIcon, {})
]
})),
/*#__PURE__*/ jsx("input", {
ref: fileInputRef,
className: "cobalt-photo-dropzone__hidden-input",
type: "file",
onChange: onFileInputChanged,
onClick: (event)=>event.stopPropagation(),
accept: ACCEPTED_PHOTOS_TYPES.map((ext)=>`.${ext}`).join(","),
multiple: false
})
]
});
};
const components_PhotoDropzone = PhotoDropzone;
export default components_PhotoDropzone;
//# sourceMappingURL=index.js.map