Files
diplom-frontend/app/components/ui/icon-upload-field.tsx
2025-05-15 05:20:01 +03:00

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>
);
}