{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "connect-wallet",
  "title": "Connect Wallet",
  "author": "Satchmo",
  "description": "An opinionated wallet connection button with provider selection dialog and connected-state dropdown, wrapping @1sat/react primitives in shadcn UI",
  "dependencies": [
    "@1sat/react",
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "avatar",
    "badge",
    "button",
    "dialog",
    "dropdown-menu"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/connect-wallet/index.tsx",
      "content": "\"use client\"\n\nimport { type VariantProps } from \"class-variance-authority\"\nimport {\n  ConnectWalletUI,\n  connectWalletVariants,\n} from \"./ui\"\nimport {\n  useConnectWallet,\n  type UseConnectWalletReturn,\n  type UseConnectWalletOptions,\n} from \"./use-connect-wallet\"\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport {\n  ConnectWalletUI,\n  connectWalletVariants,\n  type ConnectWalletUIProps,\n} from \"./ui\"\nexport {\n  useConnectWallet,\n  type UseConnectWalletReturn,\n  type UseConnectWalletOptions,\n} from \"./use-connect-wallet\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ConnectWalletProps\n  extends VariantProps<typeof connectWalletVariants> {\n  /** Additional CSS classes */\n  className?: string\n  /** Label shown on the connect button (default: \"Connect Wallet\") */\n  connectLabel?: string\n  /** Called after successful wallet connection */\n  onConnect?: () => void\n  /** Called after wallet disconnection */\n  onDisconnect?: () => void\n  /** Called when a connection error occurs */\n  onError?: (error: Error) => void\n}\n\n// ---------------------------------------------------------------------------\n// Composed component\n// ---------------------------------------------------------------------------\n\n/**\n * A themed wallet connection button wrapping `@1sat/react` primitives.\n *\n * Must be rendered inside both `WalletProvider` and `ConnectDialogProvider`\n * from `@1sat/react`.\n *\n * @example\n * ```tsx\n * import { WalletProvider, ConnectDialogProvider } from \"@1sat/react\"\n * import { ConnectWallet } from \"@/components/blocks/connect-wallet\"\n *\n * function App() {\n *   return (\n *     <WalletProvider>\n *       <ConnectDialogProvider>\n *         <ConnectWallet />\n *       </ConnectDialogProvider>\n *     </WalletProvider>\n *   )\n * }\n * ```\n */\nexport function ConnectWallet({\n  variant = \"default\",\n  className,\n  connectLabel = \"Connect Wallet\",\n  onConnect,\n  onDisconnect,\n  onError,\n}: ConnectWalletProps) {\n  const hook = useConnectWallet({ variant, onConnect, onDisconnect, onError })\n\n  return (\n    <ConnectWalletUI\n      variant={variant}\n      className={className}\n      connectLabel={connectLabel}\n      status={hook.status}\n      identityKey={hook.identityKey}\n      dialogOpen={hook.dialogOpen}\n      setDialogOpen={hook.setDialogOpen}\n      gradient={hook.gradient}\n      truncatedKey={hook.truncatedKey}\n      onTriggerClick={hook.handleTriggerClick}\n      onDisconnect={hook.handleDisconnect}\n      onCopy={hook.handleCopy}\n      copied={hook.copied}\n      error={hook.error}\n    />\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/connect-wallet/index.tsx"
    },
    {
      "path": "registry/new-york/blocks/connect-wallet/ui.tsx",
      "content": "\"use client\"\n\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { Avatar, AvatarFallback } from \"@/components/ui/avatar\"\nimport {\n  WalletSelector,\n  type WalletSelectorProviderInfo,\n} from \"@1sat/react\"\nimport {\n  Wallet,\n  Loader2,\n  ChevronDown,\n  Copy,\n  Check,\n  LogOut,\n} from \"lucide-react\"\n\n// ---------------------------------------------------------------------------\n// Variant definitions\n// ---------------------------------------------------------------------------\n\nexport const connectWalletVariants = cva(\n  \"inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"rounded-md bg-primary text-primary-foreground shadow hover:bg-primary/90 px-4 py-2 text-sm\",\n        compact:\n          \"rounded-md bg-primary text-primary-foreground shadow hover:bg-primary/90 size-9 p-0\",\n        outline:\n          \"rounded-md border border-input bg-background text-foreground shadow-sm hover:bg-accent hover:text-accent-foreground px-3 py-1.5 text-xs\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n)\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ConnectWalletUIProps\n  extends VariantProps<typeof connectWalletVariants> {\n  /** Additional CSS classes */\n  className?: string\n  /** Label shown on the connect button (default: \"Connect Wallet\") */\n  connectLabel?: string\n  /** Current wallet connection status */\n  status: string\n  /** Identity key when connected */\n  identityKey: string | undefined\n  /** Whether the provider selection dialog is open */\n  dialogOpen: boolean\n  /** Set the dialog open state */\n  setDialogOpen: (open: boolean) => void\n  /** Deterministic gradient CSS string for the identity key */\n  gradient: string\n  /** Truncated identity key for display */\n  truncatedKey: string\n  /** Handle trigger button click */\n  onTriggerClick: () => void\n  /** Handle disconnect */\n  onDisconnect: () => void\n  /** Copy identity key to clipboard */\n  onCopy: () => void\n  /** Whether the identity key was recently copied */\n  copied: boolean\n  /** Error from the wallet provider */\n  error: Error | null\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\ninterface ProviderListProps {\n  providers: WalletSelectorProviderInfo[]\n  error: Error | null\n}\n\nfunction ProviderList({ providers, error }: ProviderListProps) {\n  return (\n    <div className=\"flex flex-col gap-2\">\n      {providers.map((provider) => (\n        <Button\n          key={provider.type}\n          variant=\"outline\"\n          className=\"w-full justify-start gap-3\"\n          disabled={provider.isConnecting}\n          onClick={() => void provider.connect()}\n        >\n          {provider.icon ? (\n            <img\n              src={provider.icon}\n              alt=\"\"\n              className=\"size-5 rounded-sm\"\n              aria-hidden=\"true\"\n            />\n          ) : (\n            <Wallet className=\"size-5\" aria-hidden=\"true\" />\n          )}\n          <span className=\"flex-1 text-left\">{provider.name}</span>\n          {provider.isConnecting && (\n            <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n          )}\n          {provider.detected && !provider.isConnecting && (\n            <Badge variant=\"secondary\" className=\"text-[10px]\">\n              Detected\n            </Badge>\n          )}\n        </Button>\n      ))}\n      {error && (\n        <p className=\"mt-2 text-sm text-destructive\" role=\"alert\">\n          {error.message}\n        </p>\n      )}\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Connected dropdown\n// ---------------------------------------------------------------------------\n\ninterface ConnectedDropdownProps {\n  variant: \"default\" | \"compact\" | \"outline\" | null | undefined\n  className: string | undefined\n  identityKey: string\n  gradient: string\n  truncatedKey: string\n  onDisconnect: () => void\n  onCopy: () => void\n  copied: boolean\n}\n\nfunction ConnectedDropdown({\n  variant,\n  className,\n  identityKey,\n  gradient,\n  truncatedKey,\n  onDisconnect,\n  onCopy,\n  copied,\n}: ConnectedDropdownProps) {\n  const isCompact = variant === \"compact\"\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <button\n          type=\"button\"\n          className={cn(\n            connectWalletVariants({ variant }),\n            \"cursor-pointer\",\n            className,\n          )}\n          aria-label=\"Wallet menu\"\n        >\n          <Avatar className=\"size-5\">\n            <AvatarFallback\n              className=\"text-[0px]\"\n              style={{ background: gradient }}\n            >\n              {identityKey.slice(0, 2)}\n            </AvatarFallback>\n          </Avatar>\n          {!isCompact && (\n            <>\n              <span>{truncatedKey}</span>\n              <ChevronDown className=\"size-3.5 opacity-50\" aria-hidden=\"true\" />\n            </>\n          )}\n        </button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-64\">\n        <div className=\"px-3 py-2\">\n          <p className=\"text-xs text-muted-foreground\">Connected</p>\n          <p className=\"mt-0.5 break-all font-mono text-xs\">{identityKey}</p>\n        </div>\n        <DropdownMenuSeparator />\n        <DropdownMenuItem onClick={onCopy} className=\"cursor-pointer gap-2\">\n          {copied ? (\n            <Check className=\"size-4 text-primary\" aria-hidden=\"true\" />\n          ) : (\n            <Copy className=\"size-4\" aria-hidden=\"true\" />\n          )}\n          {copied ? \"Copied!\" : \"Copy Identity Key\"}\n        </DropdownMenuItem>\n        <DropdownMenuSeparator />\n        <DropdownMenuItem\n          onClick={onDisconnect}\n          className=\"cursor-pointer gap-2 text-destructive focus:text-destructive\"\n        >\n          <LogOut className=\"size-4\" aria-hidden=\"true\" />\n          Disconnect\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Main UI component\n// ---------------------------------------------------------------------------\n\nexport function ConnectWalletUI({\n  variant = \"default\",\n  className,\n  connectLabel = \"Connect Wallet\",\n  status,\n  identityKey,\n  dialogOpen,\n  setDialogOpen,\n  gradient,\n  truncatedKey,\n  onTriggerClick,\n  onDisconnect,\n  onCopy,\n  copied,\n  error,\n}: ConnectWalletUIProps) {\n  const isCompact = variant === \"compact\"\n\n  // ---- Connecting state ----\n  if (status === \"connecting\" || status === \"detecting\") {\n    return (\n      <button\n        type=\"button\"\n        disabled\n        className={cn(connectWalletVariants({ variant }), className)}\n        aria-busy=\"true\"\n        aria-label=\"Connecting wallet\"\n      >\n        <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n        {!isCompact && <span>Connecting&hellip;</span>}\n      </button>\n    )\n  }\n\n  // ---- Connected state ----\n  if (status === \"connected\" && identityKey) {\n    return (\n      <ConnectedDropdown\n        variant={variant}\n        className={className}\n        identityKey={identityKey}\n        gradient={gradient}\n        truncatedKey={truncatedKey}\n        onDisconnect={onDisconnect}\n        onCopy={onCopy}\n        copied={copied}\n      />\n    )\n  }\n\n  // ---- Selecting state (providers dialog) ----\n  if (status === \"selecting\") {\n    return (\n      <>\n        <button\n          type=\"button\"\n          className={cn(connectWalletVariants({ variant }), className)}\n          onClick={() => setDialogOpen(true)}\n          aria-label={connectLabel}\n        >\n          <Wallet className=\"size-4\" aria-hidden=\"true\" />\n          {!isCompact && <span>{connectLabel}</span>}\n        </button>\n\n        <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n          <DialogContent className=\"sm:max-w-md\">\n            <DialogHeader>\n              <DialogTitle>Connect Wallet</DialogTitle>\n              <DialogDescription>\n                Choose a wallet provider to connect.\n              </DialogDescription>\n            </DialogHeader>\n            <WalletSelector onClose={() => setDialogOpen(false)}>\n              {(renderProps) => (\n                <ProviderList\n                  providers={renderProps.providers}\n                  error={renderProps.error}\n                />\n              )}\n            </WalletSelector>\n          </DialogContent>\n        </Dialog>\n      </>\n    )\n  }\n\n  // ---- Disconnected state (default) ----\n  return (\n    <button\n      type=\"button\"\n      className={cn(connectWalletVariants({ variant }), className)}\n      onClick={onTriggerClick}\n      aria-label={connectLabel}\n    >\n      <Wallet className=\"size-4\" aria-hidden=\"true\" />\n      {!isCompact && <span>{connectLabel}</span>}\n    </button>\n  )\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/connect-wallet/ui.tsx"
    },
    {
      "path": "registry/new-york/blocks/connect-wallet/use-connect-wallet.ts",
      "content": "import { useCallback, useMemo, useState } from \"react\"\nimport { useWallet } from \"@1sat/react\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseConnectWalletReturn {\n  /** Current wallet connection status */\n  status: string\n  /** Identity key when connected */\n  identityKey: string | undefined\n  /** Whether the provider selection dialog is open */\n  dialogOpen: boolean\n  /** Set the dialog open state */\n  setDialogOpen: (open: boolean) => void\n  /** Whether the variant is compact */\n  isCompact: boolean\n  /** Deterministic gradient CSS string for the identity key */\n  gradient: string\n  /** Truncated identity key for display */\n  truncatedKey: string\n  /** Initiate a wallet connection */\n  handleConnect: () => Promise<void>\n  /** Disconnect the wallet */\n  handleDisconnect: () => void\n  /** Handle trigger button click */\n  handleTriggerClick: () => void\n  /** Copy identity key to clipboard */\n  handleCopy: () => void\n  /** Whether the identity key was recently copied */\n  copied: boolean\n  /** Error from the wallet provider */\n  error: Error | null\n}\n\nexport interface UseConnectWalletOptions {\n  /** Button variant */\n  variant?: \"default\" | \"compact\" | \"outline\" | null\n  /** Called after successful wallet connection */\n  onConnect?: () => void\n  /** Called after wallet disconnection */\n  onDisconnect?: () => void\n  /** Called when a connection error occurs */\n  onError?: (error: Error) => void\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Deterministic gradient from an address string.\n * Generates two hue values by hashing the address to create a unique gradient.\n */\nfunction addressGradient(address: string): string {\n  let hash = 0\n  for (let i = 0; i < address.length; i++) {\n    hash = address.charCodeAt(i) + ((hash << 5) - hash)\n  }\n  const hue1 = Math.abs(hash) % 360\n  const hue2 = (hue1 + 40 + (Math.abs(hash >> 8) % 80)) % 360\n  return `linear-gradient(135deg, hsl(${hue1}, 70%, 50%), hsl(${hue2}, 70%, 50%))`\n}\n\n/** Truncate a key/address for display: \"abc123...xyz9\" */\nfunction truncate(value: string, startLen = 6, endLen = 4): string {\n  if (value.length <= startLen + endLen + 3) return value\n  return `${value.slice(0, startLen)}...${value.slice(-endLen)}`\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useConnectWallet({\n  variant = \"default\",\n  onConnect,\n  onDisconnect,\n  onError,\n}: UseConnectWalletOptions = {}): UseConnectWalletReturn {\n  const { status, identityKey, connect, disconnect, error } = useWallet()\n  const [dialogOpen, setDialogOpen] = useState(false)\n  const [copied, setCopied] = useState(false)\n\n  const isCompact = variant === \"compact\"\n\n  const handleConnect = useCallback(async () => {\n    try {\n      await connect()\n      onConnect?.()\n    } catch (e) {\n      const err = e instanceof Error ? e : new Error(String(e))\n      onError?.(err)\n    }\n  }, [connect, onConnect, onError])\n\n  const handleDisconnect = useCallback(() => {\n    disconnect()\n    onDisconnect?.()\n  }, [disconnect, onDisconnect])\n\n  const handleTriggerClick = useCallback(() => {\n    void handleConnect()\n  }, [handleConnect])\n\n  const handleCopy = useCallback(() => {\n    if (!identityKey || typeof window === \"undefined\") return\n    void navigator.clipboard\n      .writeText(identityKey)\n      .then(() => {\n        setCopied(true)\n        setTimeout(() => setCopied(false), 2000)\n      })\n      .catch((err: Error) => {\n        onError?.(err)\n      })\n  }, [identityKey, onError])\n\n  const gradient = useMemo(\n    () => (identityKey ? addressGradient(identityKey) : \"\"),\n    [identityKey],\n  )\n\n  const truncatedKey = useMemo(\n    () => (identityKey ? truncate(identityKey) : \"\"),\n    [identityKey],\n  )\n\n  return {\n    status,\n    identityKey,\n    dialogOpen,\n    setDialogOpen,\n    isCompact,\n    gradient,\n    truncatedKey,\n    handleConnect,\n    handleDisconnect,\n    handleTriggerClick,\n    handleCopy,\n    copied,\n    error,\n  }\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/connect-wallet/use-connect-wallet.ts"
    }
  ],
  "type": "registry:block"
}