{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "attachment-sidebar",
  "title": "Attachment Sidebar",
  "description": "File attachment navigator built from SidebarList primitives and FileThumbnail.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@retab/utils",
    "@retab/sidebar-list",
    "@retab/file-size-format",
    "@retab/file-thumbnail"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/attachment-sidebar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Paperclip } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { createViewerResource } from \"@/lib/viewer-resource\";\nimport type { ViewerSource } from \"@/lib/viewer-source\";\n\nimport { formatFileSize } from \"./file-size-format\";\nimport { FileThumbnail } from \"./file-thumbnail\";\nimport {\n  SidebarListButton,\n  SidebarListContent,\n  SidebarListGroup,\n  SidebarListGroupContent,\n  SidebarListGroupLabel,\n  SidebarListHeader,\n  SidebarListMenu,\n  SidebarListMenuItem,\n  SidebarListRoot,\n  SidebarListSeparator,\n} from \"./sidebar-list\";\n\nexport interface AttachmentSidebarItem {\n  id: string;\n  source: ViewerSource;\n  label?: string;\n  description?: string;\n  size?: number | null;\n  isDisabled?: boolean;\n}\n\nexport interface AttachmentSidebarProps {\n  items: readonly AttachmentSidebarItem[];\n  selectedId?: string | null;\n  onSelect?: (id: string) => void;\n  header?: React.ReactNode;\n  emptyLabel?: React.ReactNode;\n  children?: React.ReactNode;\n  side?: \"left\" | \"right\";\n  width?: string;\n  className?: string;\n}\n\n/**\n * File attachment navigator built from providerless SidebarList primitives. It\n * owns attachment/file row semantics, while callers own the selected id and any\n * domain-specific rows they pass as children.\n */\nexport function AttachmentSidebar({\n  items,\n  selectedId,\n  onSelect,\n  header,\n  emptyLabel = \"No attachments.\",\n  children,\n  side = \"right\",\n  width = \"18rem\",\n  className,\n}: AttachmentSidebarProps) {\n  return (\n    <SidebarListRoot\n      width={width}\n      data-slot=\"attachment-sidebar\"\n      className={cn(\n        \"border-sidebar-border bg-sidebar\",\n        side === \"right\" ? \"md:border-l\" : \"md:border-r\",\n        className,\n      )}\n    >\n      <SidebarListHeader className=\"border-b px-3 py-2\">\n        {header ?? <DefaultAttachmentSidebarHeader count={items.length} />}\n      </SidebarListHeader>\n      <SidebarListContent>\n        {children ? (\n          <>\n            {children}\n            <SidebarListSeparator />\n          </>\n        ) : null}\n        <SidebarListGroup className=\"min-h-0 flex-1\">\n          <SidebarListGroupLabel>Attachments</SidebarListGroupLabel>\n          <SidebarListGroupContent>\n            {items.length === 0 ? (\n              <p className=\"text-sidebar-foreground/70 px-2 py-3 text-xs\">\n                {emptyLabel}\n              </p>\n            ) : (\n              <SidebarListMenu className=\"gap-2\">\n                {items.map((item) => (\n                  <AttachmentSidebarMenuItem\n                    key={item.id}\n                    item={item}\n                    isSelected={selectedId === item.id}\n                    onSelect={onSelect}\n                  />\n                ))}\n              </SidebarListMenu>\n            )}\n          </SidebarListGroupContent>\n        </SidebarListGroup>\n      </SidebarListContent>\n    </SidebarListRoot>\n  );\n}\n\nfunction DefaultAttachmentSidebarHeader({ count }: { count: number }) {\n  return (\n    <div className=\"text-sidebar-foreground flex h-6 items-center gap-2 text-xs font-medium\">\n      <Paperclip className=\"text-sidebar-accent-foreground size-3.5\" />\n      <span>\n        {count} attachment{count === 1 ? \"\" : \"s\"}\n      </span>\n    </div>\n  );\n}\n\nfunction AttachmentSidebarMenuItem({\n  item,\n  isSelected,\n  onSelect,\n}: {\n  item: AttachmentSidebarItem;\n  isSelected: boolean;\n  onSelect: ((id: string) => void) | undefined;\n}) {\n  const resource = createViewerResource(item.source);\n  const label = item.label?.trim() || resource.fileName;\n  const meta =\n    item.description?.trim() ||\n    (item.size != null\n      ? formatFileSize(item.size)\n      : (resource.mimeType ?? resource.descriptor.category));\n\n  return (\n    <SidebarListMenuItem>\n      <SidebarListButton\n        aria-current={isSelected ? \"page\" : undefined}\n        aria-label={`${label} ${meta}`}\n        className=\"data-[active=true]:border-sidebar-border h-auto items-start gap-2 rounded-lg border border-transparent p-2\"\n        disabled={item.isDisabled}\n        isActive={isSelected}\n        onClick={() => onSelect?.(item.id)}\n      >\n        <FileThumbnail\n          source={item.source}\n          presentation=\"decorative\"\n          thumbnailShape=\"document\"\n          thumbnailSize=\"md\"\n          className=\"flex-shrink-0\"\n        />\n        <span className=\"flex min-w-0 flex-1 flex-col gap-1 pt-0.5\">\n          <span className=\"truncate text-sm font-medium\">{label}</span>\n          <span className=\"text-sidebar-foreground/70 truncate text-xs\">\n            {meta}\n          </span>\n        </span>\n      </SidebarListButton>\n    </SidebarListMenuItem>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/attachment-sidebar.tsx"
    }
  ],
  "type": "registry:ui"
}