"use client" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" import { UploadIcon } from "lucide-react" import type { ReactNode } from "react" import { createContext, useContext } from "react" import type { DropEvent, DropzoneOptions, FileRejection } from "react-dropzone" import { useDropzone } from "react-dropzone" type DropzoneContextType = { src?: File[] accept?: DropzoneOptions["accept"] maxSize?: DropzoneOptions["maxSize"] minSize?: DropzoneOptions["minSize"] maxFiles?: DropzoneOptions["maxFiles"] } const renderBytes = (bytes: number) => { const units = ["B", "KB", "MB", "GB", "TB", "PB"] let size = bytes let unitIndex = 0 while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024 unitIndex++ } return `${size.toFixed(2)}${units[unitIndex]}` } const DropzoneContext = createContext( undefined, ) export type DropzoneProps = Omit & { src?: File[] className?: string onDrop?: ( acceptedFiles: File[], fileRejections: FileRejection[], event: DropEvent, ) => void children?: ReactNode } export const Dropzone = ({ accept, maxFiles = 1, maxSize, minSize, onDrop, onError, disabled, src, className, children, ...props }: DropzoneProps) => { const { getRootProps, getInputProps, isDragActive } = useDropzone({ accept, maxFiles, maxSize, minSize, onError, disabled, onDrop: (acceptedFiles, fileRejections, event) => { if (fileRejections.length > 0) { const message = fileRejections.at(0)?.errors.at(0)?.message onError?.(new Error(message)) return } onDrop?.(acceptedFiles, fileRejections, event) }, ...props, }) return ( ) } const useDropzoneContext = () => { const context = useContext(DropzoneContext) if (!context) { throw new Error("useDropzoneContext must be used within a Dropzone") } return context } export type DropzoneContentProps = { children?: ReactNode className?: string } const maxLabelItems = 1 export const DropzoneContent = ({ children, className, }: DropzoneContentProps) => { const { src } = useDropzoneContext() if (!src) { return null } if (children) { return children } return (

{src.length > maxLabelItems ? `${new Intl.ListFormat("en").format( src.slice(0, maxLabelItems).map((file) => file.name), )} and ${src.length - maxLabelItems} more` : new Intl.ListFormat("en").format(src.map((file) => file.name))}

Drag and drop or click to replace

) } export type DropzoneEmptyStateProps = { children?: ReactNode className?: string } export const DropzoneEmptyState = ({ children, className, }: DropzoneEmptyStateProps) => { const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext() if (src) { return null } if (children) { return children } let caption = "" if (accept) { caption += "Accepts " caption += new Intl.ListFormat("en").format(Object.keys(accept)) } if (minSize && maxSize) { caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}` } else if (minSize) { caption += ` at least ${renderBytes(minSize)}` } else if (maxSize) { caption += ` less than ${renderBytes(maxSize)}` } return (

Upload {maxFiles === 1 ? "a file" : "files"}

Drag and drop or click to upload

{caption && (

{caption}.

)}
) }