{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cookie-consent",
  "title": "Cookie Consent",
  "description": "GDPR-friendly cookie banner with equal choices and per-category toggles.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.cuelark.com/r/theme.json",
    "https://ui.cuelark.com/r/motion.json",
    "https://ui.cuelark.com/r/switch.json"
  ],
  "files": [
    {
      "path": "src/registry/cuelark/ui/cookie-consent.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { CookieIcon } from \"lucide-react\";\n\nimport { enterOnce, gsap, motionScale } from \"@/lib/motion\";\nimport { Switch } from \"@/registry/cuelark/ui/switch\";\nimport { cn } from \"@/lib/utils\";\n\ntype Category = { id: string; label: string; description: string; required?: boolean };\ntype Consent = Record<string, boolean>;\n\ntype CookieConsentProps = {\n  categories?: Category[];\n  /** Called with the choice; persist it and load scripts accordingly. */\n  onConsent?: (consent: Consent) => void;\n  /** localStorage key; the banner stays hidden once a choice is saved. */\n  storageKey?: string;\n  policyHref?: string;\n  /** Remember the choice in localStorage (turn off for previews and tests). */\n  persist?: boolean;\n  /** Render in place instead of fixed to the bottom corner. */\n  inline?: boolean;\n  className?: string;\n};\n\nconst DEFAULTS: Category[] = [\n  { id: \"necessary\", label: \"Necessary\", description: \"Sign-in, security and your preferences.\", required: true },\n  { id: \"analytics\", label: \"Analytics\", description: \"Anonymous usage that helps us improve.\", },\n  { id: \"marketing\", label: \"Marketing\", description: \"Personalized ads on other sites.\" },\n];\n\n/**\n * A GDPR-friendly cookie banner: equal-weight Accept and Reject buttons, and a Customize panel with per-category\n * switches that expands in place. It slides up once, remembers the choice, and never blocks the page.\n */\nfunction CookieConsent({ categories = DEFAULTS, onConsent, storageKey = \"cookie-consent\", policyHref = \"/privacy\", persist = true, inline = false, className }: CookieConsentProps) {\n  const [open, setOpen] = React.useState(false);\n  const [custom, setCustom] = React.useState(false);\n  const [choice, setChoice] = React.useState<Consent>(() => Object.fromEntries(categories.map((c) => [c.id, !!c.required])));\n  const ref = React.useRef<HTMLDivElement>(null);\n  const panel = React.useRef<HTMLDivElement>(null);\n\n  React.useEffect(() => {\n    if (!persist || !localStorage.getItem(storageKey)) setOpen(true);\n  }, [storageKey, persist]);\n\n  React.useLayoutEffect(() => {\n    if (open) enterOnce(ref.current, (s) => ({ y: 24 * s, opacity: 0 }), { duration: 0.45, delay: inline ? 0.1 : 0.6 });\n  }, [open]);\n\n  React.useLayoutEffect(() => {\n    const el = panel.current;\n    if (!el || !custom || !motionScale(el)) return;\n    gsap.fromTo(el, { height: 0, opacity: 0 }, { height: \"auto\", opacity: 1, duration: 0.3, ease: \"cue\" });\n  }, [custom]);\n\n  const save = (consent: Consent) => {\n    if (persist) localStorage.setItem(storageKey, JSON.stringify(consent));\n    onConsent?.(consent);\n    const el = ref.current;\n    if (el && motionScale(el)) gsap.to(el, { y: 24, opacity: 0, duration: 0.25, ease: \"cue\", onComplete: () => setOpen(false) });\n    else setOpen(false);\n  };\n  const all = (v: boolean) => Object.fromEntries(categories.map((c) => [c.id, c.required || v]));\n\n  if (!open) return null;\n\n  return (\n    <div\n      ref={ref}\n      role=\"region\"\n      aria-label=\"Cookie consent\"\n      data-slot=\"cookie-consent\"\n      className={cn(\n        \"grid w-full max-w-lg gap-4 rounded-2xl bg-popover p-5 text-sm text-popover-foreground shadow-overlay ring-1 ring-foreground/10\",\n        !inline && \"fixed inset-x-3 bottom-3 z-[90] mx-auto sm:inset-x-auto sm:right-5 sm:bottom-5\",\n        className,\n      )}\n    >\n      <div className=\"flex gap-3\">\n        <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-muted\">\n          <CookieIcon className=\"size-4.5\" />\n        </span>\n        <div className=\"grid gap-1\">\n          <p className=\"font-semibold\">We use cookies</p>\n          <p className=\"text-muted-foreground\">\n            Some keep the site working; others are optional. Choose what is fine with you.{\" \"}\n            <a href={policyHref} className=\"font-medium text-foreground underline decoration-border underline-offset-4\">\n              Cookie policy\n            </a>\n          </p>\n        </div>\n      </div>\n      {custom && (\n        <div ref={panel} className=\"grid gap-3 overflow-hidden border-t border-border pt-4\">\n          {categories.map((c) => (\n            <label key={c.id} className=\"flex items-start justify-between gap-4\">\n              <span className=\"grid gap-0.5\">\n                <span className=\"font-medium\">{c.label}</span>\n                <span className=\"text-xs text-muted-foreground\">{c.description}</span>\n              </span>\n              <Switch size=\"sm\" checked={choice[c.id]} disabled={c.required} onCheckedChange={(v) => setChoice((s) => ({ ...s, [c.id]: v }))} aria-label={c.label} />\n            </label>\n          ))}\n        </div>\n      )}\n      <div className=\"flex flex-wrap items-center gap-2\">\n        <button\n          type=\"button\"\n          onClick={() => (custom ? save(choice) : setCustom(true))}\n          className=\"h-ctl-sm rounded-md px-3 font-medium text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50\"\n        >\n          {custom ? \"Save choices\" : \"Customize\"}\n        </button>\n        <span className=\"ms-auto flex gap-2\">\n          <button\n            type=\"button\"\n            onClick={() => save(all(false))}\n            className=\"h-ctl-sm rounded-md border border-border bg-background px-3 font-medium outline-none transition-[scale] hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.97]\"\n          >\n            Reject all\n          </button>\n          <button\n            type=\"button\"\n            onClick={() => save(all(true))}\n            className=\"h-ctl-sm rounded-md bg-primary px-3 font-medium text-primary-foreground outline-none transition-[scale] hover:bg-primary/90 focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.97]\"\n          >\n            Accept all\n          </button>\n        </span>\n      </div>\n    </div>\n  );\n}\n\nexport { CookieConsent };\nexport type { CookieConsentProps };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Docs and examples: https://ui.cuelark.com/components/cookie-consent",
  "categories": [
    "overlays"
  ],
  "type": "registry:ui"
}