{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "attachment",
  "type": "registry:component",
  "files": [
    {
      "content": "\"use client\";\n\nimport { type PropsWithChildren, useEffect, useState, type FC } from \"react\";\nimport { XIcon, PlusIcon, FileText } from \"lucide-react\";\nimport {\n  AttachmentPrimitive,\n  ComposerPrimitive,\n  MessagePrimitive,\n  useAuiState,\n  useAui,\n} from \"@assistant-ui/react\";\nimport { useShallow } from \"zustand/shallow\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport {\n  Dialog,\n  DialogTitle,\n  DialogContent,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\";\nimport { TooltipIconButton } from \"@/components/assistant-ui/tooltip-icon-button\";\nimport { cn } from \"@/lib/utils\";\n\nconst useFileSrc = (file: File | undefined) => {\n  const [src, setSrc] = useState<string | undefined>(undefined);\n\n  useEffect(() => {\n    if (!file) {\n      setSrc(undefined);\n      return;\n    }\n\n    const objectUrl = URL.createObjectURL(file);\n    setSrc(objectUrl);\n\n    return () => {\n      URL.revokeObjectURL(objectUrl);\n    };\n  }, [file]);\n\n  return src;\n};\n\nconst useAttachmentSrc = () => {\n  const { file, src } = useAuiState(\n    useShallow((s): { file?: File; src?: string } => {\n      if (s.attachment.type !== \"image\") return {};\n      if (s.attachment.file) return { file: s.attachment.file };\n      const src = s.attachment.content?.filter((c) => c.type === \"image\")[0]\n        ?.image;\n      if (!src) return {};\n      return { src };\n    }),\n  );\n\n  return useFileSrc(file) ?? src;\n};\n\ntype AttachmentPreviewProps = {\n  src: string;\n};\n\nconst AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {\n  const [isLoaded, setIsLoaded] = useState(false);\n  return (\n    <img\n      src={src}\n      alt=\"Attachment preview\"\n      className={cn(\n        \"block h-auto max-h-[80vh] w-auto max-w-full object-contain\",\n        isLoaded\n          ? \"aui-attachment-preview-image-loaded\"\n          : \"aui-attachment-preview-image-loading invisible\",\n      )}\n      onLoad={() => setIsLoaded(true)}\n    />\n  );\n};\n\nconst AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {\n  const src = useAttachmentSrc();\n\n  if (!src) return children;\n\n  return (\n    <Dialog>\n      <DialogTrigger\n        className=\"aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50\"\n        asChild\n      >\n        {children}\n      </DialogTrigger>\n      <DialogContent className=\"aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive\">\n        <DialogTitle className=\"aui-sr-only sr-only\">\n          Image Attachment Preview\n        </DialogTitle>\n        <div className=\"aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background\">\n          <AttachmentPreview src={src} />\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n};\n\nconst AttachmentThumb: FC = () => {\n  const src = useAttachmentSrc();\n\n  return (\n    <Avatar className=\"aui-attachment-tile-avatar h-full w-full rounded-none\">\n      <AvatarImage\n        src={src}\n        alt=\"Attachment preview\"\n        className=\"aui-attachment-tile-image object-cover\"\n      />\n      <AvatarFallback>\n        <FileText className=\"aui-attachment-tile-fallback-icon size-8 text-muted-foreground\" />\n      </AvatarFallback>\n    </Avatar>\n  );\n};\n\nconst AttachmentUI: FC = () => {\n  const aui = useAui();\n  const isComposer = aui.attachment.source !== \"message\";\n\n  const isImage = useAuiState((s) => s.attachment.type === \"image\");\n  const typeLabel = useAuiState((s) => {\n    const type = s.attachment.type;\n    switch (type) {\n      case \"image\":\n        return \"Image\";\n      case \"document\":\n        return \"Document\";\n      case \"file\":\n        return \"File\";\n      default:\n        return type;\n    }\n  });\n\n  return (\n    <Tooltip>\n      <AttachmentPrimitive.Root\n        className={cn(\n          \"aui-attachment-root relative\",\n          isImage && \"aui-attachment-root-composer only:*:first:size-24\",\n        )}\n      >\n        <AttachmentPreviewDialog>\n          <TooltipTrigger asChild>\n            <div\n              className=\"aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[calc(var(--composer-radius)-var(--composer-padding))] border bg-muted transition-opacity hover:opacity-75\"\n              role=\"button\"\n              tabIndex={0}\n              aria-label={`${typeLabel} attachment`}\n            >\n              <AttachmentThumb />\n            </div>\n          </TooltipTrigger>\n        </AttachmentPreviewDialog>\n        {isComposer && <AttachmentRemove />}\n      </AttachmentPrimitive.Root>\n      <TooltipContent side=\"top\">\n        <AttachmentPrimitive.Name />\n      </TooltipContent>\n    </Tooltip>\n  );\n};\n\nconst AttachmentRemove: FC = () => {\n  return (\n    <AttachmentPrimitive.Remove asChild>\n      <TooltipIconButton\n        tooltip=\"Remove file\"\n        className=\"aui-attachment-tile-remove absolute end-1.5 top-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black hover:[&_svg]:text-destructive\"\n        side=\"top\"\n      >\n        <XIcon className=\"aui-attachment-remove-icon size-3 dark:stroke-[2.5px]\" />\n      </TooltipIconButton>\n    </AttachmentPrimitive.Remove>\n  );\n};\n\nexport const UserMessageAttachments: FC = () => {\n  return (\n    <div className=\"aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2\">\n      <MessagePrimitive.Attachments>\n        {() => <AttachmentUI />}\n      </MessagePrimitive.Attachments>\n    </div>\n  );\n};\n\nexport const ComposerAttachments: FC = () => {\n  return (\n    <div className=\"aui-composer-attachments flex w-full flex-row items-center gap-2 overflow-x-auto empty:hidden\">\n      <ComposerPrimitive.Attachments>\n        {() => <AttachmentUI />}\n      </ComposerPrimitive.Attachments>\n    </div>\n  );\n};\n\nexport const ComposerAddAttachment: FC = () => {\n  return (\n    <ComposerPrimitive.AddAttachment asChild>\n      <TooltipIconButton\n        tooltip=\"Add Attachment\"\n        side=\"bottom\"\n        variant=\"ghost\"\n        size=\"icon\"\n        className=\"aui-composer-add-attachment size-8 rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30\"\n        aria-label=\"Add Attachment\"\n      >\n        <PlusIcon className=\"aui-attachment-add-icon size-5 stroke-[1.5px]\" />\n      </TooltipIconButton>\n    </ComposerPrimitive.AddAttachment>\n  );\n};\n",
      "type": "registry:component",
      "path": "components/assistant-ui/attachment.tsx"
    }
  ],
  "registryDependencies": [
    "dialog",
    "tooltip",
    "avatar",
    "https://r.assistant-ui.com/tooltip-icon-button.json"
  ],
  "dependencies": [
    "@assistant-ui/react",
    "lucide-react",
    "zustand"
  ]
}