{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "conversation-sidebar",
  "type": "registry:ui",
  "title": "Conversation sidebar",
  "description": "React chat history sidebar with a New chat button and a scrolling list of conversations showing title and relative time. Highlights the active chat.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://ui.minidev.pro/r/button.json",
    "https://ui.minidev.pro/r/relative-time.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/conversation-sidebar.tsx",
      "type": "registry:ui",
      "target": "components/ui/conversation-sidebar.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { PlusIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { RelativeTime } from \"@/components/ui/relative-time\"\n\n/** A chat app's conversation list: a new-chat button on top, then titled threads with their last activity. */\ntype Conv = { id: string; title: string; updatedAt: string | Date }\n\nconst HOUR = 3_600_000\nconst SAMPLE: { id: string; title: string; agoMs: number }[] = [\n  { id: \"c1\", title: \"Q3 revenue summary for Northwind\", agoMs: 0.4 * HOUR },\n  { id: \"c2\", title: \"Draft reminder for INV-2041\", agoMs: 3 * HOUR },\n  { id: \"c3\", title: \"Why did the Friday deploy fail?\", agoMs: 7 * HOUR },\n  { id: \"c4\", title: \"Churn risks in enterprise accounts\", agoMs: 26 * HOUR },\n  { id: \"c5\", title: \"Reconcile Stripe payouts\", agoMs: 2 * 24 * HOUR },\n  { id: \"c6\", title: \"Onboarding checklist for Priya\", agoMs: 5 * 24 * HOUR },\n]\n\ntype ConversationSidebarProps = {\n  /** Conversations, newest first. Defaults to a few Lumen threads timed relative to now. */\n  items?: Conv[]\n  /** Controlled active conversation. */\n  activeId?: string\n  /** Active conversation when uncontrolled. */\n  defaultActiveId?: string\n  onSelect?: (id: string) => void\n  onNew?: () => void\n  className?: string\n}\n\nfunction ConversationSidebar({\n  items,\n  activeId,\n  defaultActiveId = SAMPLE[0].id,\n  onSelect,\n  onNew,\n  className,\n}: ConversationSidebarProps) {\n  // Sample times are fixed offsets from now, rounded to the minute so server and client agree.\n  const sample = React.useMemo<Conv[]>(() => {\n    const now = Math.floor(Date.now() / 60_000) * 60_000\n    return SAMPLE.map(({ id, title, agoMs }) => ({ id, title, updatedAt: new Date(now - agoMs) }))\n  }, [])\n  const list = items ?? sample\n  const [innerActive, setInnerActive] = React.useState(defaultActiveId)\n  const active = activeId ?? innerActive\n  return (\n    <aside data-slot=\"conversation-sidebar\" className={cn(\"flex h-full w-64 flex-col border-r border-border bg-surface\", className)}>\n      <div className=\"border-b border-border p-3\">\n        <Button type=\"button\" className=\"w-full\" size=\"sm\" onClick={onNew}><PlusIcon /> New chat</Button>\n      </div>\n      <ul className=\"flex-1 overflow-auto p-2\">\n        {list.map((c) => (\n          <li key={c.id}>\n            <button\n              type=\"button\"\n              aria-current={c.id === active ? \"true\" : undefined}\n              onClick={() => {\n                if (activeId === undefined) setInnerActive(c.id)\n                onSelect?.(c.id)\n              }}\n              className={cn(\n                \"flex w-full flex-col rounded-lg px-2.5 py-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-accent\",\n                c.id === active ? \"bg-sunken\" : \"hover:bg-sunken/60\"\n              )}\n            >\n              <span className=\"truncate text-sm font-medium text-fg\">{c.title}</span>\n              <RelativeTime date={c.updatedAt} />\n            </button>\n          </li>\n        ))}\n      </ul>\n    </aside>\n  )\n}\nexport { ConversationSidebar }\nexport type { ConversationSidebarProps, Conv as Conversation }\n"
    }
  ]
}
