{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "banner-announcement",
  "type": "registry:ui",
  "title": "Banner Announcement",
  "description": "A dismissible announcement banner with multiple variants and action links.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/ruixenui/banner-announcement.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { ArrowRight, X } from \"lucide-react\";\n\nexport interface BannerAnnouncementProps {\n  children: React.ReactNode;\n  badge?: string;\n  action?: { label: string; href: string };\n  dismissible?: boolean;\n  onDismiss?: () => void;\n  className?: string;\n}\n\nexport function BannerAnnouncement({\n  children,\n  badge,\n  action,\n  dismissible = true,\n  onDismiss,\n  className,\n}: BannerAnnouncementProps) {\n  const [mounted, setMounted] = React.useState(false);\n  const [dismissed, setDismissed] = React.useState(false);\n\n  React.useEffect(() => {\n    requestAnimationFrame(() => {\n      requestAnimationFrame(() => setMounted(true));\n    });\n  }, []);\n\n  const handleDismiss = () => {\n    setDismissed(true);\n    if (onDismiss) setTimeout(onDismiss, 350);\n  };\n\n  return (\n    <div\n      className={cn(\n        \"grid transition-[grid-template-rows] ease-[cubic-bezier(0.16,1,0.3,1)]\",\n        mounted && !dismissed\n          ? \"[grid-template-rows:1fr] duration-500\"\n          : \"[grid-template-rows:0fr] duration-300\",\n      )}\n    >\n      <div className=\"overflow-hidden\">\n        <div\n          className={cn(\n            \"relative flex items-center justify-center gap-2.5 border-b px-10 py-2.5 text-sm\",\n            \"bg-muted/40\",\n            /* ── shimmer sweep ── */\n            \"before:pointer-events-none before:absolute before:inset-0\",\n            \"before:bg-[length:250%_100%] before:bg-no-repeat\",\n            \"before:bg-[linear-gradient(110deg,transparent_30%,hsl(var(--foreground)/0.04)_44%,hsl(var(--foreground)/0.08)_50%,hsl(var(--foreground)/0.04)_56%,transparent_70%)]\",\n            \"before:animate-[banner-shimmer_1.8s_ease-out_0.3s_1_both]\",\n            /* ── content fade on dismiss ── */\n            \"transition-[opacity,transform] duration-150\",\n            dismissed && \"opacity-0 -translate-y-1\",\n            className,\n          )}\n        >\n          {badge && (\n            <span className=\"shrink-0 rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary\">\n              {badge}\n            </span>\n          )}\n\n          <p className=\"text-center font-medium text-foreground/80\">\n            {children}\n          </p>\n\n          {action && (\n            <a\n              href={action.href}\n              className=\"group inline-flex shrink-0 items-center gap-0.5 font-semibold text-foreground transition-colors hover:text-foreground/70\"\n            >\n              {action.label}\n              <ArrowRight className=\"size-3.5 transition-transform duration-200 group-hover:translate-x-0.5\" />\n            </a>\n          )}\n\n          {dismissible && (\n            <button\n              type=\"button\"\n              onClick={handleDismiss}\n              className=\"absolute right-3 top-1/2 -translate-y-1/2 rounded-full p-1 text-foreground/30 transition-all duration-150 hover:bg-foreground/5 hover:text-foreground/60 active:scale-90\"\n              aria-label=\"Dismiss\"\n            >\n              <X className=\"size-3.5\" />\n            </button>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ruixen/banner-announcement.tsx"
    }
  ]
}