{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"tool-call","type":"registry:ui","title":"Tool Call","description":"How to show a function call in chat — the arguments sent, the result returned, run status, and an expand toggle.","author":"Scrim UI (https://scrimui.dev)","categories":["tool-calls"],"docs":"https://scrimui.dev/components/tool-call","dependencies":[],"registryDependencies":[],"files":[{"path":"src/showcase/tool-call/tool-call.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\n\n/* ------------------------------------------------------------------ */\n/* Types                                                               */\n/* ------------------------------------------------------------------ */\n\nexport type ToolStatus = \"running\" | \"success\" | \"error\";\n\nexport type ToolCallProps = {\n  name: string;\n  input?: string;\n  output?: string;\n  status?: ToolStatus;\n  duration?: string;\n  icon?: React.ReactNode;\n  onCancel?: () => void;\n  defaultOpen?: boolean;\n  className?: string;\n};\n\n/* ------------------------------------------------------------------ */\n/* Icons                                                               */\n/* ------------------------------------------------------------------ */\n\nfunction TerminalIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"14\" height=\"14\" {...props}>\n      <path d=\"m4 17 6-6-6-6M12 19h8\" />\n    </svg>\n  );\n}\n\nfunction ChevronIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"14\" height=\"14\" {...props}>\n      <path d=\"m6 9 6 6 6-6\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"12\" height=\"12\" {...props}>\n      <path d=\"M20 6 9 17l-5-5\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"12\" height=\"12\" {...props}>\n      <path d=\"M18 6 6 18M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction StopIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"10\" height=\"10\" {...props}>\n      <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n    </svg>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/* Status pill                                                         */\n/* ------------------------------------------------------------------ */\n\nfunction StatusPill({ status, onCancel }: { status: ToolStatus; onCancel?: () => void }) {\n  if (status === \"running\") {\n    return (\n      <button\n        type=\"button\"\n        onClick={onCancel}\n        disabled={!onCancel}\n        title=\"Cancel\"\n        /* min-h-6: py-0.5 on 11px text left a 20.5px-tall tap target, under the\n           24x24 minimum. The pill's look is unchanged at this size. */\n        className=\"inline-flex min-h-6 items-center gap-1.5 rounded-full bg-zinc-100 px-2 py-0.5 text-[11px] text-zinc-600 transition-colors hover:bg-zinc-200 disabled:hover:bg-zinc-100 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:disabled:hover:bg-zinc-800\"\n      >\n        <span className=\"h-2.5 w-2.5 animate-spin rounded-full border-[1.5px] border-current border-t-transparent\" />\n        Running\n        {onCancel && (\n          <span className=\"text-zinc-400\">\n            <StopIcon />\n          </span>\n        )}\n      </button>\n    );\n  }\n  if (status === \"success\") {\n    return (\n      <span className=\"inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400\">\n        <CheckIcon />\n        Completed\n      </span>\n    );\n  }\n  return (\n    <span className=\"inline-flex items-center gap-1 rounded-full bg-red-100 px-2 py-0.5 text-[11px] text-red-700 dark:bg-red-900/40 dark:text-red-400\">\n      <XIcon />\n      Failed\n    </span>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/* ToolCall                                                            */\n/* ------------------------------------------------------------------ */\n\nexport function ToolCall({\n  name,\n  input,\n  output,\n  status = \"running\",\n  duration,\n  icon,\n  onCancel,\n  defaultOpen = status !== \"running\",\n  className = \"\",\n}: ToolCallProps) {\n  const [open, setOpen] = React.useState(defaultOpen);\n  const hasDetails = (input?.length ?? 0) > 0 || (output?.length ?? 0) > 0;\n\n  return (\n    <div\n      className={`overflow-hidden rounded-xl border border-zinc-200 bg-white transition-colors dark:border-zinc-800 dark:bg-zinc-900 ${\n        status === \"error\"\n          ? \"border-red-200 dark:border-red-900/60\"\n          : status === \"running\"\n            ? \"border-zinc-300 dark:border-zinc-700\"\n            : \"\"\n      } ${className}`}\n    >\n      {/* Header.\n\n          The disclosure button covers the icon, name and duration only. It\n          used to wrap the whole row, which put StatusPill's Cancel button\n          inside it — nested buttons are invalid HTML, and React refuses to\n          hydrate them. The pill and the chevron are siblings of the button\n          now, so both controls stay independently clickable. */}\n      <div className=\"flex w-full items-center gap-2.5 px-3.5 py-2.5\">\n        <button\n          type=\"button\"\n          onClick={() => hasDetails && setOpen((v) => !v)}\n          aria-expanded={open}\n          disabled={!hasDetails}\n          className=\"flex min-w-0 flex-1 items-center gap-2.5 text-left\"\n        >\n        <span\n          className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-lg ${\n            status === \"error\"\n              ? \"bg-red-100 text-red-600 dark:bg-red-900/40 dark:text-red-400\"\n              : status === \"running\"\n                ? \"bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300\"\n                : \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400\"\n          }`}\n        >\n          {icon ?? <TerminalIcon />}\n        </span>\n        <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-zinc-900 dark:text-zinc-100\">\n          {name}\n        </span>\n        {duration && <span className=\"shrink-0 text-xs tabular-nums text-zinc-500 dark:text-zinc-400\">{duration}</span>}\n        </button>\n        <span className=\"shrink-0\">\n          <StatusPill status={status} onCancel={onCancel} />\n        </span>\n        {hasDetails && (\n          <button\n            type=\"button\"\n            onClick={() => setOpen((v) => !v)}\n            aria-expanded={open}\n            aria-label={open ? \"Hide details\" : \"Show details\"}\n            className={`shrink-0 text-zinc-400 transition-transform ${open ? \"rotate-180\" : \"\"}`}\n          >\n            <ChevronIcon />\n          </button>\n        )}\n      </div>\n\n      {/* Details */}\n      {open && hasDetails && (\n        <div className=\"space-y-2 border-t border-zinc-100 px-3.5 py-3 dark:border-zinc-800\">\n          {input && (\n            <div>\n              <span className=\"text-[11px] font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400\">\n                Input\n              </span>\n              <pre className=\"mt-1 overflow-x-auto rounded-lg bg-zinc-50 p-2.5 font-mono text-xs leading-5 text-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-300\">\n                {input}\n              </pre>\n            </div>\n          )}\n          {output && (\n            <div>\n              <span className=\"text-[11px] font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400\">\n                Output\n              </span>\n              <pre className=\"mt-1 overflow-x-auto rounded-lg bg-zinc-50 p-2.5 font-mono text-xs leading-5 text-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-300\">\n                {output}\n              </pre>\n            </div>\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n","type":"registry:ui","target":"components/ui/tool-call.tsx"}]}