{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cookie-consent",
  "title": "Cookie Consent",
  "description": "Dismissible cookie-consent banner with positional variants and accept / reject actions for privacy compliance.",
  "dependencies": [
    "@vllnt/ui@^0.3.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/cookie-consent/cookie-consent.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@vllnt/ui\";\nimport { Button } from \"@vllnt/ui\";\n\nconst cookieConsentVariants = cva(\n  // Base: safe-area-inset for notched devices\n  // Desktop: compact single-line layout, height matches button\n  \"fixed z-50 rounded-lg border bg-background shadow-lg transition-all duration-300 max-w-[calc(100vw-2rem)] p-4 sm:py-1.5 sm:px-3\",\n  {\n    defaultVariants: {\n      position: \"bottom-left\",\n    },\n    variants: {\n      position: {\n        // Mobile: 16px (1rem) from edges, respects safe area\n        // Desktop: 16px from edges\n        \"bottom-center\": \"bottom-4 left-1/2 -translate-x-1/2 mb-safe\",\n        \"bottom-left\": \"bottom-4 left-4 right-4 sm:right-auto mb-safe ml-safe\",\n        \"bottom-right\": \"bottom-4 right-4 left-4 sm:left-auto mb-safe mr-safe\",\n      },\n    },\n  },\n);\n\nexport type CookieConsentProps = {\n  /** Text for the accept button */\n  acceptText?: string;\n  /** Text for the decline button (optional, hidden if not provided) */\n  declineText?: string;\n  /** Text to display in the banner */\n  message?: string;\n  /** Called when user accepts */\n  onAccept?: () => void;\n  /** Called when user declines */\n  onDecline?: () => void;\n  /** Called when visibility changes */\n  onOpenChange?: (open: boolean) => void;\n  /** Whether the banner is visible */\n  open?: boolean;\n  /** URL for privacy policy / settings page */\n  settingsHref?: string;\n  /** Text for the settings/learn more link */\n  settingsText?: string;\n  /** Whether to show the close button */\n  showCloseButton?: boolean;\n} & VariantProps<typeof cookieConsentVariants> &\n  Omit<React.HTMLAttributes<HTMLDivElement>, \"children\">;\n\n/**\n * Cookie consent banner component (Vercel-style)\n *\n * Positioned in bottom-left by default with minimal, clean design.\n * Shows accept button prominently, with optional decline and settings link.\n */\nconst CookieConsent = ({\n  acceptText = \"Accept\",\n  className,\n  declineText,\n  message = \"This site uses cookies to improve your experience.\",\n  onAccept,\n  onDecline,\n  onOpenChange,\n  open = true,\n  position,\n  ref: reference,\n  settingsHref,\n  settingsText = \"Learn more\",\n  showCloseButton = false,\n  ...props\n}: CookieConsentProps & { ref?: React.Ref<HTMLDivElement> }) => {\n  const [isVisible, setIsVisible] = useState(false);\n  const [isAnimatingOut, setIsAnimatingOut] = useState(false);\n\n  // Handle visibility with animation\n  useEffect(() => {\n    if (open) {\n      // Small delay for mount animation\n      const timer = setTimeout(() => {\n        setIsVisible(true);\n      }, 50);\n      return () => {\n        clearTimeout(timer);\n      };\n    }\n    const rafId = requestAnimationFrame(() => {\n      setIsVisible(false);\n    });\n    return () => {\n      cancelAnimationFrame(rafId);\n    };\n  }, [open]);\n\n  const handleClose = useCallback(() => {\n    setIsAnimatingOut(true);\n    setTimeout(() => {\n      setIsAnimatingOut(false);\n      onOpenChange?.(false);\n    }, 200);\n  }, [onOpenChange]);\n\n  const handleAccept = useCallback(() => {\n    onAccept?.();\n    handleClose();\n  }, [onAccept, handleClose]);\n\n  const handleDecline = useCallback(() => {\n    onDecline?.();\n    handleClose();\n  }, [onDecline, handleClose]);\n\n  if (!open && !isAnimatingOut) return null;\n\n  return (\n    <div\n      aria-label=\"Cookie consent\"\n      aria-live=\"polite\"\n      className={cn(\n        cookieConsentVariants({ position }),\n        // Animation states\n        isVisible && !isAnimatingOut\n          ? \"translate-y-0 opacity-100\"\n          : \"translate-y-4 opacity-0\",\n        className,\n      )}\n      ref={reference}\n      role=\"dialog\"\n      {...props}\n    >\n      {/* Mobile: stacked layout */}\n      <div className=\"flex flex-col gap-3 sm:hidden\">\n        <p className=\"text-sm text-muted-foreground\">{message}</p>\n        <div className=\"flex items-center gap-2\">\n          {settingsHref ? (\n            <a\n              className=\"text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground\"\n              href={settingsHref}\n            >\n              {settingsText}\n            </a>\n          ) : null}\n          {declineText ? (\n            <Button\n              onClick={handleDecline}\n              size=\"sm\"\n              type=\"button\"\n              variant=\"ghost\"\n            >\n              {declineText}\n            </Button>\n          ) : null}\n          <Button\n            onClick={handleAccept}\n            size=\"sm\"\n            type=\"button\"\n            variant=\"default\"\n          >\n            {acceptText}\n          </Button>\n        </div>\n      </div>\n\n      {/* Desktop: single line, all inline */}\n      <div className=\"hidden sm:flex sm:items-center sm:gap-3\">\n        <p className=\"text-sm text-muted-foreground\">{message}</p>\n        {settingsHref ? (\n          <a\n            className=\"text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground whitespace-nowrap\"\n            href={settingsHref}\n          >\n            {settingsText}\n          </a>\n        ) : null}\n        {declineText ? (\n          <Button\n            className=\"h-7 px-3 text-xs\"\n            onClick={handleDecline}\n            type=\"button\"\n            variant=\"ghost\"\n          >\n            {declineText}\n          </Button>\n        ) : null}\n        <Button\n          className=\"h-7 px-3 text-xs\"\n          onClick={handleAccept}\n          type=\"button\"\n          variant=\"default\"\n        >\n          {acceptText}\n        </Button>\n      </div>\n\n      {showCloseButton ? (\n        <button\n          aria-label=\"Close cookie consent\"\n          className=\"absolute -right-2 -top-2 rounded-full border bg-background p-1 text-muted-foreground hover:text-foreground\"\n          onClick={handleClose}\n          type=\"button\"\n        >\n          <X className=\"size-3\" />\n        </button>\n      ) : null}\n    </div>\n  );\n};\nCookieConsent.displayName = \"CookieConsent\";\n\nexport { CookieConsent, cookieConsentVariants };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.3.0",
  "stability": "stable"
}
