{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "db-connection-card",
  "title": "DBConnectionCard",
  "description": "Neon database connection card: pooled/direct toggle, role and database selectors, URI / psql / parameters views, one shared secret reveal, and copy that always carries the real string.",
  "dependencies": [
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/button.json",
    "https://ui.neon.com/r/select.json",
    "https://ui.neon.com/r/tabs.json",
    "https://ui.neon.com/r/skeleton.json",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/tooltip.json"
  ],
  "files": [
    {
      "path": "src/components/db-connection-card/db-connection-card.tsx",
      "content": "\"use client\";\n\nimport {\n  Copy01Icon,\n  Tick02Icon,\n  ViewIcon,\n  ViewOffSlashIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { Fragment, useEffect, useRef, useState } from \"react\";\nimport type { ComponentProps, ReactElement, ReactNode } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\nconst COPY_FLASH_MS = 1500;\nconst MASK = \"\\u2022\\u2022\\u2022\\u2022\\u2022\";\nconst DEFAULT_PORT = \"5432\";\n\ntype ConnectionFormat = \"uri\" | \"psql\" | \"params\" | \"agent\";\n\nexport interface ConnectionSelection {\n  role: string;\n  database: string;\n  /** Pooled (PgBouncer) connection when true, direct when false. */\n  pooled: boolean;\n}\n\nexport interface ConnectionEntry extends ConnectionSelection {\n  /** The full connection string for this role/database/pooled combination. */\n  uri: string;\n}\n\ninterface ConnectionParams {\n  host: string;\n  port: string;\n  database: string;\n  user: string;\n  password: string;\n}\n\ninterface ParsedUri {\n  /** scheme + role, up to the password. */\n  head: string;\n  password: string;\n  /** From the at-sign on: host, database, and query. */\n  tail: string;\n}\n\n/** Split so only the password needs hiding. Null when not URL-shaped. */\nconst parseUri = (uri: string): ParsedUri | null => {\n  try {\n    const url = new URL(uri);\n\n    if (!url.password) {\n      return null;\n    }\n\n    return {\n      head: `${url.protocol}//${decodeURIComponent(url.username)}:`,\n      password: decodeURIComponent(url.password),\n      tail: uri.slice(uri.indexOf(\"@\")),\n    };\n  } catch {\n    return null;\n  }\n};\n\nconst parseParams = (uri: string): ConnectionParams | null => {\n  try {\n    const url = new URL(uri);\n\n    return {\n      database: url.pathname.replace(/^\\//u, \"\"),\n      host: url.hostname,\n      password: decodeURIComponent(url.password),\n      port: url.port || DEFAULT_PORT,\n      user: decodeURIComponent(url.username),\n    };\n  } catch {\n    return null;\n  }\n};\n\n/** A paste-ready agent prompt, secret excluded: point the agent at the env var. */\nconst buildAgentPrompt = (params: ConnectionParams | null, pooled: boolean) => {\n  if (!params) {\n    return \"\";\n  }\n\n  return [\n    \"Connect to a Neon Postgres database.\",\n    \"Read the connection string from the DATABASE_URL environment variable; never hardcode credentials.\",\n    \"\",\n    `host: ${params.host}`,\n    `database: ${params.database}`,\n    `role: ${params.user}`,\n    `connection: ${pooled ? \"pooled (PgBouncer)\" : \"direct\"}, SSL required`,\n  ].join(\"\\n\");\n};\n\nconst unique = (values: string[]) => [...new Set(values)];\n\n/** The longest option; in a mono font the longest string is also the widest. */\nconst widestOption = (options: string[]) => {\n  let widest = \"\";\n  for (const option of options) {\n    if (option.length > widest.length) {\n      widest = option;\n    }\n  }\n  return widest;\n};\n\nconst matches = (entry: ConnectionEntry, selection: ConnectionSelection) =>\n  entry.role === selection.role &&\n  entry.database === selection.database &&\n  entry.pooled === selection.pooled;\n\n/** Hover/focus label for an icon-only control. */\nconst IconTooltip = ({\n  label,\n  children,\n}: {\n  label: string;\n  children: ReactElement;\n}) => (\n  <Tooltip>\n    <TooltipTrigger render={children} />\n    <TooltipContent>{label}</TooltipContent>\n  </Tooltip>\n);\n\n/* ─────────────────────────────────────────────────────────\n * A copy that carries the real value and flashes a primary\n * check for 1.5s, announcing through a polite live region.\n * ───────────────────────────────────────────────────────── */\nconst CopyButton = ({ value, label }: { value: string; label: string }) => {\n  const [copied, setCopied] = useState(false);\n\n  const copy = async () => {\n    setCopied(true);\n    window.setTimeout(() => setCopied(false), COPY_FLASH_MS);\n\n    try {\n      await navigator.clipboard.writeText(value);\n    } catch {\n      // Clipboard unavailable (blur, permissions); the feedback still shows.\n    }\n  };\n\n  return (\n    <div className=\"relative flex shrink-0 items-center\">\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"pointer-events-none absolute inset-y-0 right-full flex items-center bg-gradient-to-r from-transparent via-card to-card pr-2 pl-8 font-mono text-[10px] text-primary transition-all duration-200 ease-out motion-reduce:transition-none\",\n          copied ? \"translate-x-0 opacity-100\" : \"translate-x-1 opacity-0\"\n        )}\n      >\n        Copied\n      </span>\n      <IconTooltip label={label}>\n        <Button\n          aria-label={label}\n          onClick={copy}\n          size=\"icon-xs\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          <HugeiconsIcon\n            className={cn(\"transition-colors\", copied && \"text-primary\")}\n            icon={copied ? Tick02Icon : Copy01Icon}\n            strokeWidth={2}\n          />\n        </Button>\n      </IconTooltip>\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {copied ? `${label} copied` : \"\"}\n      </span>\n    </div>\n  );\n};\n\n/** The connection URI with only the password hidden; masks whole if not URL-shaped. */\nconst UriText = ({ uri, revealed }: { uri: string; revealed: boolean }) => {\n  const parsed = parseUri(uri);\n\n  if (!parsed) {\n    return revealed ? uri : MASK;\n  }\n\n  return (\n    <>\n      {parsed.head}\n      <span className={cn(revealed ? undefined : \"text-muted-foreground\")}>\n        {revealed ? parsed.password : MASK}\n      </span>\n      {parsed.tail}\n    </>\n  );\n};\n\nconst secretRowClassName =\n  \"flex min-w-0 items-center gap-1 rounded-md border border-border/60 bg-background py-1.5 pr-1 pl-2.5 transition-colors hover:border-border has-[:focus-visible]:border-primary/50 has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-primary/15\";\nconst panelClassName =\n  \"fade-in-0 slide-in-from-bottom-1 animate-in duration-200 motion-reduce:animate-none\";\nconst codeClassName =\n  \"min-w-0 flex-1 truncate font-mono text-[11px] leading-relaxed\";\n\nconst SecretRow = ({\n  copyLabel,\n  copyValue,\n  children,\n}: {\n  copyLabel: string;\n  copyValue: string;\n  children: ReactNode;\n}) => (\n  <div className={secretRowClassName}>\n    <code className={codeClassName}>{children}</code>\n    <CopyButton label={copyLabel} value={copyValue} />\n  </div>\n);\n\nconst ParamsView = ({\n  params,\n  revealed,\n}: {\n  params: ConnectionParams | null;\n  revealed: boolean;\n}) => {\n  if (!params) {\n    return (\n      <p className=\"rounded-md border border-border/60 border-dashed bg-background px-2.5 py-2 font-mono text-[11px] text-muted-foreground/70\">\n        no connection string\n      </p>\n    );\n  }\n\n  const rows: [string, string][] = [\n    [\"host\", params.host],\n    [\"port\", params.port],\n    [\"database\", params.database],\n    [\"user\", params.user],\n  ];\n\n  return (\n    <dl\n      className=\"grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-3 gap-y-1.5 rounded-md border border-border/60 bg-background px-2.5 py-2\"\n      data-slot=\"db-connection-params\"\n    >\n      {rows.map(([key, value]) => (\n        <Fragment key={key}>\n          <dt className=\"font-mono text-[10px] text-muted-foreground/70\">\n            {key}\n          </dt>\n          <dd className=\"m-0 min-w-0 truncate font-mono text-[11px] tabular-nums\">\n            {value}\n          </dd>\n          <CopyButton label={`Copy ${key}`} value={value} />\n        </Fragment>\n      ))}\n      <dt className=\"font-mono text-[10px] text-muted-foreground/70\">\n        password\n      </dt>\n      <dd\n        className={cn(\n          \"m-0 min-w-0 truncate font-mono text-[11px]\",\n          revealed ? undefined : \"text-muted-foreground\"\n        )}\n      >\n        {revealed ? params.password : MASK}\n      </dd>\n      <CopyButton label=\"Copy password\" value={params.password} />\n    </dl>\n  );\n};\n\nconst SelectField = ({\n  caption,\n  value,\n  options,\n  onChange,\n}: {\n  caption: string;\n  value: string;\n  options: string[];\n  onChange: (next: string) => void;\n}) => {\n  if (options.length <= 1) {\n    return (\n      <span className=\"inline-flex items-center gap-1.5 font-mono text-xs\">\n        <span className=\"text-[10px] text-muted-foreground/60\">{caption}</span>\n        <span className=\"text-foreground\">{value}</span>\n      </span>\n    );\n  }\n\n  return (\n    <Select\n      onValueChange={(next) => {\n        if (next !== null) {\n          onChange(next);\n        }\n      }}\n      value={value}\n    >\n      <SelectTrigger\n        aria-label={caption}\n        className=\"h-7 gap-2 rounded-md border-border/60 bg-muted/20 pr-1.5 pl-2 font-mono text-xs transition-colors hover:border-border hover:bg-muted/40\"\n        size=\"sm\"\n      >\n        <span className=\"text-[10px] text-muted-foreground/60\">{caption}</span>\n        {/* Reserve the widest option's width (mono: longest string is widest)\n            so the field doesn't reshape the row when the selection changes. */}\n        <span className=\"grid\">\n          <span\n            aria-hidden=\"true\"\n            className=\"invisible col-start-1 row-start-1 whitespace-nowrap\"\n          >\n            {widestOption(options)}\n          </span>\n          <span className=\"col-start-1 row-start-1\">\n            <SelectValue />\n          </span>\n        </span>\n      </SelectTrigger>\n      <SelectContent>\n        {options.map((option) => (\n          <SelectItem className=\"font-mono text-xs\" key={option} value={option}>\n            {option}\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\n  );\n};\n\nconst MODES = [\n  {\n    hint: \"Routes through PgBouncer. Best for serverless functions and many short-lived connections.\",\n    label: \"pooled\",\n    pooled: true,\n  },\n  {\n    hint: \"Connects straight to the compute. Use it for migrations, long-lived clients, and session features like LISTEN/NOTIFY.\",\n    label: \"direct\",\n    pooled: false,\n  },\n] as const;\n\nconst ModeToggle = ({\n  pooled,\n  onSelect,\n}: {\n  pooled: boolean;\n  onSelect: (pooled: boolean) => void;\n}) => (\n  <fieldset className=\"m-0 inline-flex shrink-0 rounded-md border border-border/60 p-0.5\">\n    <legend className=\"sr-only\">Connection mode</legend>\n    {MODES.map((mode) => (\n      <Tooltip key={mode.label}>\n        <TooltipTrigger\n          render={\n            <button\n              aria-pressed={pooled === mode.pooled}\n              className={cn(\n                \"rounded-[calc(var(--radius-md)-3px)] px-2 py-0.5 font-mono text-[11px] transition-colors active:scale-[0.98] motion-reduce:active:scale-100\",\n                pooled === mode.pooled\n                  ? \"bg-primary/10 font-medium text-primary\"\n                  : \"text-muted-foreground hover:text-foreground\"\n              )}\n              onClick={() => onSelect(mode.pooled)}\n              type=\"button\"\n            >\n              {mode.label}\n            </button>\n          }\n        />\n        <TooltipContent className=\"max-w-60 font-sans text-xs leading-relaxed\">\n          {mode.hint}\n        </TooltipContent>\n      </Tooltip>\n    ))}\n  </fieldset>\n);\n\nconst FORMATS: { value: ConnectionFormat; label: string }[] = [\n  { label: \"URI\", value: \"uri\" },\n  { label: \"psql\", value: \"psql\" },\n  { label: \"Parameters\", value: \"params\" },\n  { label: \"Agent\", value: \"agent\" },\n];\n\n/**\n * Animate the panel area's height as its content changes size (URI and psql\n * are one row; parameters is a grid). Measures the live content and transitions\n * `height`, so the card grows and shrinks smoothly instead of jumping.\n */\nconst AnimatedHeight = ({ children }: { children: ReactNode }) => {\n  const innerRef = useRef<HTMLDivElement>(null);\n  const [height, setHeight] = useState<number>();\n\n  useEffect(() => {\n    const inner = innerRef.current;\n\n    if (!inner) {\n      return;\n    }\n\n    const observer = new ResizeObserver(() => setHeight(inner.offsetHeight));\n    observer.observe(inner);\n    setHeight(inner.offsetHeight);\n\n    return () => observer.disconnect();\n  }, []);\n\n  return (\n    <div\n      className=\"overflow-hidden transition-[height] duration-300 ease-out motion-reduce:transition-none\"\n      style={{ height }}\n    >\n      <div ref={innerRef}>{children}</div>\n    </div>\n  );\n};\n\nconst RevealButton = ({\n  revealed,\n  onToggle,\n}: {\n  revealed: boolean;\n  onToggle: () => void;\n}) => (\n  <IconTooltip label={revealed ? \"Hide password\" : \"Reveal password\"}>\n    <Button\n      aria-label={revealed ? \"Hide password\" : \"Reveal password\"}\n      aria-pressed={revealed}\n      className={cn(\"shrink-0 transition-colors\", revealed && \"text-primary\")}\n      onClick={onToggle}\n      size=\"icon-xs\"\n      type=\"button\"\n      variant=\"ghost\"\n    >\n      <HugeiconsIcon\n        icon={revealed ? ViewOffSlashIcon : ViewIcon}\n        strokeWidth={2}\n      />\n    </Button>\n  </IconTooltip>\n);\n\nconst SelectorRow = ({\n  roleOptions,\n  databaseOptions,\n  role,\n  database,\n  onRole,\n  onDatabase,\n}: {\n  roleOptions: string[];\n  databaseOptions: string[];\n  role: string;\n  database: string;\n  onRole: (next: string) => void;\n  onDatabase: (next: string) => void;\n}) => {\n  if (roleOptions.length === 0 && databaseOptions.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"flex flex-wrap items-center gap-x-4 gap-y-2\">\n      {roleOptions.length > 0 ? (\n        <SelectField\n          caption=\"role\"\n          onChange={onRole}\n          options={roleOptions}\n          value={role}\n        />\n      ) : null}\n      {databaseOptions.length > 0 ? (\n        <SelectField\n          caption=\"database\"\n          onChange={onDatabase}\n          options={databaseOptions}\n          value={database}\n        />\n      ) : null}\n    </div>\n  );\n};\n\nconst FormatTabs = ({\n  format,\n  onFormat,\n  revealed,\n  onToggleReveal,\n  uri,\n  psql,\n  params,\n  agentPrompt,\n  secret,\n}: {\n  format: ConnectionFormat;\n  onFormat: (format: ConnectionFormat) => void;\n  revealed: boolean;\n  onToggleReveal: () => void;\n  uri: string;\n  psql: string;\n  params: ConnectionParams | null;\n  agentPrompt: string;\n  secret: ReactNode;\n}) => (\n  <Tabs\n    onValueChange={(next) => onFormat(next as ConnectionFormat)}\n    value={format}\n  >\n    <div className=\"flex items-center justify-between gap-2\">\n      <TabsList className=\"gap-3\" variant=\"line\">\n        {FORMATS.map((entry) => (\n          <TabsTrigger\n            className=\"flex-none px-0.5 font-mono text-xs transition-colors data-active:text-primary data-active:after:bg-primary\"\n            key={entry.value}\n            value={entry.value}\n          >\n            {entry.label}\n          </TabsTrigger>\n        ))}\n      </TabsList>\n      {format === \"agent\" ? null : (\n        <RevealButton onToggle={onToggleReveal} revealed={revealed} />\n      )}\n    </div>\n\n    <AnimatedHeight>\n      <TabsContent className={panelClassName} value=\"uri\">\n        <SecretRow copyLabel=\"Copy connection string\" copyValue={uri}>\n          {secret}\n        </SecretRow>\n      </TabsContent>\n\n      <TabsContent className={panelClassName} value=\"psql\">\n        <SecretRow copyLabel=\"Copy psql command\" copyValue={psql}>\n          psql &apos;{secret}&apos;\n        </SecretRow>\n      </TabsContent>\n\n      <TabsContent className={panelClassName} value=\"params\">\n        <ParamsView params={params} revealed={revealed} />\n      </TabsContent>\n\n      <TabsContent className={panelClassName} value=\"agent\">\n        <div className=\"rounded-md border border-border/60 bg-background p-3\">\n          <div className=\"flex items-start justify-between gap-2\">\n            <p className=\"min-w-0 whitespace-pre-wrap font-mono text-[11px] text-foreground/90 leading-relaxed\">\n              {agentPrompt}\n            </p>\n            <CopyButton label=\"Copy agent prompt\" value={agentPrompt} />\n          </div>\n          <p className=\"mt-2 font-mono text-[10px] text-foreground/90 leading-relaxed\">\n            password excluded; the agent reads DATABASE_URL\n          </p>\n        </div>\n      </TabsContent>\n    </AnimatedHeight>\n  </Tabs>\n);\n\nexport type DBConnectionCardProps = Omit<\n  ComponentProps<\"div\">,\n  \"children\" | \"onChange\"\n> & {\n  /** Card title. */\n  label?: string;\n  /**\n   * Every connection string, one per role/database/pooled combination. The\n   * consumer owns the secret: prefetch the URIs server-side and pass them as\n   * plain data. Roles, databases, and the pooled toggle are derived from this.\n   */\n  connections: ConnectionEntry[];\n  /** Order or limit the role options; defaults to those found in connections. */\n  roles?: string[];\n  /** Order or limit the database options; defaults to those found in connections. */\n  databases?: string[];\n  defaultRole?: string;\n  defaultDatabase?: string;\n  /** Start on the pooled connection. */\n  defaultPooled?: boolean;\n  /** Force the pooled/direct toggle; defaults to on when both kinds exist. */\n  poolable?: boolean;\n  /** Notified when the role, database, or mode changes. */\n  onSelectionChange?: (selection: ConnectionSelection) => void;\n  isLoading?: boolean;\n};\n\nconst cardClassName =\n  \"flex w-full min-w-0 flex-col gap-3 rounded-lg border border-border/60 bg-card p-4 shadow-none ring-0\";\n\nconst LoadingCard = ({ className, ...props }: ComponentProps<\"div\">) => (\n  <div\n    aria-busy=\"true\"\n    className={cn(cardClassName, className)}\n    data-slot=\"db-connection-card\"\n    {...props}\n  >\n    <div className=\"flex items-center justify-between gap-3\">\n      <Skeleton aria-hidden=\"true\" className=\"h-4 w-32\" />\n      <Skeleton aria-hidden=\"true\" className=\"h-6 w-28\" />\n    </div>\n    <Skeleton aria-hidden=\"true\" className=\"h-6 w-40\" />\n    <Skeleton aria-hidden=\"true\" className=\"h-9 w-full\" />\n  </div>\n);\n\nexport const DBConnectionCard = ({\n  label = \"Database connection\",\n  connections,\n  roles,\n  databases,\n  defaultRole,\n  defaultDatabase,\n  defaultPooled = false,\n  poolable,\n  onSelectionChange,\n  isLoading = false,\n  className,\n  ...props\n}: DBConnectionCardProps) => {\n  const roleOptions = roles ?? unique(connections.map((entry) => entry.role));\n  const databaseOptions =\n    databases ?? unique(connections.map((entry) => entry.database));\n  const showToggle =\n    poolable ??\n    (connections.some((entry) => entry.pooled) &&\n      connections.some((entry) => !entry.pooled));\n\n  const [role, setRole] = useState(defaultRole ?? roleOptions[0] ?? \"\");\n  const [database, setDatabase] = useState(\n    defaultDatabase ?? databaseOptions[0] ?? \"\"\n  );\n  const [pooled, setPooled] = useState(defaultPooled);\n  const [revealed, setRevealed] = useState(false);\n  const [format, setFormat] = useState<ConnectionFormat>(\"uri\");\n\n  const commit = (next: ConnectionSelection) => {\n    setRole(next.role);\n    setDatabase(next.database);\n    setPooled(next.pooled);\n    onSelectionChange?.(next);\n  };\n\n  if (isLoading) {\n    return <LoadingCard className={className} {...props} />;\n  }\n\n  const selection: ConnectionSelection = { database, pooled, role };\n  const uri = connections.find((entry) => matches(entry, selection))?.uri ?? \"\";\n  const psql = uri ? `psql '${uri}'` : \"\";\n  const params = uri ? parseParams(uri) : null;\n  const agentPrompt = buildAgentPrompt(params, pooled);\n  const secret = <UriText revealed={revealed} uri={uri} />;\n\n  return (\n    <TooltipProvider>\n      <div\n        className={cn(cardClassName, className)}\n        data-pooled={pooled || undefined}\n        data-slot=\"db-connection-card\"\n        {...props}\n      >\n        <div className=\"flex items-center justify-between gap-3\">\n          <p className=\"truncate font-medium text-foreground text-sm\">\n            {label}\n          </p>\n          {showToggle ? (\n            <ModeToggle\n              onSelect={(next) => commit({ ...selection, pooled: next })}\n              pooled={pooled}\n            />\n          ) : null}\n        </div>\n\n        <SelectorRow\n          database={database}\n          databaseOptions={databaseOptions}\n          onDatabase={(next) => commit({ ...selection, database: next })}\n          onRole={(next) => commit({ ...selection, role: next })}\n          role={role}\n          roleOptions={roleOptions}\n        />\n\n        <FormatTabs\n          agentPrompt={agentPrompt}\n          format={format}\n          onFormat={setFormat}\n          onToggleReveal={() => setRevealed((current) => !current)}\n          params={params}\n          psql={psql}\n          revealed={revealed}\n          secret={secret}\n          uri={uri}\n        />\n      </div>\n    </TooltipProvider>\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}