{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "announcement-banner",
  "title": "Announcement Banner",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/announcement-banner/announcement-banner.component.ts",
      "content": "// Deprecated alias of the announcement broadcast center (use ui-announcement). rotating live ticker with CLI copy and prev/next\n// controls, plus floating-pill and top-bar modes. Port of the Vue/React block 1:1 —\n// same three announcements, same 6s autoplay that pauses on hover, same category\n// badge tones. `variant` picks the placement; `autoplay` starts the rotation.\nimport {\n  ChangeDetectionStrategy,\n  Component,\n  DestroyRef,\n  Input,\n  OnDestroy,\n  OnInit,\n  computed,\n  inject,\n  signal,\n} from '@angular/core'\nimport { ArrowRight, ChevronLeft, ChevronRight, LucideAngularModule, Megaphone, Terminal, X } from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\n\nexport type AnnouncementVariant = 'floating-pill' | 'top-bar' | 'interactive-ticker'\nexport type AnnouncementCategory = 'release' | 'security' | 'maintenance' | 'feature'\n\nexport interface AnnouncementItem {\n  id: string\n  category: AnnouncementCategory\n  badgeText: string\n  title: string\n  actionLabel: string\n  actionUrl?: string\n  cliSnippet?: string\n}\n\nconst announcements: AnnouncementItem[] = [\n  {\n    id: 'ann-1',\n    category: 'release',\n    badgeText: 'v2.4.0 Release',\n    title: 'Tailwind CSS v4 OKLCH tokens & 281+ production workbenches are live.',\n    actionLabel: 'Read Changelog',\n    actionUrl: 'https://uipkge.dev/changelog',\n    cliSnippet: 'npx shadcn-vue@latest add https://uipkge.dev/r/vue/init.json',\n  },\n  {\n    id: 'ann-2',\n    category: 'feature',\n    badgeText: 'Dual-Framework',\n    title: '100% Vue 3.5 & React 19 component parity achieved with zero runtime CSS.',\n    actionLabel: 'Explore Parity Matrix',\n    actionUrl: 'https://uipkge.dev/components',\n    cliSnippet: 'npx shadcn@latest add https://uipkge.dev/r/react/init.json',\n  },\n  {\n    id: 'ann-3',\n    category: 'security',\n    badgeText: 'Security Notice',\n    title: 'Upstream Reka UI & Radix dependency security patches verified and updated.',\n    actionLabel: 'Security Advisory',\n    actionUrl: 'https://uipkge.dev/security',\n  },\n]\n\nconst categoryBadgeClass: Record<AnnouncementCategory, string> = {\n  release: 'bg-primary/10 text-primary border-primary/20',\n  security: 'border-warning/20 bg-warning/10 text-warning',\n  maintenance: '',\n  feature: 'border-success/20 bg-success/10 text-success',\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-announcement-banner, [ui-announcement-banner]',\n  standalone: true,\n  // The block renders its own root <div>: keep this wrapper out of the layout.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [LucideAngularModule, UiBadgeComponent],\n  template: `\n    @if (!dismissed()) {\n      <div\n        data-slot=\"announcement-banner\"\n        [attr.data-variant]=\"variant\"\n        role=\"region\"\n        aria-label=\"Announcement\"\n        [class]=\"rootClass\"\n        (mouseenter)=\"isPaused.set(true)\"\n        (mouseleave)=\"isPaused.set(false)\"\n      >\n        @if (variant === 'interactive-ticker') {\n          <div\n            class=\"bg-muted/70 border-border text-foreground flex items-center justify-between gap-3 overflow-hidden border-b px-4 py-2 text-xs shadow-xs backdrop-blur-md\"\n          >\n            <div class=\"mx-auto flex w-full max-w-6xl items-center justify-between gap-4\">\n              <!-- Left: Status Beacon & Item Indicator -->\n              <div class=\"flex shrink-0 items-center gap-3\">\n                <div class=\"text-muted-foreground flex items-center gap-1.5 font-mono text-xs\">\n                  <span class=\"bg-primary size-2 animate-pulse rounded-full\"></span>\n                  <span class=\"text-foreground font-semibold\">Live Dispatch</span>\n                </div>\n                <span ui-badge variant=\"secondary\" [class]=\"badgeClass()\">{{ current().badgeText }}</span>\n              </div>\n\n              <!-- Center: Dynamic Headline & Action Link -->\n              <div class=\"flex flex-1 items-center justify-center gap-2 truncate\">\n                <p class=\"text-foreground truncate font-medium\">{{ current().title }}</p>\n                @if (current().actionUrl; as actionUrl) {\n                  <a\n                    [href]=\"actionUrl\"\n                    class=\"text-primary hidden shrink-0 items-center gap-1 font-semibold hover:underline sm:inline-flex\"\n                  >\n                    {{ current().actionLabel }} <lucide-icon [img]=\"ArrowRight\" class=\"size-3\" />\n                  </a>\n                }\n              </div>\n\n              <!-- Right: Ticker Navigation & CLI / Dismiss -->\n              <div class=\"flex shrink-0 items-center gap-2\">\n                @if (current().cliSnippet; as snippet) {\n                  <button\n                    type=\"button\"\n                    class=\"bg-background border-border text-muted-foreground hover:text-foreground hidden items-center gap-1.5 rounded border px-2 py-0.5 font-mono text-xs transition-colors md:inline-flex\"\n                    (click)=\"copyCliSnippet(snippet)\"\n                  >\n                    <lucide-icon [img]=\"Terminal\" class=\"text-primary size-3\" />\n                    <span>{{ copiedCli() ? 'Copied CLI!' : 'Copy CLI' }}</span>\n                  </button>\n                }\n                <div class=\"border-border bg-background flex items-center overflow-hidden rounded-md border\">\n                  <button\n                    type=\"button\"\n                    class=\"hover:bg-muted text-muted-foreground hover:text-foreground p-1 transition-colors\"\n                    aria-label=\"Previous announcement\"\n                    (click)=\"prevAnnouncement()\"\n                  >\n                    <lucide-icon [img]=\"ChevronLeft\" class=\"size-3.5\" />\n                  </button>\n                  <span class=\"text-muted-foreground px-1.5 font-mono text-xs\">\n                    {{ currentIndex() + 1 }}/{{ announcementCount }}\n                  </span>\n                  <button\n                    type=\"button\"\n                    class=\"hover:bg-muted text-muted-foreground hover:text-foreground p-1 transition-colors\"\n                    aria-label=\"Next announcement\"\n                    (click)=\"nextAnnouncement()\"\n                  >\n                    <lucide-icon [img]=\"ChevronRight\" class=\"size-3.5\" />\n                  </button>\n                </div>\n                <button\n                  type=\"button\"\n                  aria-label=\"Dismiss banner\"\n                  class=\"text-muted-foreground hover:text-foreground hover:bg-muted rounded-md p-1 transition-colors\"\n                  (click)=\"dismiss()\"\n                >\n                  <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n                </button>\n              </div>\n            </div>\n          </div>\n        } @else if (variant === 'floating-pill') {\n          <div class=\"mx-auto w-fit max-w-2xl px-4 py-2\">\n            <div\n              class=\"bg-card/90 border-border text-foreground hover:border-primary/40 flex items-center gap-3 rounded-full border py-1.5 pr-2 pl-3 text-xs shadow-md backdrop-blur-md transition-colors\"\n            >\n              <span ui-badge variant=\"secondary\" [class]=\"pillBadgeClass\">{{ current().badgeText }}</span>\n              <p class=\"text-foreground max-w-sm truncate font-medium\">{{ current().title }}</p>\n              @if (current().actionUrl; as actionUrl) {\n                <a\n                  [href]=\"actionUrl\"\n                  class=\"text-primary inline-flex shrink-0 items-center gap-1 font-semibold hover:underline\"\n                >\n                  {{ current().actionLabel }} <lucide-icon [img]=\"ArrowRight\" class=\"size-3\" />\n                </a>\n              }\n              <button\n                type=\"button\"\n                aria-label=\"Dismiss announcement\"\n                class=\"text-muted-foreground hover:text-foreground hover:bg-muted rounded-full p-1 transition-colors\"\n                (click)=\"dismiss()\"\n              >\n                <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n              </button>\n            </div>\n          </div>\n        } @else {\n          <div\n            class=\"bg-primary text-primary-foreground flex items-center justify-between gap-3 px-4 py-2 text-xs shadow-xs\"\n          >\n            <div class=\"mx-auto flex w-full max-w-6xl items-center justify-between gap-4\">\n              <div class=\"flex items-center gap-2 truncate\">\n                <lucide-icon [img]=\"Megaphone\" class=\"size-4 shrink-0\" />\n                <span class=\"font-mono font-bold\">[{{ current().badgeText }}]</span>\n                <span class=\"truncate\">{{ current().title }}</span>\n              </div>\n              <div class=\"flex shrink-0 items-center gap-3\">\n                @if (current().actionUrl; as actionUrl) {\n                  <a\n                    [href]=\"actionUrl\"\n                    class=\"inline-flex items-center gap-1 font-semibold underline-offset-4 hover:underline\"\n                  >\n                    {{ current().actionLabel }} <lucide-icon [img]=\"ArrowRight\" class=\"size-3\" />\n                  </a>\n                }\n                <button\n                  type=\"button\"\n                  aria-label=\"Dismiss banner\"\n                  class=\"text-primary-foreground/80 hover:text-primary-foreground hover:bg-primary-foreground/15 rounded p-1 transition-colors\"\n                  (click)=\"dismiss()\"\n                >\n                  <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n                </button>\n              </div>\n            </div>\n          </div>\n        }\n      </div>\n    }\n  `,\n})\nexport class UiAnnouncementBannerComponent implements OnInit, OnDestroy {\n  @Input() variant: AnnouncementVariant = 'interactive-ticker'\n  @Input() autoplay = true\n  @Input('class') className?: string\n\n  readonly currentIndex = signal(0)\n  readonly dismissed = signal(false)\n  readonly isPaused = signal(false)\n  readonly copiedCli = signal(false)\n\n  protected readonly announcementCount = announcements.length\n  protected readonly pillBadgeClass = 'bg-primary/10 text-primary border-primary/20 px-2 py-0.5 font-mono text-xs'\n\n  protected readonly ArrowRight = ArrowRight\n  protected readonly ChevronLeft = ChevronLeft\n  protected readonly ChevronRight = ChevronRight\n  protected readonly Megaphone = Megaphone\n  protected readonly Terminal = Terminal\n  protected readonly X = X\n\n  private timer: ReturnType<typeof setInterval> | undefined\n  private copyTimer: ReturnType<typeof setTimeout> | undefined\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => {\n      clearInterval(this.timer)\n      clearTimeout(this.copyTimer)\n    })\n  }\n\n  ngOnInit(): void {\n    this.startAutoplay()\n  }\n\n  ngOnDestroy(): void {\n    this.stopAutoplay()\n  }\n\n  readonly current = computed(() => announcements[this.currentIndex()]!)\n\n  readonly badgeClass = computed(() => cn('px-2 py-0.5 font-mono text-xs', categoryBadgeClass[this.current().category]))\n\n  get rootClass(): string {\n    return cn('relative z-50 transition-colors duration-200', this.className)\n  }\n\n  nextAnnouncement(): void {\n    this.currentIndex.update((i) => (i + 1) % announcements.length)\n  }\n\n  prevAnnouncement(): void {\n    this.currentIndex.update((i) => (i - 1 + announcements.length) % announcements.length)\n  }\n\n  copyCliSnippet(snippet?: string): void {\n    if (!snippet) return\n    try {\n      void navigator.clipboard.writeText(snippet).catch(() => {})\n    } catch {\n      // Blocked clipboard: the label flip below still confirms the intent.\n    }\n    this.copiedCli.set(true)\n    clearTimeout(this.copyTimer)\n    this.copyTimer = setTimeout(() => this.copiedCli.set(false), 2000)\n  }\n\n  dismiss(): void {\n    this.dismissed.set(true)\n  }\n\n  private startAutoplay(): void {\n    if (!this.autoplay) return\n    this.stopAutoplay()\n    this.timer = setInterval(() => {\n      if (!this.isPaused()) this.nextAnnouncement()\n    }, 6000)\n  }\n\n  private stopAutoplay(): void {\n    clearInterval(this.timer)\n    this.timer = undefined\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/announcement-banner/announcement-banner.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/announcement-banner/index.ts",
      "content": "export {\n  UiAnnouncementBannerComponent,\n  type AnnouncementCategory,\n  type AnnouncementItem,\n  type AnnouncementVariant,\n} from './announcement-banner.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/announcement-banner/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json"
  ],
  "description": "Interactive release, security, and maintenance broadcast center with rotating live ticker, CLI copy button, and floating pill / top-bar modes.",
  "categories": [
    "marketing"
  ],
  "deprecated": true,
  "replacedBy": "announcement"
}