{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "task-detail",
  "title": "Task Detail",
  "description": "Detailed view of a task with subtasks, comments, attachments, and activity log. Includes URL-synced tabs.",
  "dependencies": [
    "nuqs"
  ],
  "registryDependencies": [
    "avatar",
    "badge",
    "button",
    "card",
    "checkbox",
    "dialog",
    "dropdown-menu",
    "input",
    "task-list",
    "tabs",
    "textarea"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/tasks/task-detail.tsx",
      "content": "\"use client\";\n\nimport {\n  Calendar,\n  FileText,\n  MessageSquare,\n  MoreVertical,\n  Paperclip,\n  Plus,\n} from \"lucide-react\";\nimport { parseAsStringEnum, useQueryState } from \"nuqs\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Avatar,\n  AvatarFallback,\n  AvatarImage,\n} from \"@/registry/new-york/ui/avatar\";\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 { Checkbox } from \"@/registry/new-york/ui/checkbox\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/registry/new-york/ui/dialog\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport { Input } from \"@/registry/new-york/ui/input\";\nimport {\n  Tabs,\n  TabsContent,\n  TabsList,\n  TabsTrigger,\n} from \"@/registry/new-york/ui/tabs\";\nimport { Textarea } from \"@/registry/new-york/ui/textarea\";\nimport type { Task, TaskAssignee, TaskPriority, TaskStatus } from \"./task-list\";\n\nexport interface TaskComment {\n  id: string;\n  content: string;\n  author: TaskAssignee;\n  createdAt: Date;\n  updatedAt?: Date;\n}\n\nexport interface TaskAttachment {\n  id: string;\n  name: string;\n  url: string;\n  size: number;\n  type: string;\n  uploadedBy: TaskAssignee;\n  uploadedAt: Date;\n}\n\nexport interface TaskActivity {\n  id: string;\n  type: \"created\" | \"updated\" | \"assigned\" | \"commented\" | \"completed\";\n  user: TaskAssignee;\n  description: string;\n  timestamp: Date;\n}\n\nexport interface Subtask {\n  id: string;\n  title: string;\n  completed: boolean;\n  dueDate?: Date;\n}\n\nexport interface TaskDetailProps {\n  task?: Task | null;\n  comments?: TaskComment[];\n  attachments?: TaskAttachment[];\n  activities?: TaskActivity[];\n  subtasks?: Subtask[];\n  onUpdate?: (updates: Partial<Task>) => Promise<void>;\n  onDelete?: () => Promise<void>;\n  onCommentAdd?: (content: string) => Promise<void>;\n  onAttachmentUpload?: (file: File) => Promise<void>;\n  onSubtaskToggle?: (subtaskId: string, completed: boolean) => Promise<void>;\n  onSubtaskAdd?: (title: string) => Promise<void>;\n  className?: string;\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 formatDateTime(date: Date): string {\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n    hour: \"numeric\",\n    minute: \"2-digit\",\n  }).format(date);\n}\n\nfunction getPriorityColor(priority: TaskPriority): string {\n  switch (priority) {\n    case \"urgent\":\n      return \"bg-red-500\";\n    case \"high\":\n      return \"bg-orange-500\";\n    case \"medium\":\n      return \"bg-yellow-500\";\n    default:\n      return \"bg-gray-500\";\n  }\n}\n\nfunction getStatusLabel(status: TaskStatus): string {\n  switch (status) {\n    case \"todo\":\n      return \"To Do\";\n    case \"in_progress\":\n      return \"In Progress\";\n    case \"done\":\n      return \"Done\";\n    case \"cancelled\":\n      return \"Cancelled\";\n  }\n}\n\nconst tabValues = [\n  \"details\",\n  \"subtasks\",\n  \"comments\",\n  \"attachments\",\n  \"activity\",\n] as const;\n\nconst parseTab = parseAsStringEnum([...tabValues] as string[]).withDefault(\n  \"details\"\n);\n\nexport default function TaskDetail({\n  task,\n  comments = [],\n  attachments = [],\n  activities = [],\n  subtasks = [],\n  onUpdate,\n  onDelete,\n  onCommentAdd,\n  onAttachmentUpload,\n  onSubtaskToggle,\n  onSubtaskAdd,\n  className,\n}: TaskDetailProps) {\n  const [activeTab, setActiveTab] = useQueryState(\"tab\", parseTab);\n  const [isEditing, setIsEditing] = useState(false);\n  const [editTitle, setEditTitle] = useState(task?.title || \"\");\n  const [editDescription, setEditDescription] = useState(\n    task?.description || \"\"\n  );\n  const [commentText, setCommentText] = useState(\"\");\n  const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n\n  if (!task) {\n    return (\n      <Card className={cn(\"w-full shadow-xs\", className)}>\n        <CardContent className=\"flex items-center justify-center p-12\">\n          <div className=\"flex flex-col items-center gap-4 text-center\">\n            <FileText className=\"size-12 text-muted-foreground\" />\n            <p className=\"text-muted-foreground text-sm\">\n              Select a task to view details\n            </p>\n          </div>\n        </CardContent>\n      </Card>\n    );\n  }\n\n  const handleSave = async () => {\n    await onUpdate?.({\n      title: editTitle,\n      description: editDescription,\n    });\n    setIsEditing(false);\n  };\n\n  const handleCancel = () => {\n    setEditTitle(task.title);\n    setEditDescription(task.description || \"\");\n    setIsEditing(false);\n  };\n\n  const handleCommentSubmit = async () => {\n    if (!commentText.trim()) return;\n    await onCommentAdd?.(commentText);\n    setCommentText(\"\");\n  };\n\n  const completedSubtasks = subtasks.filter((s) => s.completed).length;\n  const subtaskProgress =\n    subtasks.length > 0 ? (completedSubtasks / subtasks.length) * 100 : 0;\n\n  return (\n    <>\n      <Card className={cn(\"w-full shadow-xs\", className)}>\n        <CardHeader>\n          <div className=\"flex items-start justify-between gap-4\">\n            <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n              {isEditing ? (\n                <div className=\"flex flex-col gap-2\">\n                  <Input\n                    className=\"w-full font-semibold text-base\"\n                    onChange={(e) => setEditTitle(e.target.value)}\n                    value={editTitle}\n                  />\n                  <Textarea\n                    className=\"min-h-[100px] resize-none\"\n                    onChange={(e) => setEditDescription(e.target.value)}\n                    value={editDescription}\n                  />\n                  <div className=\"flex gap-2\">\n                    <Button onClick={handleSave} size=\"sm\" type=\"button\">\n                      Save\n                    </Button>\n                    <Button\n                      onClick={handleCancel}\n                      size=\"sm\"\n                      type=\"button\"\n                      variant=\"outline\"\n                    >\n                      Cancel\n                    </Button>\n                  </div>\n                </div>\n              ) : (\n                <>\n                  <CardTitle className=\"wrap-break-word\">\n                    {task.title}\n                  </CardTitle>\n                  {task.description && (\n                    <CardDescription className=\"wrap-break-word\">\n                      {task.description}\n                    </CardDescription>\n                  )}\n                </>\n              )}\n            </div>\n            <DropdownMenu>\n              <DropdownMenuTrigger asChild>\n                <Button size=\"icon\" type=\"button\" variant=\"ghost\">\n                  <MoreVertical className=\"size-4\" />\n                </Button>\n              </DropdownMenuTrigger>\n              <DropdownMenuContent align=\"end\">\n                <DropdownMenuItem onClick={() => setIsEditing(true)}>\n                  Edit\n                </DropdownMenuItem>\n                <DropdownMenuSeparator />\n                <DropdownMenuItem\n                  onClick={() => setShowDeleteDialog(true)}\n                  variant=\"destructive\"\n                >\n                  Delete\n                </DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </div>\n        </CardHeader>\n        <CardContent>\n          <div className=\"flex flex-col gap-6\">\n            <div className=\"flex flex-wrap items-center gap-4\">\n              <div className=\"flex items-center gap-2\">\n                <span className=\"text-muted-foreground text-sm\">Status:</span>\n                <Badge variant=\"outline\">{getStatusLabel(task.status)}</Badge>\n              </div>\n              <div className=\"flex items-center gap-2\">\n                <span className=\"text-muted-foreground text-sm\">Priority:</span>\n                <div className=\"flex items-center gap-1.5\">\n                  <div\n                    className={cn(\n                      \"size-2 rounded-full\",\n                      getPriorityColor(task.priority)\n                    )}\n                  />\n                  <span className=\"text-sm capitalize\">{task.priority}</span>\n                </div>\n              </div>\n              {task.dueDate && (\n                <div className=\"flex items-center gap-2\">\n                  <Calendar className=\"size-4 text-muted-foreground\" />\n                  <span className=\"text-muted-foreground text-sm\">\n                    {formatDate(task.dueDate)}\n                  </span>\n                </div>\n              )}\n            </div>\n\n            {task.assignees && task.assignees.length > 0 && (\n              <div className=\"flex flex-col gap-2\">\n                <span className=\"text-muted-foreground text-sm\">Assignees</span>\n                <div className=\"flex flex-wrap gap-2\">\n                  {task.assignees.map((assignee) => (\n                    <div\n                      className=\"flex items-center gap-2 rounded-lg border bg-card p-2\"\n                      key={assignee.id}\n                    >\n                      <Avatar className=\"size-6\">\n                        <AvatarImage\n                          alt={assignee.name}\n                          src={assignee.avatar}\n                        />\n                        <AvatarFallback>\n                          {assignee.name.charAt(0).toUpperCase()}\n                        </AvatarFallback>\n                      </Avatar>\n                      <span className=\"text-sm\">{assignee.name}</span>\n                    </div>\n                  ))}\n                </div>\n              </div>\n            )}\n\n            {task.tags && task.tags.length > 0 && (\n              <div className=\"flex flex-col gap-2\">\n                <span className=\"text-muted-foreground text-sm\">Tags</span>\n                <div className=\"flex flex-wrap gap-2\">\n                  {task.tags.map((tag) => (\n                    <Badge key={tag} variant=\"outline\">\n                      {tag}\n                    </Badge>\n                  ))}\n                </div>\n              </div>\n            )}\n\n            <Tabs\n              className=\"flex w-full flex-col gap-4\"\n              onValueChange={setActiveTab}\n              value={activeTab}\n            >\n              <TabsList>\n                <TabsTrigger value=\"details\">Details</TabsTrigger>\n                <TabsTrigger value=\"subtasks\">\n                  Subtasks ({subtasks.length})\n                </TabsTrigger>\n                <TabsTrigger value=\"comments\">\n                  Comments ({comments.length})\n                </TabsTrigger>\n                <TabsTrigger value=\"attachments\">\n                  Attachments ({attachments.length})\n                </TabsTrigger>\n                <TabsTrigger value=\"activity\">Activity</TabsTrigger>\n              </TabsList>\n\n              <div>\n                <TabsContent value=\"details\">\n                  <div className=\"flex flex-col gap-4\">\n                    <div>\n                      <span className=\"text-muted-foreground text-sm\">\n                        Created: {formatDateTime(task.createdAt)}\n                      </span>\n                    </div>\n                    <div>\n                      <span className=\"text-muted-foreground text-sm\">\n                        Updated: {formatDateTime(task.updatedAt)}\n                      </span>\n                    </div>\n                  </div>\n                </TabsContent>\n\n                <TabsContent value=\"subtasks\">\n                  <div className=\"flex flex-col gap-4\">\n                    {subtasks.length > 0 && (\n                      <div className=\"flex items-center gap-2\">\n                        <div className=\"flex-1 rounded-full bg-muted\">\n                          <div\n                            className=\"h-2 rounded-full bg-primary transition-all\"\n                            style={{ width: `${subtaskProgress}%` }}\n                          />\n                        </div>\n                        <span className=\"text-muted-foreground text-sm\">\n                          {completedSubtasks} of {subtasks.length}\n                        </span>\n                      </div>\n                    )}\n                    <div className=\"flex flex-col gap-2\">\n                      {subtasks.map((subtask) => (\n                        <div\n                          className=\"flex items-center gap-2 rounded-lg border bg-card p-2\"\n                          key={subtask.id}\n                        >\n                          <label className=\"flex flex-1 cursor-pointer items-center gap-2\">\n                            <Checkbox\n                              aria-label={`Mark subtask '${subtask.title}' as complete`}\n                              checked={subtask.completed}\n                              className=\"size-4\"\n                              onCheckedChange={() =>\n                                onSubtaskToggle?.(\n                                  subtask.id,\n                                  !subtask.completed\n                                )\n                              }\n                              tabIndex={0}\n                            />\n                            <span\n                              className={cn(\n                                \"flex-1 text-sm\",\n                                subtask.completed &&\n                                  \"text-muted-foreground line-through\"\n                              )}\n                            >\n                              {subtask.title}\n                            </span>\n                          </label>\n                        </div>\n                      ))}\n                    </div>\n                    {onSubtaskAdd && (\n                      <Button\n                        onClick={() => {\n                          const title = prompt(\"Subtask title:\");\n                          if (title) onSubtaskAdd(title);\n                        }}\n                        size=\"sm\"\n                        type=\"button\"\n                        variant=\"outline\"\n                      >\n                        <Plus className=\"size-4\" />\n                        Add Subtask\n                      </Button>\n                    )}\n                  </div>\n                </TabsContent>\n\n                <TabsContent value=\"comments\">\n                  <div className=\"flex flex-col gap-4\">\n                    {comments.map((comment) => (\n                      <div\n                        className=\"flex gap-3 rounded-lg border bg-card p-3\"\n                        key={comment.id}\n                      >\n                        <Avatar className=\"size-8\">\n                          <AvatarImage\n                            alt={comment.author.name}\n                            src={comment.author.avatar}\n                          />\n                          <AvatarFallback>\n                            {comment.author.name.charAt(0).toUpperCase()}\n                          </AvatarFallback>\n                        </Avatar>\n                        <div className=\"flex-1\">\n                          <div className=\"flex items-center gap-2\">\n                            <span className=\"font-medium text-sm\">\n                              {comment.author.name}\n                            </span>\n                            <span className=\"text-muted-foreground text-xs\">\n                              {formatDateTime(comment.createdAt)}\n                            </span>\n                          </div>\n                          <p className=\"wrap-break-word text-sm\">\n                            {comment.content}\n                          </p>\n                        </div>\n                      </div>\n                    ))}\n                    {onCommentAdd && (\n                      <div className=\"flex flex-col gap-2\">\n                        <Textarea\n                          onChange={(e) => setCommentText(e.target.value)}\n                          placeholder=\"Add a comment…\"\n                          value={commentText}\n                        />\n                        <Button\n                          onClick={handleCommentSubmit}\n                          size=\"sm\"\n                          type=\"button\"\n                        >\n                          <MessageSquare className=\"size-4\" />\n                          Post Comment\n                        </Button>\n                      </div>\n                    )}\n                  </div>\n                </TabsContent>\n\n                <TabsContent value=\"attachments\">\n                  <div className=\"flex flex-col gap-2\">\n                    {attachments.map((attachment) => (\n                      <div\n                        className=\"flex items-center justify-between rounded-lg border bg-card p-3\"\n                        key={attachment.id}\n                      >\n                        <div className=\"flex items-center gap-3\">\n                          <Paperclip className=\"size-4 text-muted-foreground\" />\n                          <div className=\"flex flex-col\">\n                            <span className=\"text-sm\">{attachment.name}</span>\n                            <span className=\"text-muted-foreground text-xs\">\n                              {formatDateTime(attachment.uploadedAt)}\n                            </span>\n                          </div>\n                        </div>\n                        <Button size=\"sm\" type=\"button\" variant=\"outline\">\n                          Download\n                        </Button>\n                      </div>\n                    ))}\n                    {onAttachmentUpload && (\n                      <Button\n                        onClick={() => {\n                          const input = document.createElement(\"input\");\n                          input.type = \"file\";\n                          input.onchange = (e) => {\n                            const file = (e.target as HTMLInputElement)\n                              .files?.[0];\n                            if (file) onAttachmentUpload(file);\n                          };\n                          input.click();\n                        }}\n                        size=\"sm\"\n                        type=\"button\"\n                        variant=\"outline\"\n                      >\n                        <Paperclip className=\"size-4\" />\n                        Upload Attachment\n                      </Button>\n                    )}\n                  </div>\n                </TabsContent>\n\n                <TabsContent value=\"activity\">\n                  <div className=\"flex flex-col gap-3\">\n                    {activities.map((activity) => (\n                      <div className=\"flex gap-3\" key={activity.id}>\n                        <Avatar className=\"size-8\">\n                          <AvatarImage\n                            alt={activity.user.name}\n                            src={activity.user.avatar}\n                          />\n                          <AvatarFallback>\n                            {activity.user.name.charAt(0).toUpperCase()}\n                          </AvatarFallback>\n                        </Avatar>\n                        <div className=\"flex-1\">\n                          <p className=\"text-sm\">\n                            <span className=\"font-medium\">\n                              {activity.user.name}\n                            </span>{\" \"}\n                            {activity.description}\n                          </p>\n                          <span className=\"text-muted-foreground text-xs\">\n                            {formatDateTime(activity.timestamp)}\n                          </span>\n                        </div>\n                      </div>\n                    ))}\n                  </div>\n                </TabsContent>\n              </div>\n            </Tabs>\n          </div>\n        </CardContent>\n      </Card>\n\n      <Dialog onOpenChange={setShowDeleteDialog} open={showDeleteDialog}>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>Delete Task</DialogTitle>\n            <DialogDescription>\n              Are you sure you want to delete this task? This action cannot be\n              undone.\n            </DialogDescription>\n          </DialogHeader>\n          <div className=\"flex justify-end gap-2\">\n            <Button\n              onClick={() => setShowDeleteDialog(false)}\n              type=\"button\"\n              variant=\"outline\"\n            >\n              Cancel\n            </Button>\n            <Button\n              onClick={async () => {\n                await onDelete?.();\n                setShowDeleteDialog(false);\n              }}\n              type=\"button\"\n              variant=\"destructive\"\n            >\n              Delete\n            </Button>\n          </div>\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "tasks"
  ],
  "type": "registry:ui"
}