116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import { FileIcon, RotateCcw, UploadIcon, XIcon } from 'lucide-react';
|
|
import { useRef, useState } from 'react';
|
|
import InputError from '@/components/input-error';
|
|
import {
|
|
Attachment,
|
|
AttachmentAction,
|
|
AttachmentActions,
|
|
AttachmentContent,
|
|
AttachmentDescription,
|
|
AttachmentMedia,
|
|
AttachmentTitle,
|
|
AttachmentTrigger,
|
|
} from '@/components/ui/attachment';
|
|
import { Label } from '@/components/ui/label';
|
|
|
|
type FileUploadFieldProps = {
|
|
label?: string;
|
|
/** Form field name for the native file input. */
|
|
name?: string;
|
|
existingFileName?: string | null;
|
|
existingFileUrl?: string | null;
|
|
error?: string;
|
|
accept?: string;
|
|
helpText?: string;
|
|
};
|
|
|
|
export function FileUploadField({
|
|
label = 'File',
|
|
name = 'file',
|
|
existingFileName,
|
|
existingFileUrl,
|
|
error,
|
|
accept,
|
|
helpText = 'PDF, Word, PPT, Excel, gambar, video, atau zip (maks 10MB)',
|
|
}: FileUploadFieldProps) {
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
|
|
function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
setSelectedFile(e.target.files?.[0] ?? null);
|
|
}
|
|
|
|
function handleReset() {
|
|
setSelectedFile(null);
|
|
|
|
if (inputRef.current) {
|
|
inputRef.current.value = '';
|
|
}
|
|
}
|
|
|
|
const hasNewFile = selectedFile !== null;
|
|
const hasExistingFile = !hasNewFile && !!existingFileName;
|
|
const displayName = selectedFile?.name ?? existingFileName ?? null;
|
|
const displayUrl = hasNewFile ? null : existingFileUrl;
|
|
const state = error ? 'error' : displayName ? 'done' : 'idle';
|
|
|
|
return (
|
|
<div className="grid gap-2">
|
|
<Label>{label}</Label>
|
|
<input
|
|
ref={inputRef}
|
|
type="file"
|
|
name={name}
|
|
accept={accept}
|
|
onChange={handleFileChange}
|
|
className="hidden"
|
|
/>
|
|
|
|
<Attachment state={state}>
|
|
<AttachmentTrigger onClick={() => inputRef.current?.click()} />
|
|
<AttachmentMedia>
|
|
<FileIcon />
|
|
</AttachmentMedia>
|
|
<AttachmentContent>
|
|
<AttachmentTitle>
|
|
{displayName ?? 'Klik untuk pilih file'}
|
|
</AttachmentTitle>
|
|
<AttachmentDescription>
|
|
{displayName
|
|
? hasExistingFile
|
|
? 'File saat ini — klik untuk mengganti'
|
|
: 'Klik untuk mengganti file'
|
|
: helpText}
|
|
</AttachmentDescription>
|
|
</AttachmentContent>
|
|
<AttachmentActions>
|
|
{displayUrl && (
|
|
<AttachmentAction asChild>
|
|
<a
|
|
href={displayUrl}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<UploadIcon className="rotate-180" />
|
|
</a>
|
|
</AttachmentAction>
|
|
)}
|
|
{hasNewFile && (
|
|
<AttachmentAction
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleReset();
|
|
}}
|
|
>
|
|
{hasExistingFile ? <RotateCcw /> : <XIcon />}
|
|
</AttachmentAction>
|
|
)}
|
|
</AttachmentActions>
|
|
</Attachment>
|
|
|
|
<InputError message={error} />
|
|
</div>
|
|
);
|
|
}
|