diff --git a/web/src/components/ui/file-uploader.tsx b/web/src/components/ui/file-uploader.tsx index 759619e21..77d1fa018 100644 --- a/web/src/components/ui/file-uploader.tsx +++ b/web/src/components/ui/file-uploader.tsx @@ -1,14 +1,11 @@ -import { ArrowUpTrayIcon, DocumentTextIcon, XMarkIcon } from "@heroicons/react/24/solid" -import * as React from "react" -import Dropzone, { - type DropzoneProps, - type FileRejection, -} from "react-dropzone" +import { ArrowUpTrayIcon, DocumentTextIcon, XMarkIcon } from '@heroicons/react/24/solid'; +import * as React from 'react'; +import Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone'; -import { Button } from "@/components/ui/button" -import { useControllableState } from "@/components/ui/use-controllable-state" -import { cn, formatBytes } from "@/lib/utils" -import { toast } from "./use-toast" +import { Button } from '@/components/ui/button'; +import { useControllableState } from '@/components/ui/use-controllable-state'; +import { cn, formatBytes } from '@/lib/utils'; +import { toast } from './use-toast'; interface FileUploaderProps extends React.HTMLAttributes { /** @@ -17,7 +14,7 @@ interface FileUploaderProps extends React.HTMLAttributes { * @default undefined * @example value={files} */ - value?: File[] + value?: File[]; /** * Function to be called when the value changes. @@ -25,7 +22,7 @@ interface FileUploaderProps extends React.HTMLAttributes { * @default undefined * @example onValueChange={(files) => setFiles(files)} */ - onValueChange?: (files: File[]) => void + onValueChange?: (files: File[]) => void; /** * Accepted file types for the uploader. @@ -36,7 +33,7 @@ interface FileUploaderProps extends React.HTMLAttributes { * ``` * @example accept={["image/png", "image/jpeg"]} */ - accept?: DropzoneProps["accept"] + accept?: DropzoneProps['accept']; /** * Maximum file size for the uploader. @@ -44,7 +41,7 @@ interface FileUploaderProps extends React.HTMLAttributes { * @default 1024 * 1024 * 2 // 2MB * @example maxSize={1024 * 1024 * 2} // 2MB */ - maxSize?: DropzoneProps["maxSize"] + maxSize?: DropzoneProps['maxSize']; /** * Maximum number of files for the uploader. @@ -52,7 +49,7 @@ interface FileUploaderProps extends React.HTMLAttributes { * @default 1 * @example maxFileCount={4} */ - maxFileCount?: DropzoneProps["maxFiles"] + maxFileCount?: DropzoneProps['maxFiles']; /** * Whether the uploader should accept multiple files. @@ -60,7 +57,7 @@ interface FileUploaderProps extends React.HTMLAttributes { * @default false * @example multiple */ - multiple?: boolean + multiple?: boolean; /** * Whether the uploader is disabled. @@ -68,7 +65,7 @@ interface FileUploaderProps extends React.HTMLAttributes { * @default false * @example disabled */ - disabled?: boolean + disabled?: boolean; } export function FileUploader(props: FileUploaderProps) { @@ -76,7 +73,7 @@ export function FileUploader(props: FileUploaderProps) { value: valueProp, onValueChange, accept = { - "image/*": [], + 'image/*': [], }, maxSize = 1024 * 1024 * 2, maxFileCount = 1, @@ -84,176 +81,147 @@ export function FileUploader(props: FileUploaderProps) { disabled = false, className, ...dropzoneProps - } = props + } = props; const [files, setFiles] = useControllableState({ prop: valueProp, onChange: onValueChange, - }) + }); const onDrop = React.useCallback( (acceptedFiles: File[], rejectedFiles: FileRejection[]) => { if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) { toast({ - title:"Cannot upload more than 1 file at a time", - variant: 'destructive' - }) - return + title: 'Cannot upload more than 1 file at a time', + variant: 'destructive', + }); + return; } if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) { - toast({title:`Cannot upload more than ${maxFileCount} files`, variant: 'destructive'}) - return + toast({ title: `Cannot upload more than ${maxFileCount} files`, variant: 'destructive' }); + return; } const newFiles = acceptedFiles.map((file) => Object.assign(file, { preview: URL.createObjectURL(file), }) - ) + ); - const updatedFiles = files ? [...files, ...newFiles] : newFiles + const updatedFiles = files ? [...files, ...newFiles] : newFiles; - setFiles(updatedFiles) + setFiles(updatedFiles); if (rejectedFiles.length > 0) { rejectedFiles.forEach(({ file }) => { - toast({title: `File ${file.name} was rejected`, variant: 'destructive'}) - }) + toast({ title: `File ${file.name} was rejected`, variant: 'destructive' }); + }); } }, [files, maxFileCount, multiple, setFiles] - ) + ); function onRemove(index: number) { - if (!files) return - const newFiles = files.filter((_, i) => i !== index) - setFiles(newFiles) - onValueChange?.(newFiles) + if (!files) return; + const newFiles = files.filter((_, i) => i !== index); + setFiles(newFiles); + onValueChange?.(newFiles); } - const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount + const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount; return ( -
- {!files?.length || (files?.length ?? 0) < maxFileCount ? 1 || multiple} - disabled={isDisabled} - > - {({ getRootProps, getInputProps, isDragActive }) => ( -
- - {isDragActive ? ( -
-
-
-

- Drop the files here -

-
- ) : ( -
-
-
- ) + ); } interface FileCardProps { - file: File - onRemove: () => void + file: File; + onRemove: () => void; } function FileCard({ file, onRemove }: FileCardProps) { return ( -
-
-
-
-

- {file.name} -

-

- {formatBytes(file.size)} -

+
+
+
+
+

{file.name}

+

{formatBytes(file.size)}

-
-
- ) + ); } -interface FilePreviewProps { -} +interface FilePreviewProps {} function FilePreview(props: FilePreviewProps) { - return ( -
{' '} - +