{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chat-thread-list",
  "title": "Chat thread list",
  "description": "past conversations, grouped by when · `threads activeId onSelect onNew?`",
  "dependencies": [
    "cn@^0.4.0",
    "lucide-react@^1.52.0"
  ],
  "registryDependencies": [
    "@kit/button"
  ],
  "files": [
    {
      "path": "components/kit/chat-thread-list.tsx",
      "content": "'use client';\nimport { PlusIcon } from 'lucide-react';\nimport { cn } from 'cn';\nimport { Button } from '@/components/ui/button';\n\nexport type ChatThread = {\n  id: string;\n  title: string;\n  /** The last line of the conversation, one line. */\n  preview?: string;\n  /** A heading the thread sits under: \"Today\", \"Previous 7 days\". Threads in a group must be adjacent. */\n  group?: string;\n};\n\ntype Props = {\n  threads: ChatThread[];\n  activeId?: string;\n  onSelect: (id: string) => void;\n  /** Shows a \"New chat\" button above the list. */\n  onNew?: () => void;\n  className?: string;\n};\n\n/**\n * Past conversations in an AI product: the sidebar beside a chat, or the content of a Sheet on small\n * screens. Newest first, grouped by when. The open conversation is marked with aria-current.\n */\nexport function ChatThreadList({ threads, activeId, onSelect, onNew, className }: Props) {\n  const groups: { label?: string; threads: ChatThread[] }[] = [];\n  for (const t of threads) {\n    const last = groups.at(-1);\n    if (last && last.label === t.group) last.threads.push(t);\n    else groups.push({ label: t.group, threads: [t] });\n  }\n\n  return (\n    <nav aria-label=\"Conversations\" className={cn('flex flex-col gap-4', className)}>\n      {onNew ? (\n        <Button variant=\"outline\" className=\"justify-start\" onClick={onNew}>\n          <PlusIcon aria-hidden />\n          New chat\n        </Button>\n      ) : null}\n      {threads.length === 0 ? <p className=\"text-muted-foreground px-2 text-sm\">No conversations yet.</p> : null}\n      {groups.map((g, i) => (\n        <div key={`${g.label ?? ''}-${i}`} className=\"flex flex-col gap-1\">\n          {g.label ? <h3 className=\"text-muted-foreground px-2 pb-1 text-xs font-medium\">{g.label}</h3> : null}\n          <ul className=\"flex flex-col gap-0.5\">\n            {g.threads.map((t) => {\n              const active = t.id === activeId;\n              return (\n                <li key={t.id}>\n                  <button\n                    type=\"button\"\n                    onClick={() => onSelect(t.id)}\n                    aria-current={active ? 'true' : undefined}\n                    className={cn(\n                      'focus-visible:ring-ring flex w-full flex-col gap-0.5 rounded-md px-2 py-1.5 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none',\n                      active ? 'bg-accent text-accent-foreground' : 'hover:bg-accent/60'\n                    )}>\n                    <span className=\"truncate text-sm font-medium\">{t.title}</span>\n                    {t.preview ? <span className=\"text-muted-foreground truncate text-xs\">{t.preview}</span> : null}\n                  </button>\n                </li>\n              );\n            })}\n          </ul>\n        </div>\n      ))}\n    </nav>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/kit/chat-thread-list.tsx"
    }
  ],
  "meta": {
    "exports": [
      "ChatThreadList"
    ],
    "api": "<ChatThreadList threads={[{id,title,preview?,group?}]} activeId onSelect onNew? />"
  },
  "docs": "Needs the kit theme (`@kit/theme`) and a next-themes ThemeProvider (attribute=\"class\") at the root.",
  "categories": [
    "ai"
  ],
  "type": "registry:component"
}