{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "attachment-manager",
  "type": "registry:block",
  "title": "Attachment manager",
  "description": "Validated uploads, source downloads and confirmed asynchronous removal.",
  "dependencies": [],
  "registryDependencies": [
    "button",
    "alert-dialog",
    "https://shine-blond.vercel.app/r/file-upload.json"
  ],
  "files": [
    {
      "path": "blocks/attachment-manager.tsx",
      "type": "registry:component",
      "target": "components/shine/attachment-manager.tsx",
      "content": "\"use client\";\nimport { useRef,useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FileUpload,type UploadContext } from \"@/components/shine/file-upload\";\nimport { AlertDialog,AlertDialogContent,AlertDialogHeader,AlertDialogTitle,AlertDialogDescription,AlertDialogFooter,AlertDialogCancel,AlertDialogAction } from \"@/components/ui/alert-dialog\";\nexport function AttachmentManager({files,accept,maxBytes,onUpload,onRemove}:{files:{id:string;name:string;bytes:number;href:string}[];accept:string;maxBytes:number;onUpload:(files:File[],context:UploadContext)=>Promise<void>;onRemove:(id:string)=>Promise<void>}) {\n const [remove,setRemove]=useState<string|null>(null),[busy,setBusy]=useState(false),[error,setError]=useState(\"\");const lock=useRef(false);return <section data-product-pattern=\"attachment-manager\" className=\"space-y-4\"><FileUpload label=\"Attachments\" accept={accept} maxBytes={maxBytes} multiple onUpload={onUpload} disabled={busy}/>{!files.length?<p className=\"text-sm text-muted-foreground\">No attachments.</p>:<ul className=\"divide-y divide-border\">{files.map(file=><li key={file.id} className=\"flex items-center justify-between gap-3 py-3\"><div className=\"min-w-0\"><a href={file.href} download={file.name} className=\"break-all text-sm underline\">{file.name}</a><p className=\"text-sm text-muted-foreground\">{new Intl.NumberFormat().format(file.bytes)} bytes</p></div><Button variant=\"outline\" disabled={busy} aria-label={\"Remove \"+file.name} onClick={()=>{setRemove(file.id);setError(\"\");}}>Remove</Button></li>)}</ul>}<AlertDialog open={remove!==null} onOpenChange={open=>{if(!open&&!lock.current)setRemove(null);}}><AlertDialogContent><AlertDialogHeader><AlertDialogTitle>Remove attachment?</AlertDialogTitle><AlertDialogDescription>{files.find(file=>file.id===remove)?.name} will be removed from this record.</AlertDialogDescription></AlertDialogHeader><p role=\"alert\" className=\"text-sm text-destructive\">{error}</p><AlertDialogFooter><AlertDialogCancel disabled={busy}>Keep attachment</AlertDialogCancel><AlertDialogAction disabled={busy} onClick={async event=>{event.preventDefault();if(lock.current||!remove)return;lock.current=true;setBusy(true);try{await onRemove(remove);setRemove(null);}catch(cause){setError(cause instanceof Error?cause.message:\"Removal failed. Try again.\");}finally{lock.current=false;setBusy(false);}}}>{busy?\"Removing…\":\"Remove attachment\"}</AlertDialogAction></AlertDialogFooter></AlertDialogContent></AlertDialog></section>;\n}\n"
    }
  ]
}
