{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cookie-consent",
  "title": "Cookie Consent",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/cookie-consent/CookieConsentBanner.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Cookie } from 'lucide-react'\nimport { Button } from '@/components/ui/button'\nimport { Separator } from '@/components/ui/separator'\nimport { Switch } from '@/components/ui/switch'\nimport { cn } from '@/lib/utils'\n\nexport interface CookieConsentBannerProps {\n  variant?: 'bottom-bar' | 'card'\n  className?: string\n}\n\nexport function CookieConsentBanner({ variant = 'bottom-bar', className }: CookieConsentBannerProps) {\n  const [visible, setVisible] = React.useState(true)\n  const [showPreferences, setShowPreferences] = React.useState(false)\n  const [analytics, setAnalytics] = React.useState(true)\n  const [marketing, setMarketing] = React.useState(false)\n\n  const dismiss = () => setVisible(false)\n\n  const preferences = (\n    <>\n      <Separator />\n      <div className=\"flex items-center justify-between gap-4 py-1\">\n        <div>\n          <p className=\"text-sm font-medium\">Necessary</p>\n          <p className=\"text-muted-foreground text-xs\">Required for the site to function.</p>\n        </div>\n        <Switch checked disabled aria-label=\"Necessary cookies\" />\n      </div>\n      <div className=\"flex items-center justify-between gap-4 py-1\">\n        <div>\n          <p className=\"text-sm font-medium\">Analytics</p>\n          <p className=\"text-muted-foreground text-xs\">Helps us understand usage patterns.</p>\n        </div>\n        <Switch checked={analytics} onCheckedChange={setAnalytics} aria-label=\"Analytics cookies\" />\n      </div>\n      <div className=\"flex items-center justify-between gap-4 py-1\">\n        <div>\n          <p className=\"text-sm font-medium\">Marketing</p>\n          <p className=\"text-muted-foreground text-xs\">Used to personalize campaigns.</p>\n        </div>\n        <Switch checked={marketing} onCheckedChange={setMarketing} aria-label=\"Marketing cookies\" />\n      </div>\n    </>\n  )\n\n  if (!visible) return null\n\n  return (\n    <div\n      data-slot=\"cookie-consent-banner\"\n      role=\"dialog\"\n      aria-label=\"Cookie consent\"\n      className={cn('animate-in fade-in duration-150', className)}\n    >\n      {variant === 'bottom-bar' ? (\n        <div className=\"bg-card border-border w-full border-t px-4 py-4 sm:px-6\">\n          <div className=\"mx-auto flex max-w-5xl flex-col gap-4\">\n            <div className=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n              <div className=\"flex items-start gap-3\">\n                <span className=\"bg-primary/10 text-primary mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full\">\n                  <Cookie className=\"size-4\" aria-hidden=\"true\" />\n                </span>\n                <p className=\"text-muted-foreground max-w-xl text-sm leading-relaxed\">\n                  We use cookies to improve your experience and analyze traffic. Read our{' '}\n                  <a\n                    href=\"#\"\n                    className=\"text-foreground focus-visible:ring-ring rounded-sm font-medium underline underline-offset-4 outline-none focus-visible:ring-2\"\n                  >\n                    Cookie Policy\n                  </a>\n                  .\n                </p>\n              </div>\n              <div className=\"flex shrink-0 flex-wrap items-center gap-2\">\n                <Button variant=\"ghost\" size=\"sm\" onClick={() => setShowPreferences((v) => !v)}>\n                  {showPreferences ? 'Hide preferences' : 'Customize'}\n                </Button>\n                <Button variant=\"outline\" size=\"sm\" onClick={dismiss}>\n                  Reject\n                </Button>\n                <Button size=\"sm\" onClick={dismiss}>\n                  Accept all\n                </Button>\n              </div>\n            </div>\n\n            {showPreferences && (\n              <div className=\"flex flex-col gap-3\">\n                {preferences}\n                <div className=\"mt-1 flex justify-end\">\n                  <Button size=\"sm\" onClick={dismiss}>\n                    Save preferences\n                  </Button>\n                </div>\n              </div>\n            )}\n          </div>\n        </div>\n      ) : (\n        <div className=\"bg-card border-border mx-auto max-w-md rounded-xl border p-6 shadow-xs\">\n          <div className=\"flex flex-col gap-4\">\n            <div className=\"flex items-start gap-3\">\n              <span className=\"bg-primary/10 text-primary flex size-9 shrink-0 items-center justify-center rounded-full\">\n                <Cookie className=\"size-4\" aria-hidden=\"true\" />\n              </span>\n              <div>\n                <h3 className=\"text-sm font-semibold\">We value your privacy</h3>\n                <p className=\"text-muted-foreground mt-1 text-sm leading-relaxed\">\n                  We use cookies to improve your experience and analyze traffic. Read our{' '}\n                  <a\n                    href=\"#\"\n                    className=\"text-foreground focus-visible:ring-ring rounded-sm font-medium underline underline-offset-4 outline-none focus-visible:ring-2\"\n                  >\n                    Cookie Policy\n                  </a>\n                  .\n                </p>\n              </div>\n            </div>\n\n            {showPreferences && preferences}\n\n            <div className=\"flex flex-wrap items-center gap-2\">\n              <Button variant=\"ghost\" size=\"sm\" onClick={() => setShowPreferences((v) => !v)}>\n                {showPreferences ? 'Hide preferences' : 'Customize'}\n              </Button>\n              <div className=\"ml-auto flex items-center gap-2\">\n                <Button variant=\"outline\" size=\"sm\" onClick={dismiss}>\n                  Reject\n                </Button>\n                <Button size=\"sm\" onClick={dismiss}>\n                  {showPreferences ? 'Save preferences' : 'Accept all'}\n                </Button>\n              </div>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/cookie-consent/CookieConsentBanner.tsx"
    },
    {
      "path": "packages/registry-react/blocks/cookie-consent/CookieConsentBar.tsx",
      "content": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport { Cookie } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Separator } from '@/components/ui/separator'\n\nexport function CookieConsentBar({ storageKey = 'uipkge:consent' }: { storageKey?: string }) {\n  const [visible, setVisible] = useState(false)\n\n  useEffect(() => {\n    try {\n      setVisible(!window.localStorage.getItem(storageKey))\n    } catch {\n      setVisible(true)\n    }\n  }, [storageKey])\n\n  function decide(choice: 'accepted' | 'rejected') {\n    setVisible(false)\n    try {\n      window.localStorage.setItem(storageKey, choice)\n    } catch {\n      // Blocked storage: the choice holds for this page view only.\n    }\n  }\n\n  if (!visible) return null\n\n  return (\n    <div\n      data-slot=\"cookie-consent-bar\"\n      className=\"border-border bg-card/95 fixed inset-x-0 bottom-0 z-50 border-t backdrop-blur\"\n      role=\"region\"\n      aria-label=\"Cookie consent\"\n    >\n      <div className=\"mx-auto flex max-w-6xl flex-col gap-4 px-6 py-4 lg:flex-row lg:items-center lg:gap-8\">\n        <div className=\"flex min-w-0 items-start gap-3\">\n          <Cookie className=\"text-muted-foreground mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n          <div className=\"min-w-0\">\n            <div className=\"flex flex-wrap items-center gap-2\">\n              <p className=\"text-sm font-medium\">We store two things</p>\n              <Badge variant=\"outline\">No ad tracking</Badge>\n            </div>\n            <p className=\"text-muted-foreground mt-1 text-sm leading-relaxed\">\n              A session cookie so you stay signed in, and an analytics cookie recording which pages get read. Rejecting\n              keeps the first and drops the second.\n            </p>\n          </div>\n        </div>\n\n        <Separator orientation=\"vertical\" className=\"hidden h-10 lg:block\" />\n\n        {/* Reject carries the same visual weight as accept. A ghost \"reject\"\n            beside a solid \"accept\" is a dark pattern with extra steps. */}\n        <div className=\"flex shrink-0 flex-wrap items-center gap-2\">\n          <Button variant=\"outline\" onClick={() => decide('rejected')}>\n            Reject analytics\n          </Button>\n          <Button onClick={() => decide('accepted')}>Accept</Button>\n          <Button variant=\"ghost\" size=\"sm\">\n            Manage\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/cookie-consent/CookieConsentBar.tsx"
    },
    {
      "path": "packages/registry-react/blocks/cookie-consent/CookieConsentPreferences.tsx",
      "content": "'use client'\n\nimport { useState } from 'react'\nimport { Lock } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from '@/components/ui/dialog'\nimport { Separator } from '@/components/ui/separator'\nimport { Switch } from '@/components/ui/switch'\n\nconst categories = [\n  {\n    id: 'necessary',\n    label: 'Strictly necessary',\n    purpose: 'Session and sign-in state. Without these the app cannot keep you logged in.',\n    retention: 'Session',\n    locked: true,\n  },\n  {\n    id: 'analytics',\n    label: 'Product analytics',\n    purpose: 'Which pages are read and where people give up. Aggregated, never sold.',\n    retention: '13 months',\n    locked: false,\n  },\n  {\n    id: 'support',\n    label: 'Support chat',\n    purpose: 'Keeps a conversation open across page loads so you do not repeat yourself.',\n    retention: '30 days',\n    locked: false,\n  },\n]\n\nexport function CookieConsentPreferences() {\n  const [open, setOpen] = useState(true)\n  const [choices, setChoices] = useState<Record<string, boolean>>({\n    necessary: true,\n    analytics: false,\n    support: false,\n  })\n\n  return (\n    <div data-slot=\"cookie-consent-preferences\">\n      <Dialog open={open} onOpenChange={setOpen}>\n        <DialogContent className=\"sm:max-w-lg\">\n          <DialogHeader>\n            <Badge variant=\"secondary\" className=\"w-fit\">\n              Privacy\n            </Badge>\n            <DialogTitle className=\"mt-3 text-xl\">Choose what we store</DialogTitle>\n            <DialogDescription>\n              Each category says what it is for and how long it is kept. Nothing here is advertising.\n            </DialogDescription>\n          </DialogHeader>\n\n          <Separator />\n\n          <ul className=\"space-y-4\">\n            {categories.map((category) => (\n              <li key={category.id} className=\"flex items-start gap-4\">\n                <div className=\"min-w-0 flex-1\">\n                  <div className=\"flex flex-wrap items-center gap-2\">\n                    <p className=\"text-sm font-medium\">{category.label}</p>\n                    {category.locked && (\n                      <Badge variant=\"outline\" className=\"gap-1\">\n                        <Lock className=\"size-2.5\" aria-hidden=\"true\" />\n                        Required\n                      </Badge>\n                    )}\n                  </div>\n                  <p className=\"text-muted-foreground mt-1 text-xs leading-relaxed\">{category.purpose}</p>\n                  <p className=\"text-muted-foreground/80 mt-1 font-mono text-xs\">Kept {category.retention}</p>\n                </div>\n                <Switch\n                  checked={choices[category.id]}\n                  onCheckedChange={(value: boolean) => setChoices((c) => ({ ...c, [category.id]: value }))}\n                  disabled={category.locked}\n                  aria-label={`Allow ${category.label}`}\n                  className=\"mt-1 shrink-0\"\n                />\n              </li>\n            ))}\n          </ul>\n\n          <DialogFooter className=\"sm:justify-start\">\n            <Button onClick={() => setOpen(false)}>Save choices</Button>\n            <Button variant=\"outline\" onClick={() => setOpen(false)}>\n              Reject optional\n            </Button>\n          </DialogFooter>\n        </DialogContent>\n      </Dialog>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/cookie-consent/CookieConsentPreferences.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/dialog.json",
    "https://uipkge.dev/r/react/separator.json",
    "https://uipkge.dev/r/react/switch.json"
  ],
  "description": "Consent trio covering the whole banner-to-dialog flow: a full-width banner with inline preferences, a bottom bar with equally weighted accept, reject, and manage actions, and a granular preferences dialog with per-category purpose, retention, and switches.",
  "categories": [
    "legal",
    "marketing",
    "feedback"
  ]
}