{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "announcement-countdown-bar",
  "title": "Announcement — Countdown Bar",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/announcement-countdown-bar/announcement-countdown-bar.component.ts",
      "content": "// Top strip counting down to `deadline` in days, hours, minutes and seconds, with\n// the offer line, an inline CTA, and a dismiss control that keeps it closed for the\n// session. Port of the Vue/React block 1:1. The digits only appear once the timer is\n// running (the server has no clock the client agrees with); tabular-nums plus\n// fixed-width cells keep the bar from reflowing each second.\nimport {\n  ChangeDetectionStrategy,\n  Component,\n  DestroyRef,\n  Input,\n  OnInit,\n  afterNextRender,\n  computed,\n  inject,\n  signal,\n} from '@angular/core'\nimport { ArrowRight, LucideAngularModule, Timer, X } from 'lucide-angular'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\n\nexport interface CountdownPart {\n  label: string\n  value: number\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-announcement-countdown-bar, [ui-announcement-countdown-bar]',\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, UiButtonComponent, UiSeparatorComponent],\n  template: `\n    @if (!dismissed()) {\n      <div data-slot=\"announcement-countdown-bar\" class=\"border-border bg-muted/60 border-b backdrop-blur\">\n        <div class=\"mx-auto flex min-h-11 max-w-6xl flex-wrap items-center gap-x-4 gap-y-2 px-6 py-2 text-sm\">\n          <span ui-badge variant=\"secondary\" class=\"shrink-0 gap-1.5\">\n            <lucide-icon [img]=\"Timer\" class=\"size-3\" aria-hidden=\"true\" />\n            Ends soon\n          </span>\n\n          <p class=\"min-w-0\">\n            <span class=\"font-medium\">Annual plans are 20% off</span>\n            <span class=\"text-muted-foreground\"> for teams that start before the quarter closes.</span>\n          </p>\n\n          <!-- tabular-nums + fixed-width cells keep the bar from reflowing each second. -->\n          @if (remaining() !== null) {\n            <div class=\"text-muted-foreground flex items-center gap-1 font-mono text-xs\">\n              @for (part of parts(); track part.label) {\n                <span class=\"tabular-nums\">\n                  <span class=\"text-foreground inline-block min-w-[2ch] text-right font-semibold\">{{\n                    pad2(part.value)\n                  }}</span\n                  >{{ part.label }}\n                </span>\n              }\n            </div>\n          }\n\n          <div class=\"ml-auto flex shrink-0 items-center gap-1\">\n            <button ui-button variant=\"ghost\" size=\"sm\" class=\"h-7\">\n              Claim it\n              <lucide-icon [img]=\"ArrowRight\" [class]=\"'ml-1 size-3.5'\" aria-hidden=\"true\" />\n            </button>\n            <ui-separator orientation=\"vertical\" class=\"hidden h-5 sm:block\" />\n            <button\n              ui-button\n              variant=\"ghost\"\n              size=\"icon\"\n              class=\"size-7\"\n              aria-label=\"Dismiss announcement\"\n              (click)=\"dismiss()\"\n            >\n              <lucide-icon [img]=\"X\" class=\"size-3.5\" aria-hidden=\"true\" />\n            </button>\n          </div>\n        </div>\n      </div>\n    }\n  `,\n})\nexport class UiAnnouncementCountdownBarComponent implements OnInit {\n  /** ISO timestamp the countdown runs to. */\n  @Input() deadline = ''\n  /** sessionStorage key holding the dismissal. */\n  @Input() storageKey = 'uipkge:countdown-dismissed'\n\n  // Null until the browser timer starts: the server has no clock the client agrees with.\n  readonly remaining = signal<number | null>(null)\n  readonly dismissed = signal(false)\n  readonly targetMs = signal(0)\n\n  protected readonly Timer = Timer\n  protected readonly X = X\n  protected readonly ArrowRight = ArrowRight\n\n  private timer: ReturnType<typeof setInterval> | undefined\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => clearInterval(this.timer))\n    // Browser only, after the first render (the Vue onMounted / React useEffect).\n    afterNextRender(() => {\n      try {\n        this.dismissed.set(window.sessionStorage.getItem(this.storageKey) === '1')\n      } catch {\n        this.dismissed.set(false)\n      }\n      this.tick()\n      this.timer = setInterval(() => this.tick(), 1000)\n    })\n  }\n\n  ngOnInit(): void {\n    const parsed = this.deadline ? Date.parse(this.deadline) : Number.NaN\n    // Fall back to 72 hours out so the demo and a mis-set prop still read sensibly.\n    this.targetMs.set(Number.isNaN(parsed) ? Date.now() + 72 * 60 * 60 * 1000 : parsed)\n  }\n\n  readonly parts = computed<CountdownPart[]>(() => {\n    const total = Math.floor(Math.max(this.remaining() ?? 0, 0) / 1000)\n    return [\n      { label: 'd', value: Math.floor(total / 86400) },\n      { label: 'h', value: Math.floor((total % 86400) / 3600) },\n      { label: 'm', value: Math.floor((total % 3600) / 60) },\n      { label: 's', value: total % 60 },\n    ]\n  })\n\n  tick(): void {\n    this.remaining.set(this.targetMs() - Date.now())\n  }\n\n  pad2(n: number): string {\n    return String(n).padStart(2, '0')\n  }\n\n  dismiss(): void {\n    this.dismissed.set(true)\n    try {\n      window.sessionStorage.setItem(this.storageKey, '1')\n    } catch {\n      // Blocked storage: the dismissal still holds for this page view.\n    }\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/announcement-countdown-bar/announcement-countdown-bar.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/announcement-countdown-bar/index.ts",
      "content": "export { UiAnnouncementCountdownBarComponent, type CountdownPart } from './announcement-countdown-bar.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/announcement-countdown-bar/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/separator.json"
  ],
  "description": "Top strip counting down to a deadline in days, hours, minutes and seconds, with the offer line, an inline CTA, and a dismiss control that keeps it closed for the session.",
  "categories": [
    "marketing"
  ],
  "deprecated": true,
  "replacedBy": "announcement"
}