{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropzone",
  "type": "registry:ui",
  "title": "Dropzone",
  "description": "Composable file intake with validation, managed selection, and drag-activated overlays.",
  "dependencies": [
    "lucide-react@^1.27.0"
  ],
  "registryDependencies": [
    "http://127.0.0.1:3000/r/button.json",
    "http://127.0.0.1:3000/r/core.json",
    "http://127.0.0.1:3000/r/item.json"
  ],
  "files": [
    {
      "path": "src/registry/hooks/use-dropzone.ts",
      "target": "@components/control-ui/hooks/use-dropzone.ts",
      "type": "registry:hook",
      "content": "\"use client\";\n\nimport {\n  type ChangeEvent,\n  type ComponentProps,\n  type DragEvent as ReactDragEvent,\n  type Ref,\n  type RefObject,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\n\nimport type { DropzoneValueChangeReason, DropzoneVisualState } from \"../contracts\";\nimport {\n  compileDropzoneAccept,\n  type DropzoneFileMetadata,\n  type DropzoneFileRejection,\n  type DropzonePolicy,\n  type DropzoneProcessResult,\n  getDropzoneDragVerdict,\n  processDropzoneFiles,\n  toDropzoneError,\n} from \"../lib/dropzone-validation\";\n\nexport type DropzoneEvent = ReactDragEvent<HTMLElement> | ChangeEvent<HTMLInputElement>;\nexport type DropzoneGetFilesFromEvent = (event: DropzoneEvent, signal: AbortSignal) => readonly File[] | Promise<readonly File[]>;\nexport type DropzoneValueChangeDetails = {\n  reason: DropzoneValueChangeReason;\n  addedFiles: readonly File[];\n  removedFiles: readonly File[];\n};\nexport type DropzoneDropDetails = {\n  acceptedFiles: readonly File[];\n  fileRejections: readonly DropzoneFileRejection[];\n  event: DropzoneEvent;\n};\nexport type UseDropzoneOptions = {\n  value?: readonly File[];\n  defaultValue?: readonly File[];\n  onValueChange?: (value: readonly File[], details: DropzoneValueChangeDetails) => void;\n  policy?: DropzonePolicy;\n  disabled?: boolean;\n  drag?: boolean;\n  preventDropOnDocument?: boolean;\n  trackDocumentDrag?: boolean;\n  getFilesFromEvent?: DropzoneGetFilesFromEvent;\n  onDrop?: (details: DropzoneDropDetails) => void;\n  onError?: (error: Error) => void;\n};\nexport type UseDropzoneReturn = {\n  value: readonly File[];\n  fileRejections: readonly DropzoneFileRejection[];\n  accept: string | undefined;\n  multiple: boolean;\n  disabled: boolean;\n  dragEnabled: boolean;\n  visualState: DropzoneVisualState;\n  isDragActive: boolean;\n  isDragAccept: boolean;\n  isDragReject: boolean;\n  isDragUnknown: boolean;\n  isDragGlobal: boolean;\n  isFileDialogActive: boolean;\n  isProcessing: boolean;\n  rootRef: RefObject<HTMLDivElement | null>;\n  inputRef: RefObject<HTMLInputElement | null>;\n  open: () => void;\n  removeFile: (file: File) => void;\n  clearFiles: () => void;\n  clearRejections: () => void;\n  reset: () => void;\n  getRootProps: (props?: ComponentProps<\"div\">) => ComponentProps<\"div\">;\n  getInputProps: (props?: ComponentProps<\"input\">) => ComponentProps<\"input\">;\n};\n\ntype IntakeReason = Extract<DropzoneValueChangeReason, \"drop\" | \"input\">;\ntype ActiveDragState = Exclude<DropzoneVisualState, \"idle\" | \"processing\">;\n\nconst registeredDropzoneRoots = new WeakSet<EventTarget>();\n\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport function useDropzone({\n  value: controlledValue,\n  defaultValue = [],\n  onValueChange,\n  policy,\n  disabled = false,\n  drag = true,\n  preventDropOnDocument = true,\n  trackDocumentDrag = false,\n  getFilesFromEvent,\n  onDrop,\n  onError,\n}: UseDropzoneOptions = {}): UseDropzoneReturn {\n  const [uncontrolledValue, setUncontrolledValue] = useState<readonly File[]>(defaultValue);\n  const [fileRejections, setFileRejections] = useState<readonly DropzoneFileRejection[]>([]);\n  const [isDragActive, setIsDragActive] = useState(false);\n  const [activeDragState, setActiveDragState] = useState<ActiveDragState>(\"unknown\");\n  const [isDragGlobal, setIsDragGlobal] = useState(false);\n  const [isFileDialogActive, setIsFileDialogActive] = useState(false);\n  const [isProcessing, setIsProcessing] = useState(false);\n  const rootRef = useRef<HTMLDivElement>(null);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const mountedInput = useRef<HTMLInputElement | null>(null);\n  const localDragTargets = useRef(new Set<EventTarget>());\n  const globalDragTargets = useRef(new Set<EventTarget>());\n  const intakeController = useRef<AbortController | null>(null);\n  const fallbackDialogCleanup = useRef<(() => void) | null>(null);\n  const processingRef = useRef(false);\n  const currentValue = controlledValue ?? uncontrolledValue;\n  const currentValueRef = useRef(currentValue);\n  const disabledRef = useRef(disabled);\n  const dragEnabled = drag && !disabled;\n  const dragEnabledRef = useRef(dragEnabled);\n  const policyRef = useRef(policy);\n  const customExtractionRef = useRef(Boolean(getFilesFromEvent));\n\n  useIsomorphicLayoutEffect(() => {\n    currentValueRef.current = currentValue;\n    disabledRef.current = disabled;\n    dragEnabledRef.current = dragEnabled;\n    policyRef.current = policy;\n    customExtractionRef.current = Boolean(getFilesFromEvent);\n  });\n\n  const [handleNativeCancel] = useState(() => () => {\n    fallbackDialogCleanup.current?.();\n    fallbackDialogCleanup.current = null;\n    setIsFileDialogActive(false);\n  });\n\n  function resetLocalDrag() {\n    localDragTargets.current.clear();\n    setIsDragActive(false);\n    setActiveDragState(\"unknown\");\n  }\n\n  function resetGlobalDrag() {\n    globalDragTargets.current.clear();\n    setIsDragGlobal(false);\n  }\n\n  function resetDragState() {\n    resetLocalDrag();\n    resetGlobalDrag();\n  }\n\n  function commitValue(nextValue: readonly File[], details: DropzoneValueChangeDetails) {\n    if (areSameFiles(currentValueRef.current, nextValue)) return false;\n    currentValueRef.current = nextValue;\n    if (controlledValue === undefined) setUncontrolledValue(nextValue);\n    onValueChange?.(nextValue, details);\n    return true;\n  }\n\n  async function intake(event: DropzoneEvent, reason: IntakeReason) {\n    intakeController.current?.abort();\n    const controller = new AbortController();\n    intakeController.current = controller;\n    processingRef.current = true;\n    setIsProcessing(true);\n\n    let result: DropzoneProcessResult | undefined;\n    let shouldCommit = false;\n    try {\n      const extractedFiles = await (getFilesFromEvent ?? defaultGetFilesFromEvent)(event, controller.signal);\n      controller.signal.throwIfAborted();\n      result = await processDropzoneFiles(extractedFiles, currentValueRef.current, policy, controller.signal);\n    } catch (error) {\n      if (!controller.signal.aborted && intakeController.current === controller) onError?.(toDropzoneError(error));\n      return;\n    } finally {\n      if (!controller.signal.aborted && intakeController.current === controller) {\n        shouldCommit = true;\n        intakeController.current = null;\n        processingRef.current = false;\n        setIsProcessing(false);\n      }\n    }\n\n    if (!shouldCommit || !result) return;\n    setFileRejections(result.fileRejections);\n    commitValue(result.value, {\n      reason,\n      addedFiles: result.acceptedFiles,\n      removedFiles: result.removedFiles,\n    });\n    onDrop?.({\n      acceptedFiles: result.acceptedFiles,\n      fileRejections: result.fileRejections,\n      event,\n    });\n  }\n\n  function open() {\n    if (disabledRef.current || processingRef.current) return;\n    const input = inputRef.current;\n    if (!input) return;\n\n    fallbackDialogCleanup.current?.();\n    fallbackDialogCleanup.current = null;\n    input.value = \"\";\n    setIsFileDialogActive(true);\n\n    if (input.oncancel === undefined) {\n      const view = input.ownerDocument.defaultView;\n      let timeout: ReturnType<typeof setTimeout> | undefined;\n      const handleFocus = () => {\n        timeout = setTimeout(() => setIsFileDialogActive(false), 300);\n      };\n      view?.addEventListener(\"focus\", handleFocus, { once: true });\n      fallbackDialogCleanup.current = () => {\n        view?.removeEventListener(\"focus\", handleFocus);\n        clearTimeout(timeout);\n      };\n    }\n\n    input.click();\n  }\n\n  function removeFile(file: File) {\n    if (disabledRef.current || processingRef.current) return;\n    const index = currentValueRef.current.indexOf(file);\n    if (index < 0) return;\n    const nextValue = currentValueRef.current.filter((_, fileIndex) => fileIndex !== index);\n    commitValue(nextValue, { reason: \"remove\", addedFiles: [], removedFiles: [file] });\n  }\n\n  function clearFiles() {\n    if (disabledRef.current || processingRef.current || currentValueRef.current.length === 0) return;\n    const removedFiles = currentValueRef.current;\n    commitValue([], { reason: \"clear\", addedFiles: [], removedFiles });\n  }\n\n  function clearRejections() {\n    if (disabledRef.current || processingRef.current || fileRejections.length === 0) return;\n    setFileRejections([]);\n  }\n\n  function reset() {\n    if (disabledRef.current || processingRef.current) return;\n    if (currentValueRef.current.length > 0) {\n      const removedFiles = currentValueRef.current;\n      commitValue([], { reason: \"clear\", addedFiles: [], removedFiles });\n    }\n    if (fileRejections.length > 0) setFileRejections([]);\n  }\n\n  function getRootProps(props: ComponentProps<\"div\"> = {}): ComponentProps<\"div\"> {\n    const { onDragEnter, onDragOver, onDragLeave, onDragEnd, onDrop: callerOnDrop, ref, ...rootProps } = props;\n\n    return {\n      ...rootProps,\n      ref: (node) => {\n        if (rootRef.current !== node) {\n          if (rootRef.current) registeredDropzoneRoots.delete(rootRef.current);\n          rootRef.current = node;\n          if (node) registeredDropzoneRoots.add(node);\n        }\n        assignRef(ref, node);\n      },\n      onDragEnter: (event) => {\n        onDragEnter?.(event);\n        if (!isFileDrag(event.dataTransfer)) return;\n        event.stopPropagation();\n        if (event.defaultPrevented || !dragEnabledRef.current) return;\n        event.preventDefault();\n        localDragTargets.current.add(event.target);\n        setActiveDragState(\n          getDropzoneDragVerdict(getDragMetadata(event.dataTransfer), currentValueRef.current, policyRef.current, {\n            customExtraction: customExtractionRef.current,\n          }),\n        );\n        setIsDragActive(true);\n      },\n      onDragOver: (event) => {\n        onDragOver?.(event);\n        if (!isFileDrag(event.dataTransfer)) return;\n        event.stopPropagation();\n        if (event.defaultPrevented || !dragEnabledRef.current) return;\n        event.preventDefault();\n        event.dataTransfer.dropEffect = \"copy\";\n        if (!isDragActive) {\n          setActiveDragState(\n            getDropzoneDragVerdict(getDragMetadata(event.dataTransfer), currentValueRef.current, policyRef.current, {\n              customExtraction: customExtractionRef.current,\n            }),\n          );\n          setIsDragActive(true);\n        }\n      },\n      onDragLeave: (event) => {\n        onDragLeave?.(event);\n        if (!isFileDrag(event.dataTransfer)) return;\n        event.stopPropagation();\n        localDragTargets.current.delete(event.target);\n        if (event.relatedTarget === null || localDragTargets.current.size === 0) resetLocalDrag();\n      },\n      onDragEnd: (event) => {\n        onDragEnd?.(event);\n        if (!isFileDrag(event.dataTransfer)) return;\n        event.stopPropagation();\n        resetDragState();\n      },\n      onDrop: (event) => {\n        callerOnDrop?.(event);\n        if (!isFileDrag(event.dataTransfer)) return;\n        event.stopPropagation();\n        resetDragState();\n        if (event.defaultPrevented || !dragEnabledRef.current) return;\n        event.preventDefault();\n        void intake(event, \"drop\");\n      },\n    };\n  }\n\n  function getInputProps(props: ComponentProps<\"input\"> = {}): ComponentProps<\"input\"> {\n    const { onChange, ref, ...inputProps } = props;\n    return {\n      ...inputProps,\n      type: \"file\",\n      accept: compileDropzoneAccept(policy?.accept),\n      multiple: policy?.multiple ?? true,\n      disabled,\n      ref: (node) => {\n        if (mountedInput.current !== node) {\n          mountedInput.current?.removeEventListener(\"cancel\", handleNativeCancel);\n          mountedInput.current = node;\n          node?.addEventListener(\"cancel\", handleNativeCancel);\n        }\n        inputRef.current = node;\n        assignRef(ref, node);\n      },\n      onChange: (event) => {\n        onChange?.(event);\n        fallbackDialogCleanup.current?.();\n        fallbackDialogCleanup.current = null;\n        setIsFileDialogActive(false);\n        if (!event.defaultPrevented && !disabledRef.current) void intake(event, \"input\");\n      },\n    };\n  }\n\n  useEffect(() => {\n    if (!disabled && drag) return;\n    if (disabled) {\n      intakeController.current?.abort();\n      intakeController.current = null;\n      processingRef.current = false;\n      setIsProcessing(false);\n      fallbackDialogCleanup.current?.();\n      fallbackDialogCleanup.current = null;\n      setIsFileDialogActive(false);\n    }\n    localDragTargets.current.clear();\n    globalDragTargets.current.clear();\n    setIsDragActive(false);\n    setActiveDragState(\"unknown\");\n    setIsDragGlobal(false);\n  }, [disabled, drag]);\n\n  useEffect(() => {\n    const ownerDocument = rootRef.current?.ownerDocument ?? document;\n    const ownerWindow = ownerDocument.defaultView;\n\n    function resetDocumentGlobalDrag() {\n      globalDragTargets.current.clear();\n      setIsDragGlobal(false);\n    }\n\n    function resetDocumentDragState() {\n      localDragTargets.current.clear();\n      globalDragTargets.current.clear();\n      setIsDragActive(false);\n      setActiveDragState(\"unknown\");\n      setIsDragGlobal(false);\n    }\n\n    function handleDocumentDragOver(event: DragEvent) {\n      if (preventDropOnDocument && isFileDrag(event.dataTransfer)) event.preventDefault();\n    }\n\n    function handleDocumentDrop(event: DragEvent) {\n      resetDocumentDragState();\n      if (preventDropOnDocument && isFileDrag(event.dataTransfer)) event.preventDefault();\n    }\n\n    function handleGlobalDragEnter(event: DragEvent) {\n      if (!trackDocumentDrag || !dragEnabledRef.current || !isFileDrag(event.dataTransfer)) return;\n      const dragOwner = getRegisteredDropzoneRoot(event);\n      if (dragOwner && dragOwner !== rootRef.current) {\n        resetDocumentGlobalDrag();\n        return;\n      }\n      globalDragTargets.current.add(event.target ?? ownerDocument);\n      setIsDragGlobal(true);\n    }\n\n    function handleGlobalDragLeave(event: DragEvent) {\n      if (!isFileDrag(event.dataTransfer)) return;\n      globalDragTargets.current.delete(event.target ?? ownerDocument);\n      if (event.relatedTarget === null || globalDragTargets.current.size === 0) {\n        resetDocumentGlobalDrag();\n      }\n    }\n\n    function handleTerminalDrag() {\n      resetDocumentDragState();\n    }\n\n    ownerDocument.addEventListener(\"dragover\", handleDocumentDragOver);\n    ownerDocument.addEventListener(\"drop\", handleDocumentDrop);\n    ownerDocument.addEventListener(\"dragenter\", handleGlobalDragEnter, true);\n    ownerDocument.addEventListener(\"dragleave\", handleGlobalDragLeave, true);\n    ownerDocument.addEventListener(\"drop\", handleTerminalDrag, true);\n    ownerDocument.addEventListener(\"dragend\", handleTerminalDrag, true);\n    ownerWindow?.addEventListener(\"blur\", handleTerminalDrag);\n\n    return () => {\n      ownerDocument.removeEventListener(\"dragover\", handleDocumentDragOver);\n      ownerDocument.removeEventListener(\"drop\", handleDocumentDrop);\n      ownerDocument.removeEventListener(\"dragenter\", handleGlobalDragEnter, true);\n      ownerDocument.removeEventListener(\"dragleave\", handleGlobalDragLeave, true);\n      ownerDocument.removeEventListener(\"drop\", handleTerminalDrag, true);\n      ownerDocument.removeEventListener(\"dragend\", handleTerminalDrag, true);\n      ownerWindow?.removeEventListener(\"blur\", handleTerminalDrag);\n    };\n  }, [preventDropOnDocument, trackDocumentDrag]);\n\n  useEffect(\n    () => () => {\n      intakeController.current?.abort();\n      if (rootRef.current) registeredDropzoneRoots.delete(rootRef.current);\n      mountedInput.current?.removeEventListener(\"cancel\", handleNativeCancel);\n      fallbackDialogCleanup.current?.();\n    },\n    [handleNativeCancel],\n  );\n\n  let visualState: DropzoneVisualState = \"idle\";\n  if (isProcessing) visualState = \"processing\";\n  else if (isDragActive) visualState = activeDragState;\n  else if (isDragGlobal) visualState = \"unknown\";\n\n  return {\n    value: currentValue,\n    fileRejections,\n    accept: compileDropzoneAccept(policy?.accept),\n    multiple: policy?.multiple ?? true,\n    disabled,\n    dragEnabled,\n    visualState,\n    isDragActive,\n    isDragAccept: visualState === \"accept\",\n    isDragReject: visualState === \"reject\",\n    isDragUnknown: visualState === \"unknown\",\n    isDragGlobal,\n    isFileDialogActive,\n    isProcessing,\n    rootRef,\n    inputRef,\n    open,\n    removeFile,\n    clearFiles,\n    clearRejections,\n    reset,\n    getRootProps,\n    getInputProps,\n  };\n}\n\nexport function defaultGetFilesFromEvent(event: DropzoneEvent): readonly File[] {\n  return \"dataTransfer\" in event ? Array.from(event.dataTransfer.files) : Array.from(event.currentTarget.files ?? []);\n}\n\nfunction isFileDrag(dataTransfer: DataTransfer | null) {\n  return dataTransfer ? Array.from(dataTransfer.types).includes(\"Files\") : false;\n}\n\nfunction getRegisteredDropzoneRoot(event: DragEvent) {\n  for (const target of event.composedPath()) {\n    if (target && registeredDropzoneRoots.has(target)) return target;\n  }\n  return undefined;\n}\n\nfunction getDragMetadata(dataTransfer: DataTransfer): readonly DropzoneFileMetadata[] | null {\n  const files = Array.from(dataTransfer.files);\n  if (files.length > 0) return files;\n\n  const fileItems = Array.from(dataTransfer.items).filter((item) => item.kind === \"file\");\n  if (fileItems.length === 0) return null;\n  return fileItems.map((item) => item.getAsFile() ?? { type: item.type || undefined });\n}\n\nfunction areSameFiles(left: readonly File[], right: readonly File[]) {\n  return left.length === right.length && left.every((file, index) => file === right[index]);\n}\n\nfunction assignRef<Element>(ref: Ref<Element> | undefined, value: Element | null) {\n  if (typeof ref === \"function\") ref(value);\n  else if (ref) ref.current = value;\n}\n"
    },
    {
      "path": "src/registry/lib/dropzone-validation.ts",
      "target": "@components/control-ui/lib/dropzone-validation.ts",
      "type": "registry:lib",
      "content": "import type { DropzoneSelectionMode, DropzoneVisualState } from \"../contracts\";\n\nexport const DropzoneErrorCode = {\n  FileInvalidType: \"file-invalid-type\",\n  FileTooLarge: \"file-too-large\",\n  FileTooSmall: \"file-too-small\",\n  TooManyFiles: \"too-many-files\",\n  FileAlreadySelected: \"file-already-selected\",\n} as const;\n\nexport type DropzoneErrorCodeValue = (typeof DropzoneErrorCode)[keyof typeof DropzoneErrorCode];\nexport type DropzoneAccept = Readonly<Record<string, readonly string[]>>;\nexport type DropzoneFileError = { code: DropzoneErrorCodeValue | string; message: string };\nexport type DropzoneFileRejection = { file: File; errors: readonly DropzoneFileError[] };\nexport type DropzoneValidatorResult = DropzoneFileError | readonly DropzoneFileError[] | null;\nexport type DropzoneValidator = (file: File, signal: AbortSignal) => DropzoneValidatorResult | Promise<DropzoneValidatorResult>;\nexport type DropzonePolicy = {\n  accept?: DropzoneAccept;\n  minSize?: number;\n  maxSize?: number;\n  maxFiles?: number;\n  multiple?: boolean;\n  selectionMode?: DropzoneSelectionMode;\n  allowDuplicates?: boolean;\n  validator?: DropzoneValidator;\n};\n\nexport type DropzoneFileMetadata = {\n  name?: string;\n  size?: number;\n  type?: string;\n  lastModified?: number;\n  webkitRelativePath?: string;\n};\n\nexport type DropzoneProcessResult = {\n  acceptedFiles: readonly File[];\n  fileRejections: readonly DropzoneFileRejection[];\n  value: readonly File[];\n  removedFiles: readonly File[];\n};\n\nexport type DropzoneDragVerdictOptions = {\n  customExtraction?: boolean;\n};\n\ntype NormalizedAcceptEntry = {\n  mime: string;\n  extensions: readonly string[];\n};\n\ntype FileAcceptance = \"accept\" | \"reject\" | \"unknown\";\n\nconst DEFAULT_POLICY = {\n  minSize: 0,\n  maxSize: Number.POSITIVE_INFINITY,\n  maxFiles: 0,\n  multiple: true,\n  selectionMode: \"append\",\n  allowDuplicates: false,\n} satisfies Required<Pick<DropzonePolicy, \"minSize\" | \"maxSize\" | \"maxFiles\" | \"multiple\" | \"selectionMode\" | \"allowDuplicates\">>;\n\nexport function normalizeDropzoneAccept(accept: DropzoneAccept | undefined): readonly NormalizedAcceptEntry[] {\n  if (!accept) return [];\n\n  return Object.entries(accept).flatMap(([rawMime, rawExtensions]) => {\n    const mime = rawMime.trim().toLowerCase();\n    const extensions = Array.from(\n      new Set(\n        rawExtensions.flatMap((rawExtension) => {\n          const extension = rawExtension.trim().toLowerCase();\n          if (!extension) return [];\n          return [extension.startsWith(\".\") ? extension : `.${extension}`];\n        }),\n      ),\n    );\n\n    return mime || extensions.length > 0 ? [{ mime, extensions }] : [];\n  });\n}\n\nexport function compileDropzoneAccept(accept: DropzoneAccept | undefined): string | undefined {\n  const values = normalizeDropzoneAccept(accept).flatMap(({ mime, extensions }) => {\n    if (extensions.length > 0) return extensions;\n    return mime ? [mime] : [];\n  });\n  return values.length > 0 ? values.join(\",\") : undefined;\n}\n\nexport function isDropzoneFileAccepted(file: Pick<File, \"name\" | \"type\">, accept: DropzoneAccept | undefined) {\n  return getFileAcceptance(file, normalizeDropzoneAccept(accept)) === \"accept\";\n}\n\nexport function normalizeDropzoneMaxFiles(maxFiles: number | undefined) {\n  const value = maxFiles ?? DEFAULT_POLICY.maxFiles;\n  if (!Number.isFinite(value) || value <= 0) return Number.POSITIVE_INFINITY;\n  return Math.max(1, Math.floor(value));\n}\n\nexport function getDropzoneSelectionMode(policy: DropzonePolicy | undefined): DropzoneSelectionMode {\n  return policy?.multiple === false ? \"replace\" : (policy?.selectionMode ?? DEFAULT_POLICY.selectionMode);\n}\n\nexport function getDropzoneCapacity(policy: DropzonePolicy | undefined) {\n  return policy?.multiple === false ? 1 : normalizeDropzoneMaxFiles(policy?.maxFiles);\n}\n\nexport function getDropzoneFileIdentity(file: DropzoneFileMetadata) {\n  const path = file.webkitRelativePath || file.name || \"\";\n  return `${path}\\0${file.size ?? \"\"}\\0${file.type?.toLowerCase() ?? \"\"}\\0${file.lastModified ?? \"\"}`;\n}\n\nexport function toDropzoneError(value: unknown) {\n  return value instanceof Error ? value : new Error(String(value));\n}\n\nexport async function validateDropzoneFile(\n  file: File,\n  policy: DropzonePolicy | undefined,\n  signal: AbortSignal,\n): Promise<readonly DropzoneFileError[]> {\n  const errors: DropzoneFileError[] = [];\n  const minSize = policy?.minSize ?? DEFAULT_POLICY.minSize;\n  const maxSize = policy?.maxSize ?? DEFAULT_POLICY.maxSize;\n\n  if (!isDropzoneFileAccepted(file, policy?.accept)) {\n    errors.push({\n      code: DropzoneErrorCode.FileInvalidType,\n      message: \"File type is not accepted.\",\n    });\n  }\n  if (file.size < minSize) {\n    errors.push({\n      code: DropzoneErrorCode.FileTooSmall,\n      message: `File is smaller than the ${formatDropzoneFileSize(minSize)} minimum.`,\n    });\n  }\n  if (file.size > maxSize) {\n    errors.push({\n      code: DropzoneErrorCode.FileTooLarge,\n      message: `File is larger than the ${formatDropzoneFileSize(maxSize)} limit.`,\n    });\n  }\n\n  if (policy?.validator) {\n    signal.throwIfAborted();\n    const result = await policy.validator(file, signal);\n    signal.throwIfAborted();\n    if (result) errors.push(...normalizeValidatorErrors(result));\n  }\n\n  return errors;\n}\n\nexport async function processDropzoneFiles(\n  files: readonly File[],\n  currentValue: readonly File[],\n  policy: DropzonePolicy | undefined,\n  signal: AbortSignal,\n): Promise<DropzoneProcessResult> {\n  signal.throwIfAborted();\n  const validatedFiles = await Promise.all(files.map(async (file) => ({ file, errors: await validateDropzoneFile(file, policy, signal) })));\n  signal.throwIfAborted();\n\n  const acceptedFiles: File[] = [];\n  const fileRejections: DropzoneFileRejection[] = [];\n  const selectionMode = getDropzoneSelectionMode(policy);\n  const capacity = getDropzoneCapacity(policy);\n  const selectedFiles = selectionMode === \"append\" ? [...currentValue] : [];\n  const selectedIdentities = new Set(\n    policy?.allowDuplicates ? [] : (selectionMode === \"append\" ? currentValue : []).map(getDropzoneFileIdentity),\n  );\n\n  for (const { file, errors } of validatedFiles) {\n    if (errors.length > 0) {\n      fileRejections.push({ file, errors });\n      continue;\n    }\n\n    const identity = getDropzoneFileIdentity(file);\n    if (!policy?.allowDuplicates && selectedIdentities.has(identity)) {\n      fileRejections.push({\n        file,\n        errors: [\n          {\n            code: DropzoneErrorCode.FileAlreadySelected,\n            message: `File \"${file.name}\" is already selected.`,\n          },\n        ],\n      });\n      continue;\n    }\n\n    if (selectedFiles.length >= capacity) {\n      fileRejections.push({\n        file,\n        errors: [{ code: DropzoneErrorCode.TooManyFiles, message: \"Too many files.\" }],\n      });\n      continue;\n    }\n\n    acceptedFiles.push(file);\n    selectedFiles.push(file);\n    selectedIdentities.add(identity);\n  }\n\n  if (selectionMode === \"replace\" && acceptedFiles.length === 0) {\n    return {\n      acceptedFiles,\n      fileRejections,\n      value: currentValue,\n      removedFiles: [],\n    };\n  }\n\n  return {\n    acceptedFiles,\n    fileRejections,\n    value: selectedFiles,\n    removedFiles: selectionMode === \"replace\" ? getRemovedFiles(currentValue, selectedFiles) : [],\n  };\n}\n\nexport function getDropzoneDragVerdict(\n  files: readonly DropzoneFileMetadata[] | null,\n  currentValue: readonly File[],\n  policy: DropzonePolicy | undefined,\n  options: DropzoneDragVerdictOptions = {},\n): Exclude<DropzoneVisualState, \"idle\" | \"processing\"> {\n  const selectionMode = getDropzoneSelectionMode(policy);\n  const capacity = getDropzoneCapacity(policy);\n  if (selectionMode === \"append\" && currentValue.length >= capacity) return \"reject\";\n  if (!files || files.length === 0) return \"unknown\";\n\n  const availableSlots = selectionMode === \"append\" ? capacity - currentValue.length : capacity;\n  if (files.length > availableSlots) return \"reject\";\n\n  const normalizedAccept = normalizeDropzoneAccept(policy?.accept);\n  const metadataVerdicts = files.map((file) => getDropzoneMetadataVerdict(file, normalizedAccept, policy));\n  if (metadataVerdicts.includes(\"reject\")) return \"reject\";\n\n  const duplicateVerdict = getDropzoneDuplicateVerdict(files, currentValue, policy, selectionMode);\n  if (duplicateVerdict === \"reject\") return \"reject\";\n  if (policy?.validator || options.customExtraction || metadataVerdicts.includes(\"unknown\") || duplicateVerdict === \"unknown\") {\n    return \"unknown\";\n  }\n  return \"accept\";\n}\n\nexport function formatDropzoneFileSize(size: number) {\n  const safeSize = Number.isFinite(size) && size >= 0 ? size : 0;\n  const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n  if (safeSize === 0) return \"0 B\";\n\n  const unitIndex = Math.min(Math.floor(Math.log(safeSize) / Math.log(1024)), units.length - 1);\n  const value = Math.round((safeSize / 1024 ** unitIndex) * 10) / 10;\n  return `${value} ${units[unitIndex]}`;\n}\n\nfunction getFileAcceptance(file: Pick<DropzoneFileMetadata, \"name\" | \"type\">, entries: readonly NormalizedAcceptEntry[]): FileAcceptance {\n  if (entries.length === 0) return \"accept\";\n  const verdicts = entries.map((entry) => getAcceptEntryVerdict(file, entry));\n  if (verdicts.includes(\"accept\")) return \"accept\";\n  return verdicts.includes(\"unknown\") ? \"unknown\" : \"reject\";\n}\n\nfunction getAcceptEntryVerdict(\n  file: Pick<DropzoneFileMetadata, \"name\" | \"type\">,\n  { mime, extensions }: NormalizedAcceptEntry,\n): FileAcceptance {\n  if (extensions.length > 0) {\n    if (!file.name) return \"unknown\";\n    const name = file.name.toLowerCase();\n    return extensions.some((extension) => name.endsWith(extension)) ? \"accept\" : \"reject\";\n  }\n\n  if (!file.type) return \"unknown\";\n  const type = file.type.toLowerCase();\n  const matchesMime = mime.endsWith(\"/*\") ? type.startsWith(mime.slice(0, -1)) : type === mime;\n  return matchesMime ? \"accept\" : \"reject\";\n}\n\nfunction getDropzoneMetadataVerdict(\n  file: DropzoneFileMetadata,\n  normalizedAccept: readonly NormalizedAcceptEntry[],\n  policy: DropzonePolicy | undefined,\n): FileAcceptance {\n  const acceptance = getFileAcceptance(file, normalizedAccept);\n  if (acceptance === \"reject\") return \"reject\";\n  if (file.size === undefined) return \"unknown\";\n  if (file.size < (policy?.minSize ?? DEFAULT_POLICY.minSize)) return \"reject\";\n  if (file.size > (policy?.maxSize ?? DEFAULT_POLICY.maxSize)) return \"reject\";\n  return acceptance;\n}\n\nfunction getDropzoneDuplicateVerdict(\n  files: readonly DropzoneFileMetadata[],\n  currentValue: readonly File[],\n  policy: DropzonePolicy | undefined,\n  selectionMode: DropzoneSelectionMode,\n): FileAcceptance {\n  if (policy?.allowDuplicates) return \"accept\";\n\n  const selectedIdentities = new Set(selectionMode === \"append\" ? currentValue.map(getDropzoneFileIdentity) : []);\n  let hasUnknownIdentity = false;\n  for (const file of files) {\n    if (!hasCompleteIdentity(file)) {\n      hasUnknownIdentity = true;\n      continue;\n    }\n    const identity = getDropzoneFileIdentity(file);\n    if (selectedIdentities.has(identity)) return \"reject\";\n    selectedIdentities.add(identity);\n  }\n  return hasUnknownIdentity ? \"unknown\" : \"accept\";\n}\n\nfunction isDropzoneErrorList(result: DropzoneFileError | readonly DropzoneFileError[]): result is readonly DropzoneFileError[] {\n  return Array.isArray(result);\n}\n\nfunction normalizeValidatorErrors(result: DropzoneFileError | readonly DropzoneFileError[]): readonly DropzoneFileError[] {\n  return isDropzoneErrorList(result) ? result : [result];\n}\n\nfunction hasCompleteIdentity(file: DropzoneFileMetadata) {\n  return (\n    Boolean(file.webkitRelativePath || file.name) && file.size !== undefined && file.type !== undefined && file.lastModified !== undefined\n  );\n}\n\nfunction getRemovedFiles(currentValue: readonly File[], nextValue: readonly File[]) {\n  const unmatchedNext = [...nextValue];\n  return currentValue.filter((file) => {\n    const index = unmatchedNext.indexOf(file);\n    if (index < 0) return true;\n    unmatchedNext.splice(index, 1);\n    return false;\n  });\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/ui/dropzone.tsx",
      "target": "@components/control-ui/ui/dropzone.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { CircleAlert, CloudUpload, FileIcon, LoaderCircle, Search, XIcon } from \"lucide-react\";\nimport { type ComponentProps, createContext, type ReactNode, useContext, useEffect, useState } from \"react\";\nimport type { DropzoneOverlayScope, DropzoneVisualState } from \"@/components/control-ui/contracts\";\nimport {\n  type DropzoneDropDetails,\n  type DropzoneGetFilesFromEvent,\n  type DropzoneValueChangeDetails,\n  type UseDropzoneReturn,\n  useDropzone,\n} from \"@/components/control-ui/hooks/use-dropzone\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\nimport { type DropzoneFileRejection, type DropzonePolicy, formatDropzoneFileSize } from \"@/components/control-ui/lib/dropzone-validation\";\nimport { skinSlot } from \"@/components/control-ui/skin\";\nimport { Button } from \"@/components/control-ui/ui/button\";\nimport { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle } from \"@/components/control-ui/ui/item\";\n\nexport type DropzoneProps = Omit<ComponentProps<\"div\">, \"defaultValue\" | \"onChange\" | \"onDrop\" | \"onError\"> & {\n  value?: readonly File[];\n  defaultValue?: readonly File[];\n  onValueChange?: (value: readonly File[], details: DropzoneValueChangeDetails) => void;\n  policy?: DropzonePolicy;\n  disabled?: boolean;\n  getFilesFromEvent?: DropzoneGetFilesFromEvent;\n  onDrop?: (details: DropzoneDropDetails) => void;\n  onError?: (error: Error) => void;\n};\nexport type DropzoneAreaProps = ComponentProps<\"div\">;\nexport type DropzoneInputProps = Omit<ComponentProps<\"input\">, \"accept\" | \"disabled\" | \"multiple\" | \"onChange\" | \"onClick\" | \"type\">;\nexport type DropzoneTriggerProps = ComponentProps<\"button\">;\nexport type DropzoneOverlayProps = ComponentProps<\"div\"> & { scope?: DropzoneOverlayScope };\nexport type DropzoneFileListProps = Omit<ComponentProps<\"ul\">, \"children\"> & {\n  children?: (file: File, index: number) => ReactNode;\n};\nexport type DropzoneRejectionListProps = Omit<ComponentProps<\"ul\">, \"children\"> & {\n  children?: (rejection: DropzoneFileRejection, index: number) => ReactNode;\n};\nexport type DropzoneContextValue = UseDropzoneReturn & {\n  registerGlobalOverlay: () => () => void;\n};\nexport type DropzoneStatusProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n  children?: (state: DropzoneContextValue) => ReactNode;\n};\nexport type DropzoneRemoveProps = ComponentProps<typeof Button> & { file: File };\nexport type DropzoneClearProps = ComponentProps<typeof Button>;\n\nconst DropzoneContext = createContext<DropzoneContextValue | null>(null);\nconst fileRenderKeys = new WeakMap<File, string>();\nlet nextFileRenderKey = 0;\n\nfunction getFileRenderKey(file: File) {\n  const existingKey = fileRenderKeys.get(file);\n  if (existingKey) return existingKey;\n  const key = `dropzone-file-${nextFileRenderKey}`;\n  nextFileRenderKey += 1;\n  fileRenderKeys.set(file, key);\n  return key;\n}\n\nexport function useDropzoneContext() {\n  const context = useContext(DropzoneContext);\n  if (!context) throw new Error(\"Dropzone parts must be used inside Dropzone.\");\n  return context;\n}\n\nexport function Dropzone({\n  value,\n  defaultValue,\n  onValueChange,\n  policy,\n  disabled,\n  getFilesFromEvent,\n  onDrop,\n  onError,\n  className,\n  children,\n  ...props\n}: DropzoneProps) {\n  const [globalOverlayCount, setGlobalOverlayCount] = useState(0);\n  const [registerGlobalOverlay] = useState<() => () => void>(() => () => {\n    let registered = true;\n    setGlobalOverlayCount((count) => count + 1);\n    return () => {\n      if (!registered) return;\n      registered = false;\n      setGlobalOverlayCount((count) => Math.max(0, count - 1));\n    };\n  });\n  const dropzone = useDropzone({\n    value,\n    defaultValue,\n    onValueChange,\n    policy,\n    disabled,\n    getFilesFromEvent,\n    onDrop,\n    onError,\n    trackDocumentDrag: globalOverlayCount > 0,\n  });\n  const context = { ...dropzone, registerGlobalOverlay };\n  const empty = dropzone.value.length === 0 && dropzone.fileRejections.length === 0;\n\n  return (\n    <DropzoneContext.Provider value={context}>\n      <div\n        {...props}\n        data-control-ui=\"dropzone\"\n        data-slot=\"root\"\n        data-disabled={dropzone.disabled ? \"true\" : undefined}\n        data-empty={empty ? \"true\" : undefined}\n        className={cn(\"min-w-0\", skinSlot(\"dropzone\", \"root\", { disabled: dropzone.disabled, empty }), className)}\n      >\n        {children}\n      </div>\n    </DropzoneContext.Provider>\n  );\n}\n\nexport function DropzoneArea({ className, ...props }: DropzoneAreaProps) {\n  const context = useDropzoneContext();\n  const areaProps = context.getRootProps(props);\n  return (\n    <div\n      {...areaProps}\n      aria-busy={context.isProcessing ? \"true\" : \"false\"}\n      data-control-ui=\"dropzone\"\n      data-slot=\"area\"\n      data-state={context.visualState}\n      data-disabled={context.disabled ? \"true\" : undefined}\n      className={cn(\n        \"relative isolate min-w-0\",\n        skinSlot(\"dropzone\", \"area\", {\n          state: context.visualState,\n          disabled: context.disabled,\n        }),\n        className,\n      )}\n    />\n  );\n}\n\nexport function DropzoneInput({ className, \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledBy, ...props }: DropzoneInputProps) {\n  const context = useDropzoneContext();\n  const inputProps = context.getInputProps({\n    ...props,\n    \"aria-label\": ariaLabelledBy ? ariaLabel : (ariaLabel ?? \"Choose files\"),\n    \"aria-labelledby\": ariaLabelledBy,\n  });\n  return (\n    <input\n      {...inputProps}\n      data-control-ui=\"dropzone\"\n      data-slot=\"input\"\n      className={cn(\"sr-only\", skinSlot(\"dropzone\", \"input\", {}), className)}\n    />\n  );\n}\n\nexport function DropzoneTrigger({ className, children, onClick, disabled, ...props }: DropzoneTriggerProps) {\n  const context = useDropzoneContext();\n  const triggerDisabled = disabled || context.disabled || context.isProcessing;\n\n  return (\n    <button\n      {...props}\n      type=\"button\"\n      disabled={triggerDisabled}\n      data-control-ui=\"dropzone\"\n      data-slot=\"trigger\"\n      data-state={context.visualState}\n      data-disabled={triggerDisabled ? \"true\" : undefined}\n      className={cn(\n        \"flex min-h-32 w-full cursor-pointer flex-col items-center justify-center gap-3 rounded-[var(--radius-panel)] border border-dashed border-border bg-card p-6 text-center text-foreground outline-none transition-[background-color,border-color,color,opacity] duration-[var(--duration-fast)] ease-[var(--ease-standard)] hover:bg-muted/50 focus-visible:ring-2 focus-visible:ring-foreground/20 disabled:cursor-not-allowed disabled:opacity-50 data-[state=accept]:border-primary data-[state=accept]:text-primary-text data-[state=reject]:border-destructive data-[state=reject]:text-destructive-text data-[state=unknown]:border-muted-foreground data-[state=unknown]:text-muted-foreground\",\n        skinSlot(\"dropzone\", \"trigger\", {\n          state: context.visualState,\n          disabled: triggerDisabled,\n        }),\n        className,\n      )}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) context.open();\n      }}\n    >\n      {children ?? <DropzoneFeedback state={context.visualState} />}\n    </button>\n  );\n}\n\nexport function DropzoneOverlay({ scope = \"local\", className, children, ...props }: DropzoneOverlayProps) {\n  const context = useDropzoneContext();\n  const active = scope === \"global\" ? context.isDragGlobal : context.isDragActive;\n\n  useEffect(() => {\n    if (scope !== \"global\") return;\n    return context.registerGlobalOverlay();\n  }, [context.registerGlobalOverlay, scope]);\n\n  return (\n    <div\n      {...props}\n      aria-hidden=\"true\"\n      data-control-ui=\"dropzone\"\n      data-slot=\"overlay\"\n      data-state={context.visualState}\n      data-active={active ? \"true\" : undefined}\n      data-scope={scope}\n      className={cn(\n        \"pointer-events-none absolute inset-0 z-10 hidden items-center justify-center rounded-[var(--radius-panel)] border border-primary bg-card/95 p-6 text-center text-primary-text opacity-0 transition-[display,opacity] transition-discrete duration-[var(--duration-fast)] ease-[var(--ease-standard)] data-[active=true]:flex data-[active=true]:opacity-100 data-[state=reject]:border-destructive data-[state=reject]:text-destructive-text data-[state=unknown]:border-muted-foreground data-[state=unknown]:text-muted-foreground starting:opacity-0\",\n        skinSlot(\"dropzone\", \"overlay\", {\n          state: context.visualState,\n          active,\n          scope,\n        }),\n        className,\n      )}\n    >\n      {children ?? <DropzoneFeedback state={context.visualState} />}\n    </div>\n  );\n}\n\nexport function DropzoneFileList({\n  className,\n  children,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}: DropzoneFileListProps) {\n  const context = useDropzoneContext();\n  const empty = context.value.length === 0;\n\n  return (\n    <ul\n      {...props}\n      hidden={empty}\n      aria-label={ariaLabelledBy ? ariaLabel : (ariaLabel ?? \"Selected files\")}\n      aria-labelledby={ariaLabelledBy}\n      data-control-ui=\"dropzone\"\n      data-slot=\"file-list\"\n      data-empty={empty ? \"true\" : undefined}\n      className={cn(\"mt-3 flex flex-col gap-2\", skinSlot(\"dropzone\", \"file-list\", { empty }), className)}\n    >\n      {context.value.map((file, index) => (\n        <li key={getFileRenderKey(file)} data-control-ui=\"dropzone\" data-slot=\"file\" className={skinSlot(\"dropzone\", \"file\", {})}>\n          {children ? children(file, index) : <DefaultFile file={file} />}\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nexport function DropzoneRejectionList({\n  className,\n  children,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}: DropzoneRejectionListProps) {\n  const context = useDropzoneContext();\n  const empty = context.fileRejections.length === 0;\n\n  return (\n    <ul\n      {...props}\n      hidden={empty}\n      aria-label={ariaLabelledBy ? ariaLabel : (ariaLabel ?? \"Rejected files\")}\n      aria-labelledby={ariaLabelledBy}\n      data-control-ui=\"dropzone\"\n      data-slot=\"rejection-list\"\n      data-empty={empty ? \"true\" : undefined}\n      className={cn(\"mt-3 flex flex-col gap-2\", skinSlot(\"dropzone\", \"rejection-list\", { empty }), className)}\n    >\n      {context.fileRejections.map((rejection, index) => (\n        <li\n          key={getFileRenderKey(rejection.file)}\n          data-control-ui=\"dropzone\"\n          data-slot=\"rejection\"\n          className={skinSlot(\"dropzone\", \"rejection\", {})}\n        >\n          {children ? children(rejection, index) : <DefaultRejection rejection={rejection} />}\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nexport function DropzoneStatus({ className, children, role = \"status\", \"aria-live\": ariaLive = \"polite\", ...props }: DropzoneStatusProps) {\n  const context = useDropzoneContext();\n  return (\n    <div\n      {...props}\n      role={role}\n      aria-live={ariaLive}\n      data-control-ui=\"dropzone\"\n      data-slot=\"status\"\n      data-state={context.visualState}\n      className={cn(\"mt-3 text-sm text-muted-foreground\", skinSlot(\"dropzone\", \"status\", { state: context.visualState }), className)}\n    >\n      {children ? children(context) : getDropzoneStatusMessage(context)}\n    </div>\n  );\n}\n\nexport function DropzoneRemove({ file, children, onClick, disabled, ...props }: DropzoneRemoveProps) {\n  const context = useDropzoneContext();\n  return (\n    <Button\n      {...props}\n      type=\"button\"\n      variant={props.variant ?? \"quiet\"}\n      size={props.size ?? \"sm\"}\n      iconOnly={props.iconOnly ?? true}\n      aria-label={props[\"aria-label\"] ?? `Remove ${file.name}`}\n      disabled={disabled || context.disabled || context.isProcessing}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) context.removeFile(file);\n      }}\n    >\n      {children ?? <XIcon aria-hidden=\"true\" />}\n    </Button>\n  );\n}\n\nexport function DropzoneClear({ children, onClick, disabled, ...props }: DropzoneClearProps) {\n  const context = useDropzoneContext();\n  const empty = context.value.length === 0 && context.fileRejections.length === 0;\n  return (\n    <Button\n      {...props}\n      type=\"button\"\n      variant={props.variant ?? \"quiet\"}\n      size={props.size ?? \"sm\"}\n      disabled={disabled || context.disabled || context.isProcessing || empty}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) context.reset();\n      }}\n    >\n      {children ?? \"Clear all\"}\n    </Button>\n  );\n}\n\nfunction DropzoneFeedback({ state }: { state: DropzoneVisualState }) {\n  let icon: ReactNode = <CloudUpload aria-hidden=\"true\" />;\n  let message = \"Drop files here or choose files.\";\n  if (state === \"processing\") {\n    icon = <LoaderCircle className=\"animate-spin\" aria-hidden=\"true\" />;\n    message = \"Checking files…\";\n  } else if (state === \"accept\") {\n    icon = <CloudUpload aria-hidden=\"true\" />;\n    message = \"Release to add files.\";\n  } else if (state === \"reject\") {\n    icon = <CircleAlert aria-hidden=\"true\" />;\n    message = \"Some files are not accepted.\";\n  } else if (state === \"unknown\") {\n    icon = <Search aria-hidden=\"true\" />;\n    message = \"Release to check files.\";\n  }\n\n  return (\n    <span className=\"flex flex-col items-center gap-2\">\n      <span className=\"[&>svg]:size-6\">{icon}</span>\n      <span className=\"text-sm font-medium\">{message}</span>\n    </span>\n  );\n}\n\nfunction DefaultFile({ file }: { file: File }) {\n  return (\n    <Item variant=\"muted\" className=\"py-2\">\n      <ItemMedia>\n        <FileIcon aria-hidden=\"true\" />\n      </ItemMedia>\n      <ItemContent>\n        <ItemTitle className=\"wrap-anywhere leading-normal\">{file.name}</ItemTitle>\n        <ItemDescription>{formatDropzoneFileSize(file.size)}</ItemDescription>\n      </ItemContent>\n      <ItemActions>\n        <DropzoneRemove file={file} />\n      </ItemActions>\n    </Item>\n  );\n}\n\nfunction DefaultRejection({ rejection }: { rejection: DropzoneFileRejection }) {\n  return (\n    <Item variant=\"muted\" className=\"py-2\">\n      <ItemMedia className=\"text-destructive-text\">\n        <CircleAlert aria-hidden=\"true\" />\n      </ItemMedia>\n      <ItemContent>\n        <ItemTitle className=\"wrap-anywhere leading-normal\">{rejection.file.name}</ItemTitle>\n        {rejection.errors.map((error) => (\n          <ItemDescription key={`${error.code}-${error.message}`} className=\"text-destructive-text\">\n            {error.message}\n          </ItemDescription>\n        ))}\n      </ItemContent>\n    </Item>\n  );\n}\n\nfunction getDropzoneStatusMessage(context: DropzoneContextValue) {\n  if (context.isProcessing) return \"Checking files…\";\n  if (context.isDragActive && context.isDragReject) return \"Some files are not accepted.\";\n  if (context.isDragActive && context.isDragAccept) return \"Release to add files.\";\n  if (context.isDragActive && context.isDragUnknown) return \"Release to check files.\";\n  if (context.fileRejections.length > 0) {\n    const count = context.fileRejections.length;\n    return `${count} ${count === 1 ? \"file\" : \"files\"} could not be added.`;\n  }\n  if (context.value.length > 0) {\n    const count = context.value.length;\n    return `${count} ${count === 1 ? \"file\" : \"files\"} selected.`;\n  }\n  return \"No files selected.\";\n}\n\nexport type {\n  DropzoneOverlayScope,\n  DropzoneSelectionMode,\n  DropzoneValueChangeReason,\n  DropzoneVisualState,\n} from \"@/components/control-ui/contracts\";\nexport type {\n  DropzoneDropDetails,\n  DropzoneEvent,\n  DropzoneGetFilesFromEvent,\n  DropzoneValueChangeDetails,\n  UseDropzoneOptions,\n  UseDropzoneReturn,\n} from \"@/components/control-ui/hooks/use-dropzone\";\n// biome-ignore lint/performance/noBarrelFile: The component intentionally exposes one install-facing API.\nexport { useDropzone } from \"@/components/control-ui/hooks/use-dropzone\";\nexport type {\n  DropzoneAccept,\n  DropzoneErrorCodeValue,\n  DropzoneFileError,\n  DropzoneFileRejection,\n  DropzonePolicy,\n  DropzoneValidator,\n  DropzoneValidatorResult,\n} from \"@/components/control-ui/lib/dropzone-validation\";\nexport {\n  DropzoneErrorCode,\n  formatDropzoneFileSize,\n} from \"@/components/control-ui/lib/dropzone-validation\";\n"
    }
  ],
  "meta": {}
}
