90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
|
|
export function ImagePreview({ file }: { file: File }) {
|
|
const [url, setUrl] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
const objectUrl = URL.createObjectURL(file);
|
|
setUrl(objectUrl);
|
|
return () => URL.revokeObjectURL(objectUrl);
|
|
}, [file]);
|
|
|
|
if (!url) return null;
|
|
return <img src={url} alt="Preview" className="object-cover w-full h-full" />;
|
|
}
|
|
|
|
export function FileInput({
|
|
label,
|
|
type,
|
|
required,
|
|
value,
|
|
onChange,
|
|
}: {
|
|
label: string;
|
|
type: 'image' | 'file' | string;
|
|
required?: boolean;
|
|
value: File[] | undefined | null | unknown;
|
|
onChange: (files: File[]) => void;
|
|
}) {
|
|
const files = Array.isArray(value) ? value : (value instanceof File ? [value] : []);
|
|
|
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (e.target.files) {
|
|
const newFiles = Array.from(e.target.files);
|
|
onChange([...files, ...newFiles]);
|
|
}
|
|
// reset input so the same file can be selected again if needed
|
|
e.target.value = '';
|
|
};
|
|
|
|
const removeFile = (index: number) => {
|
|
const newFiles = [...files];
|
|
newFiles.splice(index, 1);
|
|
onChange(newFiles);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-1.5 font-sans">
|
|
<span className="text-sm font-medium text-muted">
|
|
{label}
|
|
{required && <span className="text-ruby-600"> *</span>}
|
|
</span>
|
|
|
|
<div className="flex flex-col gap-3">
|
|
<input
|
|
type="file"
|
|
multiple
|
|
required={required && files.length === 0}
|
|
accept={type === 'image' ? 'image/*' : undefined}
|
|
onChange={handleFileChange}
|
|
className="w-full text-sm text-strong file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-slate-100 file:text-navy-700 hover:file:bg-slate-200"
|
|
/>
|
|
|
|
{files.length > 0 && (
|
|
<div className="flex flex-wrap gap-3">
|
|
{files.map((file, i) => (
|
|
<div key={i} className="relative w-24 h-24 rounded-md overflow-hidden border border-border-default shadow-sm bg-slate-50 flex-shrink-0 group">
|
|
{type === 'image' ? (
|
|
<ImagePreview file={file} />
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center p-2 text-xs text-center break-all overflow-hidden text-muted">
|
|
{file.name}
|
|
</div>
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={() => removeFile(i)}
|
|
className="absolute top-1 right-1 bg-ruby-600/90 hover:bg-ruby-700 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity"
|
|
title="Remove file"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|