Skip to content

Instantly share code, notes, and snippets.

@joydeep-bhowmik
Created May 13, 2024 09:39
Show Gist options
  • Save joydeep-bhowmik/0b54a1e268c086663be0aa4347af2197 to your computer and use it in GitHub Desktop.
Save joydeep-bhowmik/0b54a1e268c086663be0aa4347af2197 to your computer and use it in GitHub Desktop.
import {
ChangeEvent,
HTMLInputTypeAttribute,
useState,
useRef,
useId,
HTMLProps,
} from "react";
import { mediaUrl } from "@/lib/utils";
type ImageWithPreview = {
file: File;
url: string;
};
export default function ImageInput({
className,
onChangeGetFiles,
multiple = false,
onImageRemove,
Removing = null,
images = [],
...props
}: {
className?: string;
onChangeGetFiles?: CallableFunction;
onImageRemove?: CallableFunction;
multiple?: boolean;
images?: any[];
Removing?: string | null;
props?: HTMLInputTypeAttribute;
}) {
const [Files, setFiles] = useState<any>([]);
const [dragging, setDragging] = useState<boolean>(false);
const localRef = useRef<HTMLInputElement>(null);
const id = useId();
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
if (!files) return;
onChangeGetFiles && onChangeGetFiles([...files]);
const newFiles = Array.from(e.target.files || []).map((file: File) => ({
file,
url: URL.createObjectURL(file),
}));
if (localRef.current) {
localRef.current.value = "";
}
setFiles(() => (multiple ? newFiles : [newFiles[0]]));
};
const handleDragEnter = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault();
setDragging(true);
};
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault();
setDragging(false);
};
const handleDragOver = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault();
e.dataTransfer.dropEffect = "copy"; // to show the "copy" cursor
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault();
setDragging(false);
const newFiles: ImageWithPreview[] = Array.from(
e.dataTransfer.files || []
).map((file: File) => ({
file,
url: URL.createObjectURL(file),
}));
setFiles((pre: any) =>
multiple ? [...pre, ...newFiles] : [newFiles[0]]
);
};
const Image = ({ src, index }: { src: any; index: number }) => {
const handleRemove = () => {
const filteredFiles = Files.filter(
(file: ImageWithPreview) => file.url !== src
);
const files: File[] = filteredFiles.map(
(image: ImageWithPreview) => image.file
);
onChangeGetFiles && onChangeGetFiles(files);
setFiles(filteredFiles);
};
return (
<div className="relative bg-black">
<button
onClick={handleRemove}
className="absolute top-3 right-3 bg-black p-1 rounded-full text-white"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</button>
<img src={src} className="w-full mx-auto max-w-sm" />
</div>
);
};
function Image2({
src,
...props
}: {
src: string;
props?: HTMLProps<HTMLDivElement>;
}) {
const handleRemove = () => {
onImageRemove && onImageRemove(src);
};
return (
<div className="relative bg-black" {...props}>
{multiple && (
<button
onClick={handleRemove}
className="absolute top-3 right-3 bg-black p-1 rounded-full text-white"
>
{Removing === src ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
className="w-6 h-6 animate-spin"
>
<path d="M18.364 5.636L16.95 7.05A7 7 0 1019 12h2a9 9 0 11-2.636-6.364z"></path>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
)}
</button>
)}
<img src={mediaUrl(src)} className="w-full mx-auto max-w-sm" />
</div>
);
}
return (
<div
className="input block border overflow-hidden"
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
<label htmlFor={id}>
<input
type="file"
id={id}
onChange={handleChange}
className={`hidden ` + className}
ref={localRef}
multiple={multiple}
{...props}
/>
<div className="text-xs p-3 ">
Drag or Drop your files or{" "}
<span className="text-[--primary]"> Browse</span>
</div>
</label>
<div>
{!multiple
? Files.length
? Files.map((f: ImageWithPreview, i: number) => {
return <Image src={f.url} index={i} key={i} />;
})
: images.length
? images.map((image, i) =>
image ? (
<Image2 src={image} key={image + i} />
) : (
""
)
)
: ""
: ""}
{multiple && Files.length
? Files.map((f: ImageWithPreview, i: number) => {
return <Image src={f.url} index={i} key={i} />;
})
: ""}
{multiple && images.length
? images.map((image, i) =>
image ? <Image2 src={image} key={image + i} /> : ""
)
: ""}
</div>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment