feat: add file upload section for cover image and article content in create article form
This commit is contained in:
113
src/ui/components/internal/file-upload-field.tsx
Normal file
113
src/ui/components/internal/file-upload-field.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
'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 { X } from 'lucide-react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
export interface FileUploadFieldProps {
|
||||
file: File | null;
|
||||
onFileChange: (file: File | null) => void;
|
||||
accept?: string;
|
||||
validate?: (file: File) => string | null;
|
||||
onFileReject?: (file: File, message: string) => void;
|
||||
label?: string;
|
||||
description?: string;
|
||||
error?: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const FileUploadField: React.FC<FileUploadFieldProps> = ({
|
||||
file,
|
||||
onFileChange,
|
||||
accept,
|
||||
validate,
|
||||
onFileReject,
|
||||
label = 'File',
|
||||
description,
|
||||
error,
|
||||
icon,
|
||||
}) => {
|
||||
const handleAccept = useCallback(
|
||||
(files: File[]) => {
|
||||
const accepted = files[0];
|
||||
if (accepted) onFileChange(accepted);
|
||||
},
|
||||
[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 />
|
||||
<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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user