{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "token-status", "title": "Token Status", "author": "MR ", "description": "Status line for an OAuth grant: connection state, token expiry, and held scopes. Server-safe.", "registryDependencies": [ "badge", "@cantera/status-tokens", "@cantera/oauth-types" ], "files": [ { "path": "registry/ui/token-status.tsx", "content": "import type * as React from 'react'\n\nimport { Badge } from '@/components/ui/badge'\nimport {\n connectionExpiry,\n isExpiringSoon,\n type OAuthConnection,\n type OAuthConnectionStatus,\n} from '@/lib/oauth-types'\nimport { cn } from '@/lib/utils'\n\n/**\n * The four semantic status tones. One color, one meaning — never a generic\n * badge variant. Requires the `@cantera/status-tokens` CSS variables; the\n * utilities fall back to foreground / destructive / muted without them.\n */\ntype StatusTone = 'success' | 'warning' | 'danger' | 'neutral'\n\n/**\n * Solid fills, not low-alpha tints: a tablet in direct sunlight loses a 10%\n * tint entirely. Every pair below is contrast-verified in both appearances.\n */\nconst statusToneClasses: Record = {\n success: 'bg-status-success text-status-success-foreground',\n warning: 'bg-status-warning text-status-warning-foreground',\n danger: 'bg-status-danger text-status-danger-foreground',\n neutral: 'bg-status-neutral text-status-neutral-foreground',\n}\n\n/** The same tones as ink, for text sitting on the page or on a `-surface`. */\nconst statusInkClasses: Record = {\n success: 'text-status-success',\n warning: 'text-status-warning',\n danger: 'text-status-danger',\n neutral: 'text-status-neutral',\n}\n\nconst statusTone: Record = {\n connected: 'success',\n // Expiry is recoverable — a refresh away, not a failure. Warning, not danger.\n expired: 'warning',\n error: 'danger',\n disconnected: 'neutral',\n}\n\nconst statusLabel: Record = {\n connected: 'Connected',\n expired: 'Expired',\n error: 'Error',\n disconnected: 'Not connected',\n}\n\n/**\n * Relative expiry, clamped at zero: a token that is already gone reads\n * \"expired\", never \"expires 5 min. ago\".\n */\nfunction formatExpiry(expiry: Date, locale?: string | string[]): string {\n const deltaMs = expiry.getTime() - Date.now()\n if (deltaMs <= 0) return 'expired'\n const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'always', style: 'narrow' })\n const minutes = Math.round(deltaMs / 60_000)\n if (minutes < 1) return 'expires now'\n if (minutes < 60) return `expires ${rtf.format(minutes, 'minute')}`\n const hours = Math.round(minutes / 60)\n if (hours < 48) return `expires ${rtf.format(hours, 'hour')}`\n return `expires ${rtf.format(Math.round(hours / 24), 'day')}`\n}\n\ninterface TokenStatusProps extends React.ComponentProps<'div'> {\n connection: OAuthConnection\n showExpiry?: boolean\n showScopes?: boolean\n /** BCP 47 locale(s) for the relative expiry. Defaults to the runtime locale. */\n locale?: string | string[]\n /** How far ahead counts as \"expiring soon\". Default five minutes. */\n expiringSoonMs?: number\n}\n\n/**\n * Status line for an OAuth grant: connection state, token expiry, and the\n * scopes it holds. Server-safe.\n */\nfunction TokenStatus({\n connection,\n showExpiry = true,\n showScopes = false,\n locale,\n expiringSoonMs,\n className,\n ...props\n}: TokenStatusProps) {\n const expiry = connectionExpiry(connection)\n const expiringSoon =\n connection.status === 'connected' && isExpiringSoon(connection, expiringSoonMs)\n const tone = expiringSoon ? 'warning' : statusTone[connection.status]\n const label = expiringSoon ? 'Expiring soon' : statusLabel[connection.status]\n\n return (\n \n {label}\n {showExpiry && expiry && connection.status === 'connected' && (\n \n {formatExpiry(expiry, locale)}\n \n )}\n {connection.status === 'error' && connection.error && (\n {connection.error}\n )}\n {showScopes &&\n connection.scopes?.map((scope) => (\n \n {scope}\n \n ))}\n \n )\n}\n\nexport { type StatusTone, statusInkClasses, statusToneClasses, TokenStatus, type TokenStatusProps }\n", "type": "registry:ui" } ], "categories": [ "authentication", "status" ], "type": "registry:component" }