{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "api-endpoint",
  "title": "API Endpoint",
  "description": "A collapsible card that documents one HTTP endpoint with its parameters, request example and responses.",
  "dependencies": [
    "@radix-ui/react-collapsible",
    "@radix-ui/react-tabs",
    "lucide-react"
  ],
  "registryDependencies": [
    "@tealmac/copy-button",
    "@tealmac/highlight"
  ],
  "files": [
    {
      "path": "registry/tealmac/api-endpoint.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\nimport { ChevronDown, Lock } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { CopyButton } from \"@/registry/tealmac/copy-button\"\nimport {\n  highlightLines,\n  tokenClass,\n  type HighlightLanguage,\n} from \"@/registry/tealmac/highlight\"\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\"\n\ntype EndpointParameter = {\n  /** Parameter name. */\n  name: string\n  /** Where it goes. */\n  in: \"path\" | \"query\" | \"header\" | \"body\"\n  /** Type shown in mono, such as \"string\" or \"integer\". */\n  type: string\n  /** The caller must send it. */\n  required?: boolean\n  /** What it means. */\n  description?: string\n  /** Default value when omitted. */\n  default?: string\n}\n\ntype EndpointResponse = {\n  /** HTTP status code. */\n  status: number\n  /** Short meaning, such as \"Customer created\". */\n  description: string\n  /** Example body, usually JSON. */\n  example?: string\n  /** Highlighter for the example. */\n  language?: HighlightLanguage\n}\n\nconst METHOD_STYLE: Record<HttpMethod, string> = {\n  GET: \"border-chart-3/40 bg-chart-3/10\",\n  POST: \"border-chart-1/40 bg-chart-1/10\",\n  PUT: \"border-chart-4/40 bg-chart-4/10\",\n  PATCH: \"border-chart-4/40 bg-chart-4/10\",\n  DELETE: \"border-destructive/40 bg-destructive/10\",\n}\n\nfunction statusMeaning(code: number) {\n  if (code < 300) return \"Success\"\n  if (code < 400) return \"Redirect\"\n  if (code < 500) return \"Client error\"\n  return \"Server error\"\n}\n\nfunction statusDot(code: number) {\n  return code < 300\n    ? \"bg-chart-3\"\n    : code < 400\n      ? \"bg-chart-1\"\n      : code < 500\n        ? \"bg-chart-4\"\n        : \"bg-destructive\"\n}\n\n/** Splits \"/v1/customers/{id}\" so path parameters can be styled. */\nfunction pathParts(path: string) {\n  return path.split(/(\\{[^}]+\\})/g).filter(Boolean)\n}\n\nfunction Code({\n  code,\n  language,\n  label,\n}: {\n  code: string\n  language: HighlightLanguage\n  label: string\n}) {\n  const lines = React.useMemo(\n    () => highlightLines(code, language),\n    [code, language]\n  )\n  return (\n    <div className=\"relative\">\n      <pre\n        dir=\"ltr\"\n        role=\"region\"\n        aria-label={label}\n        // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- scrollable region; focusable so keyboard users can scroll it\n        tabIndex={0}\n        className=\"bg-muted/40 text-foreground focus-visible:ring-ring/50 max-h-72 overflow-auto rounded-lg border p-3.5 font-mono text-xs leading-5 outline-none focus-visible:ring-[3px]\"\n      >\n        <code className=\"grid\">\n          {lines.map((line, i) => (\n            <span key={i} className=\"whitespace-pre\">\n              {line.length === 0\n                ? \" \"\n                : line.map((t, j) => (\n                    <span key={j} className={tokenClass[t.type]}>\n                      {t.text}\n                    </span>\n                  ))}\n            </span>\n          ))}\n        </code>\n      </pre>\n      <CopyButton\n        size=\"sm\"\n        variant=\"outline\"\n        value={code}\n        ariaLabel={`Copy ${label}`}\n        className=\"bg-background/90 absolute end-2 top-2 backdrop-blur\"\n      />\n    </div>\n  )\n}\n\ntype ApiEndpointProps = Omit<\n  React.ComponentProps<\"div\">,\n  \"title\" | \"children\"\n> & {\n  /** HTTP method. */\n  method: HttpMethod\n  /** Path, with `{name}` for path parameters. */\n  path: string\n  /** Short name of the operation, such as \"Create a customer\". */\n  summary: string\n  /** Longer explanation. */\n  description?: string\n  /** Base URL used by the \"Copy URL\" button. */\n  baseUrl?: string\n  /** Authentication needed, such as \"Bearer token\". Shown as a lock chip. */\n  auth?: string\n  /** Request fields. */\n  parameters?: EndpointParameter[]\n  /** Example request body. */\n  requestExample?: string\n  /** Highlighter for the request body. */\n  requestLanguage?: HighlightLanguage\n  /** Possible responses. */\n  responses?: EndpointResponse[]\n  /** Open at first. */\n  defaultOpen?: boolean\n  /** Controlled open state. */\n  open?: boolean\n  /** Called when the card opens or closes. */\n  onOpenChange?: (open: boolean) => void\n}\n\nconst GROUPS: EndpointParameter[\"in\"][] = [\"path\", \"query\", \"header\", \"body\"]\n\nfunction ApiEndpoint({\n  method,\n  path,\n  summary,\n  description,\n  baseUrl,\n  auth,\n  parameters = [],\n  requestExample,\n  requestLanguage = \"json\",\n  responses = [],\n  defaultOpen = true,\n  open,\n  onOpenChange,\n  className,\n  ...props\n}: ApiEndpointProps) {\n  const panelId = React.useId()\n  const tabs = [\n    ...(requestExample\n      ? [{ id: \"request\", label: \"Request\", dot: \"bg-muted-foreground\" }]\n      : []),\n    ...responses.map((r) => ({\n      id: String(r.status),\n      label: String(r.status),\n      dot: statusDot(r.status),\n    })),\n  ]\n  const [tab, setTab] = React.useState(tabs[0]?.id ?? \"\")\n  const url = `${(baseUrl ?? \"\").replace(/\\/$/, \"\")}${path}`\n\n  return (\n    <CollapsiblePrimitive.Root\n      data-slot=\"api-endpoint\"\n      defaultOpen={defaultOpen}\n      open={open}\n      onOpenChange={onOpenChange}\n      className={cn(\n        \"bg-card text-card-foreground w-full overflow-hidden rounded-xl border shadow-xs\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"flex items-center gap-1 pe-2\">\n        <CollapsiblePrimitive.Trigger\n          aria-controls={panelId}\n          className=\"group hover:bg-accent/40 focus-visible:ring-ring/50 flex min-h-14 min-w-0 flex-1 items-center gap-3 px-4 py-2.5 text-start transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-inset motion-reduce:transition-none\"\n        >\n          <span\n            className={cn(\n              \"text-foreground inline-flex h-6 min-w-14 shrink-0 items-center justify-center rounded-md border px-2 font-mono text-[11px] font-bold tracking-wide\",\n              METHOD_STYLE[method]\n            )}\n          >\n            {method}\n          </span>\n          <span className=\"min-w-0 flex-1\">\n            <span className=\"text-foreground block truncate font-mono text-[13px] font-medium\">\n              {pathParts(path).map((part, i) =>\n                part.startsWith(\"{\") ? (\n                  <span\n                    key={i}\n                    className=\"bg-muted text-foreground -mx-px rounded-[4px] px-0.5\"\n                  >\n                    {part}\n                  </span>\n                ) : (\n                  <React.Fragment key={i}>{part}</React.Fragment>\n                )\n              )}\n            </span>\n            <span className=\"text-muted-foreground block truncate text-xs\">\n              {summary}\n            </span>\n          </span>\n          {auth && (\n            <span className=\"text-foreground hidden shrink-0 items-center gap-1 rounded-full border px-2 py-0.5 text-xs sm:inline-flex\">\n              <Lock aria-hidden=\"true\" className=\"size-3\" />\n              {auth}\n            </span>\n          )}\n          <ChevronDown\n            aria-hidden=\"true\"\n            className=\"text-muted-foreground size-4 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-180 motion-reduce:transition-none\"\n          />\n        </CollapsiblePrimitive.Trigger>\n        <CopyButton\n          size=\"sm\"\n          value={url}\n          ariaLabel={`Copy URL for ${method} ${path}`}\n        />\n      </div>\n      <CollapsiblePrimitive.Content\n        id={panelId}\n        className=\"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 overflow-hidden motion-reduce:animate-none\"\n      >\n        <div className=\"@container grid gap-5 border-t p-4\">\n          {description && (\n            <p className=\"text-muted-foreground text-sm leading-6\">\n              {description}\n            </p>\n          )}\n          {auth && (\n            <p className=\"text-foreground inline-flex w-fit items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs sm:hidden\">\n              <Lock aria-hidden=\"true\" className=\"size-3\" />\n              {auth}\n            </p>\n          )}\n\n          {GROUPS.map((group) => {\n            const items = parameters.filter((p) => p.in === group)\n            if (items.length === 0) return null\n            return (\n              <section key={group} aria-label={`${group} parameters`}>\n                <h4 className=\"text-muted-foreground mb-1.5 text-xs font-semibold tracking-wide uppercase\">\n                  {group === \"body\"\n                    ? \"Body\"\n                    : `${group[0]!.toUpperCase()}${group.slice(1)} parameters`}\n                </h4>\n                <ul className=\"divide-y rounded-lg border\">\n                  {items.map((p) => (\n                    <li\n                      key={p.name}\n                      className=\"grid gap-1 px-3 py-2.5 @md:grid-cols-[minmax(0,13rem)_1fr] @md:gap-4\"\n                    >\n                      <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n                        <code className=\"text-foreground font-mono text-[13px] font-medium\">\n                          {p.name}\n                        </code>\n                        <span className=\"text-muted-foreground font-mono text-xs\">\n                          {p.type}\n                        </span>\n                        {p.required && (\n                          <span className=\"border-destructive/40 bg-destructive/10 text-foreground rounded-full border px-1.5 text-[11px] leading-4 font-medium\">\n                            required\n                          </span>\n                        )}\n                      </div>\n                      <p className=\"text-muted-foreground text-[13px] leading-5\">\n                        {p.description}\n                        {p.default !== undefined && (\n                          <span>\n                            {p.description ? \" \" : \"\"}\n                            Default:{\" \"}\n                            <code className=\"text-foreground font-mono\">\n                              {p.default}\n                            </code>\n                          </span>\n                        )}\n                      </p>\n                    </li>\n                  ))}\n                </ul>\n              </section>\n            )\n          })}\n\n          {tabs.length > 0 && (\n            <TabsPrimitive.Root\n              value={tab}\n              onValueChange={setTab}\n              className=\"grid gap-2.5\"\n            >\n              <TabsPrimitive.List\n                aria-label=\"Request and responses\"\n                className=\"flex flex-wrap items-center gap-1\"\n              >\n                {tabs.map((t) => (\n                  <TabsPrimitive.Trigger\n                    key={t.id}\n                    value={t.id}\n                    className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 data-[state=active]:bg-muted data-[state=active]:text-foreground inline-flex h-7 items-center gap-1.5 rounded-md px-2.5 font-mono text-xs font-medium transition-colors outline-none focus-visible:ring-[3px] motion-reduce:transition-none\"\n                  >\n                    <span\n                      aria-hidden=\"true\"\n                      className={cn(\"size-1.5 rounded-full\", t.dot)}\n                    />\n                    {t.label}\n                  </TabsPrimitive.Trigger>\n                ))}\n              </TabsPrimitive.List>\n              {requestExample && (\n                <TabsPrimitive.Content\n                  value=\"request\"\n                  className=\"focus-visible:ring-ring/50 grid gap-2 rounded-lg outline-none focus-visible:ring-[3px]\"\n                >\n                  <Code\n                    code={requestExample}\n                    language={requestLanguage}\n                    label=\"request body\"\n                  />\n                </TabsPrimitive.Content>\n              )}\n              {responses.map((r) => (\n                <TabsPrimitive.Content\n                  key={r.status}\n                  value={String(r.status)}\n                  className=\"focus-visible:ring-ring/50 grid gap-2 rounded-lg outline-none focus-visible:ring-[3px]\"\n                >\n                  <p className=\"text-foreground text-[13px]\">\n                    <span className=\"font-medium\">\n                      {statusMeaning(r.status)}.\n                    </span>{\" \"}\n                    <span className=\"text-muted-foreground\">\n                      {r.description}\n                    </span>\n                  </p>\n                  {r.example && (\n                    <Code\n                      code={r.example}\n                      language={r.language ?? \"json\"}\n                      label={`${r.status} response body`}\n                    />\n                  )}\n                </TabsPrimitive.Content>\n              ))}\n            </TabsPrimitive.Root>\n          )}\n        </div>\n      </CollapsiblePrimitive.Content>\n    </CollapsiblePrimitive.Root>\n  )\n}\n\nexport {\n  ApiEndpoint,\n  type ApiEndpointProps,\n  type EndpointParameter,\n  type EndpointResponse,\n  type HttpMethod,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}