{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "team-files",
  "title": "Team Files",
  "description": "Manage and share team files with upload and download.",
  "registryDependencies": [
    "badge",
    "button",
    "card",
    "dropdown-menu",
    "empty",
    "input-group"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/team/team-files.tsx",
      "content": "\"use client\";\n\nimport {\n  Download,\n  File,\n  FileImage,\n  FileText,\n  MoreVertical,\n  Search,\n  Sparkles,\n  Trash2,\n  Upload,\n} from \"lucide-react\";\nimport { useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport {\n  Empty,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle,\n} from \"@/registry/new-york/ui/empty\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\n\nexport interface TeamFile {\n  id: string;\n  name: string;\n  type: string;\n  size: number;\n  uploadedBy: {\n    id: string;\n    name: string;\n    avatar?: string;\n  };\n  uploadedAt: Date;\n  tags?: string[];\n  aiAccessible?: boolean;\n  url?: string;\n}\n\nexport interface TeamFilesProps {\n  files?: TeamFile[];\n  onUpload?: (files: File[]) => Promise<void>;\n  onDelete?: (fileId: string) => Promise<void>;\n  onDownload?: (fileId: string) => Promise<void>;\n  onToggleAIAccess?: (fileId: string, enabled: boolean) => Promise<void>;\n  className?: string;\n  showSearch?: boolean;\n  showTags?: boolean;\n  maxSize?: number;\n}\n\nfunction formatBytes(bytes: number): string {\n  if (bytes === 0) return \"0 Bytes\";\n  const k = 1024;\n  const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"];\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\n  return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;\n}\n\nfunction formatDate(date: Date): string {\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n  }).format(date);\n}\n\nfunction getFileIcon(type: string) {\n  if (type.startsWith(\"image/\")) {\n    return FileImage;\n  }\n  if (type.includes(\"pdf\") || type.includes(\"document\")) {\n    return FileText;\n  }\n  return File;\n}\n\nfunction getFileTypeLabel(type: string): string {\n  if (type.startsWith(\"image/\")) {\n    return \"Image\";\n  }\n  if (type.includes(\"pdf\")) {\n    return \"PDF\";\n  }\n  if (type.includes(\"text\")) {\n    return \"Text\";\n  }\n  return \"File\";\n}\n\nexport default function TeamFiles({\n  files = [],\n  onUpload,\n  onDelete,\n  onDownload,\n  onToggleAIAccess,\n  className,\n  showSearch = true,\n  showTags = true,\n  maxSize = 100 * 1024 * 1024, // 100MB default\n}: TeamFilesProps) {\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [isDragging, setIsDragging] = useState(false);\n  const fileInputRef = useRef<HTMLInputElement | null>(null);\n\n  const filteredFiles = files.filter((file) => {\n    if (!searchQuery.trim()) return true;\n    const query = searchQuery.toLowerCase();\n    return (\n      file.name.toLowerCase().includes(query) ||\n      file.tags?.some((tag) => tag.toLowerCase().includes(query))\n    );\n  });\n\n  const handleDragOver = (e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDragging(true);\n  };\n\n  const handleDragLeave = (e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDragging(false);\n  };\n\n  const handleDrop = async (e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDragging(false);\n\n    const droppedFiles = Array.from(e.dataTransfer.files);\n    const validFiles = droppedFiles.filter((file) => file.size <= maxSize);\n\n    if (validFiles.length > 0) {\n      await onUpload?.(validFiles);\n    }\n  };\n\n  const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {\n    const selectedFiles = Array.from(e.target.files || []);\n    const validFiles = selectedFiles.filter((file) => file.size <= maxSize);\n    if (validFiles.length > 0) {\n      await onUpload?.(validFiles);\n    }\n    if (fileInputRef.current) {\n      fileInputRef.current.value = \"\";\n    }\n  };\n\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-4\">\n          <div className=\"flex flex-col gap-1\">\n            <CardTitle>Team Files</CardTitle>\n            <CardDescription>\n              {files.length} file{files.length !== 1 ? \"s\" : \"\"} shared with\n              your team\n            </CardDescription>\n          </div>\n          {showSearch && (\n            <InputGroup>\n              <InputGroupAddon>\n                <Search className=\"size-4\" />\n              </InputGroupAddon>\n              <InputGroupInput\n                onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n                  setSearchQuery(e.target.value)\n                }\n                placeholder=\"Search files…\"\n                type=\"search\"\n                value={searchQuery}\n              />\n            </InputGroup>\n          )}\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-4\">\n          {/* Upload Area */}\n          {onUpload && (\n            <div\n              className={cn(\n                \"flex cursor-pointer flex-col items-center justify-center gap-4 rounded-lg border-2 border-dashed p-8 transition-colors\",\n                isDragging\n                  ? \"border-primary bg-primary/5\"\n                  : \"border-muted bg-muted/30 hover:border-primary/50\"\n              )}\n              onDragLeave={handleDragLeave}\n              onDragOver={handleDragOver}\n              onDrop={handleDrop}\n            >\n              <div className=\"flex size-12 items-center justify-center rounded-full bg-primary/10\">\n                <Upload className=\"size-6 text-primary\" />\n              </div>\n              <div className=\"flex flex-col gap-2 text-center\">\n                <p className=\"font-medium text-sm\">\n                  Drag and drop files here, or{\" \"}\n                  <button\n                    className=\"text-primary underline\"\n                    onClick={() => fileInputRef.current?.click()}\n                    type=\"button\"\n                  >\n                    click to browse\n                  </button>\n                </p>\n                <p className=\"text-muted-foreground text-xs\">\n                  Max file size: {formatBytes(maxSize)}\n                </p>\n              </div>\n              <input\n                accept=\"*/*\"\n                className=\"hidden\"\n                multiple\n                onChange={handleFileSelect}\n                ref={fileInputRef}\n                type=\"file\"\n              />\n            </div>\n          )}\n\n          {/* Files List */}\n          {filteredFiles.length === 0 ? (\n            <Empty>\n              <EmptyHeader>\n                <EmptyMedia variant=\"icon\">\n                  <File className=\"size-6\" />\n                </EmptyMedia>\n                <EmptyTitle>\n                  {searchQuery ? \"No files found\" : \"No files yet\"}\n                </EmptyTitle>\n              </EmptyHeader>\n            </Empty>\n          ) : (\n            <div className=\"flex flex-col gap-2\">\n              {filteredFiles.map((file, idx) => {\n                const Icon = getFileIcon(file.type);\n                return (\n                  <div\n                    className=\"flex items-center gap-4 rounded-lg border bg-card p-4 transition-colors hover:bg-muted/50\"\n                    key={file.id}\n                  >\n                    <div className=\"flex size-10 shrink-0 items-center justify-center rounded-md bg-muted\">\n                      <Icon className=\"size-5 text-muted-foreground\" />\n                    </div>\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                      <div className=\"flex flex-wrap items-center gap-1\">\n                        <span className=\"wrap-break-word font-medium text-sm\">\n                          {file.name}\n                        </span>\n                        {file.aiAccessible && (\n                          <Badge className=\"text-xs\" variant=\"secondary\">\n                            AI Accessible\n                          </Badge>\n                        )}\n                      </div>\n                      <div className=\"flex flex-wrap items-center gap-2 text-muted-foreground text-xs\">\n                        <span>{getFileTypeLabel(file.type)}</span>\n                        <span aria-hidden=\"true\">•</span>\n                        <span>{formatBytes(file.size)}</span>\n                        <span aria-hidden=\"true\">•</span>\n                        <span>\n                          Uploaded by {file.uploadedBy.name} on{\" \"}\n                          {formatDate(file.uploadedAt)}\n                        </span>\n                      </div>\n                      {showTags && file.tags && file.tags.length > 0 && (\n                        <div className=\"flex flex-wrap gap-1\">\n                          {file.tags.map((tag) => (\n                            <Badge\n                              className=\"text-xs\"\n                              key={tag}\n                              variant=\"outline\"\n                            >\n                              {tag}\n                            </Badge>\n                          ))}\n                        </div>\n                      )}\n                    </div>\n                    <DropdownMenu>\n                      <DropdownMenuTrigger asChild>\n                        <Button\n                          aria-label={`More options for ${file.name}`}\n                          size=\"icon\"\n                          type=\"button\"\n                          variant=\"ghost\"\n                        >\n                          <MoreVertical className=\"size-4\" />\n                        </Button>\n                      </DropdownMenuTrigger>\n                      <DropdownMenuContent\n                        align=\"end\"\n                        collisionPadding={8}\n                        sideOffset={4}\n                      >\n                        {onDownload && (\n                          <DropdownMenuItem\n                            onSelect={() => onDownload(file.id)}\n                          >\n                            <Download className=\"size-4\" />\n                            Download\n                          </DropdownMenuItem>\n                        )}\n                        {onToggleAIAccess && (\n                          <DropdownMenuItem\n                            onSelect={() =>\n                              onToggleAIAccess(file.id, !file.aiAccessible)\n                            }\n                          >\n                            <Sparkles className=\"size-4\" />\n                            {file.aiAccessible\n                              ? \"Disable AI Access\"\n                              : \"Enable AI Access\"}\n                          </DropdownMenuItem>\n                        )}\n                        {onDelete && (\n                          <>\n                            <DropdownMenuSeparator />\n                            <DropdownMenuItem\n                              onSelect={() => onDelete(file.id)}\n                              variant=\"destructive\"\n                            >\n                              <Trash2 className=\"size-4\" />\n                              Delete\n                            </DropdownMenuItem>\n                          </>\n                        )}\n                      </DropdownMenuContent>\n                    </DropdownMenu>\n                  </div>\n                );\n              })}\n            </div>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "team"
  ],
  "type": "registry:ui"
}