{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "github-inline-comments",
  "title": "github-inline-comments",
  "description": "A github inline comments component.",
  "dependencies": [
    "react",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "utils",
    "tooltip",
    "badge",
    "textarea",
    "separator",
    "avatar"
  ],
  "files": [
    {
      "path": "registry/eldoraui/github-inline-comments.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport { CheckCircle2, MessageSquarePlus, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Avatar, AvatarFallback } from \"@/components/ui/avatar\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { Textarea } from \"@/components/ui/textarea\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\n\ntype Line =\n  | { kind: \"hunk\"; content: string }\n  | { kind: \"context\"; old: number | null; new: number | null; content: string }\n  | { kind: \"add\"; old: number | null; new: number | null; content: string }\n  | { kind: \"del\"; old: number | null; new: number | null; content: string }\n\nexport default function GithubInlineComments({\n  diff,\n  fileName,\n}: {\n  diff: readonly Line[]\n  fileName: string\n}) {\n  return <DiffList diff={diff} fileName={fileName} />\n}\n\nfunction DiffList({\n  diff,\n  fileName,\n}: {\n  diff: readonly Line[]\n  fileName: string\n}) {\n  const rows = diff ?? []\n\n  // Tracks which line index currently has an open thread\n  const [openThreadAt, setOpenThreadAt] = useState<number | null>(null)\n  // Tracks thread status per line\n  const [resolvedMap, setResolvedMap] = useState<Record<number, boolean>>({})\n\n  function toggleResolve(idx: number) {\n    setResolvedMap((m) => ({ ...m, [idx]: !m[idx] }))\n  }\n\n  return (\n    <TooltipProvider delayDuration={150}>\n      <div\n        role=\"table\"\n        aria-label={`Diff of ${fileName}`}\n        className=\"bg-card rounded-md border dark:border-white/10\"\n      >\n        <div className=\"flex items-center justify-between border-b px-2 py-1 dark:border-white/10\">\n          <div className=\"flex items-center gap-2\">\n            <span className=\"text-[13px] font-medium\">{fileName}</span>\n            <Badge\n              variant=\"secondary\"\n              aria-label=\"File status\"\n              className=\"h-5 px-1.5 text-[11px]\"\n            >\n              modified\n            </Badge>\n          </div>\n        </div>\n\n        <ol role=\"rowgroup\" className=\"divide-y dark:divide-white/10\">\n          {rows.map((line, idx) => {\n            const isChange = line.kind === \"add\" || line.kind === \"del\"\n            const isOpen = openThreadAt === idx\n            const isResolved = !!resolvedMap[idx]\n\n            return (\n              <li\n                key={idx}\n                role=\"row\"\n                className={cn(\n                  \"group relative flex items-stretch text-[13px]\",\n                  line.kind === \"hunk\" && \"bg-muted/50 text-muted-foreground\",\n                  line.kind === \"add\" &&\n                    \"bg-emerald-50/60 dark:bg-emerald-950/20\",\n                  line.kind === \"del\" && \"bg-rose-50/60 dark:bg-rose-950/20\"\n                )}\n              >\n                <div className=\"absolute top-1/2 -left-4 -translate-y-1/2 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n                  {line.kind !== \"hunk\" && (\n                    <Tooltip>\n                      <TooltipTrigger asChild>\n                        <Button\n                          size=\"icon\"\n                          variant=\"secondary\"\n                          aria-label=\"Add inline comment\"\n                          className=\"h-5 w-5 rounded-full shadow-sm\"\n                          onClick={() => setOpenThreadAt(isOpen ? null : idx)}\n                        >\n                          <MessageSquarePlus className=\"h-3 w-3\" />\n                        </Button>\n                      </TooltipTrigger>\n                      <TooltipContent side=\"left\">Add comment</TooltipContent>\n                    </Tooltip>\n                  )}\n                </div>\n\n                <div\n                  role=\"cell\"\n                  className={cn(\n                    \"text-muted-foreground grid w-16 shrink-0 grid-cols-2 border-r text-[11px] dark:border-white/10\"\n                  )}\n                >\n                  <span className=\"px-2 py-1 text-right tabular-nums\">\n                    {line.kind === \"add\"\n                      ? \"\"\n                      : line.kind === \"hunk\"\n                        ? \"\"\n                        : (line.old ?? \"\")}\n                  </span>\n                  <span className=\"px-2 py-1 text-right tabular-nums\">\n                    {line.kind === \"del\"\n                      ? \"\"\n                      : line.kind === \"hunk\"\n                        ? \"\"\n                        : (line.new ?? \"\")}\n                  </span>\n                </div>\n\n                <div role=\"cell\" className=\"flex-1\">\n                  <pre\n                    className={cn(\n                      \"px-2 py-1 font-mono text-[12px] leading-5 whitespace-pre-wrap\",\n                      isChange && \"pl-5\"\n                    )}\n                    aria-label={`${line.kind} line`}\n                  >\n                    <span\n                      aria-hidden\n                      className={cn(\n                        \"mr-1 inline-block w-2 text-center font-semibold\",\n                        line.kind === \"add\" && \"text-emerald-600\",\n                        line.kind === \"del\" && \"text-rose-600\"\n                      )}\n                    >\n                      {line.kind === \"add\"\n                        ? \"+\"\n                        : line.kind === \"del\"\n                          ? \"-\"\n                          : \" \"}\n                    </span>\n                    {line.content}\n                  </pre>\n\n                  {openThreadAt === idx && line.kind !== \"hunk\" && (\n                    <div className=\"bg-background border-t px-2 py-1.5 dark:border-white/10\">\n                      <InlineThread\n                        resolved={isResolved}\n                        onToggleResolve={() => toggleResolve(idx)}\n                        onClose={() => setOpenThreadAt(null)}\n                      />\n                    </div>\n                  )}\n                </div>\n              </li>\n            )\n          })}\n        </ol>\n      </div>\n    </TooltipProvider>\n  )\n}\n\ntype Comment = {\n  id: string\n  author: string\n  initials: string\n  body: string\n  createdAt: string\n}\n\nfunction InlineThread({\n  resolved,\n  onToggleResolve,\n  onClose,\n}: {\n  resolved: boolean\n  onToggleResolve: () => void\n  onClose: () => void\n}) {\n  const [comments, setComments] = useState<Comment[]>([\n    {\n      id: \"c1\",\n      author: \"Reviewer\",\n      initials: \"RV\",\n      body: \"Consider handling the undefined case explicitly.\",\n      createdAt: \"just now\",\n    },\n  ])\n  const [draft, setDraft] = useState(\"\")\n  const textRef = useRef<HTMLTextAreaElement | null>(null)\n\n  function addComment() {\n    const text = draft.trim()\n    if (!text) return\n    setComments((c) => [\n      ...c,\n      {\n        id: crypto.randomUUID(),\n        author: \"You\",\n        initials: \"YO\",\n        body: text,\n        createdAt: \"now\",\n      },\n    ])\n    setDraft(\"\")\n    // focus back for fast sequences\n    requestAnimationFrame(() => textRef.current?.focus())\n  }\n\n  useEffect(() => {\n    function onKeyDown(e: KeyboardEvent) {\n      if (e.key === \"Escape\") {\n        onClose()\n      }\n    }\n    window.addEventListener(\"keydown\", onKeyDown)\n    return () => window.removeEventListener(\"keydown\", onKeyDown)\n  }, [onClose])\n\n  return (\n    <div className=\"bg-card rounded-md border dark:border-white/10\">\n      {/* Header with status chip */}\n      <div className=\"flex items-center justify-between gap-2 px-2 py-1\">\n        <div className=\"flex items-center gap-2\">\n          <Badge\n            className={cn(\n              \"h-5 gap-1 px-1.5 text-[11px]\",\n              resolved\n                ? \"bg-emerald-600 text-white hover:bg-emerald-600/90 dark:bg-emerald-500 dark:hover:bg-emerald-500/90\"\n                : \"bg-secondary text-foreground dark:bg-neutral-800 dark:text-neutral-100\"\n            )}\n          >\n            {resolved ? <CheckCircle2 className=\"h-3.5 w-3.5\" /> : null}\n            {resolved ? \"Resolved\" : \"Open\"}\n          </Badge>\n        </div>\n\n        <div className=\"flex items-center gap-1.5\">\n          <Button\n            variant={resolved ? \"secondary\" : \"default\"}\n            size=\"sm\"\n            onClick={onToggleResolve}\n            aria-pressed={resolved}\n            className=\"h-7 px-2 text-[12px]\"\n          >\n            {resolved ? \"Reopen\" : \"Resolve\"}\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            aria-label=\"Close thread\"\n            onClick={onClose}\n            className=\"h-7 w-7\"\n          >\n            <X className=\"h-4 w-4\" />\n          </Button>\n        </div>\n      </div>\n\n      {/* Comments list */}\n      <ul className=\"space-y-1 px-2 py-1.5\">\n        {comments.map((c) => (\n          <li key={c.id} className=\"flex items-start gap-2\">\n            <Avatar className=\"h-5 w-5\">\n              <AvatarFallback className=\"text-[9px]\">\n                {c.initials}\n              </AvatarFallback>\n            </Avatar>\n            <div className=\"min-w-0 flex-1\">\n              <div className=\"flex items-center justify-between gap-2\">\n                <p className=\"truncate text-[12px] font-medium\">{c.author}</p>\n                <span className=\"text-muted-foreground ml-2 shrink-0 text-[10px]\">\n                  {c.createdAt}\n                </span>\n              </div>\n              <p className=\"mt-0.5 text-[13px] leading-5\">{c.body}</p>\n            </div>\n          </li>\n        ))}\n      </ul>\n\n      <Separator />\n\n      {/* Editor */}\n      <div className=\"flex flex-col gap-2 px-2 py-1.5\">\n        <label htmlFor=\"inline-comment\" className=\"sr-only\">\n          Add a comment\n        </label>\n        <Textarea\n          id=\"inline-comment\"\n          ref={textRef}\n          value={draft}\n          onChange={(e) => setDraft(e.target.value)}\n          placeholder=\"Comment\"\n          rows={2}\n          className=\"min-h-[40px] py-1.5 text-[13px]\"\n        />\n        <div className=\"flex items-center justify-end gap-1.5\">\n          <Button\n            variant=\"ghost\"\n            size=\"sm\"\n            onClick={onClose}\n            className=\"h-7 px-2 text-[12px]\"\n          >\n            Cancel\n          </Button>\n          <Button\n            size=\"sm\"\n            onClick={addComment}\n            disabled={!draft.trim()}\n            className=\"h-7 px-2 text-[12px]\"\n          >\n            Comment\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}