{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "api-playground",
  "title": "API Playground",
  "author": "AlgorizeTech",
  "description": "An interactive API request builder with method selector, headers, query params and a response viewer. Use it in developer documentation so readers can try endpoints inline.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://avaraui.com/r/select.json",
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/ApiPlaygroundBlock.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { SelectBlock } from \"./SelectBlock\";\nimport {\n  Activity,\n  Check,\n  CheckCircle2,\n  Clock,\n  Play,\n  Plus,\n  TerminalSquare,\n  Trash2,\n  XCircle,\n} from \"lucide-react\";\nimport React, { forwardRef, useState } from \"react\";\nimport { getThemeStyles } from \"./api-playground-theme\";\n\n// ==========================================\n// THEME & CONFIGURATION\n// ==========================================\nexport type ApiColor =\n  | \"primary\"\n  | \"default\"\n  | \"lavender\"\n  | \"indigo\"\n  | \"rose\"\n  | \"emerald\"\n  | \"zinc\"\n  | (string & {});\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n// Theme colour fed to SelectBlock so the dropdown highlight, ring and chevron\n// keep the per-method colour coding of the old native <select>.\nconst methodThemeColors: Record<HttpMethod, string> = {\n  GET: \"#3b82f6\",\n  POST: \"#22c55e\",\n  PUT: \"#f97316\",\n  PATCH: \"#eab308\",\n  DELETE: \"#ef4444\",\n};\n\nconst METHOD_OPTIONS = (\n  [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"] as HttpMethod[]\n).map((m) => ({ label: m, value: m }));\n\n// ==========================================\n// INTERFACES\n// ==========================================\nexport interface KeyValuePair {\n  id: string;\n  key: string;\n  value: string;\n  enabled: boolean;\n}\n\nexport interface ApiResponse {\n  status: number;\n  timeMs: number;\n  data: unknown;\n}\n\n/** The request snapshot handed to `mockHandler` when the reader hits Send. */\nexport interface ApiPlaygroundRequest {\n  method: HttpMethod;\n  url: string;\n  headers: KeyValuePair[];\n  params: KeyValuePair[];\n  body: string;\n}\n\n/**\n * Decides how the playground answers a request. Annotate the whole handler with\n * this type rather than the `req` parameter alone — that way the returned\n * response shape is checked too:\n *\n *   const handleApiMock: ApiMockHandler = async (req) => { ... };\n */\nexport type ApiMockHandler = (\n  req: ApiPlaygroundRequest,\n) => Promise<ApiResponse>;\n\nexport interface ApiPlaygroundProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  defaultUrl?: string;\n  defaultMethod?: HttpMethod;\n  color?: ApiColor;\n  mockHandler?: ApiMockHandler;\n}\n\n// Helper to rebuild URL with query params\nconst buildFullUrl = (currentUrl: string, paramsList: KeyValuePair[]) => {\n  const basePath = currentUrl.split(\"?\")[0];\n  const activeParams = paramsList.filter(\n    (p) => p.enabled && p.key.trim() !== \"\",\n  );\n\n  if (activeParams.length === 0) {\n    return basePath;\n  }\n\n  const queryParts = activeParams.map((p) => {\n    const k = encodeURIComponent(p.key.trim());\n    const v = encodeURIComponent(p.value);\n    return `${k}=${v}`;\n  });\n\n  return `${basePath}?${queryParts.join(\"&\")}`;\n};\n\n// Helper to parse query parameters from URL string\nconst parseParamsFromUrl = (fullUrl: string): KeyValuePair[] => {\n  const queryIndex = fullUrl.indexOf(\"?\");\n  if (queryIndex === -1) return [];\n\n  const queryString = fullUrl.slice(queryIndex + 1);\n  if (!queryString) return [];\n\n  const searchParams = new URLSearchParams(queryString);\n  const parsed: KeyValuePair[] = [];\n\n  searchParams.forEach((value, key) => {\n    parsed.push({\n      id: Math.random().toString(36).substring(7),\n      key,\n      value,\n      enabled: true,\n    });\n  });\n\n  return parsed;\n};\n\n// ==========================================\n// COMPONENT\n// ==========================================\nexport const ApiPlaygroundBlock = forwardRef<\n  HTMLDivElement,\n  ApiPlaygroundProps\n>(\n  (\n    {\n      defaultUrl = \"https://api.example.com/v1/users\",\n      defaultMethod = \"GET\",\n      color = \"primary\",\n      mockHandler,\n      className,\n      ...props\n    },\n    ref,\n  ) => {\n    const { theme, style } = getThemeStyles(color as string);\n\n    // State\n    const [method, setMethod] = useState<HttpMethod>(defaultMethod);\n    const [url, setUrl] = useState(() => defaultUrl);\n    const [activeTab, setActiveTab] = useState<\"params\" | \"headers\" | \"body\">(\n      \"params\",\n    );\n\n    const [params, setParams] = useState<KeyValuePair[]>(() => {\n      const initialParsed = parseParamsFromUrl(defaultUrl);\n      return initialParsed.length > 0\n        ? initialParsed\n        : [{ id: \"1\", key: \"\", value: \"\", enabled: true }];\n    });\n    const [headers, setHeaders] = useState<KeyValuePair[]>([\n      {\n        id: \"h1\",\n        key: \"Authorization\",\n        value: \"Bearer sk_test_...\",\n        enabled: true,\n      },\n      {\n        id: \"h2\",\n        key: \"Content-Type\",\n        value: \"application/json\",\n        enabled: true,\n      },\n    ]);\n    const [body, setBody] = useState(\"{\\n  \\n}\");\n\n    const [isLoading, setIsLoading] = useState(false);\n    const [response, setResponse] = useState<ApiResponse | null>(null);\n\n    // Dynamic 2-Way Binding Sync Handlers\n    const syncParamsToUrl = (newParams: KeyValuePair[]) => {\n      setParams(newParams);\n      setUrl((prevUrl) => buildFullUrl(prevUrl, newParams));\n    };\n\n    const handleAddPair = (\n      setter: React.Dispatch<React.SetStateAction<KeyValuePair[]>>,\n    ) => {\n      if (setter === setParams) {\n        const next = [\n          ...params,\n          {\n            id: Math.random().toString(36).substring(7),\n            key: \"\",\n            value: \"\",\n            enabled: true,\n          },\n        ];\n        syncParamsToUrl(next);\n      } else {\n        setter((prev) => [\n          ...prev,\n          {\n            id: Math.random().toString(36).substring(7),\n            key: \"\",\n            value: \"\",\n            enabled: true,\n          },\n        ]);\n      }\n    };\n\n    const handleUpdatePair = (\n      setter: React.Dispatch<React.SetStateAction<KeyValuePair[]>>,\n      id: string,\n      field: keyof KeyValuePair,\n      value: string | boolean,\n    ) => {\n      if (setter === setParams) {\n        const next = params.map((p) =>\n          p.id === id ? { ...p, [field]: value } : p,\n        );\n        syncParamsToUrl(next);\n      } else {\n        setter((prev) =>\n          prev.map((p) => (p.id === id ? { ...p, [field]: value } : p)),\n        );\n      }\n    };\n\n    const handleRemovePair = (\n      setter: React.Dispatch<React.SetStateAction<KeyValuePair[]>>,\n      id: string,\n    ) => {\n      if (setter === setParams) {\n        const next = params.filter((p) => p.id !== id);\n        syncParamsToUrl(next);\n      } else {\n        setter((prev) => prev.filter((p) => p.id !== id));\n      }\n    };\n\n    const handleUrlChange = (newUrl: string) => {\n      setUrl(newUrl);\n      const parsed = parseParamsFromUrl(newUrl);\n      if (parsed.length > 0) {\n        setParams(parsed);\n      }\n    };\n\n    const handleSend = async () => {\n      setIsLoading(true);\n      setResponse(null);\n\n      const executeRequest =\n        mockHandler ||\n        (async (req) => {\n          await new Promise((resolve) => setTimeout(resolve, 600));\n\n          let parsedBody: unknown = null;\n          if (req.method !== \"GET\" && req.body) {\n            try {\n              parsedBody = JSON.parse(req.body);\n            } catch {\n              parsedBody = req.body;\n            }\n          }\n\n          const activeHeaders = req.headers\n            .filter((h) => h.enabled && h.key)\n            .reduce<Record<string, string>>((acc, h) => {\n              acc[h.key] = h.value;\n              return acc;\n            }, {});\n\n          const activeParams = req.params\n            .filter((p) => p.enabled && p.key)\n            .reduce<Record<string, string>>((acc, p) => {\n              acc[p.key] = p.value;\n              return acc;\n            }, {});\n\n          return {\n            status: req.method === \"POST\" ? 201 : 200,\n            timeMs: Math.floor(Math.random() * 150) + 50,\n            data: {\n              message: \"Success\",\n              echo: {\n                method: req.method,\n                url: req.url,\n                params: Object.keys(activeParams).length > 0 ? activeParams : undefined,\n                headers: Object.keys(activeHeaders).length > 0 ? activeHeaders : undefined,\n                payload: req.method !== \"GET\" ? (parsedBody ?? \"No body provided\") : undefined,\n              },\n            },\n          };\n        });\n\n      try {\n        const res = await executeRequest({\n          method,\n          url,\n          headers,\n          params,\n          body,\n        });\n        setResponse(res);\n      } catch (error) {\n        setResponse({\n          status: 500,\n          timeMs: 0,\n          data: {\n            error:\n              error instanceof Error && error.message\n                ? error.message\n                : \"Internal Server Error\",\n          },\n        });\n      } finally {\n        setIsLoading(false);\n      }\n    };\n\n    // Render Key-Value Editor (Mobile Optimized)\n    const renderKvEditor = (\n      items: KeyValuePair[],\n      setter: React.Dispatch<React.SetStateAction<KeyValuePair[]>>,\n    ) => (\n      <div className=\"flex flex-col gap-2\">\n        {items.map((item) => (\n          <div\n            key={item.id}\n            className=\"flex items-center gap-1.5 sm:gap-2 group\"\n          >\n            <label className=\"relative flex items-center justify-center cursor-pointer shrink-0\">\n              <input\n                type=\"checkbox\"\n                checked={item.enabled}\n                onChange={(e) =>\n                  handleUpdatePair(setter, item.id, \"enabled\", e.target.checked)\n                }\n                className=\"peer sr-only\"\n              />\n              <div\n                className={cn(\n                  \"w-4 h-4 rounded flex items-center justify-center border transition-all duration-150 peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-1\",\n                  item.enabled\n                    ? cn(theme.bg, theme.checkboxBorder ?? theme.border)\n                    : \"border-input bg-background hover:border-foreground/40\",\n                )}\n              >\n                <Check\n                  className={cn(\n                    \"w-3 h-3 stroke-[3] transition-transform duration-150\",\n                    item.enabled ? \"scale-100 opacity-100\" : \"scale-0 opacity-0\",\n                  )}\n                />\n              </div>\n            </label>\n            <input\n              type=\"text\"\n              placeholder=\"Key\"\n              value={item.key}\n              onChange={(e) =>\n                handleUpdatePair(setter, item.id, \"key\", e.target.value)\n              }\n              className=\"w-1/2 min-w-0 px-2 sm:px-3 py-1.5 text-xs sm:text-sm bg-background border border-input rounded-md outline-none focus:border-ring font-mono transition-colors\"\n              disabled={!item.enabled}\n            />\n            <input\n              type=\"text\"\n              placeholder=\"Value\"\n              value={item.value}\n              onChange={(e) =>\n                handleUpdatePair(setter, item.id, \"value\", e.target.value)\n              }\n              className=\"w-1/2 min-w-0 px-2 sm:px-3 py-1.5 text-xs sm:text-sm bg-background border border-input rounded-md outline-none focus:border-ring font-mono transition-colors\"\n              disabled={!item.enabled}\n            />\n            <button\n              onClick={() => handleRemovePair(setter, item.id)}\n              className=\"p-1.5 text-muted-foreground hover:text-red-500 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 transition-all rounded-md hover:bg-red-50 dark:hover:bg-red-500/10 shrink-0 cursor-pointer\"\n              title=\"Remove pair\"\n            >\n              <Trash2 className=\"w-4 h-4\" />\n            </button>\n          </div>\n        ))}\n        <button\n          onClick={() => handleAddPair(setter)}\n          className=\"flex items-center gap-1.5 text-xs font-semibold text-muted-foreground hover:text-foreground w-max mt-2 transition-colors cursor-pointer\"\n        >\n          <Plus className=\"w-3.5 h-3.5\" /> Add{\" \"}\n          {activeTab === \"params\" ? \"Parameter\" : \"Header\"}\n        </button>\n      </div>\n    );\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"w-full grid grid-cols-1 lg:grid-cols-2 gap-4 rounded-2xl bg-transparent border-none shadow-none\",\n          className,\n        )}\n        style={style}\n        {...props}\n      >\n        {/* LEFT PANE: REQUEST BUILDER */}\n        <div className=\"flex flex-col gap-4 min-w-0\">\n          {/* ── RESPONSIVE URL BAR ─────────────────────────────────────── */}\n          <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2 p-1.5 sm:p-2 bg-card border border-border rounded-xl\">\n            <div className=\"flex items-center gap-2 flex-1 min-w-0\">\n              <SelectBlock\n                options={METHOD_OPTIONS}\n                value={method}\n                onChange={(v) => setMethod(v as HttpMethod)}\n                color={methodThemeColors[method]}\n                variant=\"filled\"\n                inputSize=\"sm\"\n                className=\"w-[104px] shrink-0\"\n              />\n\n              <input\n                type=\"text\"\n                value={url}\n                onChange={(e) => handleUrlChange(e.target.value)}\n                className=\"w-full min-w-0 bg-transparent px-2 text-xs sm:text-sm font-mono text-foreground outline-none truncate\"\n                placeholder=\"https://api.example.com...\"\n              />\n            </div>\n\n            <button\n              onClick={handleSend}\n              disabled={isLoading}\n              className={cn(\n                \"flex items-center justify-center gap-2 h-9 px-4 rounded-lg text-sm font-semibold transition-all disabled:opacity-50 shrink-0 w-full sm:w-auto cursor-pointer\",\n                theme.bg,\n                !isLoading && theme.glow,\n              )}\n            >\n              {isLoading ? (\n                <Activity className=\"w-4 h-4 animate-pulse\" />\n              ) : (\n                <>\n                  <Play className=\"w-4 h-4 fill-current\" />\n                  Send\n                </>\n              )}\n            </button>\n          </div>\n\n          {/* Config Tabs */}\n          <div className=\"flex flex-col flex-1 bg-card border border-border rounded-xl overflow-hidden min-h-[250px] min-w-0\">\n            {/* ── SCROLLABLE TABS ───────────────────────────────────────── */}\n            <div className=\"flex items-center border-b border-border bg-muted/50 px-2 pt-2 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\">\n              {([\"params\", \"headers\", \"body\"] as const).map((tab) => (\n                <button\n                  key={tab}\n                  onClick={() => setActiveTab(tab)}\n                  className={cn(\n                    \"px-3 sm:px-4 py-2 text-xs font-semibold capitalize transition-all border-b-2 whitespace-nowrap shrink-0 cursor-pointer\",\n                    activeTab === tab\n                      ? cn(theme.tabBorder ?? theme.border ?? \"border-primary\", theme.text)\n                      : \"border-transparent text-muted-foreground hover:text-foreground\",\n                  )}\n                >\n                  {tab}\n                  {tab === \"params\" &&\n                    params.filter((p) => p.key).length > 0 &&\n                    ` (${params.filter((p) => p.key).length})`}\n                  {tab === \"headers\" &&\n                    headers.filter((p) => p.key).length > 0 &&\n                    ` (${headers.filter((p) => p.key).length})`}\n                  {tab === \"body\" &&\n                    method !== \"GET\" &&\n                    body.trim().length > 2 &&\n                    \" (Raw)\"}\n                </button>\n              ))}\n            </div>\n\n            <div className=\"p-3 sm:p-4 flex-1 overflow-y-auto\">\n              {activeTab === \"params\" && renderKvEditor(params, setParams)}\n              {activeTab === \"headers\" && renderKvEditor(headers, setHeaders)}\n              {activeTab === \"body\" && (\n                <div className=\"w-full h-full flex flex-col relative group\">\n                  {method === \"GET\" && (\n                    <div className=\"absolute inset-0 bg-card/80 backdrop-blur-[1px] z-10 flex items-center justify-center rounded-lg p-4 text-center\">\n                      <p className=\"text-xs sm:text-sm font-medium text-muted-foreground\">\n                        GET requests typically do not have a body.\n                      </p>\n                    </div>\n                  )}\n                  <textarea\n                    value={body}\n                    onChange={(e) => setBody(e.target.value)}\n                    className=\"w-full h-full min-h-[150px] p-3 text-xs sm:text-sm font-mono bg-muted border border-input rounded-lg outline-none focus:border-ring resize-none transition-colors\"\n                    spellCheck={false}\n                  />\n                </div>\n              )}\n            </div>\n          </div>\n        </div>\n\n        {/* RIGHT PANE: RESPONSE VIEWER */}\n        <div className=\"flex flex-col bg-card rounded-xl overflow-hidden border border-border min-h-[300px] min-w-0\">\n          {/* ── RESPONSIVE TERMINAL HEADER ──────────────────────────────── */}\n          <div className=\"flex flex-wrap items-center justify-between gap-2 px-3 sm:px-4 py-2.5 bg-muted/80 border-b border-border\">\n            <div className=\"flex items-center gap-2\">\n              <TerminalSquare className=\"w-4 h-4 text-muted-foreground shrink-0\" />\n              <span className=\"text-xs font-semibold text-muted-foreground\">\n                Response\n              </span>\n            </div>\n\n            {response && (\n              <div className=\"flex items-center gap-3 sm:gap-4 text-[11px] font-mono\">\n                <div\n                  className={cn(\n                    \"flex items-center gap-1 sm:gap-1.5 font-semibold\",\n                    response.status < 400\n                      ? \"text-emerald-600 dark:text-emerald-400\"\n                      : \"text-red-600 dark:text-red-400\",\n                  )}\n                >\n                  {response.status < 400 ? (\n                    <CheckCircle2 className=\"w-3 h-3 shrink-0\" />\n                  ) : (\n                    <XCircle className=\"w-3 h-3 shrink-0\" />\n                  )}\n                  Status: {response.status}\n                </div>\n                <div className=\"flex items-center gap-1 sm:gap-1.5 text-muted-foreground border-l border-border pl-3 sm:pl-4\">\n                  <Clock className=\"w-3 h-3 shrink-0\" />\n                  {response.timeMs}ms\n                </div>\n              </div>\n            )}\n          </div>\n\n          {/* Terminal Body */}\n          <div className=\"relative flex-1 p-3 sm:p-4 overflow-y-auto overflow-x-auto\">\n            {isLoading ? (\n              <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-card/80\">\n                <Activity className={cn(\"w-6 h-6 animate-pulse\", theme.text)} />\n                <span className=\"text-xs font-mono text-muted-foreground animate-pulse\">\n                  Awaiting response...\n                </span>\n              </div>\n            ) : response ? (\n              <pre className=\"text-xs sm:text-[13px] font-mono leading-relaxed text-foreground\">\n                <code>{JSON.stringify(response.data, null, 2)}</code>\n              </pre>\n            ) : (\n              <div className=\"h-full flex flex-col items-center justify-center gap-2 opacity-50 dark:opacity-30 select-none text-center p-4\">\n                <TerminalSquare className=\"w-10 sm:w-12 h-10 sm:h-12 text-muted-foreground mb-2\" />\n                <p className=\"text-xs sm:text-sm font-medium text-muted-foreground\">\n                  Hit Send to get a response\n                </p>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    );\n  },\n);\nApiPlaygroundBlock.displayName = \"ApiPlaygroundBlock\";\n",
      "type": "registry:component",
      "target": "components/custom/blocks/ApiPlaygroundBlock.tsx"
    },
    {
      "path": "src/components/custom/blocks/api-playground-theme.ts",
      "content": "\"use client\";\n\nimport React from \"react\";\n\nexport interface ApiThemeStyles {\n  bg: string;\n  text: string;\n  border: string;\n  tabBorder?: string;\n  checkboxBorder?: string;\n  glow: string;\n}\n\nconst colorThemes: Record<string, ApiThemeStyles> = {\n  primary: {\n    bg: \"bg-primary text-primary-foreground\",\n    text: \"text-primary\",\n    border: \"border-primary/30\",\n    tabBorder: \"border-primary\",\n    checkboxBorder: \"border-primary\",\n    glow: \"shadow-[0_0_15px_var(--primary)]\",\n  },\n  default: {\n    bg: \"bg-primary text-primary-foreground\",\n    text: \"text-primary\",\n    border: \"border-primary/30\",\n    tabBorder: \"border-primary\",\n    checkboxBorder: \"border-primary\",\n    glow: \"shadow-[0_0_15px_var(--primary)]\",\n  },\n  lavender: {\n    bg: \"bg-[#9b8afb] text-white\",\n    text: \"text-[#9b8afb]\",\n    border: \"border-[#9b8afb]/40\",\n    tabBorder: \"border-[#9b8afb]\",\n    checkboxBorder: \"border-[#9b8afb]\",\n    glow: \"shadow-[0_0_15px_rgba(155,138,251,0.35)]\",\n  },\n  indigo: {\n    bg: \"bg-indigo-600 text-white\",\n    text: \"text-indigo-600 dark:text-indigo-400\",\n    border: \"border-indigo-500\",\n    tabBorder: \"border-indigo-500\",\n    checkboxBorder: \"border-indigo-500\",\n    glow: \"shadow-[0_0_15px_rgba(99,102,241,0.3)]\",\n  },\n  rose: {\n    bg: \"bg-rose-600 text-white\",\n    text: \"text-rose-600 dark:text-rose-400\",\n    border: \"border-rose-500\",\n    tabBorder: \"border-rose-500\",\n    checkboxBorder: \"border-rose-500\",\n    glow: \"shadow-[0_0_15px_rgba(225,29,72,0.3)]\",\n  },\n  emerald: {\n    bg: \"bg-emerald-600 text-white\",\n    text: \"text-emerald-600 dark:text-emerald-400\",\n    border: \"border-emerald-500\",\n    tabBorder: \"border-emerald-500\",\n    checkboxBorder: \"border-emerald-500\",\n    glow: \"shadow-[0_0_15px_rgba(16,185,129,0.3)]\",\n  },\n  zinc: {\n    bg: \"bg-zinc-900 text-white dark:bg-zinc-100 dark:text-zinc-900\",\n    text: \"text-zinc-900 dark:text-zinc-100\",\n    border: \"border-zinc-900 dark:border-zinc-100\",\n    tabBorder: \"border-zinc-900 dark:border-zinc-100\",\n    checkboxBorder: \"border-zinc-900 dark:border-zinc-100\",\n    glow: \"shadow-[0_0_15px_rgba(255,255,255,0.2)] dark:shadow-[0_0_15px_rgba(255,255,255,0.2)]\",\n  },\n};\n\nconst hexToRgb = (hex: string) => {\n  let cleanHex = hex.replace(/^#/, \"\");\n  if (cleanHex.length === 3)\n    cleanHex = cleanHex\n      .split(\"\")\n      .map((x) => x + x)\n      .join(\"\");\n  const result = /^([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(cleanHex);\n  return result\n    ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`\n    : \"99, 102, 241\";\n};\n\nconst themeVarsMap: Record<string, { color: string; glow: string }> = {\n  primary: { color: \"var(--primary)\", glow: \"var(--primary)\" },\n  default: { color: \"var(--primary)\", glow: \"var(--primary)\" },\n  lavender: { color: \"#9b8afb\", glow: \"rgba(155, 138, 251, 0.35)\" },\n  indigo: { color: \"#6366f1\", glow: \"rgba(99, 102, 241, 0.3)\" },\n  rose: { color: \"#e11d48\", glow: \"rgba(225, 29, 72, 0.3)\" },\n  emerald: { color: \"#10b981\", glow: \"rgba(16, 185, 129, 0.3)\" },\n  zinc: { color: \"#18181b\", glow: \"rgba(255, 255, 255, 0.2)\" },\n};\n\nexport const getThemeStyles = (color: string) => {\n  if (color && color.startsWith(\"#\")) {\n    const rgb = hexToRgb(color);\n    return {\n      theme: {\n        bg: \"bg-[var(--api-color)] text-white\",\n        text: \"text-[var(--api-color)]\",\n        border: \"border-[var(--api-color)]\",\n        tabBorder: \"border-[var(--api-color)]\",\n        checkboxBorder: \"border-[var(--api-color)]\",\n        glow: \"shadow-[0_0_15px_var(--api-glow)]\",\n      },\n      style: {\n        \"--api-color\": color,\n        \"--api-glow\": `rgba(${rgb}, 0.3)`,\n      } as React.CSSProperties,\n    };\n  }\n\n  const resolvedTheme = colorThemes[color] || colorThemes.primary;\n  const vars = themeVarsMap[color] || themeVarsMap.primary;\n\n  return {\n    theme: resolvedTheme,\n    style: {\n      \"--api-color\": vars.color,\n      \"--api-glow\": vars.glow,\n    } as React.CSSProperties,\n  };\n};\n",
      "type": "registry:component",
      "target": "components/custom/blocks/api-playground-theme.ts"
    }
  ],
  "docs": "Named exports: ApiPlaygroundBlock, getThemeStyles (from api-playground-theme.ts, which installs alongside), and the types ApiPlaygroundRequest, ApiMockHandler, ApiResponse. Methods GET | POST | PUT | PATCH | DELETE are colour-coded. Ships a mock response by default — pass a `mockHandler`, or wire it to a real fetch. Depends on `select`. Sending real requests from the browser is subject to CORS.",
  "categories": [
    "code",
    "data-display"
  ],
  "type": "registry:component"
}
