Created
May 13, 2024 09:39
-
-
Save joydeep-bhowmik/0b54a1e268c086663be0aa4347af2197 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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