{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"chat-drawer","type":"registry:ui","title":"ChatDrawer","description":"ChatDrawer from react-fancy","package":"react-fancy","dependencies":[],"registryDependencies":["icon"],"files":[{"path":"components\/fancy\/chat-drawer\/ChatDrawer.tsx","content":"import { type ReactNode, useCallback } from \"react\";\r\nimport { Icon } from \"..\/Icon\";\r\n\r\n\/**\r\n * ChatDrawer \u2014 tabbed, collapsible panel that sits *above* a `PromptInput`\r\n * (via the input's `aboveInput` slot) so the drawer and composer share one\r\n * rounded shell. Each tab gets a numbered chip and a content panel; only\r\n * one panel renders at a time.\r\n *\r\n *   <PromptInput\r\n *     aboveInput={\r\n *       <ChatDrawer\r\n *         tabs={[\r\n *           { id: \"files\",   label: \"Files\" },\r\n *           { id: \"tools\",   label: \"Chat Tools\" },\r\n *           { id: \"prompts\", label: \"Chat Prompts\" },\r\n *           { id: \"deal\",    label: \"IBM Analytics Platform\" },\r\n *         ]}\r\n *         activeTabId={tab}\r\n *         onTabChange={setTab}\r\n *         open={open}\r\n *         onToggle={setOpen}\r\n *       >\r\n *         {tab === \"tools\" && <ToolsGrid \/>}\r\n *         {tab === \"deal\"  && <DealCard \/>}\r\n *       <\/ChatDrawer>\r\n *     }\r\n *     budgetTokens={200_000}\r\n *     onSubmit={(text) => send(text)}\r\n *   \/>\r\n *\r\n * The drawer is purely presentational and slot-driven \u2014 you decide what\r\n * each tab shows. Tab order in the numbered chips is the order you pass.\r\n *\/\r\n\r\nexport type ChatDrawerTab = {\r\n  \/** Stable id \u2014 used as the React key and as `activeTabId`. *\/\r\n  id: string;\r\n  \/** Human label rendered on the chip. *\/\r\n  label: string;\r\n  \/**\r\n   * Optional override for the numbered prefix (defaults to position +1).\r\n   * Set to `null` to hide the number entirely (e.g. for a context-specific\r\n   * tab like the deal one in the screenshots).\r\n   *\/\r\n  number?: number | null;\r\n};\r\n\r\nexport interface ChatDrawerProps {\r\n  tabs: ChatDrawerTab[];\r\n  activeTabId: string;\r\n  onTabChange: (id: string) => void;\r\n  \/** Body open\/closed. Default true. *\/\r\n  open?: boolean;\r\n  \/** Called when the chevron is clicked. *\/\r\n  onToggle?: (open: boolean) => void;\r\n  \/** Body content for the active tab. *\/\r\n  children?: ReactNode;\r\n  \/** Min height of the body when open, in px. Default 140. *\/\r\n  minBodyHeight?: number;\r\n  className?: string;\r\n}\r\n\r\nexport function ChatDrawer({\r\n  tabs,\r\n  activeTabId,\r\n  onTabChange,\r\n  open = true,\r\n  onToggle,\r\n  children,\r\n  minBodyHeight = 140,\r\n  className,\r\n}: ChatDrawerProps) {\r\n  const handleToggle = useCallback(() => {\r\n    onToggle?.(!open);\r\n  }, [onToggle, open]);\r\n\r\n  return (\r\n    <div className={className}>\r\n      <div className=\"flex items-center gap-1.5 px-2.5 py-2\">\r\n        <div className=\"flex flex-wrap items-center gap-1.5\">\r\n          {tabs.map((tab, i) => {\r\n            const active = tab.id === activeTabId;\r\n            const number = tab.number === null ? null : tab.number ?? i + 1;\r\n            return (\r\n              <button\r\n                key={tab.id}\r\n                type=\"button\"\r\n                onClick={() => onTabChange(tab.id)}\r\n                className={[\r\n                  \"group inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[12px] font-medium transition\",\r\n                  active\r\n                    ? \"border-violet-500 bg-violet-500 text-white\"\r\n                    : \"border-zinc-200 bg-transparent text-zinc-700 hover:border-zinc-300 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:bg-zinc-800\",\r\n                ].join(\" \")}\r\n                aria-pressed={active}\r\n              >\r\n                {number !== null && (\r\n                  <span\r\n                    className={[\r\n                      \"inline-flex h-4 w-4 items-center justify-center rounded-full text-[10px] font-semibold\",\r\n                      active\r\n                        ? \"bg-white\/20 text-white\"\r\n                        : \"bg-zinc-200 text-zinc-600 group-hover:bg-zinc-300 dark:bg-zinc-700 dark:text-zinc-300\",\r\n                    ].join(\" \")}\r\n                  >\r\n                    {number}\r\n                  <\/span>\r\n                )}\r\n                <span className=\"truncate max-w-[180px]\">{tab.label}<\/span>\r\n              <\/button>\r\n            );\r\n          })}\r\n        <\/div>\r\n        <button\r\n          type=\"button\"\r\n          onClick={handleToggle}\r\n          aria-label={open ? \"Collapse drawer\" : \"Expand drawer\"}\r\n          aria-expanded={open}\r\n          className=\"ml-auto inline-flex h-6 w-6 items-center justify-center rounded-full text-zinc-400 transition hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-200\"\r\n        >\r\n          <Icon\r\n            name={open ? \"chevron-down\" : \"chevron-up\"}\r\n            className=\"h-3.5 w-3.5\"\r\n          \/>\r\n        <\/button>\r\n      <\/div>\r\n\r\n      {open && (\r\n        <div\r\n          className=\"px-2.5 pb-2.5\"\r\n          style={{ minHeight: minBodyHeight }}\r\n        >\r\n          {children}\r\n        <\/div>\r\n      )}\r\n    <\/div>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/chat-drawer\/ChatDrawer.tsx"},{"path":"components\/fancy\/chat-drawer\/index.ts","content":"export { ChatDrawer } from \".\/ChatDrawer\";\r\nexport type { ChatDrawerProps, ChatDrawerTab } from \".\/ChatDrawer\";\r\n","type":"registry:ui","target":"components\/fancy\/chat-drawer\/index.ts"}]}