121 lines
4.8 KiB
TypeScript
121 lines
4.8 KiB
TypeScript
import { ImagePlus, XCircle } from "lucide-react";
|
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
import type { ControllerRenderProps, FieldError } from "react-hook-form";
|
|
import { Button } from "./button"; // Your existing Button component
|
|
|
|
// Props for our custom field component
|
|
interface IconUploadFieldProps {
|
|
field: ControllerRenderProps<any, string>; // Provided by RHF's FormField render prop
|
|
error?: FieldError; // Optional: if you want to pass error for internal styling
|
|
accept?: string; // e.g., "image/png, image/jpeg"
|
|
previewContainerClassName?: string; // Style for the preview box itself
|
|
// Add any other props you might want to customize its appearance/behavior
|
|
}
|
|
|
|
export function IconUploadField({
|
|
field,
|
|
error,
|
|
accept = "image/*",
|
|
previewContainerClassName = "w-24 h-24 rounded-full", // Default circular preview
|
|
}: IconUploadFieldProps) {
|
|
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const currentFileValue = field.value as File | undefined | null;
|
|
|
|
useEffect(() => {
|
|
let objectUrl: string | null = null;
|
|
if (currentFileValue && currentFileValue instanceof File) {
|
|
objectUrl = URL.createObjectURL(currentFileValue);
|
|
setPreviewUrl(objectUrl);
|
|
} else {
|
|
setPreviewUrl(null);
|
|
}
|
|
|
|
return () => {
|
|
if (objectUrl) {
|
|
URL.revokeObjectURL(objectUrl);
|
|
}
|
|
};
|
|
}, [currentFileValue]);
|
|
|
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
field.onChange(file || undefined);
|
|
if (event.target) {
|
|
event.target.value = "";
|
|
}
|
|
};
|
|
|
|
const handleRemoveImage = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
|
e.preventDefault();
|
|
field.onChange(undefined);
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.value = "";
|
|
}
|
|
}, [field]);
|
|
|
|
const triggerFileInput = useCallback(() => {
|
|
fileInputRef.current?.click();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="flex items-center space-x-4">
|
|
{/* Main clickable area for upload, also receives RHF's ref */}
|
|
<div
|
|
ref={field.ref} // Attach RHF's ref here for focus management
|
|
className={`relative ${previewContainerClassName} border-2 ${error ? "border-destructive" : "border-dashed border-muted-foreground"
|
|
} flex items-center justify-center cursor-pointer hover:border-primary transition-colors overflow-hidden`}
|
|
onClick={triggerFileInput}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" || e.key === " ") {
|
|
e.preventDefault();
|
|
triggerFileInput();
|
|
}
|
|
}}
|
|
role="button"
|
|
tabIndex={0}
|
|
aria-label={previewUrl ? "Change icon" : "Upload icon"}
|
|
aria-invalid={!!error}
|
|
aria-describedby={error ? `${field.name}-error` : undefined}
|
|
>
|
|
{previewUrl ? (
|
|
<img
|
|
src={previewUrl}
|
|
alt="Icon preview"
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
) : (
|
|
<ImagePlus className={`w-10 h-10 ${error ? "text-destructive" : "text-muted-foreground"}`} />
|
|
)}
|
|
<input
|
|
type="file"
|
|
ref={fileInputRef} // Internal ref for programmatic click
|
|
className="hidden" // Visually hidden
|
|
accept={accept}
|
|
onChange={handleFileChange}
|
|
onBlur={field.onBlur} // RHF's onBlur for touched state
|
|
name={field.name} // RHF's field name
|
|
// The `value` of a file input is not directly controlled by RHF's `field.value` (which is a File object)
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex flex-col space-y-2">
|
|
{/* <Button type="button" variant="outline" size="sm" onClick={triggerFileInput}>
|
|
{previewUrl ? "Change Icon" : "Upload Icon"}
|
|
</Button> */}
|
|
{currentFileValue && ( // Show remove button only if a file is selected
|
|
<Button
|
|
type="button"
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={handleRemoveImage}
|
|
>
|
|
<XCircle className="" />
|
|
Remove
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
} |