Files
hideyoshi-blog/src/ui/components/internal/file-upload-field.tsx

137 lines
4.7 KiB
TypeScript

'use client';
import { Button } from '@/ui/components/shadcn/button';
import {
Field,
FieldDescription,
FieldError,
} from '@/ui/components/shadcn/field';
import {
FileUpload,
FileUploadDropzone,
FileUploadItem,
FileUploadItemDelete,
FileUploadItemMetadata,
FileUploadItemPreview,
FileUploadList,
FileUploadTrigger,
} from '@/ui/components/shadcn/file-upload';
import { Spinner } from '@/ui/components/shadcn/spinner';
import { X } from 'lucide-react';
import React, { useCallback } from 'react';
export interface FileUploadFieldProps {
file: File | null;
onFileChange: (file: File | null) => Promise<void>;
accept?: string;
validate?: (file: File) => string | null;
onFileReject?: (file: File, message: string) => void;
label?: string;
description?: string;
error?: string;
icon?: React.ReactNode;
previewUrl?: string;
isUploading?: boolean;
}
export const FileUploadField: React.FC<FileUploadFieldProps> = ({
file,
onFileChange,
accept,
validate,
onFileReject,
label = 'File',
description,
error,
icon,
previewUrl,
isUploading,
}) => {
const handleAccept = useCallback(
(files: File[]) => {
const accepted = files[0];
if (!accepted) return;
onFileChange(accepted).then(() => {});
},
[onFileChange]
);
const handleValueChange = useCallback(
(files: File[]) => {
if (files.length === 0) onFileChange(null);
},
[onFileChange]
);
return (
<Field data-invalid={!!error}>
<FileUpload
value={file ? [file] : []}
onValueChange={handleValueChange}
onAccept={handleAccept}
onFileReject={onFileReject}
onFileValidate={validate}
accept={accept}
maxFiles={1}
multiple={false}
label={label}
className='min-w-0'
>
<FileUploadDropzone className='p-3'>
{icon}
<div className='flex flex-col gap-0.5 text-center'>
<p className='text-xs font-medium text-foreground'>
{label}
</p>
<p className='text-xs text-muted-foreground'>
Drag & drop or{' '}
<FileUploadTrigger className='cursor-pointer font-medium text-primary underline-offset-4 hover:underline'>
browse
</FileUploadTrigger>
</p>
</div>
</FileUploadDropzone>
<FileUploadList>
{file && (
<FileUploadItem value={file}>
<FileUploadItemPreview
render={
isUploading
? () => (
<Spinner className='size-5 text-muted-foreground' />
)
: previewUrl
? () => (
// eslint-disable-next-line @next/next/no-img-element
<img
src={previewUrl}
alt='Uploaded image'
className='size-full object-cover'
/>
)
: undefined
}
/>
<FileUploadItemMetadata size='sm' />
<FileUploadItemDelete asChild>
<Button
type='button'
variant='ghost'
size='icon'
className='ml-auto size-7 shrink-0'
>
<X className='size-3.5' />
</Button>
</FileUploadItemDelete>
</FileUploadItem>
)}
</FileUploadList>
</FileUpload>
{description && <FieldDescription>{description}</FieldDescription>}
{error && <FieldError errors={[new Error(error)]} />}
</Field>
);
};