{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chat-form-dropzone",
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "./registry/components/form/file-upload/chat-form.tsx",
      "content": "'use client';\n\nimport {\n  FileInput,\n  FileUploader,\n  FileUploaderContent,\n  FileUploaderItem,\n} from '@/components/ui/file-upload';\nimport { Paperclip, Upload } from 'lucide-react';\nimport Image from 'next/image';\nimport { useState } from 'react';\nimport type { DropzoneOptions } from 'react-dropzone';\n\nconst FileUploadDropzone = () => {\n  const [files, setFiles] = useState<File[] | null>([]);\n  const [message, setMessage] = useState('');\n\n  const dropzone = {\n    accept: {\n      'image/*': ['.jpg', '.jpeg', '.png'],\n    },\n    multiple: true,\n    maxFiles: 4,\n    maxSize: 1 * 1024 * 1024,\n  } satisfies DropzoneOptions;\n\n  return (\n    <div className='py-16'>\n      <div\n        className={`${\n          files?.length == 0 && 'flex gap-0'\n        } relative border dark:bg-neutral-950 bg-neutral-50 rounded-md p-2 w-96 mx-auto`}\n      >\n        <FileUploader\n          value={files}\n          orientation='vertical'\n          onValueChange={setFiles}\n          className='w-fit pr-3'\n          dropzoneOptions={dropzone}\n        >\n          {files?.length === 0 ? (\n            // Layout when no files are present\n            <div className='flex items-center gap-2'>\n              <FileInput\n                className='bg-primary  text-background rounded-md w-fit'\n                parentclass='w-fit'\n              >\n                <Paperclip className='size-8 p-1.5' />\n                <span className='sr-only'>Select your files</span>\n              </FileInput>\n            </div>\n          ) : (\n            // Layout when files are present\n            <div className='flex flex-col gap-2 mb-2'>\n              <div className='flex items-center gap-2 '>\n                <FileInput\n                  className='bg-primary-foreground border-primary/20 border   text-primary rounded-md w-fit'\n                  parentclass='w-fit'\n                >\n                  <Paperclip className='size-12 p-3' />\n                  <span className='sr-only'>Select your files</span>\n                </FileInput>\n                <FileUploaderContent className='flex items-start flex-row gap-1'>\n                  {files?.map((file, i) => (\n                    <FileUploaderItem\n                      key={`${file.name}-${file.lastModified}-${file.size}`}\n                      index={i}\n                      className='size-12 w-fit p-0 rounded-md overflow-hidden border'\n                      aria-roledescription={`file ${i + 1} containing ${file.name}`}\n                    >\n                      <Image\n                        src={URL.createObjectURL(file)}\n                        alt={file.name}\n                        height={80}\n                        width={80}\n                        className='size-12 rounded-md object-cover bg-primary'\n                      />\n                    </FileUploaderItem>\n                  ))}\n                </FileUploaderContent>\n              </div>\n            </div>\n          )}\n        </FileUploader>\n        <input\n          type='text'\n          className='outline-hidden dark:bg-neutral-800 bg-neutral-50 w-full'\n          placeholder='Your message here...'\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default FileUploadDropzone;\n",
      "type": "registry:component"
    },
    {
      "path": "./components/ui/file-upload.tsx",
      "content": "'use client';\n\nimport { cn } from '@/lib/utils';\nimport { Trash2 as RemoveIcon } from 'lucide-react';\nimport {\n  type Dispatch,\n  type SetStateAction,\n  createContext,\n  forwardRef,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  type DropzoneOptions,\n  type DropzoneState,\n  type FileRejection,\n  useDropzone,\n} from 'react-dropzone';\nimport { toast } from 'sonner';\n\ntype DirectionOptions = 'rtl' | 'ltr' | undefined;\n\ntype FileUploaderContextType = {\n  dropzoneState: DropzoneState;\n  isLOF: boolean;\n  isFileTooBig: boolean;\n  removeFileFromSet: (index: number) => void;\n  activeIndex: number;\n  setActiveIndex: Dispatch<SetStateAction<number>>;\n  orientation: 'horizontal' | 'vertical';\n  direction: DirectionOptions;\n};\n\nconst FileUploaderContext = createContext<FileUploaderContextType | null>(null);\n\nexport const useFileUpload = () => {\n  const context = useContext(FileUploaderContext);\n  if (!context) {\n    throw new Error('useFileUpload must be used within a FileUploaderProvider');\n  }\n  return context;\n};\n\ntype FileUploaderProps = {\n  value: File[] | null;\n  reSelect?: boolean;\n  onValueChange: (value: File[] | null) => void;\n  dropzoneOptions: DropzoneOptions;\n  orientation?: 'horizontal' | 'vertical';\n};\n\n/**\n * File upload Docs: {@link: https://localhost:3000/docs/file-upload}\n */\n\nexport const FileUploader = forwardRef<\n  HTMLDivElement,\n  FileUploaderProps & React.HTMLAttributes<HTMLDivElement>\n>(\n  (\n    {\n      className,\n      dropzoneOptions,\n      value,\n      onValueChange,\n      reSelect,\n      orientation = 'vertical',\n      children,\n      dir,\n      ...props\n    },\n    ref\n  ) => {\n    const [isFileTooBig, setIsFileTooBig] = useState(false);\n    const [isLOF, setIsLOF] = useState(false);\n    const [activeIndex, setActiveIndex] = useState(-1);\n    const {\n      accept = {\n        'image/*': ['.jpg', '.jpeg', '.png', '.gif'],\n        'video/*': ['.mp4', '.MOV', '.AVI'],\n      },\n      maxFiles = 1,\n      maxSize = 4 * 1024 * 1024,\n      multiple = true,\n    } = dropzoneOptions;\n\n    const reSelectAll = maxFiles === 1 ? true : reSelect;\n    const direction: DirectionOptions = dir === 'rtl' ? 'rtl' : 'ltr';\n\n    const removeFileFromSet = useCallback(\n      (i: number) => {\n        if (!value) return;\n        const newFiles = value.filter((_, index) => index !== i);\n        onValueChange(newFiles);\n      },\n      [value, onValueChange]\n    );\n\n    const handleKeyDown = useCallback(\n      (e: React.KeyboardEvent<HTMLDivElement>) => {\n        e.preventDefault();\n        e.stopPropagation();\n\n        if (!value) return;\n\n        const moveNext = () => {\n          const nextIndex = activeIndex + 1;\n          setActiveIndex(nextIndex > value.length - 1 ? 0 : nextIndex);\n        };\n\n        const movePrev = () => {\n          const nextIndex = activeIndex - 1;\n          setActiveIndex(nextIndex < 0 ? value.length - 1 : nextIndex);\n        };\n\n        const prevKey =\n          orientation === 'horizontal'\n            ? direction === 'ltr'\n              ? 'ArrowLeft'\n              : 'ArrowRight'\n            : 'ArrowUp';\n\n        const nextKey =\n          orientation === 'horizontal'\n            ? direction === 'ltr'\n              ? 'ArrowRight'\n              : 'ArrowLeft'\n            : 'ArrowDown';\n\n        if (e.key === nextKey) {\n          moveNext();\n        } else if (e.key === prevKey) {\n          movePrev();\n        } else if (e.key === 'Enter' || e.key === 'Space') {\n          if (activeIndex === -1) {\n            dropzoneState.inputRef.current?.click();\n          }\n        } else if (e.key === 'Delete' || e.key === 'Backspace') {\n          if (activeIndex !== -1) {\n            removeFileFromSet(activeIndex);\n            if (value.length - 1 === 0) {\n              setActiveIndex(-1);\n              return;\n            }\n            movePrev();\n          }\n        } else if (e.key === 'Escape') {\n          setActiveIndex(-1);\n        }\n      },\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n      [value, activeIndex, removeFileFromSet]\n    );\n\n    const onDrop = useCallback(\n      (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {\n        const files = acceptedFiles;\n\n        if (!files) {\n          toast.error('file error , probably too big');\n          return;\n        }\n\n        const newValues: File[] = value ? [...value] : [];\n\n        if (reSelectAll) {\n          newValues.splice(0, newValues.length);\n        }\n\n        files.forEach((file) => {\n          if (newValues.length < maxFiles) {\n            newValues.push(file);\n          }\n        });\n\n        onValueChange(newValues);\n\n        if (rejectedFiles.length > 0) {\n          for (let i = 0; i < rejectedFiles.length; i++) {\n            if (rejectedFiles[i].errors[0]?.code === 'file-too-large') {\n              toast.error(`File is too large. Max size is ${maxSize / 1024 / 1024}MB`);\n              break;\n            }\n            if (rejectedFiles[i].errors[0]?.message) {\n              toast.error(rejectedFiles[i].errors[0].message);\n              break;\n            }\n          }\n        }\n      },\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n      [reSelectAll, value]\n    );\n\n    useEffect(() => {\n      if (!value) return;\n      if (value.length === maxFiles) {\n        setIsLOF(true);\n        return;\n      }\n      setIsLOF(false);\n    }, [value, maxFiles]);\n\n    const opts = dropzoneOptions ? dropzoneOptions : { accept, maxFiles, maxSize, multiple };\n\n    const dropzoneState = useDropzone({\n      ...opts,\n      onDrop,\n      onDropRejected: () => setIsFileTooBig(true),\n      onDropAccepted: () => setIsFileTooBig(false),\n    });\n\n    return (\n      <FileUploaderContext.Provider\n        value={{\n          dropzoneState,\n          isLOF,\n          isFileTooBig,\n          removeFileFromSet,\n          activeIndex,\n          setActiveIndex,\n          orientation,\n          direction,\n        }}\n      >\n        <div\n          ref={ref}\n          tabIndex={0}\n          onKeyDownCapture={handleKeyDown}\n          className={cn('grid w-full focus:outline-hidden overflow-hidden ', className, {\n            'gap-2': value && value.length > 0,\n          })}\n          dir={dir}\n          {...props}\n        >\n          {children}\n        </div>\n      </FileUploaderContext.Provider>\n    );\n  }\n);\n\nFileUploader.displayName = 'FileUploader';\n\nexport const FileUploaderContent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ children, className, ...props }, ref) => {\n    const { orientation } = useFileUpload();\n    const containerRef = useRef<HTMLDivElement>(null);\n\n    return (\n      <div className={cn('w-full px-1')} ref={containerRef} aria-description='content file holder'>\n        <div\n          {...props}\n          ref={ref}\n          className={cn(\n            ' rounded-xl gap-1',\n            orientation === 'horizontal' ? 'grid grid-cols-2' : 'flex flex-col',\n            className\n          )}\n        >\n          {children}\n        </div>\n      </div>\n    );\n  }\n);\n\nFileUploaderContent.displayName = 'FileUploaderContent';\n\nexport const FileUploaderItem = forwardRef<\n  HTMLDivElement,\n  { index: number } & React.HTMLAttributes<HTMLDivElement>\n>(({ className, index, children, ...props }, ref) => {\n  const { removeFileFromSet, activeIndex, direction } = useFileUpload();\n  const isSelected = index === activeIndex;\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        'h-7 p-1 border rounded-md justify-between overflow-hidden  w-full cursor-pointer relative hover:bg-primary-foreground',\n        className,\n        isSelected ? 'bg-muted' : ''\n      )}\n      {...props}\n    >\n      <div className='font-medium   leading-none tracking-tight flex items-center gap-1.5 h-full w-full'>\n        {children}\n      </div>\n      <button\n        type='button'\n        className={cn(\n          'absolute bg-primary rounded-sm text-background p-1',\n          direction === 'rtl' ? 'top-1 left-1' : 'top-[0.145em] right-1'\n        )}\n        onClick={() => removeFileFromSet(index)}\n      >\n        <span className='sr-only'>remove item {index}</span>\n        <RemoveIcon className='w-3 h-3 hover:stroke-destructive  duration-200 ease-in-out' />\n      </button>\n    </div>\n  );\n});\n\nFileUploaderItem.displayName = 'FileUploaderItem';\n\ninterface FileInputProps extends React.HTMLAttributes<HTMLDivElement> {\n  parentclass?: string;\n  dropmsg?: string;\n}\nexport const FileInput = forwardRef<HTMLDivElement, FileInputProps>(\n  ({ className, parentclass, dropmsg, children, ...props }, ref) => {\n    const { dropzoneState, isFileTooBig, isLOF } = useFileUpload();\n    const rootProps = isLOF ? {} : dropzoneState.getRootProps();\n\n    return (\n      <div\n        ref={ref}\n        {...props}\n        className={cn(\n          'relative w-full',\n          parentclass,\n          isLOF ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'\n        )}\n      >\n        <div\n          className={cn(\n            'w-full rounded-lg transition-colors duration-300 ease-in-out',\n            dropzoneState.isDragAccept && 'border-green-500 bg-green-50',\n            dropzoneState.isDragReject && 'border-red-500 bg-red-50',\n            isFileTooBig && 'border-red-500 bg-red-200',\n            !dropzoneState.isDragActive && 'border-neutral-300 hover:border-neutral-400',\n            className\n          )}\n          {...rootProps}\n        >\n          {children}\n          {dropzoneState.isDragActive && (\n            <div className='absolute inset-0 flex items-center justify-center bg-primary-foreground/60 backdrop-blur-xs rounded-lg'>\n              <p className='text-primary font-medium'>Drop an image here.</p>\n            </div>\n          )}\n        </div>\n        <input\n          ref={dropzoneState.inputRef}\n          disabled={isLOF}\n          {...dropzoneState.getInputProps()}\n          className={cn(isLOF && 'cursor-not-allowed')}\n        />\n      </div>\n    );\n  }\n);\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}