{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "connection-card", "title": "Connection Card", "author": "MR ", "description": "A provider connection at a glance: account, grant status, scopes, and disconnect / reconnect actions.", "dependencies": [ "lucide-react" ], "registryDependencies": [ "button", "card", "@cantera/user-account-badge", "@cantera/token-status", "@cantera/oauth-types" ], "files": [ { "path": "registry/ui/connection-card.tsx", "content": "'use client'\n\nimport { LoaderCircleIcon } from 'lucide-react'\nimport type * as React from 'react'\nimport { useState } from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { TokenStatus } from '@/components/ui/token-status'\nimport { UserAccountBadge } from '@/components/ui/user-account-badge'\nimport type { OAuthConnection } from '@/lib/oauth-types'\nimport { cn } from '@/lib/utils'\n\ninterface ConnectionCardProps extends React.ComponentProps {\n connection: OAuthConnection\n onDisconnect?: () => void | Promise\n onReconnect?: () => void | Promise\n /**\n * Pending state for the disconnect action. The button stays mounted, keeps\n * its label, and shows a spinner — never pass `undefined` for the handler to\n * express \"busy\", that unmounts the control under the user's cursor.\n */\n disconnectPending?: boolean\n /** Pending state for the connect / reconnect action. */\n reconnectPending?: boolean\n showScopes?: boolean\n}\n\ninterface ConnectionActionProps {\n pending: boolean\n onAction: () => void | Promise\n variant?: React.ComponentProps['variant']\n className?: string\n children: React.ReactNode\n}\n\n/**\n * An action that follows the async-pending contract: disabled with a spinner\n * while it keeps its label, focusable throughout (`focusableWhenDisabled`\n * renders aria-disabled, not the native attribute), and never unmounted\n * mid-request. The spinner slot collapses at rest so the label sits centered,\n * and morphs open smoothly while busy.\n */\nfunction ConnectionAction({\n pending,\n onAction,\n variant,\n className,\n children,\n}: ConnectionActionProps) {\n const [asyncPending, setAsyncPending] = useState(false)\n const busy = pending || asyncPending\n\n return (\n {\n const result = onAction()\n if (!(result instanceof Promise)) return\n setAsyncPending(true)\n result.then(\n () => setAsyncPending(false),\n () => setAsyncPending(false),\n )\n }}\n >\n {/* Collapsed at rest so the label sits centered; morphs open while busy.\n Width + icon transitions are interruptible, and the spinner enters\n with the opacity/scale/blur crossfade rather than a bare fade. */}\n \n \n \n {children}\n \n )\n}\n\n/**\n * A provider connection at a glance: who is connected, grant status, scopes,\n * and disconnect / reconnect actions. One card per provider grant.\n */\nfunction ConnectionCard({\n connection,\n onDisconnect,\n onReconnect,\n disconnectPending = false,\n reconnectPending = false,\n showScopes = true,\n className,\n ...props\n}: ConnectionCardProps) {\n const { provider, account, status } = connection\n const needsReconnect = status === 'expired' || status === 'error' || status === 'disconnected'\n\n return (\n \n \n
\n {provider.icon && (\n \n {provider.icon}\n \n )}\n {provider.name}\n
\n {needsReconnect && onReconnect && (\n \n {status === 'disconnected' ? 'Connect' : 'Reconnect'}\n \n )}\n {status === 'connected' && onDisconnect && (\n // Disconnecting revokes a grant — destructive, not a neutral outline.\n // The ink comes from the status palette rather than the variant's\n // own --destructive: the primitives render destructive ink on a\n // 10% tint of itself, which measures 4.06:1 on a stock shadcn\n // theme. --status-danger clears 5:1 on that same tint, and ships\n // with this component through token-status. The tint stays.\n \n Disconnect\n \n )}\n
\n
\n {account && }\n \n
\n
\n )\n}\n\nexport { ConnectionCard, type ConnectionCardProps }\n", "type": "registry:ui" } ], "categories": [ "authentication", "connections" ], "type": "registry:component" }