165 lines
5.6 KiB
TypeScript
165 lines
5.6 KiB
TypeScript
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>
|
|
);
|
|
}
|