110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
|
import Button from "@/components/lib/layout/Button";
|
||
|
import Stack from "@/components/lib/layout/Stack";
|
||
|
import { ImagePlus, X } from "lucide-react";
|
||
|
import React, { DetailedHTMLProps } from "react";
|
||
|
import Card from "@/components/lib/elements/Card";
|
||
|
import Span from "@/components/lib/layout/Span";
|
||
|
import Center from "@/components/lib/layout/Center";
|
||
|
import imageInputToBase64, {
|
||
|
ImageInputToBase64FunctionReturn,
|
||
|
} from "../utils/form/imageInputToBase64";
|
||
|
import { twMerge } from "tailwind-merge";
|
||
|
|
||
|
type ImageUploadProps = {
|
||
|
onChange?: (imgData: ImageInputToBase64FunctionReturn | undefined) => any;
|
||
|
fileInputProps?: DetailedHTMLProps<
|
||
|
React.InputHTMLAttributes<HTMLInputElement>,
|
||
|
HTMLInputElement
|
||
|
>;
|
||
|
wrapperProps?: DetailedHTMLProps<
|
||
|
React.HTMLAttributes<HTMLDivElement>,
|
||
|
HTMLDivElement
|
||
|
>;
|
||
|
placeHolderWrapper?: DetailedHTMLProps<
|
||
|
React.HTMLAttributes<HTMLDivElement>,
|
||
|
HTMLDivElement
|
||
|
>;
|
||
|
previewImageWrapperProps?: DetailedHTMLProps<
|
||
|
React.HTMLAttributes<HTMLDivElement>,
|
||
|
HTMLDivElement
|
||
|
>;
|
||
|
previewImageProps?: DetailedHTMLProps<
|
||
|
React.ImgHTMLAttributes<HTMLImageElement>,
|
||
|
HTMLImageElement
|
||
|
>;
|
||
|
};
|
||
|
|
||
|
export default function ImageUpload({
|
||
|
onChange,
|
||
|
fileInputProps,
|
||
|
wrapperProps,
|
||
|
placeHolderWrapper,
|
||
|
previewImageWrapperProps,
|
||
|
previewImageProps,
|
||
|
}: ImageUploadProps) {
|
||
|
const [src, setSrc] = React.useState<string | undefined>(undefined);
|
||
|
const inputRef = React.useRef<HTMLInputElement>();
|
||
|
|
||
|
return (
|
||
|
<Stack
|
||
|
className={twMerge("w-full", wrapperProps?.className)}
|
||
|
{...wrapperProps}
|
||
|
>
|
||
|
<input
|
||
|
type="file"
|
||
|
className={twMerge("hidden", fileInputProps?.className)}
|
||
|
{...fileInputProps}
|
||
|
onChange={(e) => {
|
||
|
imageInputToBase64({ imageInput: e.target }).then((res) => {
|
||
|
setSrc(res.imageBase64Full);
|
||
|
onChange?.(res);
|
||
|
fileInputProps?.onChange?.(e);
|
||
|
});
|
||
|
}}
|
||
|
ref={inputRef as any}
|
||
|
/>
|
||
|
|
||
|
{src ? (
|
||
|
<Card className="w-full relative" {...previewImageWrapperProps}>
|
||
|
<img
|
||
|
src={src}
|
||
|
className="w-full h-[300px] object-contain"
|
||
|
{...previewImageProps}
|
||
|
/>
|
||
|
<Button
|
||
|
variant="ghost"
|
||
|
className="absolute p-2 top-2 right-2 z-20"
|
||
|
onClick={(e) => {
|
||
|
setSrc(undefined);
|
||
|
onChange?.(undefined);
|
||
|
}}
|
||
|
>
|
||
|
<X className="text-slate-950" />
|
||
|
</Button>
|
||
|
</Card>
|
||
|
) : (
|
||
|
<Card
|
||
|
className={twMerge(
|
||
|
"w-full h-[300px] 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">
|
||
|
<ImagePlus className="text-slate-400" />
|
||
|
<Span size="smaller" variant="faded">
|
||
|
Click to Upload Image
|
||
|
</Span>
|
||
|
</Stack>
|
||
|
</Center>
|
||
|
</Card>
|
||
|
)}
|
||
|
</Stack>
|
||
|
);
|
||
|
}
|