import Button from "../layout/Button"; import Stack from "../layout/Stack"; import { File, FileArchive, FilePlus, FilePlus2, ImagePlus, X, } from "lucide-react"; import React, { DetailedHTMLProps } from "react"; import Card from "../elements/Card"; import Span from "../layout/Span"; import Center from "../layout/Center"; import imageInputToBase64, { FileInputToBase64FunctionReturn, } from "../utils/form/fileInputToBase64"; import { twMerge } from "tailwind-merge"; import fileInputToBase64 from "../utils/form/fileInputToBase64"; import Row from "../layout/Row"; type ImageUploadProps = DetailedHTMLProps< React.HTMLAttributes<HTMLDivElement>, HTMLDivElement > & { onChangeHandler?: ( imgData: FileInputToBase64FunctionReturn | undefined ) => any; fileInputProps?: DetailedHTMLProps< React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement >; placeHolderWrapper?: DetailedHTMLProps< React.HTMLAttributes<HTMLDivElement>, HTMLDivElement >; previewImageWrapperProps?: DetailedHTMLProps< React.HTMLAttributes<HTMLDivElement>, HTMLDivElement >; previewImageProps?: DetailedHTMLProps< React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement >; label?: string; disablePreview?: boolean; allowedRegex?: RegExp; externalSetFile?: React.Dispatch< React.SetStateAction<FileInputToBase64FunctionReturn | undefined> >; }; /** * @note use the `onChangeHandler` prop to grab the parsed base64 image object */ export default function FileUpload({ onChangeHandler, fileInputProps, placeHolderWrapper, previewImageWrapperProps, previewImageProps, label, disablePreview, allowedRegex, externalSetFile, ...props }: ImageUploadProps) { const [file, setFile] = React.useState< FileInputToBase64FunctionReturn | undefined >(undefined); const inputRef = React.useRef<HTMLInputElement>(); return ( <Stack {...props} className={twMerge("w-full h-[300px]", props?.className)} > <input type="file" className={twMerge("hidden", fileInputProps?.className)} {...fileInputProps} onChange={(e) => { const inputFile = e.target.files?.[0]; if (!inputFile) return; fileInputToBase64({ inputFile, allowedRegex }).then( (res) => { setFile(res); externalSetFile?.(res); onChangeHandler?.(res); fileInputProps?.onChange?.(e); } ); }} ref={inputRef as any} /> {file ? ( <Card className="w-full relative h-full items-center justify-center overflow-hidden" {...previewImageWrapperProps} > {disablePreview ? ( <Span className="opacity-50" size="small"> Image Uploaded! </Span> ) : file.fileType?.match(/image/i) ? ( <img src={file.fileBase64Full} className="w-full object-contain overflow-hidden" {...previewImageProps} /> ) : ( <Row> <FileArchive size={36} strokeWidth={1} /> <Stack className="gap-0"> <Span>{file.file?.name || file.fileName}</Span> <Span size="smaller" className="opacity-70"> {file.fileType} </Span> </Stack> </Row> )} <Button variant="ghost" className={twMerge( "absolute p-2 top-2 right-2 z-20 bg-white dark:bg-black", "hover:bg-white dark:hover:bg-black" )} onClick={(e) => { setFile(undefined); externalSetFile?.(undefined); onChangeHandler?.(undefined); }} > <X className="text-slate-950 dark:text-white" /> </Button> </Card> ) : ( <Card className={twMerge( "w-full h-full cursor-pointer hover:bg-slate-100 dark:hover:bg-white/20", placeHolderWrapper?.className )} onClick={(e) => { inputRef.current?.click(); placeHolderWrapper?.onClick?.(e); }} {...placeHolderWrapper} > <Center> <Stack className="items-center gap-2"> <FilePlus2 className="text-slate-400" /> <Span size="smaller" variant="faded"> {label || "Click to Upload File"} </Span> </Stack> </Center> </Card> )} </Stack> ); }