155 lines
5.6 KiB
TypeScript
155 lines
5.6 KiB
TypeScript
import { forwardRef, Ref, RefObject, useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import InputText from "./InputText";
|
|
import UploadDocument from "../uploaders/UploadDocument";
|
|
import clsx from "clsx";
|
|
import { sanitizeIconSvg } from "~/utils/html-sanitizer";
|
|
import { XIcon } from "~/components/ui/icons/vendor";
|
|
|
|
export interface RefInputImage {
|
|
input: RefObject<HTMLInputElement> | RefObject<HTMLTextAreaElement>;
|
|
}
|
|
|
|
type WithDefaultValue = { defaultValue: string | undefined };
|
|
type WithValueAndSetValue = { value: string | undefined; setValue: React.Dispatch<React.SetStateAction<string>> };
|
|
|
|
type Props = (WithDefaultValue | WithValueAndSetValue) & {
|
|
name?: string;
|
|
title?: string;
|
|
disabled?: boolean;
|
|
size?: "small" | "medium" | "large";
|
|
square?: boolean;
|
|
required?: boolean;
|
|
hint?: React.ReactNode;
|
|
accept?: string;
|
|
placeholder?: string;
|
|
opened?: boolean;
|
|
description?: string;
|
|
className?: string;
|
|
};
|
|
const InputImage = (props: Props, ref: Ref<RefInputImage>) => {
|
|
const value = "value" in props ? props.value : undefined;
|
|
const setValue = "setValue" in props ? props.setValue : undefined;
|
|
const defaultValue = "defaultValue" in props ? props.defaultValue : undefined;
|
|
const size = props.size ?? "large";
|
|
const square = props.square ?? false;
|
|
|
|
const { t } = useTranslation();
|
|
const [showUploadImage, setShowUploadImage] = useState((props.opened && (!value || defaultValue)) ?? false);
|
|
const [image, setImage] = useState(value || defaultValue || "");
|
|
|
|
useEffect(() => {
|
|
if (setValue && value !== image) {
|
|
setValue(image);
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [image]);
|
|
|
|
const imageClassName = clsx(
|
|
"mx-auto",
|
|
square ? "object-cover" : "object-contain",
|
|
size === "small" && "h-8",
|
|
size === "medium" && "h-16",
|
|
size === "large" && "h-32",
|
|
square && clsx(size === "small" && "w-8", size === "medium" && "w-16", size === "large" && "w-32")
|
|
);
|
|
|
|
return (
|
|
<div className={clsx("space-y-1", props.className)}>
|
|
<input name={props.name} type="hidden" value={image} readOnly hidden />
|
|
|
|
<label htmlFor={props.name} className="flex items-baseline justify-between space-x-2 truncate text-xs font-medium">
|
|
<div className="flex shrink-0 items-center space-x-1 truncate">
|
|
<div className="flex space-x-1 truncate">
|
|
<div className="truncate">{props.title}</div>
|
|
{props.required && props.title && <div className="ml-1 text-red-600">*</div>}
|
|
</div>
|
|
</div>
|
|
{props.hint && <div>{props.hint}</div>}
|
|
</label>
|
|
|
|
{/* {!showUploadImage && ( */}
|
|
<InputText
|
|
name={props.name}
|
|
required={props.required}
|
|
value={image}
|
|
setValue={setImage}
|
|
readOnly={image.startsWith("data:image")}
|
|
placeholder={props.placeholder ?? t("components.input.image.urlPlaceholder")}
|
|
withLabel={false}
|
|
button={
|
|
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-1.5 ">
|
|
<div className="border-border-default text-text-neutral-tertiary bg-surface-neutral-primary inline-flex items-center rounded border px-2 font-sans text-sm font-medium">
|
|
{!image ? (
|
|
<button type="button" onClick={() => setShowUploadImage(true)}>
|
|
{t("shared.upload")}
|
|
</button>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
disabled={props.disabled}
|
|
onClick={() => {
|
|
setImage("");
|
|
setShowUploadImage(true);
|
|
}}
|
|
>
|
|
{t("shared.remove")}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
}
|
|
/>
|
|
{/* )} */}
|
|
{showUploadImage && (
|
|
<UploadDocument
|
|
name={props.name + "-file"}
|
|
disabled={props.disabled}
|
|
className="col-span-12"
|
|
accept={props.accept || "image/png, image/jpg, image/jpeg"}
|
|
// description={t("models.post.image")}
|
|
description={props.description}
|
|
onDropped={(e) => {
|
|
setImage(e);
|
|
setShowUploadImage(false);
|
|
}}
|
|
/>
|
|
)}
|
|
{!showUploadImage && image && (
|
|
<div className="col-span-12">
|
|
<div
|
|
className={clsx(
|
|
"bg-surface-neutral-primary border-border-default relative overflow-hidden rounded-lg border",
|
|
!square && "p-4",
|
|
square && size === "small" && "w-8",
|
|
square && size === "medium" && "w-16",
|
|
square && size === "large" && "w-32"
|
|
)}
|
|
>
|
|
{/* <button
|
|
type="button"
|
|
className="absolute inset-0 transition-colors duration-200"
|
|
onClick={() => setShowUploadImage(true)}
|
|
>
|
|
<div className="flex h-full items-center justify-center text-sm font-medium text-white">{t("shared.upload")}</div>
|
|
</button> */}
|
|
<button
|
|
type="button"
|
|
className="absolute right-0 top-0 bg-black/50 p-1 transition-colors duration-200 hover:bg-black/75"
|
|
onClick={() => setImage("")}
|
|
>
|
|
<XIcon className="h-4 w-4 text-white" />
|
|
</button>
|
|
{image.startsWith("<svg") ? (
|
|
<div dangerouslySetInnerHTML={{ __html: sanitizeIconSvg(image, imageClassName) }} />
|
|
) : (
|
|
<img className={imageClassName} src={image} alt={props.title} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
export default forwardRef(InputImage);
|